From da4a4b5473e809a7d0443d88a8d2be3310c1f205 Mon Sep 17 00:00:00 2001 From: Peter Ryan Date: Wed, 26 Jun 2013 19:29:16 -0400 Subject: [PATCH 1/6] MohannadA Implemented (with appropriate tests) - downloading of images from an input stream - isCached method - getSampleSize - bumpOnDisk - getImageDimensions - invalidateFileSystemUri (no test) - calculateAndSaveImageDetails - getImageDimensionsFromDisk - getDetailsPrioritizable - getBitmapSynchronouslyFromDisk - testCalculateAndSaveImageDetails - getDecodePrioritizable - Discussed progress with Jamie --- .../xtremelabs/imageutils/DiskCacheTests.java | 139 +++++++++--- .../imageutils/ImageSystemDatabaseTests.java | 38 +++- xl_image_utils_lib/project.properties | 2 +- .../com/xtremelabs/imageutils/DiskCache.java | 212 ++++++++++++++++-- .../xtremelabs/imageutils/DiskLRUCacher.java | 3 - .../xtremelabs/imageutils/DisplayUtility.java | 29 +-- .../imageutils/FileSystemManager.java | 4 + .../xtremelabs/imageutils/ImageLoader.java | 4 - .../imageutils/ImageSystemDatabase.java | 15 +- 9 files changed, 356 insertions(+), 90 deletions(-) diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java index 653a6eb..099c5b8 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java @@ -1,78 +1,165 @@ package com.xtremelabs.imageutils; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.URL; + import android.graphics.Bitmap; +import android.graphics.Point; +import android.os.AsyncTask; import android.test.AndroidTestCase; +import android.test.MoreAsserts; +import android.widget.ImageView; + +import com.xtremelabs.imageutils.DiskLRUCacher.FileFormatException; +import com.xtremelabs.imageutils.ImageLoader.Options; +import com.xtremelabs.imageutils.ImageLoader.Options.ScalingPreference; +import com.xtremelabs.imageutils.NetworkToDiskInterface.ImageDownloadResult; +import com.xtremelabs.imageutils.NetworkToDiskInterface.ImageDownloadResult.Result; public class DiskCacheTests extends AndroidTestCase { - private ImageSystemDiskCache mDiskCache; + private static final String IMAGE_URL = "http://placekitten.com/400/600"; + + private static final CacheRequest CACHE_REQUEST = new CacheRequest(IMAGE_URL); + + private DiskCache mDiskCache; @Override protected void setUp() throws Exception { super.setUp(); - mDiskCache = new DiskCache(getContext(), mImageDiskObserver) { - }; + if (mDiskCache == null) + mDiskCache = new DiskCache(getContext(), mImageDiskObserver); + + mDiskCache.clear(); } - + public void testDownloadImageFromInputStream() { - fail(); + downloadImage(); } public void testIsCached() { - fail(); + assertFalse(mDiskCache.isCached(CACHE_REQUEST)); + + downloadImage(); + + assertTrue(mDiskCache.isCached(CACHE_REQUEST)); } public void testGetSampleSize() { - fail(); - } + assertEquals(-1, mDiskCache.getSampleSize(CACHE_REQUEST)); - public void testBumpOnDisk() { - fail(); + downloadImage(); + populateImageDetails(); + + MoreAsserts.assertNotEqual(-1, mDiskCache.getSampleSize(CACHE_REQUEST)); } public void testSetDiskCacheSize() { fail(); + // TODO how to test? } public void testGetImageDimensions() { - fail(); + assertNull(mDiskCache.getImageDimensions(CACHE_REQUEST)); + + downloadImage(); + populateImageDetails(); + + assertNotNull(mDiskCache.getImageDimensions(CACHE_REQUEST)); } public void testInvalidateUri() { fail(); + // TODO how to test this? } - public void testGetBitmapSynchronouslyFromDisk() { - fail(); + public void testGetBitmapSynchronouslyFromDisk() throws FileNotFoundException, FileFormatException { + downloadImage(); + populateImageDetails(); + + DecodeSignature decodeSignature = new DecodeSignature(IMAGE_URL, 1, null); + Bitmap bitmap = mDiskCache.getBitmapSynchronouslyFromDisk(CACHE_REQUEST, decodeSignature); + assertNotNull(bitmap); } public void testCalculateAndSaveImageDetails() { - fail(); + assertFalse(mDiskCache.isCached(CACHE_REQUEST)); + + downloadImage(); + + assertEquals(1, mDiskCache.getSampleSize(CACHE_REQUEST)); + + populateImageDetails(); + + Options options = new Options(); + options.heightBounds = 300; + options.widthBounds = 200; + options.scalingPreference = ScalingPreference.MATCH_TO_SMALLER_DIMENSION; + CacheRequest cacheRequest = new CacheRequest(IMAGE_URL, getScalingInfo(null, options)); + assertEquals(2, mDiskCache.getSampleSize(cacheRequest)); } - public void testGetDetailsPrioritizable() { - fail(); + ScalingInfo getScalingInfo(ImageView imageView, final Options options) { + ScalingInfo scalingInfo = new ScalingInfo(); + if (options.overrideSampleSize != null) { + scalingInfo.sampleSize = options.overrideSampleSize; + return scalingInfo; + } + + Integer width = options.widthBounds; + Integer height = options.heightBounds; + + if (options.autoDetectBounds && imageView != null) { + Point viewBounds = ViewDimensionsUtil.getImageViewDimensions(imageView); + + width = getBounds(width, viewBounds.x); + height = getBounds(height, viewBounds.y); + } + + scalingInfo.width = width; + scalingInfo.height = height; + return scalingInfo; } - public void testGetDecodePrioritizable() { - fail(); + private static Integer getBounds(Integer currentDimension, int viewDimension) { + if (viewDimension != -1) { + if (currentDimension == null) { + currentDimension = viewDimension; + } else { + currentDimension = Math.min(currentDimension, viewDimension); + } + } + return currentDimension; + } + + private InputStream getInputStream() { + try { + return new URL(IMAGE_URL).openStream(); + } catch (Exception e) {} + return null; + } + + private void downloadImage() { + ImageDownloadResult result = mDiskCache.downloadImageFromInputStream(IMAGE_URL, getInputStream()); + assertEquals(result.getResult(), Result.SUCCESS); + } + + private void populateImageDetails() { + mDiskCache.cacheImageDetails(CACHE_REQUEST); } private final ImageDiskObserver mImageDiskObserver = new ImageDiskObserver() { @Override - public void onImageDetailsRetrieved(String uri) { - } + public void onImageDetailsRetrieved(String uri) {} @Override - public void onImageDetailsRequestFailed(String uri, String errorMessage) { - } + public void onImageDetailsRequestFailed(String uri, String errorMessage) {} @Override - public void onImageDecoded(DecodeSignature decodeSignature, Bitmap bitmap, ImageReturnedFrom returnedFrom) { - } + public void onImageDecoded(DecodeSignature decodeSignature, Bitmap bitmap, ImageReturnedFrom returnedFrom) {} @Override - public void onImageDecodeFailed(DecodeSignature decodeSignature, String error) { - } + public void onImageDecodeFailed(DecodeSignature decodeSignature, String error) {} }; } diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java index cfa002a..3439014 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java @@ -1,5 +1,6 @@ package com.xtremelabs.imageutils; +import android.os.SystemClock; import android.test.AndroidTestCase; import com.xtremelabs.imageutils.ImageSystemDatabase.ImageSystemDatabaseObserver; @@ -39,6 +40,20 @@ public void testEndWrite() { assertFalse(entry.hasDetails()); } + public void testBump() { + mDatabase.beginWrite(TEST_URI_1); + mDatabase.endWrite(TEST_URI_1); + + ImageEntry entry = mDatabase.getEntry(TEST_URI_1); + long lastAccessedTime = entry.lastAccessedTime; + + SystemClock.sleep(10); + + mDatabase.bump(TEST_URI_1); + + assertTrue(lastAccessedTime != entry.lastAccessedTime); + } + public void testWriteFailed() { mDatabase.beginWrite(TEST_URI_1); ImageEntry entry = mDatabase.getEntry(TEST_URI_1); @@ -78,6 +93,10 @@ public void testClear() { mDatabase.submitDetails(TEST_URI_3, new Dimensions(0, 0)); + assertNotNull(mDatabase.getEntry(TEST_URI_1)); + assertNotNull(mDatabase.getEntry(TEST_URI_2)); + assertNotNull(mDatabase.getEntry(TEST_URI_3)); + mDatabase.clear(); assertNull(mDatabase.getEntry(TEST_URI_1)); @@ -102,6 +121,8 @@ public void testJournalingEvictionWithNoWrite() { assertFalse(entry.hasDetails()); assertEquals(TEST_URI_1, entry.uri); + mDatabase.close(); + ImageSystemDatabase database = new ImageSystemDatabase(mDatabaseObserver); entry = database.getEntry(TEST_URI_1); assertNull(entry); @@ -134,25 +155,30 @@ public void testJournalingWithDetailsRequests() { fail(); } + public void testDetailsOnDatabaseRecovery() { + fail(); + } + public void testJournalingLruEvictions() { + // TODO on reboot make sure we have not exceeded file system size fail(); } - public void testDetailsOnDatabaseRecovery() { + public void testNoImageOnDiskTriggerDownload() { fail(); + // should probably be somewhere else } - public void testNoEvictionsForIncompleteDownloads() { + public void testNoLruEvictionsForIncompleteDownloads() { fail(); + // make sure things that have not finished writing are not considered for eviction } private final ImageSystemDatabaseObserver mDatabaseObserver = new ImageSystemDatabaseObserver() { @Override - public void onDetailsRequired(String filename) { - } + public void onDetailsRequired(String filename) {} @Override - public void onBadJournalEntry(String filename) { - } + public void onBadJournalEntry(String filename) {} }; } diff --git a/xl_image_utils_lib/project.properties b/xl_image_utils_lib/project.properties index 36f1594..484dab0 100644 --- a/xl_image_utils_lib/project.properties +++ b/xl_image_utils_lib/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-15 +target=android-17 android.library=true diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java index 63599b8..f7af357 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java @@ -1,29 +1,40 @@ package com.xtremelabs.imageutils; +import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.net.URISyntaxException; +import java.util.Map; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import com.xtremelabs.imageutils.DiskLRUCacher.FileFormatException; +import com.xtremelabs.imageutils.ImageSystemDatabase.ImageSystemDatabaseObserver; import com.xtremelabs.imageutils.NetworkToDiskInterface.ImageDownloadResult.Result; class DiskCache implements ImageSystemDiskCache { + private static final int MAX_PERMANENT_STORAGE_IMAGE_DIMENSIONS_CACHED = 25; private final Context mContext; private final ImageDiskObserver mImageDiskObserver; + private final Map mPermanentStorageMap = new LRUMap(34, MAX_PERMANENT_STORAGE_IMAGE_DIMENSIONS_CACHED); private ImageSystemDatabase mImageSystemDatabase; private FileSystemManager mFileSystemManager; DiskCache(Context context, ImageDiskObserver imageDiskObserver) { - mContext = context; + mContext = context.getApplicationContext(); mImageDiskObserver = imageDiskObserver; + mFileSystemManager = new FileSystemManager("img", mContext); + mImageSystemDatabase = new ImageSystemDatabase(mImageSystemDatabaseObserver); + mImageSystemDatabase.init(mContext); } - + @Override public ImageDownloadResult downloadImageFromInputStream(String uri, InputStream inputStream) { ImageDownloadResult result; @@ -34,6 +45,7 @@ public ImageDownloadResult downloadImageFromInputStream(String uri, InputStream mFileSystemManager.loadStreamToFile(inputStream, imageEntry.getFileName()); mImageSystemDatabase.endWrite(uri); result = new ImageDownloadResult(Result.SUCCESS); + } catch (IOException e) { mImageSystemDatabase.writeFailed(uri); result = new ImageDownloadResult(Result.FAILURE, "Failed to download image to disk! IOException caught. Error message: " + e.getMessage()); @@ -44,20 +56,30 @@ public ImageDownloadResult downloadImageFromInputStream(String uri, InputStream @Override public boolean isCached(CacheRequest cacheRequest) { - // TODO Auto-generated method stub - return false; + boolean isCached; + String uri = cacheRequest.getUri(); + if (cacheRequest.isFileSystemRequest()) { + isCached = mPermanentStorageMap.get(uri) != null; + } else { + ImageEntry entry = mImageSystemDatabase.getEntry(uri); + isCached = entry != null && entry.onDisk; + } + + return isCached; } @Override - public int getSampleSize(CacheRequest imageRequest) { - // TODO Auto-generated method stub - return 0; + public int getSampleSize(CacheRequest cacheRequest) { + Dimensions dimensions = getImageDimensions(cacheRequest); + if (dimensions == null) + return -1; + + return SampleSizeCalculationUtility.calculateSampleSize(cacheRequest, dimensions); } @Override public void bumpOnDisk(String uri) { - // TODO Auto-generated method stub - + mImageSystemDatabase.bump(uri); } @Override @@ -68,38 +90,184 @@ public void setDiskCacheSize(long sizeInBytes) { @Override public Dimensions getImageDimensions(CacheRequest cacheRequest) { - // TODO Auto-generated method stub - return null; + String uri = cacheRequest.getUri(); + Dimensions dimensions; + if (cacheRequest.isFileSystemRequest()) { + dimensions = mPermanentStorageMap.get(uri); + } else { + ImageEntry entry = mImageSystemDatabase.getEntry(uri); + dimensions = entry != null ? new Dimensions(entry.sizeX, entry.sizeY) : null; + } + return dimensions; } @Override public void invalidateFileSystemUri(String uri) { - // TODO Auto-generated method stub - + mPermanentStorageMap.remove(uri); } @Override public Bitmap getBitmapSynchronouslyFromDisk(CacheRequest cacheRequest, DecodeSignature decodeSignature) throws FileNotFoundException, FileFormatException { - // TODO Auto-generated method stub - return null; + int sampleSize = decodeSignature.sampleSize; + Bitmap.Config bitmapConfig = decodeSignature.bitmapConfig; + + File file = getFile(cacheRequest); + FileInputStream fileInputStream = new FileInputStream(file); + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = sampleSize; + opts.inPreferredConfig = bitmapConfig; + + Bitmap bitmap = BitmapFactory.decodeStream(fileInputStream, null, opts); + if (fileInputStream != null) { + try { + fileInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (bitmap == null) { + file.delete(); + throw new FileFormatException(); + } + return bitmap; } @Override public void calculateAndSaveImageDetails(CacheRequest cacheRequest) throws URISyntaxException, FileNotFoundException { - // TODO Auto-generated method stub + String uri = cacheRequest.getUri(); + File file = getFile(cacheRequest); + + Dimensions dimensions = getImageDimensionsFromDisk(file); + + if (cacheRequest.isFileSystemRequest()) { + mPermanentStorageMap.put(uri, dimensions); + } else { + mImageSystemDatabase.submitDetails(uri, dimensions); + // TODO clear least used from cache? + } + } + + private File getFile(CacheRequest cacheRequest) throws FileNotFoundException { + String uri = cacheRequest.getUri(); + File file; + if (cacheRequest.isFileSystemRequest()) { + try { + file = new File(new URI(uri.replace(" ", "%20")).getPath()); + } catch (URISyntaxException e) { + e.printStackTrace(); + throw new FileNotFoundException(); + } + } else { + ImageEntry entry = mImageSystemDatabase.getEntry(uri); + if (entry == null) + throw new FileNotFoundException(); + file = mFileSystemManager.getFile(entry.getFileName()); + } + return file; + } + + private static Dimensions getImageDimensionsFromDisk(File file) throws FileNotFoundException { + FileInputStream fileInputStream = null; + try { + fileInputStream = new FileInputStream(file); + BitmapFactory.Options o = new BitmapFactory.Options(); + o.inJustDecodeBounds = true; + BitmapFactory.decodeStream(fileInputStream, null, o); + return new Dimensions(o.outWidth, o.outHeight); + } catch (FileNotFoundException e) { + e.printStackTrace(); + throw e; + + } finally { + if (fileInputStream != null) { + try { + fileInputStream.close(); + } catch (IOException e) {} + } + } } @Override - public Prioritizable getDetailsPrioritizable(CacheRequest imageRequest) { - // TODO Auto-generated method stub - return null; + public Prioritizable getDetailsPrioritizable(final CacheRequest cacheRequest) { + return new DefaultPrioritizable(cacheRequest, new Request(cacheRequest.getUri())) { + @Override + public void execute() { + cacheImageDetails(cacheRequest); + } + }; + } + + void cacheImageDetails(CacheRequest cacheRequest) { + String uri = cacheRequest.getUri(); + try { + calculateAndSaveImageDetails(cacheRequest); + + mImageDiskObserver.onImageDetailsRetrieved(uri); + } catch (URISyntaxException e) { + mImageDiskObserver.onImageDetailsRequestFailed(uri, "URISyntaxException caught when attempting to retrieve image details. URI: " + uri); + } catch (FileNotFoundException e) { + mImageDiskObserver.onImageDetailsRequestFailed(uri, "Image file not found. URI: " + uri); + } } @Override - public Prioritizable getDecodePrioritizable(CacheRequest cacheRequest, DecodeSignature decodeSignature, ImageReturnedFrom imageReturnedFrom) { - // TODO Auto-generated method stub - return null; + public Prioritizable getDecodePrioritizable(final CacheRequest cacheRequest, final DecodeSignature decodeSignature, final ImageReturnedFrom imageReturnedFrom) { + return new DefaultPrioritizable(cacheRequest, new Request(decodeSignature)) { + @Override + public void execute() { + boolean failed = false; + String errorMessage = null; + Bitmap bitmap = null; + try { + bitmap = getBitmapSynchronouslyFromDisk(cacheRequest, decodeSignature); + + } catch (FileNotFoundException e) { + failed = true; + errorMessage = "FileNotFoundException -- Disk decode failed with error message: " + e.getMessage(); + + } catch (FileFormatException e) { + failed = true; + errorMessage = "FileFormatException -- Disk decode failed with error message: " + e.getMessage(); + } + + if (!failed) { + mImageDiskObserver.onImageDecoded(decodeSignature, bitmap, imageReturnedFrom); + + } else if (cacheRequest.isFileSystemRequest()) { + mPermanentStorageMap.remove(decodeSignature.uri); + mImageDiskObserver.onImageDecodeFailed(decodeSignature, errorMessage); + + } else { + try { + mFileSystemManager.deleteFile(getFile(cacheRequest)); + } catch (FileNotFoundException e) {} + mImageSystemDatabase.deleteEntry(decodeSignature.uri); + mImageDiskObserver.onImageDecodeFailed(decodeSignature, errorMessage); + } + // TODO need to re-start entire process if image was supposed to be on disk but wasn't + } + }; + } + + private final ImageSystemDatabaseObserver mImageSystemDatabaseObserver = new ImageSystemDatabaseObserver() { + + @Override + public void onDetailsRequired(String filename) { + cacheImageDetails(new CacheRequest(filename)); + // TODO is this right? + } + + @Override + public void onBadJournalEntry(String filename) { + mFileSystemManager.deleteFile(filename); + } + }; + + void clear() { + mImageSystemDatabase.clear(); + mFileSystemManager.clearDirectory(); } } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskLRUCacher.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskLRUCacher.java index ddfeb30..ffd38f8 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskLRUCacher.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskLRUCacher.java @@ -280,9 +280,6 @@ private static Dimensions getImageDimensionsFromDisk(File file) throws FileNotFo } public static class FileFormatException extends Exception { - /** - * - */ private static final long serialVersionUID = -2180782787028503586L; } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DisplayUtility.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DisplayUtility.java index 02c4b42..8c10b5a 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DisplayUtility.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DisplayUtility.java @@ -16,35 +16,12 @@ package com.xtremelabs.imageutils; -import android.annotation.SuppressLint; import android.content.Context; -import android.graphics.Point; -import android.os.Build; -import android.view.Display; -import android.view.WindowManager; +import android.util.DisplayMetrics; class DisplayUtility { - private volatile Dimensions displaySize; - - @SuppressLint("NewApi") - @SuppressWarnings("deprecation") public Dimensions getDisplaySize(Context applicationContext) { - if (displaySize == null) { - Display display = ((WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - - if (Build.VERSION.SDK_INT < 13) { - // These method calls are used before API level 13. - displaySize = new Dimensions(display.getWidth(), display.getHeight()); - } else { - Point size = new Point(); - display.getSize(size); - displaySize = new Dimensions(size.x, size.y); - } - } - return displaySize; - } - - public void notifyConfigurationChanged() { - displaySize = null; + DisplayMetrics metrics = applicationContext.getResources().getDisplayMetrics(); + return new Dimensions(metrics.widthPixels, metrics.heightPixels); } } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/FileSystemManager.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/FileSystemManager.java index c48610c..81982a7 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/FileSystemManager.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/FileSystemManager.java @@ -134,6 +134,10 @@ private void deleteDirectory(File directory) { public void deleteFile(String name) { File file = getFile(name); + deleteFile(file); + } + + public void deleteFile(File file) { if (!file.isDirectory()) { file.delete(); } else { diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageLoader.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageLoader.java index dcdf277..d95481c 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageLoader.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageLoader.java @@ -174,10 +174,6 @@ public void destroy() { } } - public void notifyConfigurationChanged() { - mDisplayUtility.notifyConfigurationChanged(); - } - /** * The ImageLoader will default to the options provided here if no other options are provided. * diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java index 946d576..a0404fc 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java @@ -14,7 +14,7 @@ import com.xtremelabs.imageutils.db.Database; import com.xtremelabs.imageutils.db.Table; -class ImageSystemDatabase { +class ImageSystemDatabase { // TODO should this be implementing an interface? private final ImagesTable mImagesTable = ImagesTable.getInstance(); private final ImageSystemDatabaseObserver mObserver; private Database mDatabase; @@ -29,8 +29,8 @@ public void init(Context context) { tables.add(mImagesTable); mDatabase = new Database(context, new SQLiteDatabase.CursorFactory() { - @SuppressWarnings("deprecation") @Override + @SuppressWarnings("deprecation") public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { return new SQLiteCursor(db, masterQuery, editTable, query); } @@ -43,6 +43,7 @@ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, Strin public void beginWrite(String uri) { ImageEntry entry = new ImageEntry(); entry.uri = uri; + entry.lastAccessedTime = System.currentTimeMillis(); mImagesTable.insert(entry, mDatabase.getWritableDatabase()); mCache.putEntry(entry); } @@ -50,6 +51,11 @@ public void beginWrite(String uri) { public void endWrite(String uri) { ImageEntry entry = mCache.getEntry(uri); entry.onDisk = true; + mImagesTable.insert(entry, mDatabase.getWritableDatabase()); + } + + public void bump(String uri) { + ImageEntry entry = mCache.getEntry(uri); entry.lastAccessedTime = System.currentTimeMillis(); mImagesTable.insert(entry, mDatabase.getWritableDatabase()); } @@ -61,6 +67,10 @@ public void writeFailed(String uri) { mImagesTable.delete(mDatabase.getWritableDatabase(), whereClause, whereArgs); } + public void deleteEntry(String uri) { + // TODO Auto-generated method stub + } + ImageEntry getEntry(String uri) { return mCache.getEntry(uri); } @@ -127,4 +137,5 @@ interface ImageSystemDatabaseObserver { void onDetailsRequired(String filename); } + } From 3f7673dd692a14f75a58f7ab53d2228da6274ce3 Mon Sep 17 00:00:00 2001 From: Peter Ryan Date: Tue, 2 Jul 2013 13:00:44 -0400 Subject: [PATCH 2/6] MohannadA - Incomplete implementation of LRU disk eviction system - Major refactor --- .../imageutils/ImageSystemDatabaseTests.java | 45 ++++++-- .../com/xtremelabs/imageutils/DiskCache.java | 17 ++- .../com/xtremelabs/imageutils/ImageEntry.java | 3 +- .../imageutils/ImageSystemDatabase.java | 86 +++++++-------- .../xtremelabs/imageutils/ImagesTable.java | 103 +++++++++--------- .../xtremelabs/imageutils/db/Database.java | 8 +- .../com/xtremelabs/imageutils/db/Table.java | 16 ++- 7 files changed, 165 insertions(+), 113 deletions(-) diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java index 3439014..80635cf 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java @@ -16,7 +16,7 @@ protected void setUp() throws Exception { super.setUp(); mDatabase = new ImageSystemDatabase(mDatabaseObserver); - mDatabase.init(getContext()); + mDatabase.init(mContext); mDatabase.clear(); } @@ -71,9 +71,13 @@ public void testHasDetails() { int testSizeY = 10; mDatabase.beginWrite(TEST_URI_1); mDatabase.endWrite(TEST_URI_1); - mDatabase.submitDetails(TEST_URI_1, new Dimensions(testSizeX, testSizeY)); ImageEntry entry = mDatabase.getEntry(TEST_URI_1); + assertFalse(entry.hasDetails()); + + mDatabase.submitDetails(TEST_URI_1, new Dimensions(testSizeX, testSizeY), 100L); + + entry = mDatabase.getEntry(TEST_URI_1); assertNotNull(entry); assertTrue(entry.onDisk); @@ -91,7 +95,7 @@ public void testClear() { mDatabase.endWrite(TEST_URI_2); mDatabase.endWrite(TEST_URI_3); - mDatabase.submitDetails(TEST_URI_3, new Dimensions(0, 0)); + mDatabase.submitDetails(TEST_URI_3, new Dimensions(0, 0), 100L); assertNotNull(mDatabase.getEntry(TEST_URI_1)); assertNotNull(mDatabase.getEntry(TEST_URI_2)); @@ -104,6 +108,23 @@ public void testClear() { assertNull(mDatabase.getEntry(TEST_URI_3)); } + public void testFileSize() { + mDatabase.beginWrite(TEST_URI_1); + mDatabase.beginWrite(TEST_URI_2); + mDatabase.beginWrite(TEST_URI_3); + mDatabase.endWrite(TEST_URI_1); + mDatabase.endWrite(TEST_URI_2); + mDatabase.endWrite(TEST_URI_3); + + assertEquals(0, mDatabase.getTotalFileSize()); + + mDatabase.submitDetails(TEST_URI_1, new Dimensions(0, 0), 100L); + mDatabase.submitDetails(TEST_URI_2, new Dimensions(0, 0), 100L); + mDatabase.submitDetails(TEST_URI_3, new Dimensions(0, 0), 100L); + + assertEquals(300, mDatabase.getTotalFileSize()); + } + public void testStartupDataRecovery() { fail(); } @@ -135,12 +156,21 @@ public void testJournalingEvictionsWithNoWrite() { mDatabase.endWrite(TEST_URI_2); + ImageEntry entry1 = mDatabase.getEntry(TEST_URI_1); + ImageEntry entry2 = mDatabase.getEntry(TEST_URI_2); + ImageEntry entry3 = mDatabase.getEntry(TEST_URI_3); + + assertNotNull(entry1); + assertNotNull(entry2); + assertNotNull(entry3); + mDatabase.close(); ImageSystemDatabase database = new ImageSystemDatabase(mDatabaseObserver); - ImageEntry entry1 = database.getEntry(TEST_URI_1); - ImageEntry entry2 = database.getEntry(TEST_URI_2); - ImageEntry entry3 = database.getEntry(TEST_URI_3); + database.init(mContext); + entry1 = database.getEntry(TEST_URI_1); + entry2 = database.getEntry(TEST_URI_2); + entry3 = database.getEntry(TEST_URI_3); assertNull(entry1); assertNotNull(entry2); @@ -179,6 +209,7 @@ public void testNoLruEvictionsForIncompleteDownloads() { public void onDetailsRequired(String filename) {} @Override - public void onBadJournalEntry(String filename) {} + public void onBadJournalEntry(ImageEntry entry) {} + }; } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java index f7af357..b55cc51 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java @@ -26,6 +26,7 @@ class DiskCache implements ImageSystemDiskCache { private ImageSystemDatabase mImageSystemDatabase; private FileSystemManager mFileSystemManager; + private long mMaxCacheSize = 50 * 1024 * 1024; DiskCache(Context context, ImageDiskObserver imageDiskObserver) { mContext = context.getApplicationContext(); @@ -143,8 +144,15 @@ public void calculateAndSaveImageDetails(CacheRequest cacheRequest) throws URISy if (cacheRequest.isFileSystemRequest()) { mPermanentStorageMap.put(uri, dimensions); } else { - mImageSystemDatabase.submitDetails(uri, dimensions); - // TODO clear least used from cache? + mImageSystemDatabase.submitDetails(uri, dimensions, file.length()); + clearLRUFiles(); + } + } + + private void clearLRUFiles() { + while (mImageSystemDatabase.getTotalFileSize() > mMaxCacheSize) { + ImageEntry entry = mImageSystemDatabase.removeLRU(); + mFileSystemManager.deleteFile(entry.getFileName()); } } @@ -256,12 +264,11 @@ public void execute() { @Override public void onDetailsRequired(String filename) { cacheImageDetails(new CacheRequest(filename)); - // TODO is this right? } @Override - public void onBadJournalEntry(String filename) { - mFileSystemManager.deleteFile(filename); + public void onBadJournalEntry(ImageEntry entry) { + mFileSystemManager.deleteFile(entry.getFileName()); } }; diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageEntry.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageEntry.java index 21ed1f0..d82da08 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageEntry.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageEntry.java @@ -8,12 +8,13 @@ class ImageEntry { public long lastAccessedTime = creationTime; public int sizeX = -1; public int sizeY = -1; + public long fileSize; String getFileName() { return Long.toString(id); } public boolean hasDetails() { - return sizeX != -1 || sizeY != -1; + return sizeX != -1 || sizeY != -1 || fileSize != 0; } } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java index a0404fc..a479c28 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java @@ -5,12 +5,9 @@ import android.content.Context; import android.database.Cursor; -import android.database.sqlite.SQLiteCursor; -import android.database.sqlite.SQLiteCursorDriver; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQuery; import android.util.Log; +import com.xtremelabs.imageutils.ImagesTable.Columns; import com.xtremelabs.imageutils.db.Database; import com.xtremelabs.imageutils.db.Table; @@ -28,14 +25,7 @@ public void init(Context context) { List> tables = new ArrayList>(); tables.add(mImagesTable); - mDatabase = new Database(context, new SQLiteDatabase.CursorFactory() { - @Override - @SuppressWarnings("deprecation") - public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { - return new SQLiteCursor(db, masterQuery, editTable, query); - } - }, tables); - + mDatabase = new Database(context, tables); populateCache(); performBadFileCheck(); } @@ -44,85 +34,81 @@ public void beginWrite(String uri) { ImageEntry entry = new ImageEntry(); entry.uri = uri; entry.lastAccessedTime = System.currentTimeMillis(); - mImagesTable.insert(entry, mDatabase.getWritableDatabase()); + mImagesTable.insert(mDatabase.getWritableDatabase(), entry); mCache.putEntry(entry); } public void endWrite(String uri) { ImageEntry entry = mCache.getEntry(uri); entry.onDisk = true; - mImagesTable.insert(entry, mDatabase.getWritableDatabase()); + mImagesTable.insert(mDatabase.getWritableDatabase(), entry); } public void bump(String uri) { ImageEntry entry = mCache.getEntry(uri); entry.lastAccessedTime = System.currentTimeMillis(); - mImagesTable.insert(entry, mDatabase.getWritableDatabase()); + mImagesTable.insert(mDatabase.getWritableDatabase(), entry); } public void writeFailed(String uri) { - ImageEntry entry = mCache.removeEntry(uri); - String whereClause = ImagesTable.Columns.ID.getColumnName() + "=?"; - String[] whereArgs = new String[] { Long.toString(entry.id) }; - mImagesTable.delete(mDatabase.getWritableDatabase(), whereClause, whereArgs); + deleteEntry(uri); } public void deleteEntry(String uri) { - // TODO Auto-generated method stub + mCache.removeEntry(uri); + deleteRow(uri); } ImageEntry getEntry(String uri) { return mCache.getEntry(uri); } + long getTotalFileSize() { + String[] columns = new String[] { "SUM(" + Columns.FILE_SIZE.getName() + ")" }; + Cursor cursor = mImagesTable.selectFromTable(mDatabase.getReadableDatabase(), columns, null, null, null, null, null, null); + cursor.moveToFirst(); + return cursor.getLong(0); + } + void clear() { mCache.clear(); mImagesTable.onClear(mDatabase.getWritableDatabase()); } - void submitDetails(String uri, Dimensions dimensions) { + void submitDetails(String uri, Dimensions dimensions, long fileSize) { ImageEntry entry = mCache.getEntry(uri); entry.sizeX = dimensions.width; entry.sizeY = dimensions.height; - mImagesTable.insert(entry, mDatabase.getWritableDatabase()); + entry.fileSize = fileSize; + mImagesTable.insert(mDatabase.getWritableDatabase(), entry); } private void populateCache() { Cursor allEntries = mImagesTable.selectAllFromTable(mDatabase.getReadableDatabase()); Log.d("JAMIE", "Entries in database: " + allEntries.getCount()); while (allEntries.moveToNext()) { - ImageEntry entry = new ImageEntry(); - entry.id = allEntries.getLong(allEntries.getColumnIndex(ImagesTable.Columns.ID.getColumnName())); - entry.uri = allEntries.getString(allEntries.getColumnIndex(ImagesTable.Columns.URI.getColumnName())); - entry.creationTime = allEntries.getLong(allEntries.getColumnIndex(ImagesTable.Columns.CREATION_TIME.getColumnName())); - entry.lastAccessedTime = allEntries.getLong(allEntries.getColumnIndex(ImagesTable.Columns.LAST_ACCESSED_TIME.getColumnName())); - entry.onDisk = allEntries.getInt(allEntries.getColumnIndex(ImagesTable.Columns.ON_DISK.getColumnName())) == 1; - entry.sizeX = allEntries.getInt(allEntries.getColumnIndex(ImagesTable.Columns.SIZE_X.getColumnName())); - entry.sizeY = allEntries.getInt(allEntries.getColumnIndex(ImagesTable.Columns.SIZE_Y.getColumnName())); - + ImageEntry entry = ImagesTable.getEntryFromCursor(allEntries); mCache.putEntry(entry); - Log.d("JAMIE", "Recovered URI: " + entry.uri + ", ONDISK: " + entry.onDisk); } } private void performBadFileCheck() { - String onDiskColumn = ImagesTable.Columns.ON_DISK.getColumnName(); + String onDiskColumn = ImagesTable.Columns.ON_DISK.getName(); Cursor cursor = mImagesTable.selectFromTable(mDatabase.getReadableDatabase(), onDiskColumn + "=?", new String[] { "0" }); while (cursor.moveToNext()) { - Log.d("JAMIE", "Deleting an item. URI: " + cursor.getString(cursor.getColumnIndex(ImagesTable.Columns.URI.getColumnName()))); - int columnIndex = cursor.getColumnIndex(ImagesTable.Columns.ID.getColumnName()); - int badImageEntryId = cursor.getInt(columnIndex); - String filename = Integer.toString(badImageEntryId); - - mObserver.onBadJournalEntry(filename); - deleteRow(badImageEntryId); + ImageEntry entry = ImagesTable.getEntryFromCursor(cursor); + Log.d("JAMIE", "Deleting an item. URI: " + entry.uri); + deleteEntry(entry.uri); + mObserver.onBadJournalEntry(entry); } } - private void deleteRow(int badImageEntryId) { - mImagesTable.delete(mDatabase.getWritableDatabase(), ImagesTable.Columns.ID.getColumnName() + "=?", new String[] { Integer.toString(badImageEntryId) }); + private void deleteRow(String uri) { + String whereClause = ImagesTable.Columns.URI.getName() + "=?"; + String[] whereArgs = new String[] { uri }; + mImagesTable.delete(mDatabase.getWritableDatabase(), whereClause, whereArgs); } /** @@ -133,9 +119,23 @@ void close() { } interface ImageSystemDatabaseObserver { - void onBadJournalEntry(String filename); + void onBadJournalEntry(ImageEntry entry); void onDetailsRequired(String filename); } + public ImageEntry removeLRU() { + String orderBy = Columns.LAST_ACCESSED_TIME + " ASC"; + String selection = Columns.ON_DISK + "=?"; + Cursor cursor = mImagesTable.selectFromTable(mDatabase.getReadableDatabase(), null, selection, new String[] { "1" }, null, null, orderBy, Integer.toString(0)); + + ImageEntry entry = null; + if (cursor.moveToFirst()) { + entry = ImagesTable.getEntryFromCursor(cursor); + deleteEntry(entry.uri); + } + + return entry; + } + } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java index 4dbce47..00d6285 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java @@ -3,14 +3,33 @@ import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; -import android.util.Log; import com.xtremelabs.imageutils.db.Table; class ImagesTable extends Table { + private static final String TABLE_NAMES = "images"; + + // must remain in same order as Columns enum + private static final String[] COLUMNS = { + "_id", + "uri", + "on_disk", + "creation_time", + "last_accessed_time", + "size_x", + "size_y", + "fileSize" }; + enum Columns { - ID(0), URI(1), ON_DISK(2), CREATION_TIME(3), LAST_ACCESSED_TIME(4), SIZE_X(5), SIZE_Y(6); + ID(0), + URI(1), + ON_DISK(2), + CREATION_TIME(3), + LAST_ACCESSED_TIME(4), + SIZE_X(5), + SIZE_Y(6), + FILE_SIZE(7); private final int mIndex; @@ -18,8 +37,8 @@ private Columns(int index) { mIndex = index; } - String getColumnName() { - return mColumns[mIndex]; + String getName() { + return COLUMNS[mIndex]; } } @@ -27,80 +46,66 @@ private static class InstanceHolder { private static ImagesTable sInstance = new ImagesTable(); } - private final static String[] mColumns = { "_id", "uri", "on_disk", "creation_time", "last_accessed_time", "size_x", "size_y" }; - private final String mTableName = "images"; - static ImagesTable getInstance() { return InstanceHolder.sInstance; } @Override public String[] getColumns() { - return mColumns; + return COLUMNS; } @Override public String getTableName() { - return mTableName; - } - - @Override - public void insert(ImageEntry item, SQLiteDatabase db) { - ContentValues contentValues = new ContentValues(); - contentValues.put(mColumns[1], item.uri); - contentValues.put(mColumns[2], item.onDisk); - contentValues.put(mColumns[3], item.creationTime); - contentValues.put(mColumns[4], item.lastAccessedTime); - contentValues.put(mColumns[5], item.sizeX); - contentValues.put(mColumns[6], item.sizeY); - Log.d("JAMIE", "Insertion result: " + db.insertWithOnConflict(mTableName, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE)); + return TABLE_NAMES; } @Override protected String getColumnsForCreation() { - StringBuilder builder = new StringBuilder(); - builder.append(mColumns[0] + " INTEGER PRIMARY KEY AUTOINCREMENT, "); - builder.append(mColumns[1] + " TEXT, "); - builder.append(mColumns[2] + " INTEGER, "); - builder.append(mColumns[3] + " BIGINT, "); - builder.append(mColumns[4] + " BIGINT, "); - builder.append(mColumns[5] + " INTEGER, "); - builder.append(mColumns[6] + " INTEGER, "); - return builder.toString(); + String columns = COLUMNS[0] + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMNS[1] + " TEXT, " + COLUMNS[2] + " INTEGER, " + COLUMNS[3] + " INTEGER, " + COLUMNS[4] + " INTEGER, " + COLUMNS[5] + + " INTEGER, " + COLUMNS[6] + " INTEGER, " + COLUMNS[7] + " INTEGER, "; + return columns; } @Override protected String getUniqueString() { - return mColumns[0] + ", " + mColumns[1]; + return COLUMNS[1]; } @Override protected ContentValues toContentValues(ImageEntry item) { ContentValues contentValues = new ContentValues(); - contentValues.put(mColumns[0], item.id); - contentValues.put(mColumns[1], item.uri); - contentValues.put(mColumns[2], item.onDisk); - contentValues.put(mColumns[3], item.creationTime); - contentValues.put(mColumns[4], item.lastAccessedTime); - contentValues.put(mColumns[5], item.sizeX); - contentValues.put(mColumns[6], item.sizeY); + // id is generated by DB, not model + contentValues.put(COLUMNS[1], item.uri); + contentValues.put(COLUMNS[2], item.onDisk ? 1 : 0); + contentValues.put(COLUMNS[3], item.creationTime); + contentValues.put(COLUMNS[4], item.lastAccessedTime); + contentValues.put(COLUMNS[5], item.sizeX); + contentValues.put(COLUMNS[6], item.sizeY); + contentValues.put(COLUMNS[7], item.fileSize); return contentValues; } - public ImageEntry getEntry(String uri, SQLiteDatabase db) { + public ImageEntry getEntry(SQLiteDatabase db, String uri) { ImageEntry entry = null; - Cursor cursor = selectFromTable(db, Columns.URI.getColumnName() + "=?", new String[] { uri }); - if (cursor.getCount() == 1) { - entry = new ImageEntry(); - entry.id = cursor.getLong(cursor.getColumnIndex(Columns.ID.getColumnName())); - entry.uri = cursor.getString(cursor.getColumnIndex(Columns.URI.getColumnName())); - entry.creationTime = cursor.getLong(cursor.getColumnIndex(Columns.CREATION_TIME.getColumnName())); - entry.lastAccessedTime = cursor.getLong(cursor.getColumnIndex(Columns.LAST_ACCESSED_TIME.getColumnName())); - entry.onDisk = cursor.getInt(cursor.getColumnIndex(Columns.ON_DISK.getColumnName())) == 1; - entry.sizeX = cursor.getInt(cursor.getColumnIndex(Columns.SIZE_X.getColumnName())); - entry.sizeY = cursor.getInt(cursor.getColumnIndex(Columns.SIZE_Y.getColumnName())); + Cursor cursor = selectFromTable(db, Columns.URI.getName() + "=?", new String[] { uri }); + if (cursor.moveToFirst()) { + entry = getEntryFromCursor(cursor); } return entry; } + + public static ImageEntry getEntryFromCursor(Cursor cursor) { + ImageEntry entry = new ImageEntry(); + entry.id = cursor.getLong(0); + entry.uri = cursor.getString(1); + entry.creationTime = cursor.getLong(2); + entry.lastAccessedTime = cursor.getLong(3); + entry.onDisk = cursor.getInt(4) == 1; + entry.sizeX = cursor.getInt(5); + entry.sizeY = cursor.getInt(6); + entry.fileSize = cursor.getLong(7); + return entry; + } } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Database.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Database.java index 041db3e..2e18cf8 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Database.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Database.java @@ -14,10 +14,14 @@ public class Database extends SQLiteOpenHelper { private final List> mTables; + public Database(Context context, List> tables) { + this(context, null, tables); + } + public Database(Context context, CursorFactory factory, List> tables) { super(context, DatabaseConfig.NAME, factory, DatabaseConfig.VERSION); - if (tables == null) - throw new IllegalArgumentException("The list of tables cannot be null!"); + if (tables == null || tables.isEmpty()) + throw new IllegalArgumentException("The list of tables cannot be null or empty!"); mTables = tables; } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Table.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Table.java index a8eac9f..52556c6 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Table.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Table.java @@ -55,24 +55,28 @@ public Cursor selectAllFromTable(SQLiteDatabase db) { return db.query(getTableName(), getColumns(), null, null, null, null, null); } + public Cursor selectFromTable(SQLiteDatabase db, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { + return db.query(getTableName(), columns, selection, selectionArgs, groupBy, having, orderBy, limit); + } + public Cursor selectFromTable(SQLiteDatabase db, String selection, String[] selectArgs) { return db.query(getTableName(), getColumns(), selection, selectArgs, null, null, null); } - public void insert(List items, SQLiteDatabase db) { + public void insert(SQLiteDatabase db, List items) { for (T item : items) { - insert(item, db); + insert(db, item); } } - public void insert(T item, SQLiteDatabase db) { + public void insert(SQLiteDatabase db, T item) { ContentValues values = toContentValues(item); - db.insertWithOnConflict(getTableName(), null, values, SQLiteDatabase.CONFLICT_REPLACE); + db.insert(getTableName(), null, values); } - public void update(T item, String where, String[] whereArgs, SQLiteDatabase db) { + public void update(SQLiteDatabase db, T item, String where, String[] whereArgs) { ContentValues values = toContentValues(item); - db.updateWithOnConflict(getTableName(), values, where, whereArgs, SQLiteDatabase.CONFLICT_REPLACE); + db.update(getTableName(), values, where, whereArgs); } public void delete(SQLiteDatabase db, String whereClause, String[] whereArgs) { From d62f2cc2049b2b6ea191f621428c78f0c2663452 Mon Sep 17 00:00:00 2001 From: xtreme-ray-hu Date: Tue, 2 Jul 2013 16:15:13 -0400 Subject: [PATCH 3/6] MohannadA - Made test more specific for image utils --- .../com/xtremelabs/imageutils/ImageSystemDatabaseTests.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java index 80635cf..d0c0171 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java @@ -123,6 +123,10 @@ public void testFileSize() { mDatabase.submitDetails(TEST_URI_3, new Dimensions(0, 0), 100L); assertEquals(300, mDatabase.getTotalFileSize()); + + mDatabase.submitDetails(TEST_URI_3, new Dimensions(0, 0), 200L); + + assertEquals(400, mDatabase.getTotalFileSize()); } public void testStartupDataRecovery() { From 24e904f3c0e26844fe40edb368c39cc4454ed91d Mon Sep 17 00:00:00 2001 From: Mohannad Abwah Date: Tue, 2 Jul 2013 17:54:07 -0400 Subject: [PATCH 4/6] MohannadA - Implemented setDiskCacheSize and tested it - Fixed bug with removeLRU method in ImageSystemDatabase --- .../xtremelabs/imageutils/DiskCacheTests.java | 25 ++++++++++++++++--- .../com/xtremelabs/imageutils/DiskCache.java | 4 +-- .../imageutils/ImageSystemDatabase.java | 4 +-- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java index 099c5b8..c0a516a 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java @@ -6,7 +6,6 @@ import android.graphics.Bitmap; import android.graphics.Point; -import android.os.AsyncTask; import android.test.AndroidTestCase; import android.test.MoreAsserts; import android.widget.ImageView; @@ -56,8 +55,28 @@ public void testGetSampleSize() { } public void testSetDiskCacheSize() { - fail(); - // TODO how to test? + assertFalse(mDiskCache.isCached(CACHE_REQUEST)); + + downloadImage(); + populateImageDetails(); + + assertTrue(mDiskCache.isCached(CACHE_REQUEST)); + + mDiskCache.setDiskCacheSize(1); + + assertFalse(mDiskCache.isCached(CACHE_REQUEST)); + + downloadImage(); + populateImageDetails(); + + assertFalse(mDiskCache.isCached(CACHE_REQUEST)); + + mDiskCache.setDiskCacheSize(1 * 1024 * 1024); + + downloadImage(); + populateImageDetails(); + + assertTrue(mDiskCache.isCached(CACHE_REQUEST)); } public void testGetImageDimensions() { diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java index b55cc51..ca2362a 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java @@ -85,8 +85,8 @@ public void bumpOnDisk(String uri) { @Override public void setDiskCacheSize(long sizeInBytes) { - // TODO Auto-generated method stub - + mMaxCacheSize = sizeInBytes; + clearLRUFiles(); } @Override diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java index a479c28..a354190 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java @@ -126,8 +126,8 @@ interface ImageSystemDatabaseObserver { public ImageEntry removeLRU() { String orderBy = Columns.LAST_ACCESSED_TIME + " ASC"; - String selection = Columns.ON_DISK + "=?"; - Cursor cursor = mImagesTable.selectFromTable(mDatabase.getReadableDatabase(), null, selection, new String[] { "1" }, null, null, orderBy, Integer.toString(0)); + String selection = Columns.ON_DISK.getName() + "=?"; + Cursor cursor = mImagesTable.selectFromTable(mDatabase.getReadableDatabase(), null, selection, new String[] { "1" }, null, null, orderBy, Integer.toString(1)); ImageEntry entry = null; if (cursor.moveToFirst()) { From e9a7d8cbf5d471daaa553c73c472c19356a12522 Mon Sep 17 00:00:00 2001 From: Mohannad Abwah Date: Wed, 3 Jul 2013 16:01:35 -0400 Subject: [PATCH 5/6] MohannadA - Included teardown in tests - Implemented journaling evictions (including tests) - Basic implementation of image details guarantee (needs to be thrown onto another thread) - testGetSampleSizeWithNoDetailsSaved() - Fixed critical bug with journaling on database system and added test for it - Refactored disk cache initialization into init method (instead of constructor) - Closed cursors that were being used in ImageSystemDatabase - Added test for ensuring journaled data persists correct order - Added test for removeLRU method in ImageSystemDatabase (including bump functionality) - Implemented testNoLruEvictionsForIncompleteDownloads - Removed AUTOINCREMENT from table definition --- xl-image_utils-test_suite/lint.xml | 3 - .../xtremelabs/imageutils/DiskCacheTests.java | 55 +++++++-- .../imageutils/ImageSystemDatabaseTests.java | 106 ++++++++++++++---- .../com/xtremelabs/imageutils/DiskCache.java | 30 ++++- .../imageutils/ImageSystemDatabase.java | 7 +- .../xtremelabs/imageutils/ImagesTable.java | 8 +- 6 files changed, 163 insertions(+), 46 deletions(-) delete mode 100644 xl-image_utils-test_suite/lint.xml diff --git a/xl-image_utils-test_suite/lint.xml b/xl-image_utils-test_suite/lint.xml deleted file mode 100644 index ee0eead..0000000 --- a/xl-image_utils-test_suite/lint.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java index c0a516a..71e4026 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/DiskCacheTests.java @@ -30,6 +30,12 @@ protected void setUp() throws Exception { if (mDiskCache == null) mDiskCache = new DiskCache(getContext(), mImageDiskObserver); + mDiskCache.init(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); mDiskCache.clear(); } @@ -45,15 +51,6 @@ public void testIsCached() { assertTrue(mDiskCache.isCached(CACHE_REQUEST)); } - public void testGetSampleSize() { - assertEquals(-1, mDiskCache.getSampleSize(CACHE_REQUEST)); - - downloadImage(); - populateImageDetails(); - - MoreAsserts.assertNotEqual(-1, mDiskCache.getSampleSize(CACHE_REQUEST)); - } - public void testSetDiskCacheSize() { assertFalse(mDiskCache.isCached(CACHE_REQUEST)); @@ -90,7 +87,45 @@ public void testGetImageDimensions() { public void testInvalidateUri() { fail(); - // TODO how to test this? + } + + public void testJournalingLruEvictions() { + assertFalse(mDiskCache.isCached(CACHE_REQUEST)); + + downloadImage(); + populateImageDetails(); + + assertTrue(mDiskCache.isCached(CACHE_REQUEST)); + + mDiskCache = new DiskCache(mContext, mImageDiskObserver); + mDiskCache.setDiskCacheSizeWithoutClearing(1); + mDiskCache.init(); + + assertFalse(mDiskCache.isCached(CACHE_REQUEST)); + } + + public void testGetSampleSize() { + assertEquals(-1, mDiskCache.getSampleSize(CACHE_REQUEST)); + + downloadImage(); + populateImageDetails(); + + MoreAsserts.assertNotEqual(-1, mDiskCache.getSampleSize(CACHE_REQUEST)); + } + + public void testGetSampleSizeWithNoDetailsSaved() { + assertFalse(mDiskCache.isCached(CACHE_REQUEST)); + + downloadImage(); + + Options options = new Options(); + options.heightBounds = 300; + options.widthBounds = 200; + options.scalingPreference = ScalingPreference.MATCH_TO_SMALLER_DIMENSION; + CacheRequest cacheRequest = new CacheRequest(IMAGE_URL, getScalingInfo(null, options)); + assertEquals(2, mDiskCache.getSampleSize(cacheRequest)); + + fail(); // TODO read comment in getSampleSize } public void testGetBitmapSynchronouslyFromDisk() throws FileNotFoundException, FileFormatException { diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java index d0c0171..64b333f 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java @@ -2,6 +2,7 @@ import android.os.SystemClock; import android.test.AndroidTestCase; +import android.test.MoreAsserts; import com.xtremelabs.imageutils.ImageSystemDatabase.ImageSystemDatabaseObserver; @@ -17,7 +18,12 @@ protected void setUp() throws Exception { mDatabase = new ImageSystemDatabase(mDatabaseObserver); mDatabase.init(mContext); + } + + @Override + protected void tearDown() throws Exception { mDatabase.clear(); + super.tearDown(); } public void testBeginWrite() { @@ -51,7 +57,7 @@ public void testBump() { mDatabase.bump(TEST_URI_1); - assertTrue(lastAccessedTime != entry.lastAccessedTime); + MoreAsserts.assertNotEqual(lastAccessedTime, entry.lastAccessedTime); } public void testWriteFailed() { @@ -129,12 +135,49 @@ public void testFileSize() { assertEquals(400, mDatabase.getTotalFileSize()); } - public void testStartupDataRecovery() { + public void testNoImageOnDiskTriggerDownload() { fail(); + // should probably be somewhere else } public void testStartupDataRecoveryOrdering() { - fail(); + mDatabase.beginWrite(TEST_URI_1); + mDatabase.beginWrite(TEST_URI_2); + mDatabase.beginWrite(TEST_URI_3); + + mDatabase.endWrite(TEST_URI_1); + mDatabase.endWrite(TEST_URI_2); + mDatabase.endWrite(TEST_URI_3); + + mDatabase.close(); + + mDatabase = new ImageSystemDatabase(mDatabaseObserver); + mDatabase.init(mContext); + + assertEquals(mDatabase.removeLRU().uri, TEST_URI_1); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_2); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_3); + assertNull(mDatabase.removeLRU()); + + mDatabase.beginWrite(TEST_URI_1); + mDatabase.beginWrite(TEST_URI_2); + mDatabase.beginWrite(TEST_URI_3); + + mDatabase.endWrite(TEST_URI_1); + mDatabase.endWrite(TEST_URI_2); + mDatabase.endWrite(TEST_URI_3); + + mDatabase.bump(TEST_URI_1); + + mDatabase.close(); + + mDatabase = new ImageSystemDatabase(mDatabaseObserver); + mDatabase.init(mContext); + + assertEquals(mDatabase.removeLRU().uri, TEST_URI_2); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_3); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_1); + assertNull(mDatabase.removeLRU()); } public void testJournalingEvictionWithNoWrite() { @@ -153,6 +196,10 @@ public void testJournalingEvictionWithNoWrite() { assertNull(entry); } + public void testJournalingData() { + fail(); // TODO make sure that data written is same as data read after re-start (seen it fail) + } + public void testJournalingEvictionsWithNoWrite() { mDatabase.beginWrite(TEST_URI_1); mDatabase.beginWrite(TEST_URI_2); @@ -181,31 +228,47 @@ public void testJournalingEvictionsWithNoWrite() { assertNull(entry3); } - public void testJournalingWithDetailsRequest() { - fail(); - } + public void testRemoveLRU() { + mDatabase.beginWrite(TEST_URI_1); + mDatabase.beginWrite(TEST_URI_2); + mDatabase.beginWrite(TEST_URI_3); - public void testJournalingWithDetailsRequests() { - fail(); - } + mDatabase.endWrite(TEST_URI_1); + mDatabase.endWrite(TEST_URI_2); + mDatabase.endWrite(TEST_URI_3); - public void testDetailsOnDatabaseRecovery() { - fail(); - } + assertEquals(mDatabase.removeLRU().uri, TEST_URI_1); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_2); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_3); + assertNull(mDatabase.removeLRU()); - public void testJournalingLruEvictions() { - // TODO on reboot make sure we have not exceeded file system size - fail(); - } + mDatabase.beginWrite(TEST_URI_1); + mDatabase.beginWrite(TEST_URI_2); + mDatabase.beginWrite(TEST_URI_3); - public void testNoImageOnDiskTriggerDownload() { - fail(); - // should probably be somewhere else + mDatabase.endWrite(TEST_URI_1); + mDatabase.endWrite(TEST_URI_2); + mDatabase.endWrite(TEST_URI_3); + + mDatabase.bump(TEST_URI_1); + + assertEquals(mDatabase.removeLRU().uri, TEST_URI_2); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_3); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_1); + assertNull(mDatabase.removeLRU()); } public void testNoLruEvictionsForIncompleteDownloads() { - fail(); - // make sure things that have not finished writing are not considered for eviction + mDatabase.beginWrite(TEST_URI_1); + mDatabase.beginWrite(TEST_URI_2); + mDatabase.beginWrite(TEST_URI_3); + + mDatabase.endWrite(TEST_URI_2); + mDatabase.endWrite(TEST_URI_3); + + assertEquals(mDatabase.removeLRU().uri, TEST_URI_2); + assertEquals(mDatabase.removeLRU().uri, TEST_URI_3); + assertNull(mDatabase.removeLRU()); } private final ImageSystemDatabaseObserver mDatabaseObserver = new ImageSystemDatabaseObserver() { @@ -214,6 +277,5 @@ public void onDetailsRequired(String filename) {} @Override public void onBadJournalEntry(ImageEntry entry) {} - }; } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java index ca2362a..8953c5c 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java @@ -33,7 +33,11 @@ class DiskCache implements ImageSystemDiskCache { mImageDiskObserver = imageDiskObserver; mFileSystemManager = new FileSystemManager("img", mContext); mImageSystemDatabase = new ImageSystemDatabase(mImageSystemDatabaseObserver); + } + + void init() { mImageSystemDatabase.init(mContext); + clearLRUFiles(); } @Override @@ -72,10 +76,19 @@ public boolean isCached(CacheRequest cacheRequest) { @Override public int getSampleSize(CacheRequest cacheRequest) { Dimensions dimensions = getImageDimensions(cacheRequest); - if (dimensions == null) - return -1; + if (dimensions == null) { + cacheImageDetails(cacheRequest); // FIXME needs to be moved somewhere else (this method is called from UI thread) + dimensions = getImageDimensions(cacheRequest); + } + + int sampleSize; + if (dimensions != null) { + sampleSize = SampleSizeCalculationUtility.calculateSampleSize(cacheRequest, dimensions); + } else { + sampleSize = -1; + } - return SampleSizeCalculationUtility.calculateSampleSize(cacheRequest, dimensions); + return sampleSize; } @Override @@ -97,13 +110,14 @@ public Dimensions getImageDimensions(CacheRequest cacheRequest) { dimensions = mPermanentStorageMap.get(uri); } else { ImageEntry entry = mImageSystemDatabase.getEntry(uri); - dimensions = entry != null ? new Dimensions(entry.sizeX, entry.sizeY) : null; + dimensions = entry != null && entry.hasDetails() ? new Dimensions(entry.sizeX, entry.sizeY) : null; } return dimensions; } @Override public void invalidateFileSystemUri(String uri) { + // TODO WATCH OUT FOR SYNCHRONIZATION ISSUES, probably should go through same flow that for 2 identical requests mPermanentStorageMap.remove(uri); } @@ -211,8 +225,8 @@ void cacheImageDetails(CacheRequest cacheRequest) { String uri = cacheRequest.getUri(); try { calculateAndSaveImageDetails(cacheRequest); - mImageDiskObserver.onImageDetailsRetrieved(uri); + } catch (URISyntaxException e) { mImageDiskObserver.onImageDetailsRequestFailed(uri, "URISyntaxException caught when attempting to retrieve image details. URI: " + uri); } catch (FileNotFoundException e) { @@ -272,9 +286,15 @@ public void onBadJournalEntry(ImageEntry entry) { } }; + /** ALL THE METHODS BELOW ARE FOR TESTING PURPOSES ONLY!!!! */ + void clear() { mImageSystemDatabase.clear(); mFileSystemManager.clearDirectory(); } + void setDiskCacheSizeWithoutClearing(long bytes) { + mMaxCacheSize = bytes; + } + } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java index a354190..1555ff4 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java @@ -38,7 +38,7 @@ public void beginWrite(String uri) { mCache.putEntry(entry); } - public void endWrite(String uri) { + public void endWrite(String uri) { // TODO what happens if try to endWrite with corresponding entry in cache? ImageEntry entry = mCache.getEntry(uri); entry.onDisk = true; mImagesTable.insert(mDatabase.getWritableDatabase(), entry); @@ -67,7 +67,9 @@ long getTotalFileSize() { String[] columns = new String[] { "SUM(" + Columns.FILE_SIZE.getName() + ")" }; Cursor cursor = mImagesTable.selectFromTable(mDatabase.getReadableDatabase(), columns, null, null, null, null, null, null); cursor.moveToFirst(); - return cursor.getLong(0); + long total = cursor.getLong(0); + cursor.close(); + return total; } void clear() { @@ -103,6 +105,7 @@ private void performBadFileCheck() { deleteEntry(entry.uri); mObserver.onBadJournalEntry(entry); } + cursor.close(); } private void deleteRow(String uri) { diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java index 00d6285..d3b7e8f 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java @@ -62,7 +62,7 @@ public String getTableName() { @Override protected String getColumnsForCreation() { - String columns = COLUMNS[0] + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMNS[1] + " TEXT, " + COLUMNS[2] + " INTEGER, " + COLUMNS[3] + " INTEGER, " + COLUMNS[4] + " INTEGER, " + COLUMNS[5] + String columns = COLUMNS[0] + " INTEGER PRIMARY KEY, " + COLUMNS[1] + " TEXT, " + COLUMNS[2] + " INTEGER, " + COLUMNS[3] + " INTEGER, " + COLUMNS[4] + " INTEGER, " + COLUMNS[5] + " INTEGER, " + COLUMNS[6] + " INTEGER, " + COLUMNS[7] + " INTEGER, "; return columns; } @@ -100,9 +100,9 @@ public static ImageEntry getEntryFromCursor(Cursor cursor) { ImageEntry entry = new ImageEntry(); entry.id = cursor.getLong(0); entry.uri = cursor.getString(1); - entry.creationTime = cursor.getLong(2); - entry.lastAccessedTime = cursor.getLong(3); - entry.onDisk = cursor.getInt(4) == 1; + entry.onDisk = cursor.getInt(2) == 1; + entry.creationTime = cursor.getLong(3); + entry.lastAccessedTime = cursor.getLong(4); entry.sizeX = cursor.getInt(5); entry.sizeY = cursor.getInt(6); entry.fileSize = cursor.getLong(7); From 87d3641d9b3234fc8a56c2f8375c82b66a19134c Mon Sep 17 00:00:00 2001 From: Mohannad Abwah Date: Wed, 17 Jul 2013 20:10:38 -0400 Subject: [PATCH 6/6] MohannadA - Added SHOULD_RUN flag to AdvancedMemoryCacherTests - Fixed some typos - Re-wrote ViewDimensionsUtil - Minor refactor of DisplayUtility - Added expiry to ImageSystemDatabase - Refactored ImageSystemDatabase - Fixed potential problem with missing entry ID - Table object now returns appropriate values from DB methods (insert/update/delete) --- .../imageutils/AdvancedMemoryCacherTests.java | 14 ++++- .../imageutils/ImageSystemDatabaseTests.java | 14 +++++ .../imageutils/AsyncOperationsMaps.java | 4 +- .../com/xtremelabs/imageutils/DiskCache.java | 12 +---- .../xtremelabs/imageutils/DisplayUtility.java | 10 +++- .../xtremelabs/imageutils/ImageCacher.java | 10 ++-- .../com/xtremelabs/imageutils/ImageEntry.java | 1 + .../xtremelabs/imageutils/ImageLoader.java | 4 +- .../imageutils/ImageSystemDatabase.java | 5 +- .../xtremelabs/imageutils/ImagesTable.java | 53 ++++++++++-------- .../imageutils/ViewDimensionsUtil.java | 54 +++++++------------ .../com/xtremelabs/imageutils/db/Table.java | 21 +++++--- 12 files changed, 113 insertions(+), 89 deletions(-) diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/AdvancedMemoryCacherTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/AdvancedMemoryCacherTests.java index a12e165..d00560a 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/AdvancedMemoryCacherTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/AdvancedMemoryCacherTests.java @@ -19,9 +19,11 @@ import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; +import android.os.Build; import android.test.AndroidTestCase; public class AdvancedMemoryCacherTests extends AndroidTestCase { + private static final boolean SHOULD_RUN = Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT; private AdvancedMemoryLRUCacher mMemCache; private Bitmap.Config mBitmapConfig; @@ -29,10 +31,16 @@ public class AdvancedMemoryCacherTests extends AndroidTestCase { protected void setUp() throws Exception { super.setUp(); - mMemCache = new AdvancedMemoryLRUCacher(); + if (SHOULD_RUN) { + mMemCache = new AdvancedMemoryLRUCacher(); + } } public void testClearingCache() { + if (!SHOULD_RUN) { + return; + } + assertEquals(0, mMemCache.getNumImagesInCache()); mMemCache.cacheBitmap(getBitmap(), new DecodeSignature("url1", 1, mBitmapConfig)); assertEquals(1, mMemCache.getNumImagesInCache()); @@ -44,6 +52,10 @@ public void testClearingCache() { @SuppressLint("NewApi") public void testGetBitmapAndLru() { + if (!SHOULD_RUN) { + return; + } + Bitmap bitmap = getBitmap(); mMemCache.cacheBitmap(bitmap, new DecodeSignature("url1", 1, mBitmapConfig)); mMemCache.cacheBitmap(bitmap, new DecodeSignature("url1", 2, mBitmapConfig)); diff --git a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java index 64b333f..25aebd8 100644 --- a/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java +++ b/xl-image_utils-test_suite/src/com/xtremelabs/imageutils/ImageSystemDatabaseTests.java @@ -138,6 +138,20 @@ public void testFileSize() { public void testNoImageOnDiskTriggerDownload() { fail(); // should probably be somewhere else + + /* + * 1. Stub out getBitmapSynchronouslyFromDisk to throw a FileNotFoundException. + * + * 2. Stub out the DiskCache interface with a method for "restart image load" + * + * 3. Put an entry in the database for the "fake" image we are decoding. + * + * 4. Create a decode prioritizable and synchronously call "execute" on it. + * + * 5. Assert true that the database has removed the row for our image. + * + * 6. Assert true that the "restart image load" method was called. -- This is not built out yet and may not be 100% correct. + */ } public void testStartupDataRecoveryOrdering() { diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/AsyncOperationsMaps.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/AsyncOperationsMaps.java index 59592b6..5015841 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/AsyncOperationsMaps.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/AsyncOperationsMaps.java @@ -214,7 +214,7 @@ public void transferOperation(String uri, RequestParameters requestParameters, I case DEFAULT: CacheRequest cacheRequest = requestParameters.cacheRequest; int sampleSize = mObserver.getSampleSize(cacheRequest); - DecodeSignature decodeSignature = new DecodeSignature(uri, sampleSize, requestParameters.cacheRequest.getOptions().preferedConfig); + DecodeSignature decodeSignature = new DecodeSignature(uri, sampleSize, requestParameters.cacheRequest.getOptions().preferredConfig); registerDecodeRequest(cacheRequest, decodeSignature, imageCacherListener, requestParameters.imageReturnedFrom); break; @@ -396,7 +396,7 @@ private void cancelDecodePrioritizable(ImageCacherListener imageCacherListener) private synchronized DecodeSignature getDecodeSignature(CacheRequest cacheRequest) { int sampleSize = mObserver.getSampleSize(cacheRequest); - return new DecodeSignature(cacheRequest.getUri(), sampleSize, cacheRequest.getOptions().preferedConfig); + return new DecodeSignature(cacheRequest.getUri(), sampleSize, cacheRequest.getOptions().preferredConfig); } static interface OperationsObserver { diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java index 8953c5c..c9ec117 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DiskCache.java @@ -77,18 +77,10 @@ public boolean isCached(CacheRequest cacheRequest) { public int getSampleSize(CacheRequest cacheRequest) { Dimensions dimensions = getImageDimensions(cacheRequest); if (dimensions == null) { - cacheImageDetails(cacheRequest); // FIXME needs to be moved somewhere else (this method is called from UI thread) - dimensions = getImageDimensions(cacheRequest); + return -1; } - int sampleSize; - if (dimensions != null) { - sampleSize = SampleSizeCalculationUtility.calculateSampleSize(cacheRequest, dimensions); - } else { - sampleSize = -1; - } - - return sampleSize; + return SampleSizeCalculationUtility.calculateSampleSize(cacheRequest, dimensions); } @Override diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DisplayUtility.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DisplayUtility.java index 8c10b5a..ff34dea 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/DisplayUtility.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/DisplayUtility.java @@ -20,8 +20,14 @@ import android.util.DisplayMetrics; class DisplayUtility { + private Dimensions dimensions; + public Dimensions getDisplaySize(Context applicationContext) { - DisplayMetrics metrics = applicationContext.getResources().getDisplayMetrics(); - return new Dimensions(metrics.widthPixels, metrics.heightPixels); + if (dimensions == null) { + DisplayMetrics metrics = applicationContext.getResources().getDisplayMetrics(); + dimensions = new Dimensions(metrics.widthPixels, metrics.heightPixels); + } + + return dimensions; } } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageCacher.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageCacher.java index 930c651..c4e03c9 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageCacher.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageCacher.java @@ -80,7 +80,7 @@ public ImageResponse getBitmap(CacheRequest cacheRequest, ImageCacherListener im boolean isCached = mDiskCache.isCached(cacheRequest); if (isCached && sampleSize != -1) { Bitmap bitmap; - DecodeSignature decodeSignature = new DecodeSignature(uri, sampleSize, cacheRequest.getOptions().preferedConfig); + DecodeSignature decodeSignature = new DecodeSignature(uri, sampleSize, cacheRequest.getOptions().preferredConfig); bitmap = mMemoryCache.getBitmap(decodeSignature); if (bitmap != null) return new ImageResponse(bitmap, ImageReturnedFrom.MEMORY, ImageResponseStatus.SUCCESS); @@ -118,7 +118,7 @@ public ImageResponse getBitmapSynchronouslyFromDiskOrMemory(CacheRequest cacheRe try { if (isCached && sampleSize != -1) { - DecodeSignature decodeSignature = new DecodeSignature(uri, sampleSize, cacheRequest.getOptions().preferedConfig); + DecodeSignature decodeSignature = new DecodeSignature(uri, sampleSize, cacheRequest.getOptions().preferredConfig); Bitmap bitmap; if ((bitmap = mMemoryCache.getBitmap(decodeSignature)) != null) { return new ImageResponse(bitmap, ImageReturnedFrom.MEMORY, ImageResponseStatus.SUCCESS); @@ -129,7 +129,7 @@ public ImageResponse getBitmapSynchronouslyFromDiskOrMemory(CacheRequest cacheRe mDiskCache.calculateAndSaveImageDetails(cacheRequest); sampleSize = getSampleSize(cacheRequest); if (sampleSize != -1) { - DecodeSignature decodeSignature = new DecodeSignature(uri, sampleSize, cacheRequest.getOptions().preferedConfig); + DecodeSignature decodeSignature = new DecodeSignature(uri, sampleSize, cacheRequest.getOptions().preferredConfig); return getBitmapSynchronouslyFromDisk(cacheRequest, decodeSignature); } } @@ -342,14 +342,14 @@ protected Void doInBackground(Void... params) { return null; } - DecodeSignature decodeSignature = new DecodeSignature(cacheRequest.getUri(), sampleSize, cacheRequest.getOptions().preferedConfig); + DecodeSignature decodeSignature = new DecodeSignature(cacheRequest.getUri(), sampleSize, cacheRequest.getOptions().preferredConfig); Bitmap bitmap; if ((bitmap = mMemoryCache.getBitmap(decodeSignature)) != null) { imageCacherListener.onImageAvailable(new ImageResponse(bitmap, ImageReturnedFrom.DISK, ImageResponseStatus.SUCCESS)); } else { decodeBitmapFromDisk(cacheRequest, decodeSignature, imageCacherListener); } - } else if (cacheRequest.isFileSystemRequest()) { + } else if (cacheRequest.isFileSystemRequest() || isCached) { retrieveImageDetails(cacheRequest, imageCacherListener); } else { downloadImageFromNetwork(cacheRequest, imageCacherListener); diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageEntry.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageEntry.java index d82da08..0d9a494 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageEntry.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageEntry.java @@ -9,6 +9,7 @@ class ImageEntry { public int sizeX = -1; public int sizeY = -1; public long fileSize; + public long expiry = Long.MAX_VALUE; String getFileName() { return Long.toString(id); diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageLoader.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageLoader.java index d95481c..0517c63 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageLoader.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageLoader.java @@ -610,7 +610,7 @@ public void precacheImageToDiskAndMemory(String uri, Dimensions bounds, Options Options o = new Options(); o.autoDetectBounds = options.autoDetectBounds; o.overrideSampleSize = options.overrideSampleSize; - o.preferedConfig = options.preferedConfig; + o.preferredConfig = options.preferredConfig; o.scalingPreference = options.scalingPreference; o.useScreenSizeAsBounds = options.useScreenSizeAsBounds; o.widthBounds = bounds.width; @@ -917,6 +917,6 @@ public static enum ScalingPreference { *
* Default value: null. */ - public Bitmap.Config preferedConfig = null; + public Bitmap.Config preferredConfig = null; } } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java index 1555ff4..c14fbf8 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImageSystemDatabase.java @@ -33,12 +33,11 @@ public void init(Context context) { public void beginWrite(String uri) { ImageEntry entry = new ImageEntry(); entry.uri = uri; - entry.lastAccessedTime = System.currentTimeMillis(); - mImagesTable.insert(mDatabase.getWritableDatabase(), entry); + entry.id = mImagesTable.insert(mDatabase.getWritableDatabase(), entry); mCache.putEntry(entry); } - public void endWrite(String uri) { // TODO what happens if try to endWrite with corresponding entry in cache? + public void endWrite(String uri) { ImageEntry entry = mCache.getEntry(uri); entry.onDisk = true; mImagesTable.insert(mDatabase.getWritableDatabase(), entry); diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java index d3b7e8f..172dec6 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ImagesTable.java @@ -3,6 +3,7 @@ import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.provider.BaseColumns; import com.xtremelabs.imageutils.db.Table; @@ -12,42 +13,37 @@ class ImagesTable extends Table { // must remain in same order as Columns enum private static final String[] COLUMNS = { - "_id", + BaseColumns._ID, "uri", "on_disk", "creation_time", "last_accessed_time", "size_x", "size_y", - "fileSize" }; + "fileSize", + "expiry"}; enum Columns { - ID(0), - URI(1), - ON_DISK(2), - CREATION_TIME(3), - LAST_ACCESSED_TIME(4), - SIZE_X(5), - SIZE_Y(6), - FILE_SIZE(7); - - private final int mIndex; - - private Columns(int index) { - mIndex = index; - } + ID, + URI, + ON_DISK, + CREATION_TIME, + LAST_ACCESSED_TIME, + SIZE_X, + SIZE_Y, + FILE_SIZE, + EXPIRY; String getName() { - return COLUMNS[mIndex]; + int index = ordinal(); + return COLUMNS[index]; } } - private static class InstanceHolder { - private static ImagesTable sInstance = new ImagesTable(); - } + private static final ImagesTable sInstance = new ImagesTable(); static ImagesTable getInstance() { - return InstanceHolder.sInstance; + return sInstance; } @Override @@ -62,8 +58,16 @@ public String getTableName() { @Override protected String getColumnsForCreation() { - String columns = COLUMNS[0] + " INTEGER PRIMARY KEY, " + COLUMNS[1] + " TEXT, " + COLUMNS[2] + " INTEGER, " + COLUMNS[3] + " INTEGER, " + COLUMNS[4] + " INTEGER, " + COLUMNS[5] - + " INTEGER, " + COLUMNS[6] + " INTEGER, " + COLUMNS[7] + " INTEGER, "; + String columns = + COLUMNS[0] + " INTEGER PRIMARY KEY, " + + COLUMNS[1] + " TEXT, " + + COLUMNS[2] + " INTEGER, " + + COLUMNS[3] + " INTEGER, " + + COLUMNS[4] + " INTEGER, " + + COLUMNS[5] + " INTEGER, " + + COLUMNS[6] + " INTEGER, " + + COLUMNS[7] + " INTEGER, " + + COLUMNS[8] + " INTEGER, "; return columns; } @@ -83,6 +87,7 @@ protected ContentValues toContentValues(ImageEntry item) { contentValues.put(COLUMNS[5], item.sizeX); contentValues.put(COLUMNS[6], item.sizeY); contentValues.put(COLUMNS[7], item.fileSize); + contentValues.put(COLUMNS[8], item.expiry); return contentValues; } @@ -106,6 +111,8 @@ public static ImageEntry getEntryFromCursor(Cursor cursor) { entry.sizeX = cursor.getInt(5); entry.sizeY = cursor.getInt(6); entry.fileSize = cursor.getLong(7); + entry.expiry = cursor.getLong(8); return entry; } + } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ViewDimensionsUtil.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ViewDimensionsUtil.java index b4d56aa..53939e7 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/ViewDimensionsUtil.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/ViewDimensionsUtil.java @@ -17,15 +17,15 @@ package com.xtremelabs.imageutils; import android.graphics.Point; -import android.view.ViewGroup; +import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.ImageView; class ViewDimensionsUtil { public static Point getImageViewDimensions(ImageView imageView) { Point dimensions = new Point(); - dimensions.x = getDimensions(imageView, true); - dimensions.y = getDimensions(imageView, false); + dimensions.x = getDimension(imageView, true); + dimensions.y = getDimension(imageView, false); if (dimensions.x <= 0) { dimensions.x = -1; } @@ -35,41 +35,25 @@ public static Point getImageViewDimensions(ImageView imageView) { return dimensions; } - private static int getDimensions(ImageView imageView, boolean isWidth) { - LayoutParams params = imageView.getLayoutParams(); - if (params == null) { - return -1; - } - int length = isWidth ? params.width : params.height; - if (length == LayoutParams.WRAP_CONTENT) { - return -1; - } else if (length == LayoutParams.MATCH_PARENT) { - try { - return getParentDimensions((ViewGroup) imageView.getParent(), isWidth); - } catch (ClassCastException e) { - return -1; - } - } else { - return length; - } - } - - private static int getParentDimensions(ViewGroup parent, boolean isWidth) { + private static int getDimension(View view, boolean isWidth) { LayoutParams params; - if (parent == null || (params = parent.getLayoutParams()) == null) { - return -1; - } - int length = isWidth ? params.width : params.height; - if (length == LayoutParams.WRAP_CONTENT) { - return -1; - } else if (length == LayoutParams.MATCH_PARENT) { - try { - return getParentDimensions((ViewGroup) parent.getParent(), isWidth); - } catch (ClassCastException e) { + while (view != null && (params = view.getLayoutParams()) != null) { + int length = isWidth ? params.width : params.height; + if (length == LayoutParams.WRAP_CONTENT) { return -1; + + } else if (length == LayoutParams.MATCH_PARENT) { + try { + view = (View) view.getParent(); + } catch (ClassCastException e) { + return -1; + } + + } else { + return length; } - } else { - return length; } + + return -1; } } diff --git a/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Table.java b/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Table.java index 52556c6..256b62c 100644 --- a/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Table.java +++ b/xl_image_utils_lib/src/com/xtremelabs/imageutils/db/Table.java @@ -69,17 +69,26 @@ public void insert(SQLiteDatabase db, List items) { } } - public void insert(SQLiteDatabase db, T item) { + /** + * {@link SQLiteDatabase#insert(String, String, ContentValues)} + */ + public long insert(SQLiteDatabase db, T item) { ContentValues values = toContentValues(item); - db.insert(getTableName(), null, values); + return db.insert(getTableName(), null, values); } - public void update(SQLiteDatabase db, T item, String where, String[] whereArgs) { + /** + * {@link SQLiteDatabase#update(String, ContentValues, String, String[])} + */ + public int update(SQLiteDatabase db, T item, String where, String[] whereArgs) { ContentValues values = toContentValues(item); - db.update(getTableName(), values, where, whereArgs); + return db.update(getTableName(), values, where, whereArgs); } - public void delete(SQLiteDatabase db, String whereClause, String[] whereArgs) { - db.delete(getTableName(), whereClause, whereArgs); + /** + * {@link SQLiteDatabase#delete(String, String, String[])} + */ + public int delete(SQLiteDatabase db, String whereClause, String[] whereArgs) { + return db.delete(getTableName(), whereClause, whereArgs); } }