This commit is contained in:
parent
b3a5929d6d
commit
39d3999a0d
|
@ -281,8 +281,9 @@ class AccountFeedlySyncTest: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func set(testFiles: TestFiles, with transport: TestTransport) {
|
func set(testFiles: TestFiles, with transport: TestTransport) {
|
||||||
|
// TestTransport blacklists certain query items to make mocking responses easier.
|
||||||
let endpoint = "https://sandbox7.feedly.com/v3"
|
let endpoint = "https://sandbox7.feedly.com/v3"
|
||||||
let category = "\(endpoint)/streams/contents?unreadOnly=false&count=500&streamId=user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category"
|
let category = "\(endpoint)/streams/contents?streamId=user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category"
|
||||||
|
|
||||||
switch testFiles {
|
switch testFiles {
|
||||||
case .initial:
|
case .initial:
|
||||||
|
|
|
@ -18,6 +18,13 @@ final class TestTransport: Transport {
|
||||||
var testFiles = [String: String]()
|
var testFiles = [String: String]()
|
||||||
var testStatusCodes = [String: Int]()
|
var testStatusCodes = [String: Int]()
|
||||||
|
|
||||||
|
/// Allows tests to filter time sensitive state out to make matching against test data easier.
|
||||||
|
var blacklistedQueryItemNames = Set([
|
||||||
|
"newerThan", // Feedly: Mock data has a fixed date.
|
||||||
|
"unreadOnly", // Feedly: Mock data is read/unread by test expectation.
|
||||||
|
"count", // Feedly: Mock data is limited by test expectation.
|
||||||
|
])
|
||||||
|
|
||||||
private func httpResponse(for request: URLRequest, statusCode: Int = 200) -> HTTPURLResponse {
|
private func httpResponse(for request: URLRequest, statusCode: Int = 200) -> HTTPURLResponse {
|
||||||
guard let url = request.url else {
|
guard let url = request.url else {
|
||||||
fatalError("Attempting to mock a http response for a request without a URL \(request).")
|
fatalError("Attempting to mock a http response for a request without a URL \(request).")
|
||||||
|
@ -27,7 +34,16 @@ final class TestTransport: Transport {
|
||||||
|
|
||||||
func send(request: URLRequest, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) {
|
func send(request: URLRequest, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) {
|
||||||
|
|
||||||
guard let urlString = request.url?.absoluteString else {
|
guard let url = request.url, var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
|
||||||
|
completion(.failure(TestTransportError.invalidState))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
components.queryItems = components
|
||||||
|
.queryItems?
|
||||||
|
.filter { !blacklistedQueryItemNames.contains($0.name) }
|
||||||
|
|
||||||
|
guard let urlString = components.url?.absoluteString else {
|
||||||
completion(.failure(TestTransportError.invalidState))
|
completion(.failure(TestTransportError.invalidState))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,20 +89,36 @@ final class FeedlyAPICaller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStream(for collection: FeedlyCollection, unreadOnly: Bool = false, completionHandler: @escaping (Result<FeedlyStream, Error>) -> ()) {
|
func getStream(for collection: FeedlyCollection, newerThan: Date? = nil, unreadOnly: Bool? = nil, completionHandler: @escaping (Result<FeedlyStream, Error>) -> ()) {
|
||||||
guard let accessToken = credentials?.secret else {
|
guard let accessToken = credentials?.secret else {
|
||||||
return DispatchQueue.main.async {
|
return DispatchQueue.main.async {
|
||||||
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var components = baseUrlComponents
|
var components = baseUrlComponents
|
||||||
components.path = "/v3/streams/contents"
|
components.path = "/v3/streams/contents"
|
||||||
// If you change these, check AccountFeedlySyncTest.set(testFiles:with:).
|
// If you change these, check AccountFeedlySyncTest.set(testFiles:with:).
|
||||||
components.queryItems = [
|
var queryItems = [URLQueryItem]()
|
||||||
URLQueryItem(name: "unreadOnly", value: unreadOnly ? "true" : "false"),
|
|
||||||
URLQueryItem(name: "count", value: "500"),
|
if let date = newerThan {
|
||||||
|
let value = String(Int(date.timeIntervalSince1970 * 1000))
|
||||||
|
let queryItem = URLQueryItem(name: "newerThan", value: value)
|
||||||
|
queryItems.append(queryItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let flag = unreadOnly {
|
||||||
|
let value = flag ? "true" : "false"
|
||||||
|
let queryItem = URLQueryItem(name: "unreadOnly", value: value)
|
||||||
|
queryItems.append(queryItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryItems.append(contentsOf: [
|
||||||
|
URLQueryItem(name: "count", value: "1000"),
|
||||||
URLQueryItem(name: "streamId", value: collection.id),
|
URLQueryItem(name: "streamId", value: collection.id),
|
||||||
]
|
])
|
||||||
|
|
||||||
|
components.queryItems = queryItems
|
||||||
|
|
||||||
guard let url = components.url else {
|
guard let url = components.url else {
|
||||||
fatalError("\(components) does not produce a valid URL.")
|
fatalError("\(components) does not produce a valid URL.")
|
||||||
|
|
|
@ -86,12 +86,10 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||||
progress.addToNumberOfTasksAndRemaining(1)
|
progress.addToNumberOfTasksAndRemaining(1)
|
||||||
syncStrategy?.startSync { result in
|
syncStrategy?.startSync { result in
|
||||||
os_log(.debug, log: log, "Sync took %.3f seconds", -date.timeIntervalSinceNow)
|
os_log(.debug, log: log, "Sync took %.3f seconds", -date.timeIntervalSinceNow)
|
||||||
DispatchQueue.main.async {
|
|
||||||
progress.completeTask()
|
progress.completeTask()
|
||||||
completion(result)
|
completion(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
||||||
// Ensure remote articles have the same status as they do locally.
|
// Ensure remote articles have the same status as they do locally.
|
||||||
|
|
|
@ -30,13 +30,15 @@ final class FeedlyGetCollectionStreamOperation: FeedlyOperation, FeedlyCollectio
|
||||||
|
|
||||||
let account: Account
|
let account: Account
|
||||||
let caller: FeedlyAPICaller
|
let caller: FeedlyAPICaller
|
||||||
let unreadOnly: Bool
|
let unreadOnly: Bool?
|
||||||
|
let newerThan: Date?
|
||||||
|
|
||||||
init(account: Account, collection: FeedlyCollection, caller: FeedlyAPICaller, unreadOnly: Bool = false) {
|
init(account: Account, collection: FeedlyCollection, caller: FeedlyAPICaller, newerThan: Date?, unreadOnly: Bool? = nil) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
self.unreadOnly = unreadOnly
|
self.unreadOnly = unreadOnly
|
||||||
|
self.newerThan = newerThan
|
||||||
}
|
}
|
||||||
|
|
||||||
override func main() {
|
override func main() {
|
||||||
|
@ -45,8 +47,7 @@ final class FeedlyGetCollectionStreamOperation: FeedlyOperation, FeedlyCollectio
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Use account metadata to get articles newer than some date.
|
caller.getStream(for: collection, newerThan: newerThan, unreadOnly: unreadOnly) { result in
|
||||||
caller.getStream(for: collection, unreadOnly: unreadOnly) { result in
|
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let stream):
|
case .success(let stream):
|
||||||
self.storedStream = stream
|
self.storedStream = stream
|
||||||
|
|
|
@ -23,11 +23,15 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation {
|
||||||
let caller: FeedlyAPICaller
|
let caller: FeedlyAPICaller
|
||||||
let account: Account
|
let account: Account
|
||||||
let log: OSLog
|
let log: OSLog
|
||||||
|
let newerThan: Date?
|
||||||
|
let unreadOnly: Bool?
|
||||||
|
|
||||||
init(account: Account, collectionsProvider: FeedlyCollectionProviding, caller: FeedlyAPICaller, log: OSLog) {
|
init(account: Account, collectionsProvider: FeedlyCollectionProviding, newerThan: Date?, unreadOnly: Bool?, caller: FeedlyAPICaller, log: OSLog) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
self.collectionsProvider = collectionsProvider
|
self.collectionsProvider = collectionsProvider
|
||||||
|
self.newerThan = newerThan
|
||||||
|
self.unreadOnly = unreadOnly
|
||||||
self.log = log
|
self.log = log
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +45,11 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation {
|
||||||
// TODO: Prioritise the must read collection/category before others so the most important content for the user loads first.
|
// TODO: Prioritise the must read collection/category before others so the most important content for the user loads first.
|
||||||
|
|
||||||
for collection in collectionsProvider.collections {
|
for collection in collectionsProvider.collections {
|
||||||
let operation = FeedlyGetCollectionStreamOperation(account: account, collection: collection, caller: caller)
|
let operation = FeedlyGetCollectionStreamOperation(account: account,
|
||||||
|
collection: collection,
|
||||||
|
caller: caller,
|
||||||
|
newerThan: newerThan,
|
||||||
|
unreadOnly: unreadOnly)
|
||||||
queueDelegate?.feedlyRequestStreamsOperation(self, enqueue: operation)
|
queueDelegate?.feedlyRequestStreamsOperation(self, enqueue: operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,14 @@ final class FeedlySyncStrategy {
|
||||||
|
|
||||||
private var startSyncCompletionHandler: ((Result<Void, Error>) -> ())?
|
private var startSyncCompletionHandler: ((Result<Void, Error>) -> ())?
|
||||||
|
|
||||||
|
private var newerThan: Date? {
|
||||||
|
if let date = account.metadata.lastArticleFetch {
|
||||||
|
return date
|
||||||
|
} else {
|
||||||
|
return Calendar.current.date(byAdding: .day, value: -31, to: Date())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The truth is in the cloud.
|
/// The truth is in the cloud.
|
||||||
func startSync(completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
func startSync(completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
||||||
guard operationQueue.operationCount == 0 else {
|
guard operationQueue.operationCount == 0 else {
|
||||||
|
@ -63,6 +71,8 @@ final class FeedlySyncStrategy {
|
||||||
// Get the streams for each Collection. It will call back to enqueue more operations.
|
// Get the streams for each Collection. It will call back to enqueue more operations.
|
||||||
let getCollectionStreams = FeedlyRequestStreamsOperation(account: account,
|
let getCollectionStreams = FeedlyRequestStreamsOperation(account: account,
|
||||||
collectionsProvider: getCollections,
|
collectionsProvider: getCollections,
|
||||||
|
newerThan: newerThan,
|
||||||
|
unreadOnly: false,
|
||||||
caller: caller,
|
caller: caller,
|
||||||
log: log)
|
log: log)
|
||||||
getCollectionStreams.delegate = self
|
getCollectionStreams.delegate = self
|
||||||
|
@ -71,13 +81,17 @@ final class FeedlySyncStrategy {
|
||||||
|
|
||||||
// Last operation to perform, which should be dependent on any other operation added to the queue.
|
// Last operation to perform, which should be dependent on any other operation added to the queue.
|
||||||
let syncId = UUID().uuidString
|
let syncId = UUID().uuidString
|
||||||
|
let lastArticleFetchDate = Date()
|
||||||
let completionOperation = BlockOperation { [weak self] in
|
let completionOperation = BlockOperation { [weak self] in
|
||||||
|
DispatchQueue.main.async {
|
||||||
if let self = self {
|
if let self = self {
|
||||||
|
self.account.metadata.lastArticleFetch = lastArticleFetchDate
|
||||||
os_log(.debug, log: self.log, "Sync completed: %@", syncId)
|
os_log(.debug, log: self.log, "Sync completed: %@", syncId)
|
||||||
self.startSyncCompletionHandler = nil
|
self.startSyncCompletionHandler = nil
|
||||||
}
|
}
|
||||||
completionHandler(.success(()))
|
completionHandler(.success(()))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
completionOperation.addDependency(getCollections)
|
completionOperation.addDependency(getCollections)
|
||||||
completionOperation.addDependency(mirrorCollectionsAsFolders)
|
completionOperation.addDependency(mirrorCollectionsAsFolders)
|
||||||
|
|
Loading…
Reference in New Issue