Browse Source

merge from YYCache

ibireme 9 years ago
parent
commit
431f91520d

+ 4 - 0
YYWebImage/Cache/YYDiskCache.h

@@ -131,6 +131,10 @@ NS_ASSUME_NONNULL_BEGIN
  */
 @property NSTimeInterval autoTrimInterval;
 
+/**
+ Set `YES` to enable error logs for debug.
+ */
+@property BOOL errorLogsEnabled;
 
 #pragma mark - Initializer
 ///=============================================================================

+ 26 - 0
YYWebImage/Cache/YYDiskCache.m

@@ -11,6 +11,7 @@
 
 #import "YYDiskCache.h"
 #import "YYKVStorage.h"
+#import <UIKit/UIKit.h>
 #import <CommonCrypto/CommonCrypto.h>
 #import <objc/runtime.h>
 #import <time.h>
@@ -149,8 +150,18 @@ static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
     return filename;
 }
 
+- (void)_appWillBeTerminated {
+    Lock();
+    _kv = nil;
+    Unlock();
+}
+
 #pragma mark - public
 
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
+}
+
 - (instancetype)init {
     @throw [NSException exceptionWithName:@"YYDiskCache init error" reason:@"YYDiskCache must be initialized with a path. Use 'initWithPath:' or 'initWithPath:inlineThreshold:' instead." userInfo:nil];
     return [self initWithPath:@"" inlineThreshold:0];
@@ -193,6 +204,8 @@ static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
     
     [self _trimRecursively];
     _YYDiskCacheSetGlobal(self);
+    
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
     return self;
 }
 
@@ -429,4 +442,17 @@ static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
     else return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _path];
 }
 
+- (BOOL)errorLogsEnabled {
+    Lock();
+    BOOL enabled = _kv.errorLogsEnabled;
+    Unlock();
+    return enabled;
+}
+
+- (void)setErrorLogsEnabled:(BOOL)errorLogsEnabled {
+    Lock();
+    _kv.errorLogsEnabled = errorLogsEnabled;
+    Unlock();
+}
+
 @end

+ 75 - 51
YYWebImage/Cache/YYKVStorage.m

@@ -19,6 +19,9 @@
 #import "sqlite3.h"
 #endif
 
+
+static const NSUInteger kMaxErrorRetryCount = 8;
+static const NSTimeInterval kMinRetryTimeInterval = 2.0;
 static const int kPathLengthMax = PATH_MAX - 64;
 static NSString *const kDBFileName = @"manifest.sqlite";
 static NSString *const kDBShmFileName = @"manifest.sqlite-shm";
@@ -26,7 +29,19 @@ static NSString *const kDBWalFileName = @"manifest.sqlite-wal";
 static NSString *const kDataDirectoryName = @"data";
 static NSString *const kTrashDirectoryName = @"trash";
 
+
 /*
+ File:
+ /path/
+      /manifest.sqlite
+      /manifest.sqlite-shm
+      /manifest.sqlite-wal
+      /data/
+           /e10adc3949ba59abbe56e057f20f883e
+           /e10adc3949ba59abbe56e057f20f883e
+      /trash/
+            /unused_file_or_folder
+ 
  SQL:
  create table if not exists manifest (
     key                 text,
@@ -41,6 +56,22 @@ static NSString *const kTrashDirectoryName = @"trash";
  create index if not exists last_access_time_idx on manifest(last_access_time);
  */
 
+/// Returns nil in App Extension.
+static UIApplication *_YYSharedApplication() {
+    static BOOL isAppExtension = NO;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        Class cls = NSClassFromString(@"UIApplication");
+        if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES;
+        if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES;
+    });
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wundeclared-selector"
+    return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)];
+#pragma clang diagnostic pop
+}
+
+
 @implementation YYKVStorageItem
 @end
 
@@ -54,49 +85,40 @@ static NSString *const kTrashDirectoryName = @"trash";
     
     sqlite3 *_db;
     CFMutableDictionaryRef _dbStmtCache;
