diff --git a/clients/ios/Classes/NewsBlurAppDelegate.m b/clients/ios/Classes/NewsBlurAppDelegate.m index be1ae891a8..8873aa5cc6 100644 --- a/clients/ios/Classes/NewsBlurAppDelegate.m +++ b/clients/ios/Classes/NewsBlurAppDelegate.m @@ -4738,7 +4738,7 @@ - (void)prepareActiveCachedImages:(FMDatabase *)db { feedIds = @[[storiesCollection.activeFeed objectForKey:@"id"]]; } NSString *sql = [NSString stringWithFormat:@"SELECT c.image_url, c.story_hash FROM cached_images c " - "WHERE c.image_cached = 1 AND c.failed is null AND c.story_feed_id in (\"%@\")", + "WHERE c.image_cached = 1 AND c.story_feed_id in (\"%@\")", [feedIds componentsJoinedByString:@"\",\""]]; FMResultSet *cursor = [db executeQuery:sql]; diff --git a/clients/ios/Classes/StoryDetailObjCViewController.m b/clients/ios/Classes/StoryDetailObjCViewController.m index 03819a2583..1ea3d7f654 100644 --- a/clients/ios/Classes/StoryDetailObjCViewController.m +++ b/clients/ios/Classes/StoryDetailObjCViewController.m @@ -515,20 +515,38 @@ - (void)drawStory:(BOOL)force withOrientation:(UIInterfaceOrientation)orientatio contentWidthClass, (int)floorf(CGRectGetWidth(self.view.frame))]; // if (appDelegate.feedsViewController.isOffline) { + NSFileManager *manager = [NSFileManager defaultManager]; NSString *storyHash = [self.activeStory objectForKey:@"story_hash"]; NSArray *imageUrls = [appDelegate.activeCachedImages objectForKey:storyHash]; - // NSLog(@"📚 imageUrls: %@", imageUrls); + NSLog(@"📚 %@ %@ imageUrls: %@", activeStory[@"story_title"], storyHash, imageUrls); if (imageUrls) { NSString *storyImagesDirectory = [appDelegate.documentsURL.path stringByAppendingPathComponent:@"story_images"]; for (NSString *imageUrl in imageUrls) { NSURL *cachedUrl = [NSURL fileURLWithPath:storyImagesDirectory]; +// cachedUrl = [cachedUrl URLByAppendingPathComponent:[Utilities md5:imageUrl storyHash:storyHash]]; cachedUrl = [cachedUrl URLByAppendingPathComponent:[Utilities md5:imageUrl]]; - cachedUrl = [cachedUrl URLByAppendingPathExtension:imageUrl.pathExtension]; + cachedUrl = [cachedUrl URLByAppendingPathExtension:@"jpeg"]; + + if (![manager fileExistsAtPath:cachedUrl.path]) { + if (appDelegate.feedsViewController.isOffline) { + cachedUrl = [[NSBundle mainBundle] URLForResource:@"blank" withExtension:@"png"]; + } else { + continue; + } + } + + NSLog(@"📚 %@ %@ imageURL: %@ cachedURL: %@", activeStory[@"story_title"], storyHash, imageUrl, cachedUrl); storyContent = [storyContent stringByReplacingOccurrencesOfString:imageUrl withString:cachedUrl.absoluteString]; + + NSString *escapedURL = [imageUrl stringByEncodingHTMLEntities]; + + storyContent = [storyContent + stringByReplacingOccurrencesOfString:escapedURL + withString:cachedUrl.absoluteString]; } } // } diff --git a/clients/ios/Classes/Utilities.h b/clients/ios/Classes/Utilities.h index 6ee00adc23..4dd89727c7 100644 --- a/clients/ios/Classes/Utilities.h +++ b/clients/ios/Classes/Utilities.h @@ -21,6 +21,7 @@ void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor + (UIImage *)templateImageNamed:(NSString *)imageName sized:(CGFloat)size; + (UIImage *)imageNamed:(NSString *)imageName sized:(CGFloat)size; + (UIImage *)imageWithImage:(UIImage *)image convertToSize:(CGSize)size; +//+ (NSString *)md5:(NSString *)string storyHash:(NSString *)storyHash; + (NSString *)md5:(NSString *)string; + (NSString *)formatLongDateFromTimestamp:(NSInteger)timestamp; + (NSString *)formatShortDateFromTimestamp:(NSInteger)timestamp; diff --git a/clients/ios/Classes/Utilities.m b/clients/ios/Classes/Utilities.m index fbafb64ec3..0c925a3808 100644 --- a/clients/ios/Classes/Utilities.m +++ b/clients/ios/Classes/Utilities.m @@ -93,6 +93,32 @@ + (UIImage *)imageWithImage:(UIImage *)image convertToSize:(CGSize)size { return destImage; } +// These methods were an experiment in replacing the offline image filenames while tracing the images not appearing; an improvement, but skip for now; keep for future consideration. + +//+ (NSString *)removeIllegalCharactersForFilename:(NSString *)filename { +// NSCharacterSet *illegalCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"\\/:"]; +// NSString *cleanedFilename = [[filename componentsSeparatedByCharactersInSet:illegalCharacterSet] componentsJoinedByString:@"-"]; +// +// return cleanedFilename; +//} +// +//+ (NSUInteger)checksum:(NSString *)string { +// NSUInteger base = string.length; +// NSUInteger result = base * base; +// +// for (NSUInteger i = 0; i < string.length; i++) { +// result = (result + ([string characterAtIndex:i] * (i + 34)) + (732 * i) + (base * (i + 83))) % 999999999; +// } +// +// return result; +//} +// +//+ (NSString *)md5:(NSString *)string storyHash:(NSString *)storyHash { +// NSUInteger checksum = [self checksum:string]; +// NSString *cleanedStoryHash = [self removeIllegalCharactersForFilename:storyHash]; +// return [NSString stringWithFormat:@"%@-%@-%@", cleanedStoryHash, @(checksum), [self md5:string]]; +//} + + (NSString *)md5:(NSString *)string { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" diff --git a/clients/ios/Classes/offline/OfflineFetchImages.m b/clients/ios/Classes/offline/OfflineFetchImages.m index 52c47d0f46..4271bd0591 100644 --- a/clients/ios/Classes/offline/OfflineFetchImages.m +++ b/clients/ios/Classes/offline/OfflineFetchImages.m @@ -58,34 +58,36 @@ - (BOOL)fetchImages { }); return NO; } - NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration]; - [manager.requestSerializer setTimeoutInterval:5]; + [manager.requestSerializer setTimeoutInterval:10]; manager.responseSerializer = [AFImageResponseSerializer serializer]; manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); dispatch_group_t group = dispatch_group_create(); for (NSArray *urlArray in urls) { - NSString *urlString = [[urlArray objectAtIndex:0] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + NSString *urlString = [urlArray objectAtIndex:0]; NSString *storyHash = [urlArray objectAtIndex:1]; NSInteger storyTimestamp = [[urlArray objectAtIndex:2] integerValue]; dispatch_group_enter(group); -// NSLog(@" ---> Fetching offline image: %@", urlString); + if (![NSURL URLWithString:urlString]) { + urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + } + NSLog(@"📚 Fetching offline %@ image: %@", storyHash, urlString); [manager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { -// NSLog(@" ---> Fetched %@: %@", storyHash, urlString); + NSLog(@"📚 Fetched %@: %@", storyHash, urlString); UIImage *image = (UIImage *)responseObject; [self storeCachedImage:urlString withImage:image storyHash:storyHash storyTimestamp:storyTimestamp]; dispatch_group_leave(group); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { -// NSLog(@" ---> Failed to fetch image %@: %@", storyHash, urlString); + NSLog(@"📚 Failed to fetch image %@: %@ %@", storyHash, urlString, error); [self storeFailedImage:storyHash]; dispatch_group_leave(group); }]; } - + // dispatch_sync(dispatch_get_main_queue(), ^{ // [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; // }); @@ -167,16 +169,19 @@ - (void)storeCachedImage:(NSString *)imageUrl withImage:(UIImage *)image storyHa (unsigned long)NULL), ^{ NSData *responseData = UIImageJPEGRepresentation(image, 0.6); +// NSString *md5Url = [Utilities md5:imageUrl storyHash:storyHash]; NSString *md5Url = [Utilities md5:imageUrl]; // NSLog(@"Storing image: %@ (%d bytes - %d in queue)", storyHash, [responseData length], [imageDownloadOperationQueue requestsCount]); NSFileManager *fileManager = [NSFileManager defaultManager]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *cacheDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"story_images"]; - NSString *fullPath = [cacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", md5Url, [imageUrl pathExtension]]]; + NSString *fullPath = [[cacheDirectory stringByAppendingPathComponent:md5Url] stringByAppendingPathExtension:@"jpeg"]; [fileManager createFileAtPath:fullPath contents:responseData attributes:nil]; + NSLog(@"📚 stored storyHash: %@ imageURL: %@ cachedURL: %@", storyHash, imageUrl, fullPath); + [self.appDelegate.database inDatabase:^(FMDatabase *db) { [db executeUpdate:@"UPDATE cached_images SET " "image_cached = 1 WHERE story_hash = ?", diff --git a/clients/ios/NewsBlur.xcodeproj/project.pbxproj b/clients/ios/NewsBlur.xcodeproj/project.pbxproj index b4cc16009c..8b04bcf0cd 100755 --- a/clients/ios/NewsBlur.xcodeproj/project.pbxproj +++ b/clients/ios/NewsBlur.xcodeproj/project.pbxproj @@ -747,6 +747,8 @@ 177551DF238E228A00E27818 /* Old NewsBlur Latest.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 177551D3238E228A00E27818 /* Old NewsBlur Latest.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 177D017D28B05D9500F2F2DB /* StoryPagesCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 177D017C28B05D9500F2F2DB /* StoryPagesCollectionCell.swift */; }; 17813FB723AC6E450057FB16 /* WidgetErrorTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17CE3F0523AC529B003152EF /* WidgetErrorTableViewCell.xib */; }; + 1781B97F2C9D332B00C8F344 /* blank.png in Resources */ = {isa = PBXBuildFile; fileRef = 1781B97E2C9D332B00C8F344 /* blank.png */; }; + 1781B9802C9D332B00C8F344 /* blank.png in Resources */ = {isa = PBXBuildFile; fileRef = 1781B97E2C9D332B00C8F344 /* blank.png */; }; 178552492A1F115800A8CD92 /* FeedDetailTableCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 178552472A1F115800A8CD92 /* FeedDetailTableCell.m */; }; 1785524A2A1F115800A8CD92 /* FeedDetailTableCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 178552472A1F115800A8CD92 /* FeedDetailTableCell.m */; }; 1785524C2A21693300A8CD92 /* FeedDetailLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1785524B2A21693300A8CD92 /* FeedDetailLoadingView.swift */; }; @@ -1547,6 +1549,7 @@ 177551DC238E228A00E27818 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 177551E3238E26BF00E27818 /* Widget Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Widget Extension.entitlements"; sourceTree = ""; }; 177D017C28B05D9500F2F2DB /* StoryPagesCollectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryPagesCollectionCell.swift; sourceTree = ""; }; + 1781B97E2C9D332B00C8F344 /* blank.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = blank.png; sourceTree = ""; }; 178552472A1F115800A8CD92 /* FeedDetailTableCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedDetailTableCell.m; sourceTree = ""; }; 178552482A1F115800A8CD92 /* FeedDetailTableCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedDetailTableCell.h; sourceTree = ""; }; 1785524B2A21693300A8CD92 /* FeedDetailLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedDetailLoadingView.swift; sourceTree = ""; }; @@ -2878,6 +2881,7 @@ FFB1F69C1FBD1B2D001DA171 /* white_fade.png */, FF9D59331FBC08DB00823C10 /* Shiloh.jpg */, 17E2AEB22863FECF000FAB5B /* Lyric.jpg */, + 1781B97E2C9D332B00C8F344 /* blank.png */, FF3FA8941BB26B6A001F7C32 /* copy_link@2x.png */, FF83FF121FB54692008DAC0F /* g_icn_buffer.png */, FF83FF0E1FB54691008DAC0F /* g_icn_eating.png */, @@ -4441,6 +4445,7 @@ 175792042930605500490924 /* theme_color_dark-sel.png in Resources */, 175792052930605500490924 /* traverse_background_left@2x.png in Resources */, 175792062930605500490924 /* g_icn_offline.png in Resources */, + 1781B9802C9D332B00C8F344 /* blank.png in Resources */, 175792072930605500490924 /* triangle.png in Resources */, 175792082930605500490924 /* g_icn_offline@2x.png in Resources */, 175792092930605500490924 /* columns_double@2x.png in Resources */, @@ -4867,6 +4872,7 @@ FF83FF1A1FB54693008DAC0F /* g_icn_buffer.png in Resources */, 17E2AEDD286556CC000FAB5B /* icons8-security-wi-fi-100.png in Resources */, 17432C7A1C533DCC003F8FD6 /* FeedChooserViewController.xib in Resources */, + 1781B97F2C9D332B00C8F344 /* blank.png in Resources */, FFDD847F16E887D3000AA0A2 /* g_icn_folder@2x.png in Resources */, 1740C6881C10FD75005EA453 /* theme_color_dark.png in Resources */, FFDD848016E887D3000AA0A2 /* g_icn_hidden.png in Resources */, diff --git a/clients/ios/Other Sources/NBURLCache.m b/clients/ios/Other Sources/NBURLCache.m index 158c2b21f0..2d28338496 100644 --- a/clients/ios/Other Sources/NBURLCache.m +++ b/clients/ios/Other Sources/NBURLCache.m @@ -16,7 +16,7 @@ - (NSString *)substitutePath:(NSString *)pathString { NSString *storyImagesDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"story_images"]; NSString *cachedImage = [[storyImagesDirectory - stringByAppendingPathComponent:[Utilities md5:pathString]] stringByAppendingPathExtension:[pathString pathExtension]]; + stringByAppendingPathComponent:[Utilities md5:pathString]] stringByAppendingPathExtension:@"jpeg"]; return cachedImage; } diff --git a/clients/ios/Resources/blank.png b/clients/ios/Resources/blank.png new file mode 100644 index 0000000000..9d421ec2e0 Binary files /dev/null and b/clients/ios/Resources/blank.png differ