Browse Source

Merge pull request #1898 from walkline/master into 5.x

Handle storeImageDataToDisk error.
Bogdan Poplauschi 8 years ago
parent
commit
324563c7d2

+ 21 - 5
SDWebImage/SDImageCache.h

@@ -31,6 +31,7 @@ typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);
 
 typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);
 
+typedef void(^SDWebImageCompletionWithPossibleErrorBlock)(NSError * _Nullable error);
 
 /**
  * SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed
@@ -78,7 +79,18 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
  * @param directory Directory to cache disk images in
  */
 - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
-                       diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;
+                       diskCacheDirectory:(nonnull NSString *)directory;
+
+/**
+ * Init a new cache store with a specific namespace, directory and file manager
+ *
+ * @param ns          The namespace to use for this cache store
+ * @param directory   Directory to cache disk images in
+ * @param fileManager The file manager for storing image, if nil then will be created new one
+ */
+- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
+                       diskCacheDirectory:(nonnull NSString *)directory
+                              fileManager:(nullable NSFileManager *)fileManager NS_DESIGNATED_INITIALIZER;
 
 #pragma mark - Cache paths
 
@@ -103,7 +115,7 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
  */
 - (void)storeImage:(nullable UIImage *)image
             forKey:(nullable NSString *)key
-        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
+        completion:(nullable SDWebImageCompletionWithPossibleErrorBlock)completionBlock;
 
 /**
  * Asynchronously store an image into memory and disk cache at the given key.
@@ -116,7 +128,7 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
 - (void)storeImage:(nullable UIImage *)image
             forKey:(nullable NSString *)key
             toDisk:(BOOL)toDisk
-        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
+        completion:(nullable SDWebImageCompletionWithPossibleErrorBlock)completionBlock;
 
 /**
  * Asynchronously store an image into memory and disk cache at the given key.
@@ -133,7 +145,7 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
          imageData:(nullable NSData *)imageData
             forKey:(nullable NSString *)key
             toDisk:(BOOL)toDisk
-        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
+        completion:(nullable SDWebImageCompletionWithPossibleErrorBlock)completionBlock;
 
 /**
  * Synchronously store image NSData into disk cache at the given key.
@@ -142,8 +154,12 @@ typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger tot
  *
  * @param imageData  The image data to store
  * @param key        The unique image cache key, usually it's image absolute URL
+ * @param errorPtr   NSError pointer. If error occurs then (*errorPtr) != nil.
  */
-- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;
+- (BOOL)storeImageDataToDisk:(nullable NSData *)imageData
+                      forKey:(nullable NSString *)key
+                       error:(NSError * _Nullable * _Nullable)errorPtr;
+
 
 #pragma mark - Query and Retrieve Ops
 

+ 25 - 11
SDWebImage/SDImageCache.m

@@ -81,6 +81,12 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
 
 - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                        diskCacheDirectory:(nonnull NSString *)directory {
+    return [self initWithNamespace:ns diskCacheDirectory:directory fileManager: nil];
+}
+
+- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
+                       diskCacheDirectory:(nonnull NSString *)directory
+                              fileManager:(nullable NSFileManager *)fileManager {
     if ((self = [super init])) {
         NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
         
@@ -102,7 +108,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
         }
 
         dispatch_sync(_ioQueue, ^{
-            _fileManager = [NSFileManager new];
+            _fileManager = fileManager ? fileManager : [NSFileManager new];
         });
 
 #if SD_UIKIT
@@ -184,14 +190,14 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
 
 - (void)storeImage:(nullable UIImage *)image
             forKey:(nullable NSString *)key
-        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
+        completion:(nullable SDWebImageCompletionWithPossibleErrorBlock)completionBlock {
     [self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
 }
 
 - (void)storeImage:(nullable UIImage *)image
             forKey:(nullable NSString *)key
             toDisk:(BOOL)toDisk
-        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
+        completion:(nullable SDWebImageCompletionWithPossibleErrorBlock)completionBlock {
     [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
 }
 
@@ -199,10 +205,10 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
          imageData:(nullable NSData *)imageData
             forKey:(nullable NSString *)key
             toDisk:(BOOL)toDisk
-        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
+        completion:(nullable SDWebImageCompletionWithPossibleErrorBlock)completionBlock {
     if (!image || !key) {
         if (completionBlock) {
-            completionBlock();
+            completionBlock(nil);
         }
         return;
     }
@@ -214,31 +220,34 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
     
     if (toDisk) {
         dispatch_async(self.ioQueue, ^{
+            NSError * writeError = nil;
             @autoreleasepool {
                 NSData *data = imageData;
                 if (!data && image) {
                     // If we do not have any data to detect image format, use PNG format
                     data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
                 }
-                [self storeImageDataToDisk:data forKey:key];
+                [self storeImageDataToDisk:data forKey:key error:&writeError];
             }
             
             if (completionBlock) {
                 dispatch_async(dispatch_get_main_queue(), ^{
-                    completionBlock();
+                    completionBlock(writeError);
                 });
             }
         });
     } else {
         if (completionBlock) {
-            completionBlock();
+            completionBlock(nil);
         }
     }
 }
 
-- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
+- (BOOL)storeImageDataToDisk:(nullable NSData *)imageData
+                      forKey:(nullable NSString *)key
+                       error:(NSError * _Nullable * _Nullable)errorPtr {
     if (!imageData || !key) {
-        return;
+        return NO;
     }
     
     [self checkIfQueueIsIOQueue];
@@ -252,12 +261,17 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
     // transform to NSUrl
     NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
     
-    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
+    if (![_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil] && errorPtr) {
+        *errorPtr = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
+        return NO;
+    }
     
     // disable iCloud backup
     if (self.config.shouldDisableiCloud) {
         [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
     }
+    
+    return YES;
 }
 
 #pragma mark - Query and Retrieve Ops

+ 7 - 1
Tests/SDWebImage Tests.xcodeproj/project.pbxproj

@@ -12,6 +12,7 @@
 		321259EC1F39E3240096FE0E /* TestImageStatic.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259EB1F39E3240096FE0E /* TestImageStatic.webp */; };
 		321259EE1F39E4110096FE0E /* TestImageAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259ED1F39E4110096FE0E /* TestImageAnimated.webp */; };
 		32E6F0321F3A1B4700A945E6 /* SDWebImageTestDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E6F0311F3A1B4700A945E6 /* SDWebImageTestDecoder.m */; };
+		37D122881EC48B5E00D98CEB /* SDMockFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 37D122871EC48B5E00D98CEB /* SDMockFileManager.m */; };
 		433BBBB51D7EF5C00086B6E9 /* SDWebImageDecoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */; };
 		433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 433BBBB61D7EF8200086B6E9 /* TestImage.gif */; };
 		433BBBB91D7EF8260086B6E9 /* TestImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 433BBBB81D7EF8260086B6E9 /* TestImage.png */; };
@@ -38,6 +39,8 @@
 		321259ED1F39E4110096FE0E /* TestImageAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageAnimated.webp; sourceTree = "<group>"; };
 		32E6F0301F3A1B4700A945E6 /* SDWebImageTestDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestDecoder.h; sourceTree = "<group>"; };
 		32E6F0311F3A1B4700A945E6 /* SDWebImageTestDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestDecoder.m; sourceTree = "<group>"; };
+		37D122861EC48B5E00D98CEB /* SDMockFileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDMockFileManager.h; sourceTree = "<group>"; };
+		37D122871EC48B5E00D98CEB /* SDMockFileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDMockFileManager.m; sourceTree = "<group>"; };
 		433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDecoderTests.m; sourceTree = "<group>"; };
 		433BBBB61D7EF8200086B6E9 /* TestImage.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = TestImage.gif; sourceTree = "<group>"; };
 		433BBBB81D7EF8260086B6E9 /* TestImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestImage.png; sourceTree = "<group>"; };
@@ -129,6 +132,8 @@
 				433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */,
 				4369C1D01D97F80F007E863A /* SDWebImagePrefetcherTests.m */,
 				4369C2731D9804B1007E863A /* SDCategoriesTests.m */,
+				37D122861EC48B5E00D98CEB /* SDMockFileManager.h */,
+				37D122871EC48B5E00D98CEB /* SDMockFileManager.m */,
 				2D7AF05E1F329763000083C2 /* SDTestCase.h */,
 				2D7AF05F1F329763000083C2 /* SDTestCase.m */,
 				32E6F0301F3A1B4700A945E6 /* SDWebImageTestDecoder.h */,