-    
-    BOOL _invalidated; ///< If YES, then the db should not open again, all read/write should be ignored.
-    BOOL _dbIsClosing; ///< If YES, then the db is during closing.
+    NSTimeInterval _dbLastOpenErrorTime;
+    NSUInteger _dbOpenErrorCount;
 }
 
 
 #pragma mark - db
 
 - (BOOL)_dbOpen {
-    BOOL shouldOpen = YES;
-    if (_invalidated) {
-        shouldOpen = NO;
-    } else if (_dbIsClosing) {
-        shouldOpen = NO;
-    } else if (_db){
-        shouldOpen = NO;
-    }
-    if (!shouldOpen) return YES;
+    if (_db) return YES;
     
     int result = sqlite3_open(_dbPath.UTF8String, &_db);
     if (result == SQLITE_OK) {
         CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks;
         CFDictionaryValueCallBacks valueCallbacks = {0};
         _dbStmtCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &keyCallbacks, &valueCallbacks);
+        _dbLastOpenErrorTime = 0;
+        _dbOpenErrorCount = 0;
         return YES;
     } else {
-        NSLog(@"%s line:%d sqlite open failed (%d).", __FUNCTION__, __LINE__, result);
+        _db = NULL;
+        if (_dbStmtCache) CFRelease(_dbStmtCache);
+        _dbStmtCache = NULL;
+        _dbLastOpenErrorTime = CACurrentMediaTime();
+        _dbOpenErrorCount++;
+        
+        if (_errorLogsEnabled) {
+            NSLog(@"%s line:%d sqlite open failed (%d).", __FUNCTION__, __LINE__, result);
+        }
         return NO;
     }
 }
 
 - (BOOL)_dbClose {
-    BOOL needClose = YES;
-    if (!_db) {
-        needClose = NO;
-    } else if (_invalidated) {
-        needClose = NO;
-    } else if (_dbIsClosing) {
-        needClose = NO;
-    } else {
-        _dbIsClosing = YES;
-    }
-    if (!needClose) return YES;
+    if (!_db) return YES;
     
     int  result = 0;
     BOOL retry = NO;
@@ -118,16 +140,25 @@ static NSString *const kTrashDirectoryName = @"trash";
                 }
             }
         } else if (result != SQLITE_OK) {
-            NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result);
+            if (_errorLogsEnabled) {
+                NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result);
+            }
         }
     } while (retry);
     _db = NULL;
-    _dbIsClosing = NO;
     return YES;
 }
 
