mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Merge branch 'dejal'
* dejal: iOS: #1236 (offline text) iOS: #1215 (wonky behavior)
This commit is contained in:
commit
781f9bf5f4
18 changed files with 429 additions and 55 deletions
|
@ -144,6 +144,10 @@ static UIFont *textFont = nil;
|
|||
@synthesize cell;
|
||||
|
||||
- (void)drawRect:(CGRect)r {
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
UIColor *backgroundColor;
|
||||
|
|
|
@ -210,11 +210,13 @@
|
|||
leftBorder.hidden = NO;
|
||||
}
|
||||
|
||||
[self adjustLayout];
|
||||
[self adjustLayoutCompleted:NO];
|
||||
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
||||
// leftBorder.frame = CGRectMake(0, 0, 1, CGRectGetHeight(self.view.bounds));
|
||||
|
||||
[self adjustLayout];
|
||||
if (!self.feedDetailIsVisible) {
|
||||
[self adjustLayoutCompleted:YES];
|
||||
}
|
||||
|
||||
if (self.feedDetailIsVisible) {
|
||||
// Defer this in the background, to avoid misaligning the detail views
|
||||
|
@ -239,8 +241,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)adjustLayout {
|
||||
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
|
||||
- (void)adjustLayoutCompleted:(BOOL)completed {
|
||||
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground && !completed) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -307,6 +309,8 @@
|
|||
self.leftBorder.backgroundColor = UIColorFromRGB(0xC2C5BE).CGColor;
|
||||
self.rightBorder.backgroundColor = UIColorFromRGB(0xC2C5BE).CGColor;
|
||||
|
||||
self.view.backgroundColor = UIColor.blackColor;
|
||||
|
||||
self.masterNavigationController.navigationBar.tintColor = [UINavigationBar appearance].tintColor;
|
||||
self.masterNavigationController.navigationBar.barTintColor = [UINavigationBar appearance].barTintColor;
|
||||
|
||||
|
|
|
@ -230,9 +230,12 @@ SFSafariViewControllerDelegate> {
|
|||
@property (readwrite) NSInteger savedStoriesCount;
|
||||
@property (readwrite) NSInteger totalUnfetchedStoryCount;
|
||||
@property (readwrite) NSInteger remainingUnfetchedStoryCount;
|
||||
@property (readwrite) NSInteger totalUncachedTextCount;
|
||||
@property (readwrite) NSInteger remainingUncachedTextCount;
|
||||
@property (readwrite) NSInteger totalUncachedImagesCount;
|
||||
@property (readwrite) NSInteger remainingUncachedImagesCount;
|
||||
@property (readwrite) NSInteger latestFetchedStoryDate;
|
||||
@property (readwrite) NSInteger latestCachedTextDate;
|
||||
@property (readwrite) NSInteger latestCachedImageDate;
|
||||
@property (readwrite) NSInteger selectedIntelligence;
|
||||
@property (readwrite) NSMutableDictionary * recentlyReadStories;
|
||||
|
@ -447,6 +450,7 @@ SFSafariViewControllerDelegate> {
|
|||
- (void)cancelOfflineQueue;
|
||||
- (void)startOfflineQueue;
|
||||
- (void)startOfflineFetchStories;
|
||||
- (void)startOfflineFetchText;
|
||||
- (void)startOfflineFetchImages;
|
||||
- (BOOL)isReachableForOffline;
|
||||
- (void)storeUserProfiles:(NSArray *)userProfiles;
|
||||
|
@ -456,6 +460,7 @@ SFSafariViewControllerDelegate> {
|
|||
- (void)flushQueuedReadStories:(BOOL)forceCheck withCallback:(void(^)(void))callback;
|
||||
- (void)syncQueuedReadStories:(FMDatabase *)db withStories:(NSDictionary *)hashes withCallback:(void(^)(void))callback;
|
||||
- (void)queueSavedStory:(NSDictionary *)story;
|
||||
- (void)fetchTextForStory:(NSString *)storyHash inFeed:(NSString *)feedId checkCache:(BOOL)checkCache withCallback:(void(^)(NSString *))callback;
|
||||
- (void)prepareActiveCachedImages:(FMDatabase *)db;
|
||||
- (void)cleanImageCache;
|
||||
- (void)deleteAllCachedImages;
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#import "IASKAppSettingsViewController.h"
|
||||
#import "OfflineSyncUnreads.h"
|
||||
#import "OfflineFetchStories.h"
|
||||
#import "OfflineFetchText.h"
|
||||
#import "OfflineFetchImages.h"
|
||||
#import "OfflineCleanImages.h"
|
||||
#import "NBBarButtonItem.h"
|
||||
|
@ -80,7 +81,7 @@
|
|||
|
||||
@implementation NewsBlurAppDelegate
|
||||
|
||||
#define CURRENT_DB_VERSION 36
|
||||
#define CURRENT_DB_VERSION 37
|
||||
|
||||
#define CURRENT_STATE_VERSION 1
|
||||
|
||||
|
@ -3596,6 +3597,10 @@
|
|||
[db executeUpdate:@"drop table if exists `queued_saved_hashes`"];
|
||||
}
|
||||
|
||||
if (databaseVersion < 37) {
|
||||
[db executeUpdate:@"drop table if exists `cached_text`"];
|
||||
}
|
||||
|
||||
NSLog(@"Dropped db: %@", [db lastErrorMessage]);
|
||||
sqlite3_exec(db.sqliteHandle, [[NSString stringWithFormat:@"PRAGMA user_version = %d", CURRENT_DB_VERSION] UTF8String], NULL, NULL, NULL);
|
||||
}
|
||||
|
@ -3675,6 +3680,19 @@
|
|||
")"];
|
||||
[db executeUpdate:createSavedTable];
|
||||
|
||||
NSString *createTextTable = [NSString stringWithFormat:@"create table if not exists cached_text "
|
||||
"("
|
||||
" story_feed_id varchar(20),"
|
||||
" story_hash varchar(24),"
|
||||
" story_timestamp number,"
|
||||
" text_json text"
|
||||
")"];
|
||||
[db executeUpdate:createTextTable];
|
||||
NSString *indexTextFeedId = @"CREATE INDEX IF NOT EXISTS cached_text_story_feed_id ON cached_text (story_feed_id)";
|
||||
[db executeUpdate:indexTextFeedId];
|
||||
NSString *indexTextStoryHash = @"CREATE INDEX IF NOT EXISTS cached_text_story_hash ON cached_text (story_hash)";
|
||||
[db executeUpdate:indexTextStoryHash];
|
||||
|
||||
NSString *createImagesTable = [NSString stringWithFormat:@"create table if not exists cached_images "
|
||||
"("
|
||||
" story_feed_id varchar(20),"
|
||||
|
@ -3747,6 +3765,12 @@
|
|||
// NSLog(@"Done start offline fetch stories");
|
||||
}
|
||||
|
||||
- (void)startOfflineFetchText {
|
||||
OfflineFetchText *operationFetchText = [[OfflineFetchText alloc] init];
|
||||
|
||||
[offlineQueue addOperation:operationFetchText];
|
||||
}
|
||||
|
||||
- (void)startOfflineFetchImages {
|
||||
OfflineFetchImages *operationFetchImages = [[OfflineFetchImages alloc] init];
|
||||
|
||||
|
@ -4041,6 +4065,76 @@
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)fetchTextForStory:(NSString *)storyHash inFeed:(NSString *)feedId checkCache:(BOOL)checkCache withCallback:(void(^)(NSString *))callback {
|
||||
if (checkCache) {
|
||||
[self privateGetCachedTextForStory:storyHash inFeed:feedId withCallback:^(NSString *text) {
|
||||
if (text != nil) {
|
||||
if (callback) {
|
||||
callback(text);
|
||||
}
|
||||
} else {
|
||||
[self privateFetchTextForStory:storyHash inFeed:feedId withCallback:callback];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[self privateFetchTextForStory:storyHash inFeed:feedId withCallback:callback];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)privateGetCachedTextForStory:(NSString *)storyHash inFeed:(NSString *)feedId withCallback:(void(^)(NSString *))callback {
|
||||
[self.database inDatabase:^(FMDatabase *db) {
|
||||
NSString *text = nil;
|
||||
FMResultSet *cursor = [db executeQuery:@"SELECT * FROM cached_text "
|
||||
"WHERE story_hash = ? AND story_feed_id = ? LIMIT 1",
|
||||
storyHash, feedId];
|
||||
while ([cursor next]) {
|
||||
NSDictionary *textCache = [cursor resultDictionary];
|
||||
NSString *json = [textCache objectForKey:@"text_json"];
|
||||
|
||||
if (json.length > 0) {
|
||||
NSDictionary *results = [NSJSONSerialization
|
||||
JSONObjectWithData:[json
|
||||
dataUsingEncoding:NSUTF8StringEncoding]
|
||||
options:0 error:nil];
|
||||
text = results[@"text"];
|
||||
|
||||
if (text) {
|
||||
NSLog(@"Found cached text: %@ bytes", @(text.length));
|
||||
} else {
|
||||
NSLog(@"Found cached failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
[cursor close];
|
||||
|
||||
if (callback) {
|
||||
callback(text);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)privateFetchTextForStory:(NSString *)storyHash inFeed:(NSString *)feedId withCallback:(void(^)(NSString *))callback {
|
||||
NSString *urlString = [NSString stringWithFormat:@"%@/rss_feeds/original_text", self.url];
|
||||
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
||||
[params setObject:storyHash forKey:@"story_id"];
|
||||
[params setObject:feedId forKey:@"feed_id"];
|
||||
|
||||
[self POST:urlString parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
NSString *text = [responseObject objectForKey:@"original_text"];
|
||||
|
||||
if ([[responseObject objectForKey:@"failed"] boolValue]) {
|
||||
text = nil;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(text);
|
||||
}
|
||||
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
|
||||
if (callback) {
|
||||
callback(nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)prepareActiveCachedImages:(FMDatabase *)db {
|
||||
activeCachedImages = [NSMutableDictionary dictionary];
|
||||
|
|
|
@ -138,8 +138,8 @@ UIGestureRecognizerDelegate, UISearchBarDelegate> {
|
|||
- (void)showRefreshNotifier;
|
||||
- (void)showCountingNotifier;
|
||||
- (void)showSyncingNotifier;
|
||||
- (void)showSyncingNotifier:(float)progress hoursBack:(NSInteger)days;
|
||||
- (void)showCachingNotifier:(float)progress hoursBack:(NSInteger)hours;
|
||||
- (void)showSyncingNotifier:(float)progress hoursBack:(NSInteger)hours;
|
||||
- (void)showCachingNotifier:(NSString *)prefix progress:(float)progress hoursBack:(NSInteger)hours;
|
||||
- (void)showOfflineNotifier;
|
||||
- (void)showDoneNotifier;
|
||||
- (void)hideNotifier;
|
||||
|
|
|
@ -2378,17 +2378,17 @@ heightForHeaderInSection:(NSInteger)section {
|
|||
[self.notifier show];
|
||||
}
|
||||
|
||||
- (void)showCachingNotifier:(float)progress hoursBack:(NSInteger)hours {
|
||||
- (void)showCachingNotifier:(NSString *)prefix progress:(float)progress hoursBack:(NSInteger)hours {
|
||||
// [self.notifier hide];
|
||||
self.notifier.style = NBSyncingProgressStyle;
|
||||
if (hours < 2) {
|
||||
self.notifier.title = @"Images from last hour";
|
||||
self.notifier.title = [NSString stringWithFormat:@"%@ from last hour", prefix];
|
||||
} else if (hours < 24) {
|
||||
self.notifier.title = [NSString stringWithFormat:@"Images from %ld hours ago", (long)hours];
|
||||
self.notifier.title = [NSString stringWithFormat:@"%@ from %ld hours ago", prefix, (long)hours];
|
||||
} else if (hours < 48) {
|
||||
self.notifier.title = @"Images from yesterday";
|
||||
self.notifier.title = [NSString stringWithFormat:@"%@ from yesterday", prefix];
|
||||
} else {
|
||||
self.notifier.title = [NSString stringWithFormat:@"Images from %d days ago", (int)round(hours / 24.f)];
|
||||
self.notifier.title = [NSString stringWithFormat:@"%@ from %d days ago", prefix, (int)round(hours / 24.f)];
|
||||
}
|
||||
[self.notifier setProgress:progress];
|
||||
[self.notifier show];
|
||||
|
|
|
@ -2489,25 +2489,20 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
|
|||
self.inTextView = YES;
|
||||
// NSLog(@"Fetching Text: %@", [self.activeStory objectForKey:@"story_title"]);
|
||||
if (self.activeStory == appDelegate.storyPageControl.currentPage.activeStory) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.appDelegate.storyPageControl showFetchingTextNotifier];
|
||||
});
|
||||
[self.appDelegate.storyPageControl showFetchingTextNotifier];
|
||||
}
|
||||
|
||||
NSString *urlString = [NSString stringWithFormat:@"%@/rss_feeds/original_text",
|
||||
self.appDelegate.url];
|
||||
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
||||
[params setObject:[self.activeStory objectForKey:@"id"] forKey:@"story_id"];
|
||||
[params setObject:[self.activeStory objectForKey:@"story_feed_id"] forKey:@"feed_id"];
|
||||
NSString *storyId = [self.activeStory objectForKey:@"id"];
|
||||
[appDelegate POST:urlString parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
[self finishFetchTextView:responseObject storyId:storyId];
|
||||
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
|
||||
[self failedFetchText:error];
|
||||
|
||||
[appDelegate fetchTextForStory:[self.activeStory objectForKey:@"story_hash"] inFeed:[self.activeStory objectForKey:@"story_feed_id"] checkCache:YES withCallback:^(NSString *text) {
|
||||
if (text != nil) {
|
||||
[self finishFetchText:text storyId:storyId];
|
||||
} else {
|
||||
[self failedFetchText];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)failedFetchText:(NSError *)error {
|
||||
- (void)failedFetchText {
|
||||
[self.appDelegate.storyPageControl hideNotifier];
|
||||
[MBProgressHUD hideHUDForView:self.webView animated:YES];
|
||||
if (self.activeStory == appDelegate.storyPageControl.currentPage.activeStory) {
|
||||
|
@ -2517,12 +2512,7 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
|
|||
[appDelegate.storyPageControl setTextButton:self];
|
||||
}
|
||||
|
||||
- (void)finishFetchTextView:(NSDictionary *)results storyId:(NSString *)storyId {
|
||||
if ([[results objectForKey:@"failed"] boolValue]) {
|
||||
[self failedFetchText:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)finishFetchText:(NSString *)text storyId:(NSString *)storyId {
|
||||
if (![storyId isEqualToString:[self.activeStory objectForKey:@"id"]]) {
|
||||
[self.appDelegate.storyPageControl hideNotifier];
|
||||
[MBProgressHUD hideHUDForView:self.webView animated:YES];
|
||||
|
@ -2532,7 +2522,7 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
|
|||
}
|
||||
|
||||
NSMutableDictionary *newActiveStory = [self.activeStory mutableCopy];
|
||||
[newActiveStory setObject:[results objectForKey:@"original_text"] forKey:@"original_text"];
|
||||
[newActiveStory setObject:text forKey:@"original_text"];
|
||||
if ([[self.activeStory objectForKey:@"story_hash"] isEqualToString:[appDelegate.activeStory objectForKey:@"story_hash"]]) {
|
||||
appDelegate.activeStory = newActiveStory;
|
||||
}
|
||||
|
@ -2583,7 +2573,7 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
|
|||
|
||||
- (void)finishFetchStoryChanges:(NSDictionary *)results storyId:(NSString *)storyId {
|
||||
if ([results[@"failed"] boolValue]) {
|
||||
[self failedFetchText:nil];
|
||||
[self failedFetchText];
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -93,9 +93,9 @@
|
|||
NSLog(@"Queue finished: %ld total (%ld remaining)", (long)appDelegate.totalUncachedImagesCount, (long)appDelegate.remainingUncachedImagesCount);
|
||||
[self updateProgress];
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
|
||||
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
|
||||
});
|
||||
|
||||
|
||||
// dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// [appDelegate.feedsViewController hideNotifier];
|
||||
// });
|
||||
|
@ -153,7 +153,7 @@
|
|||
(float)appDelegate.totalUncachedImagesCount);
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[appDelegate.feedsViewController showCachingNotifier:progress hoursBack:hours];
|
||||
[appDelegate.feedsViewController showCachingNotifier:@"Images" progress:progress hoursBack:hours];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -53,15 +53,17 @@
|
|||
// NSLog(@"Finished downloading unread stories. %d total", appDelegate.totalUnfetchedStoryCount);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
|
||||
|
||||
if (![[[NSUserDefaults standardUserDefaults]
|
||||
objectForKey:@"offline_image_download"] boolValue]) {
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"offline_text_download"]) {
|
||||
[appDelegate.feedsViewController showCachingNotifier:@"Text" progress:0 hoursBack:1];
|
||||
[appDelegate startOfflineFetchText];
|
||||
} else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"offline_image_download"]) {
|
||||
[appDelegate.feedsViewController showCachingNotifier:@"Images" progress:0 hoursBack:1];
|
||||
[appDelegate startOfflineFetchImages];
|
||||
} else {
|
||||
[appDelegate.feedsViewController showDoneNotifier];
|
||||
[appDelegate.feedsViewController hideNotifier];
|
||||
[appDelegate finishBackground];
|
||||
} else {
|
||||
[appDelegate.feedsViewController showCachingNotifier:0 hoursBack:1];
|
||||
[appDelegate startOfflineFetchImages];
|
||||
}
|
||||
});
|
||||
return NO;
|
||||
|
@ -164,30 +166,42 @@
|
|||
if (!strongSelf) return;
|
||||
BOOL anyInserted = NO;
|
||||
for (NSDictionary *story in [results objectForKey:@"stories"]) {
|
||||
id storyFeedId = [story objectForKey:@"story_feed_id"];
|
||||
id storyHash = [story objectForKey:@"story_hash"];
|
||||
NSString *storyTimestamp = [story objectForKey:@"story_timestamp"];
|
||||
id imageUrls = [story objectForKey:@"image_urls"];
|
||||
BOOL inserted = [db executeUpdate:@"INSERT into stories "
|
||||
"(story_feed_id, story_hash, story_timestamp, story_json) VALUES "
|
||||
"(?, ?, ?, ?)",
|
||||
[story objectForKey:@"story_feed_id"],
|
||||
[story objectForKey:@"story_hash"],
|
||||
storyFeedId,
|
||||
storyHash,
|
||||
storyTimestamp,
|
||||
[story JSONRepresentation]
|
||||
];
|
||||
if ([[story objectForKey:@"image_urls"] class] != [NSNull class] &&
|
||||
[[story objectForKey:@"image_urls"] count]) {
|
||||
for (NSString *imageUrl in [story objectForKey:@"image_urls"]) {
|
||||
if ([appDelegate isFeedInTextView:storyFeedId]) {
|
||||
[db executeUpdate:@"INSERT INTO cached_text "
|
||||
"(story_feed_id, story_hash, story_timestamp) VALUES "
|
||||
"(?, ?, ?)",
|
||||
storyFeedId,
|
||||
storyHash,
|
||||
storyTimestamp
|
||||
];
|
||||
}
|
||||
if ([imageUrls class] != [NSNull class] &&
|
||||
[imageUrls count]) {
|
||||
for (NSString *imageUrl in imageUrls) {
|
||||
[db executeUpdate:@"INSERT INTO cached_images "
|
||||
"(story_feed_id, story_hash, image_url) VALUES "
|
||||
"(?, ?, ?)",
|
||||
[story objectForKey:@"story_feed_id"],
|
||||
[story objectForKey:@"story_hash"],
|
||||
storyFeedId,
|
||||
storyHash,
|
||||
imageUrl
|
||||
];
|
||||
}
|
||||
}
|
||||
if (inserted) {
|
||||
anyInserted = YES;
|
||||
[storyHashes removeObject:[story objectForKey:@"story_hash"]];
|
||||
[storyHashes removeObject:storyHash];
|
||||
}
|
||||
if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"default_order"] isEqualToString:@"oldest"]) {
|
||||
if ([storyTimestamp intValue] > appDelegate.latestFetchedStoryDate) {
|
||||
|
|
23
clients/ios/Classes/offline/OfflineFetchText.h
Normal file
23
clients/ios/Classes/offline/OfflineFetchText.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// OfflineFetchText.h
|
||||
// NewsBlur
|
||||
//
|
||||
// Created by David Sinclair on 2019-10-25.
|
||||
// Copyright © 2019 NewsBlur. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "NewsBlurAppDelegate.h"
|
||||
#import "FMDatabaseQueue.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OfflineFetchText : NSOperation
|
||||
|
||||
@property (nonatomic) NewsBlurAppDelegate *appDelegate;
|
||||
|
||||
- (BOOL)fetchText;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
213
clients/ios/Classes/offline/OfflineFetchText.m
Normal file
213
clients/ios/Classes/offline/OfflineFetchText.m
Normal file
|
@ -0,0 +1,213 @@
|
|||
//
|
||||
// OfflineFetchText.m
|
||||
// NewsBlur
|
||||
//
|
||||
// Created by David Sinclair on 2019-10-25.
|
||||
// Copyright © 2019 NewsBlur. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OfflineFetchText.h"
|
||||
#import "NewsBlurViewController.h"
|
||||
#import "FMDatabase.h"
|
||||
#import "FMDatabaseAdditions.h"
|
||||
#import "Utilities.h"
|
||||
#import "NSObject+SBJSON.h"
|
||||
|
||||
@implementation OfflineFetchText
|
||||
|
||||
- (void)main {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
|
||||
});
|
||||
|
||||
while (YES) {
|
||||
BOOL fetched = [self fetchText];
|
||||
if (!fetched) break;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)fetchText {
|
||||
if (self.isCancelled) {
|
||||
NSLog(@"Text cancelled.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSLog(@"Fetching text...");
|
||||
NSArray *pendingTextDictionaries = [self uncachedTextDictionaries];
|
||||
|
||||
if (![[[NSUserDefaults standardUserDefaults]
|
||||
objectForKey:@"offline_text_download"] boolValue] ||
|
||||
![[[NSUserDefaults standardUserDefaults]
|
||||
objectForKey:@"offline_allowed"] boolValue] ||
|
||||
[pendingTextDictionaries count] == 0) {
|
||||
NSLog(@"Finished caching text. %ld total", (long)self.appDelegate.totalUncachedTextCount);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"offline_image_download"]) {
|
||||
[self.appDelegate.feedsViewController showCachingNotifier:@"Images" progress:0 hoursBack:1];
|
||||
[self.appDelegate startOfflineFetchImages];
|
||||
} else {
|
||||
[self.appDelegate.feedsViewController showDoneNotifier];
|
||||
[self.appDelegate.feedsViewController hideNotifier];
|
||||
[self.appDelegate finishBackground];
|
||||
}
|
||||
});
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![self.appDelegate isReachableForOffline]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.appDelegate.feedsViewController showDoneNotifier];
|
||||
[self.appDelegate.feedsViewController hideNotifier];
|
||||
});
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *urlString = [NSString stringWithFormat:@"%@/rss_feeds/original_text", self.appDelegate.url];
|
||||
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
|
||||
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
for (NSDictionary *pendingTextDictionary in pendingTextDictionaries) {
|
||||
id storyHash = pendingTextDictionary[@"story_hash"];
|
||||
id feedId = pendingTextDictionary[@"story_feed_id"];
|
||||
NSInteger storyTimestamp = [pendingTextDictionary[@"story_timestamp"] integerValue];
|
||||
|
||||
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
||||
[params setObject:storyHash forKey:@"story_id"];
|
||||
[params setObject:feedId forKey:@"feed_id"];
|
||||
|
||||
dispatch_group_enter(group);
|
||||
// NSLog(@" ---> Fetching offline text: %@", urlString);
|
||||
[manager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
// NSLog(@" ---> Fetched %@: %@", storyHash, urlString);
|
||||
NSString *text = [responseObject objectForKey:@"original_text"];
|
||||
|
||||
if ([text isKindOfClass:[NSString class]]) {
|
||||
[self storeText:text forStoryHash:storyHash storyTimestamp:storyTimestamp];
|
||||
} else {
|
||||
[self storeFailedTextForStoryHash:storyHash];
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
|
||||
// NSLog(@" ---> Failed to fetch text %@: %@", storyHash, urlString);
|
||||
[self storeFailedTextForStoryHash:storyHash];
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}
|
||||
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
|
||||
});
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
NSLog(@"Queue finished: %ld total (%ld remaining)", (long)self.appDelegate.totalUncachedTextCount, (long)self.appDelegate.remainingUncachedTextCount);
|
||||
|
||||
[self updateProgress];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
|
||||
});
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSArray *)uncachedTextDictionaries {
|
||||
NSMutableArray *pendingTextDictionaries = [NSMutableArray array];
|
||||
|
||||
[self.appDelegate.database inDatabase:^(FMDatabase *db) {
|
||||
NSString *commonQuery = @"FROM cached_text c "
|
||||
"INNER JOIN unread_hashes u ON (c.story_hash = u.story_hash) "
|
||||
"WHERE c.text_json is null ";
|
||||
int count = [db intForQuery:[NSString stringWithFormat:@"SELECT COUNT(1) %@", commonQuery]];
|
||||
if (self.appDelegate.totalUncachedTextCount == 0) {
|
||||
self.appDelegate.totalUncachedTextCount = count;
|
||||
self.appDelegate.remainingUncachedTextCount = self.appDelegate.totalUncachedTextCount;
|
||||
} else {
|
||||
self.appDelegate.remainingUncachedTextCount = count;
|
||||
}
|
||||
|
||||
int limit = 120;
|
||||
NSString *order;
|
||||
if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"default_order"] isEqualToString:@"oldest"]) {
|
||||
order = @"ASC";
|
||||
} else {
|
||||
order = @"DESC";
|
||||
}
|
||||
NSString *sql = [NSString stringWithFormat:@"SELECT * %@ ORDER BY u.story_timestamp %@ LIMIT %d", commonQuery, order, limit];
|
||||
FMResultSet *cursor = [db executeQuery:sql];
|
||||
|
||||
while ([cursor next]) {
|
||||
[pendingTextDictionaries addObject:[cursor resultDictionary]];
|
||||
}
|
||||
|
||||
[cursor close];
|
||||
}];
|
||||
|
||||
return pendingTextDictionaries;
|
||||
}
|
||||
|
||||
- (void)updateProgress {
|
||||
if (self.isCancelled) return;
|
||||
|
||||
NSInteger start = (NSInteger)[[NSDate date] timeIntervalSince1970];
|
||||
NSInteger end = self.appDelegate.latestCachedTextDate;
|
||||
NSInteger seconds = start - (end ? end : start);
|
||||
__block int hours = (int)round(seconds / 60.f / 60.f);
|
||||
|
||||
__block float progress = 0.f;
|
||||
if (self.appDelegate.totalUncachedTextCount) {
|
||||
progress = 1.f - ((float)self.appDelegate.remainingUncachedTextCount /
|
||||
(float)self.appDelegate.totalUncachedTextCount);
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.appDelegate.feedsViewController showCachingNotifier:@"Text" progress:progress hoursBack:hours];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)storeText:(NSString *)text forStoryHash:(NSString *)storyHash storyTimestamp:(NSInteger)storyTimestamp {
|
||||
if (self.isCancelled) {
|
||||
NSLog(@"Text cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *textDictionary = @{@"text" : text};
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,
|
||||
(unsigned long)NULL), ^{
|
||||
[self storeTextDictionary:textDictionary forStoryHash:storyHash];
|
||||
|
||||
if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"default_order"] isEqualToString:@"oldest"]) {
|
||||
if (storyTimestamp > self.appDelegate.latestCachedTextDate) {
|
||||
self.appDelegate.latestCachedTextDate = storyTimestamp;
|
||||
}
|
||||
} else {
|
||||
if (!self.appDelegate.latestCachedTextDate || storyTimestamp < self.appDelegate.latestCachedTextDate) {
|
||||
self.appDelegate.latestCachedTextDate = storyTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@synchronized (self) {
|
||||
self.appDelegate.remainingUncachedTextCount--;
|
||||
if (self.appDelegate.remainingUncachedTextCount % 10 == 0) {
|
||||
[self updateProgress];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)storeFailedTextForStoryHash:(NSString *)storyHash {
|
||||
NSDictionary *textDictionary = @{@"failed" : @YES};
|
||||
|
||||
[self storeTextDictionary:textDictionary forStoryHash:storyHash];
|
||||
}
|
||||
|
||||
- (void)storeTextDictionary:(NSDictionary *)textDictionary forStoryHash:(NSString *)storyHash {
|
||||
[self.appDelegate.database inDatabase:^(FMDatabase *db) {
|
||||
[db executeUpdate:@"UPDATE cached_text SET text_json = ? WHERE story_hash = ?",
|
||||
[textDictionary JSONRepresentation],
|
||||
storyHash];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
|
@ -104,6 +104,7 @@
|
|||
// NSLog(@"Deleting stories over limit: %ld - %d", (long)offlineLimit, offlineLimitTimestamp);
|
||||
[db executeUpdate:[NSString stringWithFormat:@"DELETE FROM unread_hashes WHERE story_timestamp %@ %d", orderComp, offlineLimitTimestamp]];
|
||||
[db executeUpdate:[NSString stringWithFormat:@"DELETE FROM stories WHERE story_timestamp %@ %d", orderComp, offlineLimitTimestamp]];
|
||||
[db executeUpdate:[NSString stringWithFormat:@"DELETE FROM text WHERE story_timestamp %@ %d", orderComp, offlineLimitTimestamp]];
|
||||
// [db executeUpdate:[NSString stringWithFormat:@"DELETE FROM story_scrolls WHERE story_timestamp %@ %d", orderComp, offlineLimitTimestamp]]; // Don't cleanup story scrolls just yet
|
||||
}
|
||||
}];
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
010EDEFA1B2386B7003B79DE /* OnePasswordExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 010EDEF81B2386B7003B79DE /* OnePasswordExtension.m */; };
|
||||
010EDEFC1B238722003B79DE /* 1Password.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 010EDEFB1B238722003B79DE /* 1Password.xcassets */; };
|
||||
1715D02B2166B3F900227731 /* PremiumManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1715D02A2166B3F900227731 /* PremiumManager.m */; };
|
||||
17362ADD23639B4E00A0FCCC /* OfflineFetchText.m in Sources */ = {isa = PBXBuildFile; fileRef = 17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */; };
|
||||
1740C6881C10FD75005EA453 /* theme_color_dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6841C10FD75005EA453 /* theme_color_dark.png */; };
|
||||
1740C6891C10FD75005EA453 /* theme_color_light.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6851C10FD75005EA453 /* theme_color_light.png */; };
|
||||
1740C68A1C10FD75005EA453 /* theme_color_medium.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6861C10FD75005EA453 /* theme_color_medium.png */; };
|
||||
|
@ -643,6 +644,8 @@
|
|||
010EDEFB1B238722003B79DE /* 1Password.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = 1Password.xcassets; path = "Other Sources/OnePasswordExtension/1Password.xcassets"; sourceTree = "<group>"; };
|
||||
1715D0292166B3F900227731 /* PremiumManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PremiumManager.h; sourceTree = "<group>"; };
|
||||
1715D02A2166B3F900227731 /* PremiumManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PremiumManager.m; sourceTree = "<group>"; };
|
||||
17362ADB23639B4E00A0FCCC /* OfflineFetchText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OfflineFetchText.h; path = offline/OfflineFetchText.h; sourceTree = "<group>"; };
|
||||
17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = OfflineFetchText.m; path = offline/OfflineFetchText.m; sourceTree = "<group>"; };
|
||||
1740C6841C10FD75005EA453 /* theme_color_dark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = theme_color_dark.png; sourceTree = "<group>"; };
|
||||
1740C6851C10FD75005EA453 /* theme_color_light.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = theme_color_light.png; sourceTree = "<group>"; };
|
||||
1740C6861C10FD75005EA453 /* theme_color_medium.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = theme_color_medium.png; sourceTree = "<group>"; };
|
||||
|
@ -2470,6 +2473,8 @@
|
|||
FF855B5A1794B0670098D48A /* OfflineSyncUnreads.m */,
|
||||
FF855B5C1794B0760098D48A /* OfflineFetchStories.h */,
|
||||
FF855B5D1794B0760098D48A /* OfflineFetchStories.m */,
|
||||
17362ADB23639B4E00A0FCCC /* OfflineFetchText.h */,
|
||||
17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */,
|
||||
FF855B5F1794B0830098D48A /* OfflineFetchImages.h */,
|
||||
FF855B601794B0830098D48A /* OfflineFetchImages.m */,
|
||||
FF0FAEAF17B0846C008651F9 /* OfflineCleanImages.h */,
|
||||
|
@ -2724,7 +2729,7 @@
|
|||
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1100;
|
||||
LastUpgradeCheck = 1110;
|
||||
ORGANIZATIONNAME = NewsBlur;
|
||||
TargetAttributes = {
|
||||
1749390F1C251BFE003D98AA = {
|
||||
|
@ -3391,6 +3396,7 @@
|
|||
FF855B5B1794B0670098D48A /* OfflineSyncUnreads.m in Sources */,
|
||||
FF34FD6A1E9D93CB0062F8ED /* IASKPSSliderSpecifierViewCell.m in Sources */,
|
||||
FF855B5E1794B0760098D48A /* OfflineFetchStories.m in Sources */,
|
||||
17362ADD23639B4E00A0FCCC /* OfflineFetchText.m in Sources */,
|
||||
FF855B611794B0830098D48A /* OfflineFetchImages.m in Sources */,
|
||||
FF8D1EA71BAA304E00725D8A /* Reachability.m in Sources */,
|
||||
FFCDD90117F65A71000C6483 /* NBSwipeableCell.m in Sources */,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
LastUpgradeVersion = "1110"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
LastUpgradeVersion = "1110"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
LastUpgradeVersion = "1110"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
|
|
|
@ -436,6 +436,16 @@
|
|||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Download text</string>
|
||||
<key>Key</key>
|
||||
<string>offline_text_download</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
|
|
|
@ -456,6 +456,16 @@
|
|||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Download text</string>
|
||||
<key>Key</key>
|
||||
<string>offline_text_download</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue