diff --git a/media/ios/ASI/ASINetworkQueue.m b/media/ios/ASI/ASINetworkQueue.m index b24076dbe..8af67fca8 100755 --- a/media/ios/ASI/ASINetworkQueue.m +++ b/media/ios/ASI/ASINetworkQueue.m @@ -133,7 +133,7 @@ if (![operation isKindOfClass:[ASIHTTPRequest class]]) { [NSException raise:@"AttemptToAddInvalidRequest" format:@"Attempted to add an object that was not an ASIHTTPRequest to an ASINetworkQueue"]; } - + [self setRequestsCount:[self requestsCount]+1]; ASIHTTPRequest *request = (ASIHTTPRequest *)operation; diff --git a/media/ios/Classes/NewsBlurAppDelegate.h b/media/ios/Classes/NewsBlurAppDelegate.h index 2925e0a55..78bcdf665 100644 --- a/media/ios/Classes/NewsBlurAppDelegate.h +++ b/media/ios/Classes/NewsBlurAppDelegate.h @@ -9,6 +9,7 @@ #import #import "BaseViewController.h" #import "FMDatabaseQueue.h" +#import "ASINetworkQueue.h" #define FEED_DETAIL_VIEW_TAG 1000001 #define STORY_DETAIL_VIEW_TAG 1000002 @@ -139,6 +140,7 @@ NSArray *categories; NSDictionary *categoryFeeds; UIImageView *splashView; + ASINetworkQueue *operationQueue; } @property (nonatomic) IBOutlet UIWindow *window; @@ -233,6 +235,7 @@ @property (nonatomic) NSArray *categories; @property (nonatomic) NSDictionary *categoryFeeds; @property (readwrite) FMDatabaseQueue *database; +@property (readwrite) ASINetworkQueue *operationQueue; + (NewsBlurAppDelegate*) sharedAppDelegate; - (void)startupAnimationDone:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context; @@ -338,6 +341,7 @@ - (void)storeAllUnreadStories:(ASIHTTPRequest *)request; - (void)flushQueuedReadStories:(BOOL)forceCheck withCallback:(void(^)())callback; - (void)syncQueuedReadStories:(FMDatabase *)db withStories:(NSDictionary *)hashes withCallback:(void(^)())callback; +- (void)cachedImageQueueFinished:(ASINetworkQueue *)queue; @end diff --git a/media/ios/Classes/NewsBlurAppDelegate.m b/media/ios/Classes/NewsBlurAppDelegate.m index fbe2927b8..8c9b4f6fe 100644 --- a/media/ios/Classes/NewsBlurAppDelegate.m +++ b/media/ios/Classes/NewsBlurAppDelegate.m @@ -26,6 +26,7 @@ #import "UserProfileViewController.h" #import "NBContainerViewController.h" #import "AFJSONRequestOperation.h" +#import "ASINetworkQueue.h" #import "InteractionsModule.h" #import "ActivityModule.h" #import "FirstTimeUserViewController.h" @@ -46,7 +47,7 @@ @implementation NewsBlurAppDelegate -#define CURRENT_DB_VERSION 13 +#define CURRENT_DB_VERSION 16 #define IS_IPHONE_5 ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON ) @synthesize window; @@ -144,6 +145,7 @@ @synthesize database; @synthesize categories; @synthesize categoryFeeds; +@synthesize operationQueue; + (NewsBlurAppDelegate*) sharedAppDelegate { return (NewsBlurAppDelegate*) [UIApplication sharedApplication].delegate; @@ -2108,6 +2110,7 @@ [db executeUpdate:@"drop table if exists `unread_hashes`"]; [db executeUpdate:@"drop table if exists `accounts`"]; [db executeUpdate:@"drop table if exists `feeds`"]; + [db executeUpdate:@"drop table if exists `cached_images`"]; // [db executeUpdate:@"drop table if exists `queued_read_hashes`"]; NSLog(@"Dropped db: %@", [db lastErrorMessage]); sqlite3_exec(db.sqliteHandle, [[NSString stringWithFormat:@"PRAGMA user_version = %d", CURRENT_DB_VERSION] UTF8String], NULL, NULL, NULL); @@ -2160,6 +2163,16 @@ ")"]; [db executeUpdate:createReadTable]; + NSString *createImagesTable = [NSString stringWithFormat:@"create table if not exists cached_images " + "(" + " story_feed_id number," + " story_hash varchar(24)," + " image_url varchar(1024)," + " image_cached boolean," + " UNIQUE(story_hash) ON CONFLICT IGNORE" + ")"]; + [db executeUpdate:createImagesTable]; + NSLog(@"Create db %d: %@", [db lastErrorCode], [db lastErrorMessage]); } @@ -2373,7 +2386,7 @@ (unsigned long)NULL), ^(void) { [_self.database inTransaction:^(FMDatabase *db, BOOL *rollback) { for (NSDictionary *story in [results objectForKey:@"stories"]) { - BOOL inserted = [db executeUpdate:@"INSERT into stories" + BOOL inserted = [db executeUpdate:@"INSERT into stories " "(story_feed_id, story_hash, story_timestamp, image_url, story_json) VALUES " "(?, ?, ?, ?, ?)", [story objectForKey:@"story_feed_id"], @@ -2382,6 +2395,15 @@ [story objectForKey:@"image_url"], [story JSONRepresentation] ]; + if ([[story objectForKey:@"image_url"] class] != [NSNull class]) { + [db executeUpdate:@"INSERT INTO cached_images " + "(story_feed_id, story_hash, image_url) VALUES " + "(?, ?, ?)", + [story objectForKey:@"story_feed_id"], + [story objectForKey:@"story_hash"], + [story objectForKey:@"image_url"] + ]; + } if (!anySuccess && inserted) anySuccess = YES; } if (anySuccess) { @@ -2406,9 +2428,9 @@ NSMutableArray *urls = [NSMutableArray array]; [self.database inDatabase:^(FMDatabase *db) { - NSString *commonQuery = @"FROM stories s " - "INNER JOIN unread_hashes u ON (s.story_hash = u.story_hash) " - "WHERE s.image_cached is null and s.image_url is not null"; + NSString *commonQuery = @"FROM cached_images c " + "INNER JOIN unread_hashes u ON (c.story_hash = u.story_hash) " + "WHERE c.image_cached is null "; int count = [db intForQuery:[NSString stringWithFormat:@"SELECT COUNT(1) %@", commonQuery]]; if (self.totalUncachedImagesCount == 0) { self.totalUncachedImagesCount = count; @@ -2424,10 +2446,12 @@ } else { order = @"DESC"; } - FMResultSet *cursor = [db executeQuery:[NSString stringWithFormat:@"SELECT s.image_url %@ ORDER BY u.story_timestamp %@ LIMIT %d", commonQuery, order, limit]]; + NSString *sql = [NSString stringWithFormat:@"SELECT c.image_url, c.story_hash %@ ORDER BY u.story_timestamp %@ LIMIT %d", commonQuery, order, limit]; + FMResultSet *cursor = [db executeQuery:sql]; while ([cursor next]) { - [urls addObject:[cursor objectForColumnName:@"image_url"]]; + [urls addObject:@[[cursor objectForColumnName:@"image_url"], + [cursor objectForColumnName:@"story_hash"]]]; } int start = (int)[[NSDate date] timeIntervalSince1970]; int end = self.latestFetchedStoryDate; @@ -2449,6 +2473,9 @@ - (void)fetchAllUncachedImages { NSArray *urls = [self uncachedImageUrls]; + operationQueue = [[ASINetworkQueue alloc] init]; + operationQueue.maxConcurrentOperationCount = 4; + operationQueue.delegate = self; if ([urls count] == 0) { NSLog(@"Finished caching images. %d total", self.totalUncachedImagesCount); @@ -2458,47 +2485,59 @@ return; } - for (NSString *urlString in urls) { -// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, -// (unsigned long)NULL), ^(void) { -// NSURL *url = [NSURL URLWithString:urlString]; -// ASIHTTPRequest *_request = [ASIHTTPRequest requestWithURL:url]; -// __weak ASIHTTPRequest *request = _request; -// [request setResponseEncoding:NSUTF8StringEncoding]; -// [request setDefaultResponseEncoding:NSUTF8StringEncoding]; -// [request setFailedBlock:^(void) { -// NSLog(@"Failed cache image url."); -// }]; -// [request setCompletionBlock:^(void) { -// [self storeCachedImage:request]; -// }]; -// [request setTimeOutSeconds:30]; -// [request startAsynchronous]; -// }); -// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, -// (unsigned long)NULL), ^(void) { -// -// [self fetchAllUncachedImages]; -// }); + for (NSArray *urlArray in urls) { + NSURL *url = [NSURL URLWithString:[urlArray objectAtIndex:0]]; + NSString *storyHash = [urlArray objectAtIndex:1]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self.feedsViewController hideNotifier]; - }); + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; + [request setUserInfo:@{@"story_hash": storyHash}]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(storeCachedImage:)]; + [request setDidFailSelector:@selector(storeCachedImage:)]; + [request setTimeOutSeconds:5]; + [operationQueue addOperation:request]; } + + [operationQueue setQueueDidFinishSelector:@selector(cachedImageQueueFinished:)]; + [operationQueue setShouldCancelAllRequestsOnFailure:NO]; + [operationQueue go]; + +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self.feedsViewController hideNotifier]; +// }); } - (void)storeCachedImage:(ASIHTTPRequest *)request { - NSData *responseData = [request responseData]; + NSString *storyHash = [[request userInfo] objectForKey:@"story_hash"]; - BOOL anySuccess = YES; - - if (anySuccess) { - [self fetchAllUncachedImages]; + if ([request responseStatusCode] == 200) { + NSData *responseData = [request responseData]; + NSString *md5Url = [Utilities md5:[[request originalURL] absoluteString]]; + NSLog(@"Storing image: %@ (%d bytes - %d in queue)", storyHash, [responseData length], [operationQueue requestsCount]); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *cacheDirectory = [paths objectAtIndex:0]; + NSString *fullPath = [cacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@", md5Url]]; + + [fileManager createFileAtPath:fullPath contents:responseData attributes:nil]; } else { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.feedsViewController hideNotifier]; - }); + NSLog(@"Failed to fetch: %@ / %@", [[request originalURL] absoluteString], storyHash); } + + [self.database inDatabase:^(FMDatabase *db) { + [db executeUpdate:@"UPDATE cached_images SET " + "image_cached = 1 WHERE story_hash = ?", + storyHash]; + }]; +} + +- (void)cachedImageQueueFinished:(ASINetworkQueue *)queue { + NSLog(@"Queue finished: %d total (%d remaining)", self.totalUncachedImagesCount, self.remainingUncachedImagesCount); + [self fetchAllUncachedImages]; +// dispatch_async(dispatch_get_main_queue(), ^{ +// [self.feedsViewController hideNotifier]; +// }); } @end diff --git a/media/ios/Classes/Utilities.h b/media/ios/Classes/Utilities.h index b9a2833be..b431ba53d 100644 --- a/media/ios/Classes/Utilities.h +++ b/media/ios/Classes/Utilities.h @@ -21,5 +21,6 @@ void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor + (void)drawLinearGradientWithRect:(CGRect)rect startColor:(CGColorRef)startColor endColor:(CGColorRef)endColor; + (void)saveimagesToDisk; + (UIImage *)roundCorneredImage:(UIImage *)orig radius:(CGFloat)r; ++ (NSString *)md5:(NSString *)string; @end diff --git a/media/ios/Classes/Utilities.m b/media/ios/Classes/Utilities.m index e31983a64..208c54828 100644 --- a/media/ios/Classes/Utilities.m +++ b/media/ios/Classes/Utilities.m @@ -7,6 +7,7 @@ // #import "Utilities.h" +#import void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor) { @@ -130,4 +131,17 @@ static NSMutableDictionary *imageCache; return result; } ++ (NSString *)md5:(NSString *)string { + const char *cStr = [string UTF8String]; + unsigned char result[16]; + CC_MD5( cStr, strlen(cStr), result ); // This is the md5 call + return [NSString stringWithFormat: + @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], + result[12], result[13], result[14], result[15] + ]; +} + @end \ No newline at end of file