@@ -229,7 +234,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n";
 			showEnvVarsInLog = 0;
 		};
 		85E5D3885A03BFC23B050908 /* [CP] Copy Pods Resources */ = {
@@ -280,6 +285,7 @@
 			files = (
 				32E6F0321F3A1B4700A945E6 /* SDWebImageTestDecoder.m in Sources */,
 				1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */,
+				37D122881EC48B5E00D98CEB /* SDMockFileManager.m in Sources */,
 				4369C2741D9804B1007E863A /* SDCategoriesTests.m in Sources */,
 				2D7AF0601F329763000083C2 /* SDTestCase.m in Sources */,
 				4369C1D11D97F80F007E863A /* SDWebImagePrefetcherTests.m in Sources */,

+ 29 - 2
Tests/Tests/SDImageCacheTests.m

@@ -10,6 +10,7 @@
 #import <SDWebImage/SDImageCache.h>
 #import <SDWebImage/SDWebImageCodersManager.h>
 #import "SDWebImageTestDecoder.h"
+#import "SDMockFileManager.h"
 
 NSString *kImageTestKey = @"TestImageKey.jpg";
 
@@ -196,7 +197,7 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
     
     UIImage *image = [UIImage imageWithContentsOfFile:[self testImagePath]];
     NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
-    [self.sharedImageCache storeImageDataToDisk:imageData forKey:kImageTestKey];
+    [self.sharedImageCache storeImageDataToDisk:imageData forKey:kImageTestKey error:nil];
     
     UIImage *storedImageFromMemory = [self.sharedImageCache imageFromMemoryCacheForKey:kImageTestKey];
     expect(storedImageFromMemory).to.equal(nil);
@@ -238,7 +239,7 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
     UIImage *image = [UIImage imageWithContentsOfFile:testImagePath];
     NSString *key = @"TestPNGImageEncodedToDataAndRetrieveToJPEG";
     
-    [cache storeImage:image imageData:nil forKey:key toDisk:YES completion:^{
+    [cache storeImage:image imageData:nil forKey:key toDisk:YES completion:^(NSError * _Nullable error) {
         [cache clearMemory];
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wundeclared-selector"
@@ -274,6 +275,32 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
     [self waitForExpectationsWithCommonTimeout];
 }
 
+- (void)test41StoreImageDataToDiskWithError {
+    NSData *imageData = [NSData dataWithContentsOfFile:[self testImagePath]];
+    NSError * error = nil;
+    SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"test"
+                                               diskCacheDirectory:@"/"
+                                                      fileManager:[[SDMockFileManager alloc] initWithError:EACCES]];
+    [cache storeImageDataToDisk:imageData
+                         forKey:kImageTestKey
+                          error:&error];
+    
+    XCTAssertEqual(error.code, EACCES);
+}
+
+- (void)test42StoreImageDataToDiskWithoutError {
+    NSData *imageData = [NSData dataWithContentsOfFile:[self testImagePath]];
+    NSError * error = nil;
+    SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"test"
+                                               diskCacheDirectory:@"/"
+                                                      fileManager:[[SDMockFileManager alloc] initWithError:0]];
+    [cache storeImageDataToDisk:imageData
+                         forKey:kImageTestKey
+                          error:&error];
+
+    XCTAssertNil(error);
+}
+
 #pragma mark Helper methods
 
 - (void)clearAllCaches{

+ 15 - 0
Tests/Tests/SDMockFileManager.h

@@ -0,0 +1,15 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface SDMockFileManager : NSFileManager
+
+- (id)initWithError:(int)errorNumber;
+
+@end

+ 33 - 0
Tests/Tests/SDMockFileManager.m

@@ -0,0 +1,33 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDMockFileManager.h"
+
+@interface SDMockFileManager ()
+
+@property (nonatomic, assign) int errorNumber;
+
+@end
+
+@implementation SDMockFileManager
+
+- (id)initWithError:(int)errorNumber {
+    self = [super init];
+    if (self) {
+        _errorNumber = errorNumber;
+    }
+    
+    return self;
+}
+
+- (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)data attributes:(NSDictionary<NSString *,id> *)attr {
+    errno = self.errorNumber;
+    return (self.errorNumber == 0);
+}
+
+@end