Implement status change
This commit is contained in:
parent
8f64f7230d
commit
e1d5288d3d
|
@ -8,6 +8,7 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
179DB02FFBC17AC9798F0EBC /* NewsBlurStory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB7399814F6FB3247825C /* NewsBlurStory.swift */; };
|
||||
179DB0B17A6C51B95ABC1741 /* NewsBlurStoryStatusChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB5B421C5433B45C5F13E /* NewsBlurStoryStatusChange.swift */; };
|
||||
179DB28CF49F73A945EBF5DB /* NewsBlurLoginResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB088236E3236010462E8 /* NewsBlurLoginResponse.swift */; };
|
||||
179DB49A960F8B78C4924458 /* NewsBlurGenericCodingKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB66D933E976C29159DEE /* NewsBlurGenericCodingKeys.swift */; };
|
||||
179DBED55C9B4D6A413486C1 /* NewsBlurUnreadStory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB818180A51098A9816B2 /* NewsBlurUnreadStory.swift */; };
|
||||
|
@ -229,6 +230,7 @@
|
|||
/* Begin PBXFileReference section */
|
||||
179DB088236E3236010462E8 /* NewsBlurLoginResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurLoginResponse.swift; sourceTree = "<group>"; };
|
||||
179DB1B909672E0E807B5E8C /* NewsBlurFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurFeed.swift; sourceTree = "<group>"; };
|
||||
179DB5B421C5433B45C5F13E /* NewsBlurStoryStatusChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurStoryStatusChange.swift; sourceTree = "<group>"; };
|
||||
179DB66D933E976C29159DEE /* NewsBlurGenericCodingKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurGenericCodingKeys.swift; sourceTree = "<group>"; };
|
||||
179DB7399814F6FB3247825C /* NewsBlurStory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurStory.swift; sourceTree = "<group>"; };
|
||||
179DB818180A51098A9816B2 /* NewsBlurUnreadStory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurUnreadStory.swift; sourceTree = "<group>"; };
|
||||
|
@ -457,6 +459,7 @@
|
|||
179DB7399814F6FB3247825C /* NewsBlurStory.swift */,
|
||||
179DB66D933E976C29159DEE /* NewsBlurGenericCodingKeys.swift */,
|
||||
179DB818180A51098A9816B2 /* NewsBlurUnreadStory.swift */,
|
||||
179DB5B421C5433B45C5F13E /* NewsBlurStoryStatusChange.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1151,6 +1154,7 @@
|
|||
179DB02FFBC17AC9798F0EBC /* NewsBlurStory.swift in Sources */,
|
||||
179DB49A960F8B78C4924458 /* NewsBlurGenericCodingKeys.swift in Sources */,
|
||||
179DBED55C9B4D6A413486C1 /* NewsBlurUnreadStory.swift in Sources */,
|
||||
179DB0B17A6C51B95ABC1741 /* NewsBlurStoryStatusChange.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// NewsBlurStoryStatusChange.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Anh Quang Do on 2020-03-13.
|
||||
// Copyright (c) 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct NewsBlurStoryStatusChange {
|
||||
let hashes: [String]
|
||||
}
|
||||
|
||||
extension NewsBlurStoryStatusChange {
|
||||
var asData: Data? {
|
||||
var postData = URLComponents()
|
||||
postData.queryItems = hashes.map { URLQueryItem(name: "story_hash", value: $0) }
|
||||
|
||||
return postData.percentEncodedQuery?.data(using: .utf8)
|
||||
}
|
||||
}
|
|
@ -11,11 +11,14 @@ import RSWeb
|
|||
|
||||
enum NewsBlurError: LocalizedError {
|
||||
case general(message: String)
|
||||
case unknown
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .general(let message):
|
||||
return message
|
||||
case .unknown:
|
||||
return "An unknown error occurred"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,4 +191,40 @@ final class NewsBlurAPICaller: NSObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func markAsUnread(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
sendStoryStatus(endpoint: "reader/mark_story_hash_as_unread", hashes: hashes, completion: completion)
|
||||
}
|
||||
|
||||
func markAsRead(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
sendStoryStatus(endpoint: "reader/mark_story_hashes_as_read", hashes: hashes, completion: completion)
|
||||
}
|
||||
|
||||
func star(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
sendStoryStatus(endpoint: "reader/mark_story_hash_as_starred", hashes: hashes, completion: completion)
|
||||
}
|
||||
|
||||
func unstar(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
sendStoryStatus(endpoint: "reader/mark_story_hash_as_unstarred", hashes: hashes, completion: completion)
|
||||
}
|
||||
|
||||
private func sendStoryStatus(endpoint: String, hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let callURL = baseURL.appendingPathComponent(endpoint)
|
||||
|
||||
var request = URLRequest(url: callURL, credentials: credentials)
|
||||
request.httpBody = NewsBlurStoryStatusChange(hashes: hashes).asData
|
||||
transport.send(request: request, method: HTTPMethod.post) { result in
|
||||
if self.suspended {
|
||||
completion(.failure(TransportError.suspended))
|
||||
return
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,11 +116,74 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
|||
}
|
||||
|
||||
func sendArticleStatus(for account: Account, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
completion(.success(()))
|
||||
os_log(.debug, log: log, "Sending story statuses...")
|
||||
|
||||
database.selectForProcessing { result in
|
||||
|
||||
func processStatuses(_ syncStatuses: [SyncStatus]) {
|
||||
let createUnreadStatuses = syncStatuses.filter { $0.key == ArticleStatus.Key.read && $0.flag == false }
|
||||
let deleteUnreadStatuses = syncStatuses.filter { $0.key == ArticleStatus.Key.read && $0.flag == true }
|
||||
let createStarredStatuses = syncStatuses.filter { $0.key == ArticleStatus.Key.starred && $0.flag == true }
|
||||
let deleteStarredStatuses = syncStatuses.filter { $0.key == ArticleStatus.Key.starred && $0.flag == false }
|
||||
|
||||
let group = DispatchGroup()
|
||||
var errorOccurred = false
|
||||
|
||||
group.enter()
|
||||
self.sendStoryStatuses(createUnreadStatuses, throttle: true, apiCall: self.caller.markAsUnread) { result in
|
||||
group.leave()
|
||||
if case .failure = result {
|
||||
errorOccurred = true
|
||||
}
|
||||
}
|
||||
|
||||
group.enter()
|
||||
self.sendStoryStatuses(deleteStarredStatuses, throttle: false, apiCall: self.caller.markAsRead) { result in
|
||||
group.leave()
|
||||
if case .failure = result {
|
||||
errorOccurred = true
|
||||
}
|
||||
}
|
||||
|
||||
group.enter()
|
||||
self.sendStoryStatuses(createStarredStatuses, throttle: true, apiCall: self.caller.star) { result in
|
||||
group.leave()
|
||||
if case .failure = result {
|
||||
errorOccurred = true
|
||||
}
|
||||
}
|
||||
|
||||
group.enter()
|
||||
self.sendStoryStatuses(deleteStarredStatuses, throttle: true, apiCall: self.caller.unstar) { result in
|
||||
group.leave()
|
||||
if case .failure = result {
|
||||
errorOccurred = true
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||
if errorOccurred {
|
||||
completion(.failure(NewsBlurError.unknown))
|
||||
} else {
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let syncStatuses):
|
||||
processStatuses(syncStatuses)
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshArticleStatus(for account: Account, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
completion(.success(()))
|
||||
os_log(.debug, log: log, "Refreshing article statuses...")
|
||||
|
||||
// TODO: Fill this in
|
||||
}
|
||||
|
||||
func refreshStories(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
@ -192,7 +255,18 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
|||
}
|
||||
|
||||
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||
fatalError("markArticles(for:articles:statusKey:flag:) has not been implemented")
|
||||
let syncStatuses = articles.map { article in
|
||||
return SyncStatus(articleID: article.articleID, key: statusKey, flag: flag)
|
||||
}
|
||||
database.insertStatuses(syncStatuses)
|
||||
|
||||
database.selectPendingCount { result in
|
||||
if let count = try? result.get(), count > 100 {
|
||||
self.sendArticleStatus(for: account) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
return try? account.update(articles, statusKey: statusKey, flag: flag)
|
||||
}
|
||||
|
||||
func accountDidInitialize(_ account: Account) {
|
||||
|
@ -485,4 +559,43 @@ extension NewsBlurAccountDelegate {
|
|||
|
||||
return Set(parsedItems)
|
||||
}
|
||||
|
||||
private func sendStoryStatuses(_ statuses: [SyncStatus],
|
||||
throttle: Bool,
|
||||
apiCall: ([String], @escaping (Result<Void, Error>) -> Void) -> Void,
|
||||
completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard !statuses.isEmpty else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
var errorOccurred = false
|
||||
|
||||
let storyHashes = statuses.compactMap { $0.articleID }
|
||||
let storyHashGroups = storyHashes.chunked(into: throttle ? 1 : 5) // api limit
|
||||
for storyHashGroup in storyHashGroups {
|
||||
group.enter()
|
||||
apiCall(storyHashGroup) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.database.deleteSelectedForProcessing(storyHashGroup.map { String($0) } )
|
||||
group.leave()
|
||||
case .failure(let error):
|
||||
errorOccurred = true
|
||||
os_log(.error, log: self.log, "Story status sync call failed: %@.", error.localizedDescription)
|
||||
self.database.resetSelectedForProcessing(storyHashGroup.map { String($0) } )
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
if errorOccurred {
|
||||
completion(.failure(NewsBlurError.unknown))
|
||||
} else {
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue