start supporting article sync
This commit is contained in:
parent
c7d0d23146
commit
09faf1a0c2
|
@ -13,6 +13,8 @@
|
||||||
3BF6112423572A62000EF978 /* FeedWranglerSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */; };
|
3BF6112423572A62000EF978 /* FeedWranglerSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */; };
|
||||||
3BF6112623572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */; };
|
3BF6112623572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */; };
|
||||||
3BF6119023577173000EF978 /* FeedWranglerGenericResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6118F23577173000EF978 /* FeedWranglerGenericResult.swift */; };
|
3BF6119023577173000EF978 /* FeedWranglerGenericResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6118F23577173000EF978 /* FeedWranglerGenericResult.swift */; };
|
||||||
|
3BF611922357877E000EF978 /* FeedWranglerFeedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF611912357877E000EF978 /* FeedWranglerFeedItem.swift */; };
|
||||||
|
3BF6119423578F55000EF978 /* FeedWranglerFeedItemsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6119323578F55000EF978 /* FeedWranglerFeedItemsRequest.swift */; };
|
||||||
5107A099227DE42E00C7C3C5 /* AccountCredentialsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */; };
|
5107A099227DE42E00C7C3C5 /* AccountCredentialsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */; };
|
||||||
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; };
|
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; };
|
||||||
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; };
|
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; };
|
||||||
|
@ -197,6 +199,8 @@
|
||||||
3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerSubscription.swift; sourceTree = "<group>"; };
|
3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerSubscription.swift; sourceTree = "<group>"; };
|
||||||
3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerSubscriptionsRequest.swift; sourceTree = "<group>"; };
|
3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerSubscriptionsRequest.swift; sourceTree = "<group>"; };
|
||||||
3BF6118F23577173000EF978 /* FeedWranglerGenericResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerGenericResult.swift; sourceTree = "<group>"; };
|
3BF6118F23577173000EF978 /* FeedWranglerGenericResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerGenericResult.swift; sourceTree = "<group>"; };
|
||||||
|
3BF611912357877E000EF978 /* FeedWranglerFeedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerFeedItem.swift; sourceTree = "<group>"; };
|
||||||
|
3BF6119323578F55000EF978 /* FeedWranglerFeedItemsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerFeedItemsRequest.swift; sourceTree = "<group>"; };
|
||||||
5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCredentialsTest.swift; sourceTree = "<group>"; };
|
5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCredentialsTest.swift; sourceTree = "<group>"; };
|
||||||
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = "<group>"; };
|
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = "<group>"; };
|
||||||
5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = "<group>"; };
|
5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = "<group>"; };
|
||||||
|
@ -358,6 +362,8 @@
|
||||||
3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */,
|
3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */,
|
||||||
3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */,
|
3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */,
|
||||||
3BF6118F23577173000EF978 /* FeedWranglerGenericResult.swift */,
|
3BF6118F23577173000EF978 /* FeedWranglerGenericResult.swift */,
|
||||||
|
3BF611912357877E000EF978 /* FeedWranglerFeedItem.swift */,
|
||||||
|
3BF6119323578F55000EF978 /* FeedWranglerFeedItemsRequest.swift */,
|
||||||
);
|
);
|
||||||
path = FeedWrangler;
|
path = FeedWrangler;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -939,6 +945,7 @@
|
||||||
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */,
|
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */,
|
||||||
9EAEC626233318400085D7C9 /* FeedlyStream.swift in Sources */,
|
9EAEC626233318400085D7C9 /* FeedlyStream.swift in Sources */,
|
||||||
9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */,
|
9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */,
|
||||||
|
3BF6119423578F55000EF978 /* FeedWranglerFeedItemsRequest.swift in Sources */,
|
||||||
51E3EB41229AF61B00645299 /* AccountError.swift in Sources */,
|
51E3EB41229AF61B00645299 /* AccountError.swift in Sources */,
|
||||||
9E1D155D233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift in Sources */,
|
9E1D155D233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift in Sources */,
|
||||||
51E59599228C77BC00FCC42B /* FeedbinUnreadEntry.swift in Sources */,
|
51E59599228C77BC00FCC42B /* FeedbinUnreadEntry.swift in Sources */,
|
||||||
|
@ -964,6 +971,7 @@
|
||||||
3BF6112623572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift in Sources */,
|
3BF6112623572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift in Sources */,
|
||||||
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
|
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
|
||||||
510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */,
|
510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */,
|
||||||
|
3BF611922357877E000EF978 /* FeedWranglerFeedItem.swift in Sources */,
|
||||||
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
|
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
|
||||||
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */,
|
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */,
|
||||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SyncDatabase
|
||||||
import RSWeb
|
import RSWeb
|
||||||
|
|
||||||
final class FeedWranglerAPICaller: NSObject {
|
final class FeedWranglerAPICaller: NSObject {
|
||||||
|
@ -125,4 +126,161 @@ final class FeedWranglerAPICaller: NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: FeedItems
|
||||||
|
func retrieveFeedItems(page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
|
||||||
|
guard var components = URLComponents(url: FeedWranglerConfig.clientURL.appendingPathComponent("feed_items/list"), resolvingAgainstBaseURL: false) else {
|
||||||
|
completion(.failure(TransportError.noURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// todo: handle initial sync better
|
||||||
|
components.queryItems = [
|
||||||
|
URLQueryItem(name: "read", value: "false"),
|
||||||
|
URLQueryItem(name: "offset", value: String(page * FeedWranglerConfig.pageSize)),
|
||||||
|
// URLQueryItem(name: "created_since", value: feedID),
|
||||||
|
// URLQueryItem(name: "updated_since", value: feedID),
|
||||||
|
]
|
||||||
|
|
||||||
|
guard let url = components.url else {
|
||||||
|
completion(.failure(TransportError.noURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = URLRequest(url: url, credentials: credentials)
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: FeedWranglerFeedItemsRequest.self) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let (_, results)):
|
||||||
|
completion(.success(results?.feedItems ?? []))
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveUnreadFeedItems(page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
|
||||||
|
guard var components = URLComponents(url: FeedWranglerConfig.clientURL.appendingPathComponent("feed_items/list"), resolvingAgainstBaseURL: false) else {
|
||||||
|
completion(.failure(TransportError.noURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
components.queryItems = [
|
||||||
|
URLQueryItem(name: "read", value: "false"),
|
||||||
|
URLQueryItem(name: "offset", value: String(page * FeedWranglerConfig.pageSize)),
|
||||||
|
// URLQueryItem(name: "created_since", value: feedID),
|
||||||
|
// URLQueryItem(name: "updated_since", value: feedID),
|
||||||
|
]
|
||||||
|
|
||||||
|
guard let url = components.url else {
|
||||||
|
completion(.failure(TransportError.noURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = URLRequest(url: url, credentials: credentials)
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: FeedWranglerFeedItemsRequest.self) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let (_, results)):
|
||||||
|
completion(.success(results?.feedItems ?? []))
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveAllUnreadFeedItems(foundItems: [FeedWranglerFeedItem] = [], page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
|
||||||
|
retrieveUnreadFeedItems(page: page) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let newItems):
|
||||||
|
if newItems.count > 0 {
|
||||||
|
self.retrieveAllUnreadFeedItems(foundItems: foundItems + newItems, page: (page + 1), completion: completion)
|
||||||
|
} else {
|
||||||
|
completion(.success(foundItems + newItems))
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveStarredFeedItems(page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
|
||||||
|
guard var components = URLComponents(url: FeedWranglerConfig.clientURL.appendingPathComponent("feed_items/list"), resolvingAgainstBaseURL: false) else {
|
||||||
|
completion(.failure(TransportError.noURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
components.queryItems = [
|
||||||
|
URLQueryItem(name: "starred", value: "true"),
|
||||||
|
URLQueryItem(name: "offset", value: String(page * FeedWranglerConfig.pageSize)),
|
||||||
|
// URLQueryItem(name: "created_since", value: feedID),
|
||||||
|
// URLQueryItem(name: "updated_since", value: feedID),
|
||||||
|
]
|
||||||
|
|
||||||
|
guard let url = components.url else {
|
||||||
|
completion(.failure(TransportError.noURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = URLRequest(url: url, credentials: credentials)
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: FeedWranglerFeedItemsRequest.self) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let (_, results)):
|
||||||
|
completion(.success(results?.feedItems ?? []))
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveAllStarredFeedItems(foundItems: [FeedWranglerFeedItem] = [], page: Int = 0, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) {
|
||||||
|
retrieveStarredFeedItems(page: page) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let newItems):
|
||||||
|
if newItems.count > 0 {
|
||||||
|
self.retrieveAllStarredFeedItems(foundItems: foundItems + newItems, page: (page + 1), completion: completion)
|
||||||
|
} else {
|
||||||
|
completion(.success(foundItems + newItems))
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateArticleStatus(_ articleID: String, _ statuses: [SyncStatus], completion: @escaping () -> Void) {
|
||||||
|
guard var components = URLComponents(url: FeedWranglerConfig.clientURL.appendingPathComponent("feed_items/update"), resolvingAgainstBaseURL: false) else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var queryItems = statuses.compactMap { status -> URLQueryItem? in
|
||||||
|
switch status.key {
|
||||||
|
case .read:
|
||||||
|
return URLQueryItem(name: "read", value: status.flag.description)
|
||||||
|
|
||||||
|
case .starred:
|
||||||
|
return URLQueryItem(name: "starred", value: status.flag.description)
|
||||||
|
|
||||||
|
case .userDeleted:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryItems.append(URLQueryItem(name: "feed_item_id", value: articleID))
|
||||||
|
components.queryItems = (components.queryItems ?? []) + queryItems
|
||||||
|
|
||||||
|
guard let url = components.url else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = URLRequest(url: url, credentials: credentials)
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: FeedWranglerGenericResult.self) { result in
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
|
||||||
caller = FeedWranglerAPICaller(transport: session)
|
caller = FeedWranglerAPICaller(transport: session)
|
||||||
}
|
}
|
||||||
|
|
||||||
database = SyncDatabase(databaseFilePath: dataFolder.appending("/Sync.sqlite3"))
|
database = SyncDatabase(databaseFilePath: dataFolder.appending("/DB.sqlite3"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
@ -100,9 +100,25 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshArticles(for account: Account, completion: @escaping (() -> Void)) {
|
func refreshArticles(for account: Account, page: Int = 0, completion: @escaping (() -> Void)) {
|
||||||
os_log(.debug, log: log, "Refreshing articles...")
|
os_log(.debug, log: log, "Refreshing articles, page: %d...", page)
|
||||||
completion()
|
|
||||||
|
caller.retrieveFeedItems(page: page) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let items):
|
||||||
|
self.syncFeedItems(account, items) {
|
||||||
|
if items.count == 0 {
|
||||||
|
completion()
|
||||||
|
} else {
|
||||||
|
self.refreshArticles(for: account, page: (page + 1), completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
// TODO Handle error
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshMissingArticles(for account: Account, completion: @escaping (() -> Void)) {
|
func refreshMissingArticles(for account: Account, completion: @escaping (() -> Void)) {
|
||||||
|
@ -112,12 +128,60 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
|
||||||
|
|
||||||
func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
||||||
os_log(.debug, log: log, "Sending article status...")
|
os_log(.debug, log: log, "Sending article status...")
|
||||||
completion()
|
|
||||||
|
let syncStatuses = database.selectForProcessing()
|
||||||
|
let articleStatuses = Dictionary(grouping: syncStatuses, by: { $0.articleID })
|
||||||
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
articleStatuses.forEach { articleID, statuses in
|
||||||
|
group.enter()
|
||||||
|
caller.updateArticleStatus(articleID, statuses) {
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
group.notify(queue: DispatchQueue.main) {
|
||||||
|
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||||
|
completion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
||||||
os_log(.debug, log: log, "Refreshing article status...")
|
os_log(.debug, log: log, "Refreshing article status...")
|
||||||
completion()
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
group.enter()
|
||||||
|
caller.retrieveAllUnreadFeedItems { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let items):
|
||||||
|
self.syncArticleReadState(account, items)
|
||||||
|
group.leave()
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription)
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// starred
|
||||||
|
group.enter()
|
||||||
|
caller.retrieveAllStarredFeedItems { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let items):
|
||||||
|
self.syncArticleStarredState(account, items)
|
||||||
|
group.leave()
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription)
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.notify(queue: DispatchQueue.main) {
|
||||||
|
os_log(.debug, log: self.log, "Done refreshing article statuses.")
|
||||||
|
completion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
@ -208,7 +272,14 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||||
fatalError()
|
let syncStatuses = articles.map { SyncStatus(articleID: $0.articleID, key: statusKey, flag: flag)}
|
||||||
|
database.insertStatuses(syncStatuses)
|
||||||
|
|
||||||
|
if database.selectPendingCount() > 0 {
|
||||||
|
sendArticleStatus(for: account) {} // do it in the background
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.update(articles, statusKey: statusKey, flag: flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountDidInitialize(_ account: Account) {
|
func accountDidInitialize(_ account: Account) {
|
||||||
|
@ -257,4 +328,67 @@ private extension FeedWranglerAccountDelegate {
|
||||||
account.addFeed(feed)
|
account.addFeed(feed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func syncFeedItems(_ account: Account, _ feedItems: [FeedWranglerFeedItem], completion: @escaping (() -> Void)) {
|
||||||
|
let parsedItems = feedItems.map { (item: FeedWranglerFeedItem) -> ParsedItem in
|
||||||
|
let itemID = String(item.feedItemID)
|
||||||
|
// let authors = ...
|
||||||
|
let parsedItem = ParsedItem(syncServiceID: itemID, uniqueID: itemID, feedURL: String(item.feedID), url: nil, externalURL: item.url, title: item.title, contentHTML: item.body, contentText: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: item.publishedDate, dateModified: item.updatedDate, authors: nil, tags: nil, attachments: nil)
|
||||||
|
|
||||||
|
return parsedItem
|
||||||
|
}
|
||||||
|
|
||||||
|
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { $0.feedURL }).mapValues { Set($0) }
|
||||||
|
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncArticleReadState(_ account: Account, _ unreadFeedItems: [FeedWranglerFeedItem]) {
|
||||||
|
let unreadServerItemIDs = Set(unreadFeedItems.map { String($0.feedItemID) })
|
||||||
|
let unreadLocalItemIDs = account.fetchUnreadArticleIDs()
|
||||||
|
|
||||||
|
// unread if unread on server
|
||||||
|
let unreadDiffItemIDs = unreadServerItemIDs.subtracting(unreadLocalItemIDs)
|
||||||
|
let unreadFoundArticles = account.fetchArticles(.articleIDs(unreadDiffItemIDs))
|
||||||
|
account.update(unreadFoundArticles, statusKey: .read, flag: false)
|
||||||
|
|
||||||
|
let unreadFoundItemIDs = Set(unreadFoundArticles.map { $0.articleID })
|
||||||
|
let missingArticleIDs = unreadDiffItemIDs.subtracting(unreadFoundItemIDs)
|
||||||
|
account.ensureStatuses(missingArticleIDs, true, .read, false)
|
||||||
|
|
||||||
|
let readItemIDs = unreadLocalItemIDs.subtracting(unreadServerItemIDs)
|
||||||
|
let readArtices = account.fetchArticles(.articleIDs(readItemIDs))
|
||||||
|
account.update(readArtices, statusKey: .read, flag: true)
|
||||||
|
|
||||||
|
let foundReadArticleIDs = Set(readArtices.map { $0.articleID })
|
||||||
|
let readMissingIDs = readItemIDs.subtracting(foundReadArticleIDs)
|
||||||
|
account.ensureStatuses(readMissingIDs, true, .read, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncArticleStarredState(_ account: Account, _ unreadFeedItems: [FeedWranglerFeedItem]) {
|
||||||
|
let unreadServerItemIDs = Set(unreadFeedItems.map { String($0.feedItemID) })
|
||||||
|
let unreadLocalItemIDs = account.fetchUnreadArticleIDs()
|
||||||
|
|
||||||
|
// starred if start on server
|
||||||
|
let unreadDiffItemIDs = unreadServerItemIDs.subtracting(unreadLocalItemIDs)
|
||||||
|
let unreadFoundArticles = account.fetchArticles(.articleIDs(unreadDiffItemIDs))
|
||||||
|
account.update(unreadFoundArticles, statusKey: .starred, flag: true)
|
||||||
|
|
||||||
|
let unreadFoundItemIDs = Set(unreadFoundArticles.map { $0.articleID })
|
||||||
|
let missingArticleIDs = unreadDiffItemIDs.subtracting(unreadFoundItemIDs)
|
||||||
|
account.ensureStatuses(missingArticleIDs, true, .starred, true)
|
||||||
|
|
||||||
|
let readItemIDs = unreadLocalItemIDs.subtracting(unreadServerItemIDs)
|
||||||
|
let readArtices = account.fetchArticles(.articleIDs(readItemIDs))
|
||||||
|
account.update(readArtices, statusKey: .starred, flag: false)
|
||||||
|
|
||||||
|
let foundReadArticleIDs = Set(readArtices.map { $0.articleID })
|
||||||
|
let readMissingIDs = readItemIDs.subtracting(foundReadArticleIDs)
|
||||||
|
account.ensureStatuses(readMissingIDs, true, .starred, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncArticleState(_ account: Account, key: ArticleStatus.Key, flag: Bool, serverFeedItems: [FeedWranglerFeedItem]) {
|
||||||
|
let serverFeedItemIDs = serverFeedItems.map { String($0.feedID) }
|
||||||
|
|
||||||
|
// todo generalize this logic
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
//
|
||||||
|
// FeedWranglerFeedItem.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Jonathan Bennett on 2019-10-16.4// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct FeedWranglerFeedItem: Hashable, Codable {
|
||||||
|
|
||||||
|
let feedItemID: Int
|
||||||
|
let publishedAt: Int
|
||||||
|
let createdAt: Int
|
||||||
|
let versionKey: Int
|
||||||
|
let updatedAt: Int
|
||||||
|
let url: String
|
||||||
|
let title: String
|
||||||
|
let starred: Bool
|
||||||
|
let read: Bool
|
||||||
|
let readLater: Bool
|
||||||
|
let body: String
|
||||||
|
let author: String?
|
||||||
|
let feedID: Int
|
||||||
|
let feedName: String
|
||||||
|
|
||||||
|
var publishedDate: Date {
|
||||||
|
get {
|
||||||
|
Date(timeIntervalSince1970: Double(publishedAt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var createdDate: Date {
|
||||||
|
get {
|
||||||
|
Date(timeIntervalSince1970: Double(createdAt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedDate: Date {
|
||||||
|
get {
|
||||||
|
Date(timeIntervalSince1970: Double(updatedAt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case feedItemID = "feed_item_id"
|
||||||
|
case publishedAt = "published_at"
|
||||||
|
case createdAt = "created_at"
|
||||||
|
case versionKey = "version_key"
|
||||||
|
case updatedAt = "updated_at"
|
||||||
|
case url = "url"
|
||||||
|
case title = "title"
|
||||||
|
case starred = "starred"
|
||||||
|
case read = "read"
|
||||||
|
case readLater = "read_later"
|
||||||
|
case body = "body"
|
||||||
|
case author = "author"
|
||||||
|
case feedID = "feed_id"
|
||||||
|
case feedName = "feed_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// FeedWranglerFeedItemsRequest.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Jonathan Bennett on 2019-10-16.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct FeedWranglerFeedItemsRequest: Hashable, Codable {
|
||||||
|
|
||||||
|
let count: Int
|
||||||
|
let feedItems: [FeedWranglerFeedItem]
|
||||||
|
let error: String?
|
||||||
|
let result: String
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case count = "count"
|
||||||
|
case feedItems = "feed_items"
|
||||||
|
case error = "error"
|
||||||
|
case result = "result"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue