// Created by plusminus on 21:46:41 - 25.09.2008 package de.blau.android.services.util; import java.io.File; import java.io.IOException; import java.util.concurrent.Executors; import android.content.Context; import android.os.RemoteException; import android.util.Log; import de.blau.android.resources.TileLayerServer; import de.blau.android.services.IMapTileProviderCallback; import de.blau.android.services.exceptions.EmptyCacheException; import de.blau.android.util.CustomDatabaseContext; /** * * <br/> * This class was taken from OpenStreetMapViewer (original package org.andnav.osm) in 2010-06 * by Marcus Wolschon to be integrated into the de.blau.androin * OSMEditor. * @author Nicolas Gramlich * @author Marcus Wolschon <Marcus@Wolschon.biz> * */ public class MapTileFilesystemProvider extends MapAsyncTileProvider { // =========================================================== // Constants // =========================================================== final static String DEBUGTAG = "OSM_FS_PROVIDER"; // =========================================================== // Fields // =========================================================== private final Context mCtx; private final MapTileProviderDataBase mDatabase; private final File mountPoint; private final int mMaxFSCacheByteSize; private int mCurrentFSCacheByteSize; /** online provider */ private MapTileDownloader mTileDownloader; // =========================================================== // Constructors // =========================================================== /** * @param ctx * @param mountPoint TODO * @param aMaxFSCacheByteSize the size of the cached MapTiles will not exceed this size. * @param aCache to load fs-tiles to. */ public MapTileFilesystemProvider(final Context ctx, File mountPoint, final int aMaxFSCacheByteSize) { mCtx = ctx; this.mountPoint = mountPoint; mMaxFSCacheByteSize = aMaxFSCacheByteSize; mDatabase = new MapTileProviderDataBase(new CustomDatabaseContext(ctx, mountPoint.getAbsolutePath()), this); mCurrentFSCacheByteSize = mDatabase.getCurrentFSCacheByteSize(); mThreadPool = Executors.newFixedThreadPool(4); mTileDownloader = new MapTileDownloader(ctx, this); Log.d(DEBUGTAG, "Currently used cache-size is: " + mCurrentFSCacheByteSize + " of " + mMaxFSCacheByteSize + " Bytes"); } // =========================================================== // Getter & Setter // =========================================================== public int getCurrentFSCacheByteSize() { return mCurrentFSCacheByteSize; } // =========================================================== // Methods from SuperClass/Interfaces // =========================================================== @Override protected Runnable getTileLoader(MapTile aTile, IMapTileProviderCallback aCallback) { return new TileLoader(aTile, aCallback); } // =========================================================== // Methods // =========================================================== public void saveFile(final MapTile tile, final byte[] someData) throws IOException { synchronized (this) { try { final int bytesGrown = mDatabase.addTileOrIncrement(tile, someData); mCurrentFSCacheByteSize += bytesGrown; if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "FSCache Size is now: " + mCurrentFSCacheByteSize + " Bytes"); } /* If Cache is full... */ try { if (mCurrentFSCacheByteSize > mMaxFSCacheByteSize){ if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) Log.d(DEBUGTAG, "Freeing FS cache..."); mCurrentFSCacheByteSize -= mDatabase.deleteOldest((int)(mMaxFSCacheByteSize * 0.05f)); // Free 5% of cache } } catch (EmptyCacheException e) { if(Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Cache empty", e); } } } catch (IllegalStateException e) { if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Tile saving failed", e); } } } } public void clearCurrentFSCache(){ cutCurrentFSCacheBy(Integer.MAX_VALUE); // Delete all } private void cutCurrentFSCacheBy(final int bytesToCut){ try { synchronized (this) { mDatabase.deleteOldest(Integer.MAX_VALUE); // Delete all } mCurrentFSCacheByteSize = 0; } catch (EmptyCacheException e) { if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Cache empty", e); } } } /** * delete tiles for specific provider * @param rendererID */ public void flushCache(String rendererID) { try { mDatabase.flushCache(rendererID); } catch (EmptyCacheException e) { if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Flushing tile cache failed", e); } } } // =========================================================== // Inner and Anonymous Classes // =========================================================== private class TileLoader extends MapAsyncTileProvider.TileLoader { public TileLoader(final MapTile aTile, final IMapTileProviderCallback aCallback) { super(aTile, aCallback); } @Override public void run() { try { TileLayerServer renderer = TileLayerServer.get(mCtx, mTile.rendererID, false); if (mTile.zoomLevel < renderer.getMinZoomLevel()) { // the tile doesn't exist no point in trying to get it mCallback.mapTileFailed(mTile.rendererID, mTile.zoomLevel, mTile.x, mTile.y, DOESNOTEXIST); return; } synchronized (MapTileFilesystemProvider.this) { byte[] data = MapTileFilesystemProvider.this.mDatabase.getTile(mTile); if (data == null) { if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "FS failed, request for download."); } mTileDownloader.loadMapTileAsync(mTile, mCallback); } else { // success! mCallback.mapTileLoaded(mTile.rendererID, mTile.zoomLevel, mTile.x, mTile.y, data); } } if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) Log.d(DEBUGTAG, "Loaded: " + mTile.toString()); } catch (IOException e) { if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Invalid tile: " + mTile.toString()); } } catch (RemoteException e) { if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Service failed", e); } } catch (NullPointerException e) { if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Service failed", e); } } catch (IllegalStateException e) { if (Log.isLoggable(DEBUGTAG, Log.DEBUG)) { Log.d(DEBUGTAG, "Tile loading failed", e); } } finally { finished(); } } } /** * Call when the object is no longer needed to close the database */ public void destroy() { Log.d(DEBUGTAG, "Closing tile database"); mDatabase.close(); } public void markAsInvalid(MapTile mTile) throws IOException { mDatabase.addTileOrIncrement(mTile, null); } }