package com.robert.maps.applib.tileprovider; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.NoSuchElementException; import org.andnav.osm.views.util.StreamUtils; import android.os.Handler; import android.os.Message; import com.robert.maps.applib.utils.ICacheProvider; import com.robert.maps.applib.utils.Ut; public class FSCacheProvider implements ICacheProvider { public static final String TILE_PATH_EXTENSION = ".tile"; public static final long TILE_MAX_CACHE_SIZE_BYTES = 4L * 1024 * 1024; public static final long TILE_TRIM_CACHE_SIZE_BYTES = 4L * 1024 * 1024; private File mCachePath; private long mUsedCacheSpace = 0L; private Handler mHandler = null; public FSCacheProvider(final File aCachePath) { this(aCachePath, null); } public FSCacheProvider(final File aCachePath, final Handler aHandler) { super(); this.mCachePath = aCachePath; this.mHandler = aHandler; // do this in the background because it takes a long time final Thread t = new Thread() { @Override public void run() { mUsedCacheSpace = 0; // because it's static calculateDirectorySize(aCachePath); if (mUsedCacheSpace > TILE_MAX_CACHE_SIZE_BYTES) { cutCurrentCache(); } Ut.d("Finished init thread"); if(mHandler != null) Message.obtain(mHandler).sendToTarget(); } }; t.setPriority(Thread.MIN_PRIORITY); t.start(); } public long getUsedCacheSpace() { return mUsedCacheSpace; } public byte[] getTile(String aURLstring, int aX, int aY, int aZ) { if(mCachePath == null) return null; final File file = new File(mCachePath, Ut.formatToFileName(aURLstring) + TILE_PATH_EXTENSION); if(!file.exists()) return null; OutputStream out = null; InputStream in = null; final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); try { in = new BufferedInputStream(new FileInputStream(file), StreamUtils.IO_BUFFER_SIZE); out = new BufferedOutputStream(dataStream, StreamUtils.IO_BUFFER_SIZE); Ut.copy(in, out); out.flush(); } catch (IOException e) { } finally { StreamUtils.closeStream(in); StreamUtils.closeStream(out); } final byte[] data = dataStream.toByteArray(); return data; } public void putTile(String aURLstring, int aX, int aY, int aZ, byte[] aData) { if(mCachePath == null) return; final File file = new File(mCachePath, Ut.formatToFileName(aURLstring) + TILE_PATH_EXTENSION); final File parent = file.getParentFile(); if (!parent.exists()) { return; } BufferedOutputStream outputStream = null; ByteArrayInputStream byteStream = null; try { byteStream = new ByteArrayInputStream(aData); outputStream = new BufferedOutputStream(new FileOutputStream(file.getPath()), Ut.IO_BUFFER_SIZE); final long length = Ut.copy(byteStream, outputStream); mUsedCacheSpace += length; if (mUsedCacheSpace > TILE_MAX_CACHE_SIZE_BYTES) { cutCurrentCache(); } } catch (final IOException e) { e.printStackTrace(); return; } finally { StreamUtils.closeStream(outputStream); StreamUtils.closeStream(byteStream); } } public void Free() { // TODO Auto-generated method stub } public void clearCache() { cutCurrentCacheToSize(0L); } private void cutCurrentCache() { cutCurrentCacheToSize(TILE_TRIM_CACHE_SIZE_BYTES); } private void cutCurrentCacheToSize(final long aTrimSizeBytes) { synchronized (mCachePath) { if (mUsedCacheSpace > aTrimSizeBytes) { Ut.d("Trimming tile cache from " + mUsedCacheSpace + " to " + aTrimSizeBytes); final List<File> z = getDirectoryFileList(mCachePath); final File[] files = z.toArray(new File[0]); Arrays.sort(files, new Comparator<File>() { public int compare(final File f1, final File f2) { return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified()); } }); for (final File file : files) { if (mUsedCacheSpace <= aTrimSizeBytes) { break; } final long length = file.length(); if (file.delete()) { mUsedCacheSpace -= length; } } Ut.d("Finished trimming tile cache"); } } } private List<File> getDirectoryFileList(final File aDirectory) { final List<File> files = new ArrayList<File>(); final File[] z = aDirectory.listFiles(); if (z != null) { for (final File file : z) { if (file.isFile()) { files.add(file); } if (file.isDirectory()) { files.addAll(getDirectoryFileList(file)); } } } return files; } private void calculateDirectorySize(final File pDirectory) { if(pDirectory == null) return; final File[] z = pDirectory.listFiles(); if (z != null) { for (final File file : z) { if (file.isFile()) { mUsedCacheSpace += file.length(); } if (file.isDirectory() && !isSymbolicDirectoryLink(pDirectory, file)) { calculateDirectorySize(file); // *** recurse *** } } } } private boolean isSymbolicDirectoryLink(final File pParentDirectory, final File pDirectory) { try { final String canonicalParentPath1 = pParentDirectory.getCanonicalPath(); final String canonicalParentPath2 = pDirectory.getCanonicalFile().getParent(); return !canonicalParentPath1.equals(canonicalParentPath2); } catch (final IOException e) { return true; } catch (final NoSuchElementException e) { // See: http://code.google.com/p/android/issues/detail?id=4961 // See: http://code.google.com/p/android/issues/detail?id=5807 return true; } } public double getTileLenght() { return 0; } @Override public void deleteTile(String aURLstring, int aX, int aY, int aZ) { // TODO Auto-generated method stub } }