X-Git-Url: http://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=src%2Ftim%2Fprune%2Fgui%2Fmap%2FDiskTileCacher.java;h=875fcc3b7feb5d0bbad49f55774ba257369f0451;hp=fd2042a7714acfe2ca1f5b47693ce9df06b0a79f;hb=HEAD;hpb=2302358503c38817e19f6e529f6c9e530aac0e86 diff --git a/src/tim/prune/gui/map/DiskTileCacher.java b/src/tim/prune/gui/map/DiskTileCacher.java index fd2042a..9be7841 100644 --- a/src/tim/prune/gui/map/DiskTileCacher.java +++ b/src/tim/prune/gui/map/DiskTileCacher.java @@ -25,10 +25,22 @@ public class DiskTileCacher implements Runnable private File _file = null; /** Observer to be notified */ private ImageObserver _observer = null; + /** True if cacher is active, false if blocked */ + private boolean _active = false; + /** Time limit to cache images for */ private static final long CACHE_TIME_LIMIT = 20 * 24 * 60 * 60 * 1000; // 20 days in ms /** Hashset of all blocked / 404 tiles to avoid requesting them again */ private static final HashSet BLOCKED_URLS = new HashSet(); + /**Hashset of files which are currently being processed */ + private static final HashSet DOWNLOADING_FILES = new HashSet(); + /** Number of currently active threads */ + private static int NUMBER_ACTIVE_THREADS = 0; + /** Flag to remember whether any server connection is possible */ + private static boolean CONNECTION_ACTIVE = true; + /** Flag to remember whether we have already tried to create the base path */ + private static boolean TRIED_TO_CREATE_BASEPATH = false; + /** * Private constructor @@ -40,6 +52,7 @@ public class DiskTileCacher implements Runnable _url = inUrl; _file = inFile; _observer = inObserver; + _active = registerCacher(inFile.getAbsolutePath()); } /** @@ -75,69 +88,119 @@ public class DiskTileCacher implements Runnable * @param inBasePath base path to disk cache * @param inTilePath relative path to this tile * @param inObserver observer to inform when load complete + * @throws CacheFailure if tile could not be saved */ - public static void saveTile(URL inUrl, String inBasePath, String inTilePath, ImageObserver inObserver) + public static void saveTile(URL inUrl, String inBasePath, String inTilePath, ImageObserver inObserver) throws CacheFailure { if (inBasePath == null || inTilePath == null) {return;} // save file if possible File basePath = new File(inBasePath); - if (!basePath.exists() || !basePath.isDirectory() || !basePath.canWrite()) { + if (!checkBasePath(basePath)) + { // Can't write to base path - return; + throw new CacheFailure(); } File tileFile = new File(basePath, inTilePath); - // Check if this file is already being loaded - if (isBeingLoaded(tileFile)) {return;} + // Check if it has already failed - if (BLOCKED_URLS.contains(inUrl.toString())) {return;} + if (BLOCKED_URLS.contains(inUrl.toString())) { + return; + } File dir = tileFile.getParentFile(); - // Start a new thread to load the image if necessary + // Construct a cacher to load the image if necessary if ((dir.exists() || dir.mkdirs()) && dir.canWrite()) { - new Thread(new DiskTileCacher(inUrl, tileFile, inObserver)).start(); + DiskTileCacher cacher = new DiskTileCacher(inUrl, tileFile, inObserver); + cacher.startDownloading(); } } /** - * Check whether the given tile is already being loaded - * @param inFile desired file - * @return true if temporary file with this name exists + * Check the given base path, and try (once) to create it if necessary + * @return true if base path can be written to */ - private static boolean isBeingLoaded(File inFile) + private static boolean checkBasePath(File inBasePath) { - File tempFile = new File(inFile.getAbsolutePath() + ".temp"); - if (!tempFile.exists()) { - return false; + if (!inBasePath.exists() && !TRIED_TO_CREATE_BASEPATH) + { + TRIED_TO_CREATE_BASEPATH = true; + System.out.println("Base path '" + inBasePath.getAbsolutePath() + "' does not exist, trying to create"); + return inBasePath.mkdirs(); + } + return inBasePath.exists() && inBasePath.isDirectory() && inBasePath.canWrite(); + } + + /** + * Start downloading the configured tile + */ + private void startDownloading() + { + if (_active) + { + new Thread(this).start(); } - // File exists, so check if it was created recently - final long fileAge = System.currentTimeMillis() - tempFile.lastModified(); - return fileAge < 1000000L; // overwrite if the temp file is still there after 1000s } /** * Run method for loading URL asynchronously and saving to file */ public void run() + { + waitUntilAllowedToRun(); + if (doDownload()) + { + if (!CONNECTION_ACTIVE) + { + // wasn't active before but this download worked - we've come back online + BLOCKED_URLS.clear(); + CONNECTION_ACTIVE = true; + } + } + // Release file and thread + unregisterCacher(_file.getAbsolutePath()); + threadFinished(); + } + + /** + * Blocks (in separate thread) until allowed by concurrent thread limit + */ + private void waitUntilAllowedToRun() + { + while (!canStartNewThread()) + { + try { + Thread.sleep(400); + } + catch (InterruptedException e) {} + } + } + + /** + * @return true if download was successful + */ + private boolean doDownload() { boolean finished = false; InputStream in = null; FileOutputStream out = null; File tempFile = new File(_file.getAbsolutePath() + ".temp"); - // Use a synchronized block across all threads to make sure this url is only fetched once - synchronized (DiskTileCacher.class) + + if (tempFile.exists()) { - if (tempFile.exists()) {tempFile.delete();} - try { - if (!tempFile.createNewFile()) {return;} - } - catch (Exception e) {return;} + tempFile.delete(); } + try + { + if (!tempFile.createNewFile()) {return false;} + } + catch (Exception e) {return false;} + try { // Open streams from URL and to file out = new FileOutputStream(tempFile); - //System.out.println("Opening URL: " + _url.toString()); + //System.out.println("DiskTileCacher opening URL: " + _url.toString()); // Set http user agent on connection URLConnection conn = _url.openConnection(); conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER); @@ -148,27 +211,99 @@ public class DiskTileCacher implements Runnable out.write(d); } finished = true; - } catch (IOException e) { + } + catch (IOException e) + { System.err.println("ioe: " + e.getClass().getName() + " - " + e.getMessage()); BLOCKED_URLS.add(_url.toString()); + CONNECTION_ACTIVE = false; } finally { // clean up files try {in.close();} catch (Exception e) {} // ignore try {out.close();} catch (Exception e) {} // ignore - if (!finished) { + if (!finished) + { tempFile.delete(); } } + boolean success = false; // Move temp file to desired file location - if (tempFile.exists() && !tempFile.renameTo(_file)) + if (tempFile.exists() && tempFile.length() > 0L) { - // File couldn't be moved - delete both to be sure - tempFile.delete(); - _file.delete(); + if (tempFile.renameTo(_file)) + { + success = true; + } + else if (_file.delete() && tempFile.renameTo(_file)) + { + success = true; + } + else + { + System.out.println("Failed to rename temp file: " + tempFile.getAbsolutePath()); + tempFile.delete(); + } } + // Tell parent that load is finished (parameters ignored) _observer.imageUpdate(null, ImageObserver.ALLBITS, 0, 0, 0, 0); + return success; + } + + // Blocking of cachers working on same file + + /** + * Register a cacher writing to the specified file path + * @param inFilePath destination path to tile file + * @return true if nobody else has claimed this file yet + */ + private synchronized static boolean registerCacher(String inFilePath) + { + if (DOWNLOADING_FILES.contains(inFilePath)) + { + return false; + } + // Nobody has claimed this file yet + DOWNLOADING_FILES.add(inFilePath); + return true; + } + + /** + * Cacher has finished dealing with the specified file + * @param inFilePath destination path to tile file + */ + private synchronized static void unregisterCacher(String inFilePath) + { + DOWNLOADING_FILES.remove(inFilePath); + } + + // Limiting of active threads + + /** + * @return true if another thread is allowed to become active + */ + private synchronized static boolean canStartNewThread() + { + final int MAXIMUM_NUM_THREADS = 8; + if (NUMBER_ACTIVE_THREADS < MAXIMUM_NUM_THREADS) + { + NUMBER_ACTIVE_THREADS++; + return true; + } + // Already too many threads active + return false; + } + + /** + * Inform that one of the previously active threads has now completed + */ + private synchronized static void threadFinished() + { + if (NUMBER_ACTIVE_THREADS > 0) + { + NUMBER_ACTIVE_THREADS--; + } } }