-- (BOOL)_dbIsReady {
-    return (_db && !_dbIsClosing && !_invalidated);
+- (BOOL)_dbCheck {
+    if (!_db) {
+        if (_dbOpenErrorCount < kMaxErrorRetryCount &&
+            CACurrentMediaTime() - _dbLastOpenErrorTime > kMinRetryTimeInterval) {
+            return [self _dbOpen] && [self _dbInitialize];
+        } else {
+            return NO;
+        }
+    }
+    return YES;
 }
 
 - (BOOL)_dbInitialize {
@@ -136,14 +167,14 @@ static NSString *const kTrashDirectoryName = @"trash";
 }
 
 - (void)_dbCheckpoint {
-    if (![self _dbIsReady]) return;
+    if (![self _dbCheck]) return;
     // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file.
     sqlite3_wal_checkpoint(_db, NULL);
 }
 
 - (BOOL)_dbExecute:(NSString *)sql {
     if (sql.length == 0) return NO;
-    if (![self _dbIsReady]) return NO;
+    if (![self _dbCheck]) return NO;
     
     char *error = NULL;
     int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &error);
@@ -156,7 +187,7 @@ static NSString *const kTrashDirectoryName = @"trash";
 }
 
 - (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
-    if (![self _dbIsReady] || sql.length == 0 || !_dbStmtCache) return NULL;
+    if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;
     sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));
     if (!stmt) {
         int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
@@ -230,7 +261,7 @@ static NSString *const kTrashDirectoryName = @"trash";
 }
 
 - (BOOL)_dbUpdateAccessTimeWithKeys:(NSArray *)keys {
-    if (![self _dbIsReady]) return NO;
+    if (![self _dbCheck]) return NO;
     int t = (int)time(NULL);
      NSString *sql = [NSString stringWithFormat:@"update manifest set last_access_time = %d where key in (%@);", t, [self _dbJoinedKeys:keys]];
     
@@ -266,7 +297,7 @@ static NSString *const kTrashDirectoryName = @"trash";
 }
 
 - (BOOL)_dbDeleteItemWithKeys:(NSArray *)keys {
-    if (![self _dbIsReady]) return NO;
+    if (![self _dbCheck]) return NO;
     NSString *sql =  [NSString stringWithFormat:@"delete from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
     sqlite3_stmt *stmt = NULL;
     int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
@@ -353,7 +384,7 @@ static NSString *const kTrashDirectoryName = @"trash";
 }
 
 - (NSMutableArray *)_dbGetItemWithKeys:(NSArray *)keys excludeInlineData:(BOOL)excludeInlineData {
-    if (![self _dbIsReady]) return nil;
+    if (![self _dbCheck]) return nil;
     NSString *sql;
     if (excludeInlineData) {
         sql = [NSString stringWithFormat:@"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
@@ -427,7 +458,7 @@ static NSString *const kTrashDirectoryName = @"trash";
 }
 
 - (NSMutableArray *)_dbGetFilenameWithKeys:(NSArray *)keys {
-    if (![self _dbIsReady]) return nil;
+    if (![self _dbCheck]) return nil;
     NSString *sql = [NSString stringWithFormat:@"select filename from manifest where key in (%@);", [self _dbJoinedKeys:keys]];
     sqlite3_stmt *stmt = NULL;
     int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
@@ -510,8 +541,8 @@ static NSString *const kTrashDirectoryName = @"trash";
     return filenames;
 }
 
-- (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeDescWithLimit:(int)count {
-    NSString *sql = @"select key, filename, size from manifest order by last_access_time desc limit ?1;";
+- (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeAscWithLimit:(int)count {
+    NSString *sql = @"select key, filename, size from manifest order by last_access_time asc limit ?1;";
     sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
     if (!stmt) return nil;
     sqlite3_bind_int(stmt, 1, count);
@@ -580,26 +611,22 @@ static NSString *const kTrashDirectoryName = @"trash";
 #pragma mark - file
 
 - (BOOL)_fileWriteWithName:(NSString *)filename data:(NSData *)data {
-    if (_invalidated) return NO;
     NSString *path = [_dataPath stringByAppendingPathComponent:filename];
     return [data writeToFile:path atomically:NO];
 }
 
 - (NSData *)_fileReadWithName:(NSString *)filename {
-    if (_invalidated) return nil;
     NSString *path = [_dataPath stringByAppendingPathComponent:filename];
     NSData *data = [NSData dataWithContentsOfFile:path];
     return data;
 }
 
 - (BOOL)_fileDeleteWithName:(NSString *)filename {
-    if (_invalidated) return NO;
     NSString *path = [_dataPath stringByAppendingPathComponent:filename];
     return [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
 }
 
 - (BOOL)_fileMoveAllToTrash {
-    if (_invalidated) return NO;
     CFUUIDRef uuidRef = CFUUIDCreate(NULL);
     CFStringRef uuid = CFUUIDCreateString(NULL, uuidRef);
     CFRelease(uuidRef);
@@ -613,7 +640,6 @@ static NSString *const kTrashDirectoryName = @"trash";
 }
 
 - (void)_fileEmptyTrashInBackground {
-    if (_invalidated) return;
     NSString *trashPath = _trashPath;
     dispatch_queue_t queue = _trashQueue;
     dispatch_async(queue, ^{
@@ -641,10 +667,6 @@ static NSString *const kTrashDirectoryName = @"trash";
     [self _fileEmptyTrashInBackground];
 }
 
-- (void)_appWillBeTerminated {
-    _invalidated = YES;
-}
-
 #pragma mark - public
 
 - (instancetype)init {
@@ -698,13 +720,15 @@ static NSString *const kTrashDirectoryName = @"trash";
         return nil;
     }
     [self _fileEmptyTrashInBackground]; // empty the trash if failed at last time
-    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
     return self;
 }
 
 - (void)dealloc {
-    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
+    UIBackgroundTaskIdentifier taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{}];
     [self _dbClose];
+    if (taskID != UIBackgroundTaskInvalid) {
+        [_YYSharedApplication() endBackgroundTask:taskID];
+    }
 }
 
 - (BOOL)saveItem:(YYKVStorageItem *)item {
@@ -841,7 +865,7 @@ static NSString *const kTrashDirectoryName = @"trash";
     BOOL suc = NO;
     do {
         int perCount = 16;
-        items = [self _dbGetItemSizeInfoOrderByTimeDescWithLimit:perCount];
+        items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
         for (YYKVStorageItem *item in items) {
             if (total > maxSize) {
                 if (item.filename) {
@@ -871,7 +895,7 @@ static NSString *const kTrashDirectoryName = @"trash";
     BOOL suc = NO;
     do {
         int perCount = 16;
-        items = [self _dbGetItemSizeInfoOrderByTimeDescWithLimit:perCount];
+        items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
         for (YYKVStorageItem *item in items) {
             if (total > maxCount) {
                 if (item.filename) {
@@ -909,7 +933,7 @@ static NSString *const kTrashDirectoryName = @"trash";
         NSArray *items = nil;
         BOOL suc = NO;
         do {
-            items = [self _dbGetItemSizeInfoOrderByTimeDescWithLimit:perCount];
+            items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
             for (YYKVStorageItem *item in items) {
                 if (left > 0) {
                     if (item.filename) {

+ 5 - 5
YYWebImage/Cache/YYMemoryCache.m

@@ -360,16 +360,16 @@ static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
     return totalCost;
 }
 
-- (BOOL)releaseInMainThread {
+- (BOOL)releaseOnMainThread {
     pthread_mutex_lock(&_lock);
-    BOOL releaseInMainThread = _lru->_releaseOnMainThread;
+    BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
     pthread_mutex_unlock(&_lock);
-    return releaseInMainThread;
+    return releaseOnMainThread;
 }
 
-- (void)setReleaseInMainThread:(BOOL)releaseInMainThread {
+- (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
     pthread_mutex_lock(&_lock);
-    _lru->_releaseOnMainThread = releaseInMainThread;
+    _lru->_releaseOnMainThread = releaseOnMainThread;
     pthread_mutex_unlock(&_lock);
 }