Merge pull request #1135 from kielgillard/master
Send and receive starred articles…
This commit is contained in:
commit
3d077642ed
|
@ -83,6 +83,7 @@
|
||||||
9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */; };
|
9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */; };
|
||||||
9E1773D923458D590056A5A8 /* FeedlyResourceId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */; };
|
9E1773D923458D590056A5A8 /* FeedlyResourceId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */; };
|
||||||
9E1773DB234593CF0056A5A8 /* FeedlyResourceIdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */; };
|
9E1773DB234593CF0056A5A8 /* FeedlyResourceIdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */; };
|
||||||
|
9E1AF38B2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1AF38A2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift */; };
|
||||||
9E1D154D233370D800F4944C /* FeedlySyncStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */; };
|
9E1D154D233370D800F4944C /* FeedlySyncStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */; };
|
||||||
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */; };
|
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */; };
|
||||||
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */; };
|
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */; };
|
||||||
|
@ -113,12 +114,13 @@
|
||||||
9EAEC626233318400085D7C9 /* FeedlyStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC625233318400085D7C9 /* FeedlyStream.swift */; };
|
9EAEC626233318400085D7C9 /* FeedlyStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC625233318400085D7C9 /* FeedlyStream.swift */; };
|
||||||
9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */; };
|
9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */; };
|
||||||
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */; };
|
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */; };
|
||||||
9EBC31B7233987C1002A567B /* FeedlyArticleStatusCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBC31B6233987C1002A567B /* FeedlyArticleStatusCoordinator.swift */; };
|
|
||||||
9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */; };
|
9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */; };
|
||||||
9EC688EC232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */; };
|
9EC688EC232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */; };
|
||||||
9EC688EE232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */; };
|
9EC688EE232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */; };
|
||||||
9ECC9A85234DC16E009B5144 /* FeedlyAccountDelegateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */; };
|
9ECC9A85234DC16E009B5144 /* FeedlyAccountDelegateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */; };
|
||||||
9EE4CCFA234F106600FBAE4B /* FeedlyFeedContainerValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EE4CCF9234F106600FBAE4B /* FeedlyFeedContainerValidator.swift */; };
|
9EE4CCFA234F106600FBAE4B /* FeedlyFeedContainerValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EE4CCF9234F106600FBAE4B /* FeedlyFeedContainerValidator.swift */; };
|
||||||
|
9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEEF71E23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift */; };
|
||||||
|
9EEEF7212355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEEF7202355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift */; };
|
||||||
9EF35F7A234E830E003AE2AE /* FeedlyCompoundOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */; };
|
9EF35F7A234E830E003AE2AE /* FeedlyCompoundOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
@ -261,6 +263,7 @@
|
||||||
9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyTag.swift; sourceTree = "<group>"; };
|
9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyTag.swift; sourceTree = "<group>"; };
|
||||||
9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceId.swift; sourceTree = "<group>"; };
|
9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceId.swift; sourceTree = "<group>"; };
|
||||||
9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceIdTests.swift; sourceTree = "<group>"; };
|
9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceIdTests.swift; sourceTree = "<group>"; };
|
||||||
|
9E1AF38A2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySetStarredArticlesOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncStrategy.swift; sourceTree = "<group>"; };
|
9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncStrategy.swift; sourceTree = "<group>"; };
|
||||||
9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionsOperation.swift; sourceTree = "<group>"; };
|
9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionsOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyMirrorCollectionsAsFoldersOperation.swift; sourceTree = "<group>"; };
|
9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyMirrorCollectionsAsFoldersOperation.swift; sourceTree = "<group>"; };
|
||||||
|
@ -291,12 +294,13 @@
|
||||||
9EAEC625233318400085D7C9 /* FeedlyStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyStream.swift; sourceTree = "<group>"; };
|
9EAEC625233318400085D7C9 /* FeedlyStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyStream.swift; sourceTree = "<group>"; };
|
||||||
9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCategory.swift; sourceTree = "<group>"; };
|
9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCategory.swift; sourceTree = "<group>"; };
|
||||||
9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrigin.swift; sourceTree = "<group>"; };
|
9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrigin.swift; sourceTree = "<group>"; };
|
||||||
9EBC31B6233987C1002A567B /* FeedlyArticleStatusCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyArticleStatusCoordinator.swift; sourceTree = "<group>"; };
|
|
||||||
9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAPICaller.swift; sourceTree = "<group>"; };
|
9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAPICaller.swift; sourceTree = "<group>"; };
|
||||||
9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedlyAccountDelegate+OAuth.swift"; sourceTree = "<group>"; };
|
9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedlyAccountDelegate+OAuth.swift"; sourceTree = "<group>"; };
|
||||||
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAuthorizationCodeGranting.swift; sourceTree = "<group>"; };
|
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAuthorizationCodeGranting.swift; sourceTree = "<group>"; };
|
||||||
9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegateError.swift; sourceTree = "<group>"; };
|
9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegateError.swift; sourceTree = "<group>"; };
|
||||||
9EE4CCF9234F106600FBAE4B /* FeedlyFeedContainerValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeedContainerValidator.swift; sourceTree = "<group>"; };
|
9EE4CCF9234F106600FBAE4B /* FeedlyFeedContainerValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeedContainerValidator.swift; sourceTree = "<group>"; };
|
||||||
|
9EEEF71E23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySendArticleStatusesOperation.swift; sourceTree = "<group>"; };
|
||||||
|
9EEEF7202355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncStarredArticlesOperation.swift; sourceTree = "<group>"; };
|
||||||
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCompoundOperation.swift; sourceTree = "<group>"; };
|
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCompoundOperation.swift; sourceTree = "<group>"; };
|
||||||
D511EEB5202422BB00712EC3 /* Account_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_debug.xcconfig; sourceTree = "<group>"; };
|
D511EEB5202422BB00712EC3 /* Account_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_debug.xcconfig; sourceTree = "<group>"; };
|
||||||
D511EEB6202422BB00712EC3 /* Account_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_target.xcconfig; sourceTree = "<group>"; };
|
D511EEB6202422BB00712EC3 /* Account_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_target.xcconfig; sourceTree = "<group>"; };
|
||||||
|
@ -561,7 +565,6 @@
|
||||||
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */,
|
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */,
|
||||||
9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */,
|
9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */,
|
||||||
9E7299D623505E9600DAEFB7 /* FeedlyAddFeedOperation.swift */,
|
9E7299D623505E9600DAEFB7 /* FeedlyAddFeedOperation.swift */,
|
||||||
9EBC31B6233987C1002A567B /* FeedlyArticleStatusCoordinator.swift */,
|
|
||||||
9EBC31B32338AC2E002A567B /* Models */,
|
9EBC31B32338AC2E002A567B /* Models */,
|
||||||
9EBC31B22338AC0F002A567B /* Refresh */,
|
9EBC31B22338AC0F002A567B /* Refresh */,
|
||||||
);
|
);
|
||||||
|
@ -580,6 +583,9 @@
|
||||||
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */,
|
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */,
|
||||||
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */,
|
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */,
|
||||||
9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */,
|
9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */,
|
||||||
|
9EEEF7202355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift */,
|
||||||
|
9E1AF38A2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift */,
|
||||||
|
9EEEF71E23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift */,
|
||||||
);
|
);
|
||||||
path = Refresh;
|
path = Refresh;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -837,6 +843,7 @@
|
||||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||||
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */,
|
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */,
|
||||||
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
||||||
|
9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */,
|
||||||
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
|
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
|
||||||
515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */,
|
515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */,
|
||||||
51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */,
|
51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */,
|
||||||
|
@ -851,6 +858,7 @@
|
||||||
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
|
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
|
||||||
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
|
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
|
||||||
9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */,
|
9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */,
|
||||||
|
9EEEF7212355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift in Sources */,
|
||||||
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */,
|
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */,
|
||||||
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
|
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
|
||||||
9E510D6E234F16A8002E6F1A /* FeedlyAddFeedRequest.swift in Sources */,
|
9E510D6E234F16A8002E6F1A /* FeedlyAddFeedRequest.swift in Sources */,
|
||||||
|
@ -888,8 +896,8 @@
|
||||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
||||||
9E1773D5234570E30056A5A8 /* FeedlyEntryParser.swift in Sources */,
|
9E1773D5234570E30056A5A8 /* FeedlyEntryParser.swift in Sources */,
|
||||||
9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */,
|
9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */,
|
||||||
|
9E1AF38B2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift in Sources */,
|
||||||
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
|
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
|
||||||
9EBC31B7233987C1002A567B /* FeedlyArticleStatusCoordinator.swift in Sources */,
|
|
||||||
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */,
|
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
@ -44,7 +44,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||||
|
|
||||||
private let caller: FeedlyAPICaller
|
private let caller: FeedlyAPICaller
|
||||||
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedly")
|
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedly")
|
||||||
private let articleStatusCoodinator: FeedlyArticleStatusCoordinator
|
private let database: SyncDatabase
|
||||||
|
|
||||||
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API = .default) {
|
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API = .default) {
|
||||||
|
|
||||||
|
@ -70,9 +70,8 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||||
caller = FeedlyAPICaller(transport: session, api: api)
|
caller = FeedlyAPICaller(transport: session, api: api)
|
||||||
}
|
}
|
||||||
|
|
||||||
articleStatusCoodinator = FeedlyArticleStatusCoordinator(dataFolderPath: dataFolder,
|
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
|
||||||
caller: caller,
|
self.database = SyncDatabase(databaseFilePath: databaseFilePath)
|
||||||
log: log)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Account API
|
// MARK: Account API
|
||||||
|
@ -93,7 +92,13 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||||
|
|
||||||
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.
|
||||||
articleStatusCoodinator.sendArticleStatus(for: account, completion: completion)
|
let send = FeedlySendArticleStatusesOperation(database: database, caller: caller, log: log)
|
||||||
|
send.completionBlock = {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OperationQueue.main.addOperation(send)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to ensure local articles have the same status as they do remotely.
|
/// Attempts to ensure local articles have the same status as they do remotely.
|
||||||
|
@ -216,28 +221,6 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func isValidContainer(for account: Account, container: Container) throws -> (Folder, String) {
|
|
||||||
guard let folder = container as? Folder else {
|
|
||||||
throw FeedlyAccountDelegateError.addFeedChooseFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let collectionId = folder.externalID else {
|
|
||||||
throw FeedlyAccountDelegateError.addFeedInvalidFolder(folder)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let userId = credentials?.username else {
|
|
||||||
throw FeedlyAccountDelegateError.notLoggedIn
|
|
||||||
}
|
|
||||||
|
|
||||||
let uncategorized = FeedlyCategoryResourceId.uncategorized(for: userId)
|
|
||||||
|
|
||||||
guard collectionId != uncategorized.id else {
|
|
||||||
throw FeedlyAccountDelegateError.addFeedInvalidFolder(folder)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (folder, collectionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
var createFeedRequest: FeedlyAddFeedRequest?
|
var createFeedRequest: FeedlyAddFeedRequest?
|
||||||
|
|
||||||
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||||
|
@ -411,12 +394,18 @@ final class FeedlyAccountDelegate: 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>? {
|
||||||
|
|
||||||
let acceptedStatuses = articleStatusCoodinator.articles(articles,
|
let syncStatuses = articles.map { article in
|
||||||
for: account,
|
return SyncStatus(articleID: article.articleID, key: statusKey, flag: flag)
|
||||||
didChangeStatus: statusKey,
|
}
|
||||||
flag: flag)
|
|
||||||
|
|
||||||
return acceptedStatuses
|
database.insertStatuses(syncStatuses)
|
||||||
|
os_log(.debug, log: log, "Marking %@ as %@.", articles.map { $0.title }, syncStatuses)
|
||||||
|
|
||||||
|
if database.selectPendingCount() > 100 {
|
||||||
|
sendArticleStatus(for: account) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.update(articles, statusKey: statusKey, flag: flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountDidInitialize(_ account: Account) {
|
func accountDidInitialize(_ account: Account) {
|
||||||
|
@ -424,7 +413,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||||
|
|
||||||
syncStrategy = FeedlySyncStrategy(account: account,
|
syncStrategy = FeedlySyncStrategy(account: account,
|
||||||
caller: caller,
|
caller: caller,
|
||||||
articleStatusCoordinator: articleStatusCoodinator,
|
database: database,
|
||||||
log: log)
|
log: log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
//
|
|
||||||
// FeedlyArticleStatusCoordinator.swift
|
|
||||||
// Account
|
|
||||||
//
|
|
||||||
// Created by Kiel Gillard on 24/9/19.
|
|
||||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SyncDatabase
|
|
||||||
import Articles
|
|
||||||
import os.log
|
|
||||||
|
|
||||||
final class FeedlyArticleStatusCoordinator {
|
|
||||||
private let database: SyncDatabase
|
|
||||||
private let log: OSLog
|
|
||||||
private let caller: FeedlyAPICaller
|
|
||||||
|
|
||||||
init(dataFolderPath: String, caller: FeedlyAPICaller, log: OSLog) {
|
|
||||||
let databaseFilePath = (dataFolderPath as NSString).appendingPathComponent("Sync.sqlite3")
|
|
||||||
self.database = SyncDatabase(databaseFilePath: databaseFilePath)
|
|
||||||
self.log = log
|
|
||||||
self.caller = caller
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a status for a particular article locally.
|
|
||||||
func articles(_ articles: Set<Article>, for account: Account, didChangeStatus statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
|
||||||
|
|
||||||
let syncStatuses = articles.map { article in
|
|
||||||
return SyncStatus(articleID: article.articleID, key: statusKey, flag: flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
database.insertStatuses(syncStatuses)
|
|
||||||
os_log(.debug, log: log, "Marking %@ as %@.", articles.map { $0.title }, syncStatuses)
|
|
||||||
|
|
||||||
if database.selectPendingCount() > 100 {
|
|
||||||
sendArticleStatus(for: account)
|
|
||||||
}
|
|
||||||
|
|
||||||
return account.update(articles, statusKey: statusKey, flag: flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensures local articles have the same status as they do remotely.
|
|
||||||
func refreshArticleStatus(for account: Account, entries: [FeedlyEntry], completion: @escaping (() -> Void)) {
|
|
||||||
|
|
||||||
let unreadArticleIds = Set(entries.filter { $0.unread }.map { $0.id })
|
|
||||||
|
|
||||||
// Mark articles as unread
|
|
||||||
let currentUnreadArticleIDs = account.fetchUnreadArticleIDs()
|
|
||||||
let deltaUnreadArticleIDs = unreadArticleIds.subtracting(currentUnreadArticleIDs)
|
|
||||||
let markUnreadArticles = account.fetchArticles(.articleIDs(deltaUnreadArticleIDs))
|
|
||||||
account.update(markUnreadArticles, statusKey: .read, flag: false)
|
|
||||||
|
|
||||||
let readAritcleIds = Set(entries.filter { !$0.unread }.map { $0.id })
|
|
||||||
|
|
||||||
let deltaReadArticleIDs = currentUnreadArticleIDs.intersection(readAritcleIds)
|
|
||||||
let markReadArticles = account.fetchArticles(.articleIDs(deltaReadArticleIDs))
|
|
||||||
account.update(markReadArticles, statusKey: .read, flag: true)
|
|
||||||
|
|
||||||
// os_log(.debug, log: log, "\"%@\" - updated %i UNREAD and %i read article(s).", collection.label, unreadArticleIds.count, markReadArticles.count)
|
|
||||||
|
|
||||||
completion()
|
|
||||||
|
|
||||||
// TODO: starred
|
|
||||||
|
|
||||||
// group.enter()
|
|
||||||
// caller.retrieveStarredEntries() { result in
|
|
||||||
// switch result {
|
|
||||||
// case .success(let articleIDs):
|
|
||||||
// self.syncArticleStarredState(account: account, articleIDs: articleIDs)
|
|
||||||
// group.leave()
|
|
||||||
// case .failure(let error):
|
|
||||||
// os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription)
|
|
||||||
// group.leave()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensures remote articles have the same status as they do locally.
|
|
||||||
func sendArticleStatus(for account: Account, completion: (() -> Void)? = nil) {
|
|
||||||
os_log(.debug, log: log, "Sending article statuses...")
|
|
||||||
|
|
||||||
let pending = database.selectForProcessing()
|
|
||||||
|
|
||||||
let statuses: [(status: ArticleStatus.Key, flag: Bool, action: FeedlyAPICaller.MarkAction)] = [
|
|
||||||
(.read, false, .unread),
|
|
||||||
(.read, true, .read),
|
|
||||||
(.starred, true, .saved),
|
|
||||||
(.starred, false, .unsaved),
|
|
||||||
]
|
|
||||||
|
|
||||||
let group = DispatchGroup()
|
|
||||||
|
|
||||||
for pairing in statuses {
|
|
||||||
let articleIds = pending.filter { $0.key == pairing.status && $0.flag == pairing.flag }
|
|
||||||
guard !articleIds.isEmpty else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
let ids = Set(articleIds.map { $0.articleID })
|
|
||||||
let database = self.database
|
|
||||||
group.enter()
|
|
||||||
caller.mark(ids, as: pairing.action) { result in
|
|
||||||
switch result {
|
|
||||||
case .success:
|
|
||||||
database.deleteSelectedForProcessing(Array(ids))
|
|
||||||
case .failure:
|
|
||||||
database.resetSelectedForProcessing(Array(ids))
|
|
||||||
}
|
|
||||||
group.leave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group.notify(queue: DispatchQueue.main) {
|
|
||||||
os_log(.debug, log: self.log, "Done sending article statuses.")
|
|
||||||
completion?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -37,4 +37,9 @@ final class FeedlyCompoundOperation: FeedlyOperation {
|
||||||
|
|
||||||
operationQueue.addOperations(operationsWithFinish, waitUntilFinished: false)
|
operationQueue.addOperations(operationsWithFinish, waitUntilFinished: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func cancel() {
|
||||||
|
operationQueue.cancelAllOperations()
|
||||||
|
super.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ class FeedlyOperation: Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
func didFinish(_ error: Error) {
|
func didFinish(_ error: Error) {
|
||||||
assert(delegate != nil)
|
|
||||||
delegate?.feedlyOperation(self, didFailWith: error)
|
delegate?.feedlyOperation(self, didFailWith: error)
|
||||||
didFinish()
|
didFinish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import RSParser
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
protocol FeedlyParsedItemsByFeedProviding {
|
protocol FeedlyParsedItemsByFeedProviding {
|
||||||
|
var providerName: String { get }
|
||||||
var allFeeds: Set<Feed> { get }
|
var allFeeds: Set<Feed> { get }
|
||||||
func parsedItems(for feed: Feed) -> Set<ParsedItem>?
|
func parsedItems(for feed: Feed) -> Set<ParsedItem>?
|
||||||
}
|
}
|
||||||
|
@ -22,14 +23,20 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
||||||
private let log: OSLog
|
private let log: OSLog
|
||||||
|
|
||||||
var allFeeds: Set<Feed> {
|
var allFeeds: Set<Feed> {
|
||||||
|
assert(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type.
|
||||||
let keys = Set(itemsKeyedByFeedId.keys)
|
let keys = Set(itemsKeyedByFeedId.keys)
|
||||||
return account.flattenedFeeds().filter { keys.contains($0.feedID) }
|
return account.flattenedFeeds().filter { keys.contains($0.feedID) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsedItems(for feed: Feed) -> Set<ParsedItem>? {
|
func parsedItems(for feed: Feed) -> Set<ParsedItem>? {
|
||||||
|
assert(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type.
|
||||||
return itemsKeyedByFeedId[feed.feedID]
|
return itemsKeyedByFeedId[feed.feedID]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var providerName: String {
|
||||||
|
return entryProvider.resource.id
|
||||||
|
}
|
||||||
|
|
||||||
private var itemsKeyedByFeedId = [String: Set<ParsedItem>]()
|
private var itemsKeyedByFeedId = [String: Set<ParsedItem>]()
|
||||||
|
|
||||||
init(account: Account, entryProvider: FeedlyEntryProviding, log: OSLog) {
|
init(account: Account, entryProvider: FeedlyEntryProviding, log: OSLog) {
|
||||||
|
@ -61,7 +68,7 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
||||||
guard !isCancelled else { return }
|
guard !isCancelled else { return }
|
||||||
}
|
}
|
||||||
|
|
||||||
// os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemsProvider.collection.label)
|
os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, entryProvider.resource.id)
|
||||||
|
|
||||||
itemsKeyedByFeedId = dict
|
itemsKeyedByFeedId = dict
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,9 @@ final class FeedlyRefreshStreamEntriesStatusOperation: FeedlyOperation {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let entryProvider: FeedlyEntryProviding
|
private let entryProvider: FeedlyEntryProviding
|
||||||
private let log: OSLog
|
private let log: OSLog
|
||||||
let articleStatusCoordinator: FeedlyArticleStatusCoordinator
|
|
||||||
|
|
||||||
init(account: Account, entryProvider: FeedlyEntryProviding, articleStatusCoordinator: FeedlyArticleStatusCoordinator, log: OSLog) {
|
init(account: Account, entryProvider: FeedlyEntryProviding, log: OSLog) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.articleStatusCoordinator = articleStatusCoordinator
|
|
||||||
self.entryProvider = entryProvider
|
self.entryProvider = entryProvider
|
||||||
self.log = log
|
self.log = log
|
||||||
}
|
}
|
||||||
|
@ -29,8 +27,23 @@ final class FeedlyRefreshStreamEntriesStatusOperation: FeedlyOperation {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
articleStatusCoordinator.refreshArticleStatus(for: account, entries: entryProvider.entries) {
|
let entries = entryProvider.entries
|
||||||
self.didFinish()
|
let unreadArticleIds = Set(entries.filter { $0.unread }.map { $0.id })
|
||||||
}
|
|
||||||
|
// Mark articles as unread
|
||||||
|
let currentUnreadArticleIDs = account.fetchUnreadArticleIDs()
|
||||||
|
let deltaUnreadArticleIDs = unreadArticleIds.subtracting(currentUnreadArticleIDs)
|
||||||
|
let markUnreadArticles = account.fetchArticles(.articleIDs(deltaUnreadArticleIDs))
|
||||||
|
account.update(markUnreadArticles, statusKey: .read, flag: false)
|
||||||
|
|
||||||
|
let readAritcleIds = Set(entries.filter { !$0.unread }.map { $0.id })
|
||||||
|
|
||||||
|
let deltaReadArticleIDs = currentUnreadArticleIDs.intersection(readAritcleIds)
|
||||||
|
let markReadArticles = account.fetchArticles(.articleIDs(deltaReadArticleIDs))
|
||||||
|
account.update(markReadArticles, statusKey: .read, flag: true)
|
||||||
|
|
||||||
|
// os_log(.debug, log: log, "\"%@\" - updated %i UNREAD and %i read article(s).", collection.label, unreadArticleIds.count, markReadArticles.count)
|
||||||
|
|
||||||
|
didFinish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
//
|
||||||
|
// FeedlySendArticleStatusesOperation.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Kiel Gillard on 14/10/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Articles
|
||||||
|
import SyncDatabase
|
||||||
|
import os.log
|
||||||
|
|
||||||
|
/// Single responsibility is to update or ensure articles from the entry provider are the only starred articles.
|
||||||
|
final class FeedlySendArticleStatusesOperation: FeedlyOperation {
|
||||||
|
private let database: SyncDatabase
|
||||||
|
private let log: OSLog
|
||||||
|
private let caller: FeedlyAPICaller
|
||||||
|
|
||||||
|
init(database: SyncDatabase, caller: FeedlyAPICaller, log: OSLog) {
|
||||||
|
self.database = database
|
||||||
|
self.caller = caller
|
||||||
|
self.log = log
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main() {
|
||||||
|
defer { didFinish() }
|
||||||
|
|
||||||
|
guard !isCancelled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
os_log(.debug, log: log, "Sending article statuses...")
|
||||||
|
|
||||||
|
let pending = database.selectForProcessing()
|
||||||
|
|
||||||
|
let statuses: [(status: ArticleStatus.Key, flag: Bool, action: FeedlyAPICaller.MarkAction)] = [
|
||||||
|
(.read, false, .unread),
|
||||||
|
(.read, true, .read),
|
||||||
|
(.starred, true, .saved),
|
||||||
|
(.starred, false, .unsaved),
|
||||||
|
]
|
||||||
|
|
||||||
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
for pairing in statuses {
|
||||||
|
let articleIds = pending.filter { $0.key == pairing.status && $0.flag == pairing.flag }
|
||||||
|
guard !articleIds.isEmpty else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids = Set(articleIds.map { $0.articleID })
|
||||||
|
let database = self.database
|
||||||
|
group.enter()
|
||||||
|
caller.mark(ids, as: pairing.action) { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
database.deleteSelectedForProcessing(Array(ids))
|
||||||
|
case .failure:
|
||||||
|
database.resetSelectedForProcessing(Array(ids))
|
||||||
|
}
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.notify(queue: DispatchQueue.main) {
|
||||||
|
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||||
|
self.didFinish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// FeedlySetStarredArticlesOperation.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Kiel Gillard on 14/10/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import os.log
|
||||||
|
|
||||||
|
/// Single responsibility is to update or ensure articles from the entry provider are the only starred articles.
|
||||||
|
final class FeedlySetStarredArticlesOperation: FeedlyOperation {
|
||||||
|
private let account: Account
|
||||||
|
private let allStarredEntriesProvider: FeedlyEntryProviding
|
||||||
|
private let log: OSLog
|
||||||
|
|
||||||
|
init(account: Account, allStarredEntriesProvider: FeedlyEntryProviding, log: OSLog) {
|
||||||
|
self.account = account
|
||||||
|
self.allStarredEntriesProvider = allStarredEntriesProvider
|
||||||
|
self.log = log
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main() {
|
||||||
|
defer { didFinish() }
|
||||||
|
|
||||||
|
guard !isCancelled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let remoteStarredArticleIds = Set(allStarredEntriesProvider.entries.map { $0.id })
|
||||||
|
let localStarredArticleIDs = account.fetchStarredArticleIDs()
|
||||||
|
|
||||||
|
// Mark articles as starred
|
||||||
|
let deltaStarredArticleIDs = remoteStarredArticleIds.subtracting(localStarredArticleIDs)
|
||||||
|
let markStarredArticles = account.fetchArticles(.articleIDs(deltaStarredArticleIDs))
|
||||||
|
account.update(markStarredArticles, statusKey: .starred, flag: true)
|
||||||
|
|
||||||
|
// Save any starred statuses for articles we haven't yet received
|
||||||
|
let markStarredArticleIDs = Set(markStarredArticles.map { $0.articleID })
|
||||||
|
let missingStarredArticleIDs = deltaStarredArticleIDs.subtracting(markStarredArticleIDs)
|
||||||
|
account.ensureStatuses(missingStarredArticleIDs, true, .starred, true)
|
||||||
|
|
||||||
|
// Mark articles as unstarred
|
||||||
|
let deltaUnstarredArticleIDs = localStarredArticleIDs.subtracting(remoteStarredArticleIds)
|
||||||
|
let markUnstarredArticles = account.fetchArticles(.articleIDs(deltaUnstarredArticleIDs))
|
||||||
|
account.update(markUnstarredArticles, statusKey: .starred, flag: false)
|
||||||
|
|
||||||
|
// Save any unstarred statuses for articles we haven't yet received
|
||||||
|
let markUnstarredArticleIDs = Set(markUnstarredArticles.map { $0.articleID })
|
||||||
|
let missingUnstarredArticleIDs = deltaUnstarredArticleIDs.subtracting(markUnstarredArticleIDs)
|
||||||
|
account.ensureStatuses(missingUnstarredArticleIDs, true, .starred, false)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
//
|
||||||
|
// FeedlySyncStarredArticlesOperation.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Kiel Gillard on 15/10/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import os.log
|
||||||
|
|
||||||
|
final class FeedlySyncStarredArticlesOperation: FeedlyOperation {
|
||||||
|
private let account: Account
|
||||||
|
private let operationQueue: OperationQueue
|
||||||
|
private let caller: FeedlyAPICaller
|
||||||
|
private let log: OSLog
|
||||||
|
|
||||||
|
init(account: Account, caller: FeedlyAPICaller, log: OSLog) {
|
||||||
|
self.account = account
|
||||||
|
self.caller = caller
|
||||||
|
self.operationQueue = OperationQueue()
|
||||||
|
self.log = log
|
||||||
|
}
|
||||||
|
|
||||||
|
override func cancel() {
|
||||||
|
operationQueue.cancelAllOperations()
|
||||||
|
super.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main() {
|
||||||
|
guard !isCancelled else {
|
||||||
|
didFinish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let user = caller.credentials?.username else {
|
||||||
|
didFinish(FeedlyAccountDelegateError.notLoggedIn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
class Delegate: FeedlyOperationDelegate {
|
||||||
|
var error: Error?
|
||||||
|
weak var compoundOperation: FeedlyCompoundOperation?
|
||||||
|
|
||||||
|
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
|
||||||
|
compoundOperation?.cancel()
|
||||||
|
self.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let delegate = Delegate()
|
||||||
|
|
||||||
|
let syncSaved = FeedlyCompoundOperation {
|
||||||
|
|
||||||
|
let saved = FeedlyTagResourceId.saved(for: user)
|
||||||
|
os_log(.debug, log: log, "Getting starred articles from \"%@\".", saved.id)
|
||||||
|
|
||||||
|
let getSavedStream = FeedlyGetStreamOperation(account: account,
|
||||||
|
resource: saved,
|
||||||
|
caller: caller,
|
||||||
|
newerThan: nil)
|
||||||
|
getSavedStream.delegate = delegate
|
||||||
|
|
||||||
|
// set statuses
|
||||||
|
let setStatuses = FeedlySetStarredArticlesOperation(account: account,
|
||||||
|
allStarredEntriesProvider: getSavedStream,
|
||||||
|
log: log)
|
||||||
|
setStatuses.delegate = delegate
|
||||||
|
setStatuses.addDependency(getSavedStream)
|
||||||
|
|
||||||
|
// ingest articles
|
||||||
|
let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account,
|
||||||
|
entryProvider: getSavedStream,
|
||||||
|
log: log)
|
||||||
|
organiseByFeed.delegate = delegate
|
||||||
|
organiseByFeed.addDependency(setStatuses)
|
||||||
|
|
||||||
|
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
|
||||||
|
organisedItemsProvider: organiseByFeed,
|
||||||
|
log: log)
|
||||||
|
updateAccount.delegate = delegate
|
||||||
|
updateAccount.addDependency(organiseByFeed)
|
||||||
|
|
||||||
|
return [getSavedStream, setStatuses, organiseByFeed, updateAccount]
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate.compoundOperation = syncSaved
|
||||||
|
|
||||||
|
let finalOperation = BlockOperation { [weak self] in
|
||||||
|
guard let self = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let error = delegate.error {
|
||||||
|
self.didFinish(error)
|
||||||
|
} else {
|
||||||
|
self.didFinish()
|
||||||
|
}
|
||||||
|
os_log(.debug, log: self.log, "Done syncing starred articles.")
|
||||||
|
}
|
||||||
|
|
||||||
|
finalOperation.addDependency(syncSaved)
|
||||||
|
operationQueue.addOperations([syncSaved, finalOperation], waitUntilFinished: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,21 +8,22 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import os.log
|
import os.log
|
||||||
|
import SyncDatabase
|
||||||
|
|
||||||
final class FeedlySyncStrategy {
|
final class FeedlySyncStrategy {
|
||||||
|
|
||||||
let account: Account
|
let account: Account
|
||||||
let caller: FeedlyAPICaller
|
let caller: FeedlyAPICaller
|
||||||
let operationQueue: OperationQueue
|
let operationQueue: OperationQueue
|
||||||
let articleStatusCoordinator: FeedlyArticleStatusCoordinator
|
let database: SyncDatabase
|
||||||
let log: OSLog
|
let log: OSLog
|
||||||
|
|
||||||
init(account: Account, caller: FeedlyAPICaller, articleStatusCoordinator: FeedlyArticleStatusCoordinator, log: OSLog) {
|
init(account: Account, caller: FeedlyAPICaller, database: SyncDatabase, log: OSLog) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
self.operationQueue = OperationQueue()
|
self.operationQueue = OperationQueue()
|
||||||
self.log = log
|
self.log = log
|
||||||
self.articleStatusCoordinator = articleStatusCoordinator
|
self.database = database
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancel() {
|
func cancel() {
|
||||||
|
@ -48,9 +49,14 @@ final class FeedlySyncStrategy {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sendArticleStatuses = FeedlySendArticleStatusesOperation(database: database, caller: caller, log: log)
|
||||||
|
sendArticleStatuses.delegate = self
|
||||||
|
|
||||||
|
|
||||||
// Since the truth is in the cloud, everything hinges of what Collections the user has.
|
// Since the truth is in the cloud, everything hinges of what Collections the user has.
|
||||||
let getCollections = FeedlyGetCollectionsOperation(caller: caller, log: log)
|
let getCollections = FeedlyGetCollectionsOperation(caller: caller, log: log)
|
||||||
getCollections.delegate = self
|
getCollections.delegate = self
|
||||||
|
getCollections.addDependency(sendArticleStatuses)
|
||||||
|
|
||||||
// Ensure a folder exists for each Collection, removing Folders without a corresponding Collection.
|
// Ensure a folder exists for each Collection, removing Folders without a corresponding Collection.
|
||||||
let mirrorCollectionsAsFolders = FeedlyMirrorCollectionsAsFoldersOperation(account: account,
|
let mirrorCollectionsAsFolders = FeedlyMirrorCollectionsAsFoldersOperation(account: account,
|
||||||
|
@ -79,40 +85,10 @@ final class FeedlySyncStrategy {
|
||||||
getCollectionStreams.queueDelegate = self
|
getCollectionStreams.queueDelegate = self
|
||||||
getCollectionStreams.addDependency(getCollections)
|
getCollectionStreams.addDependency(getCollections)
|
||||||
|
|
||||||
// if let user = caller.credentials?.username {
|
let syncStarred = FeedlySyncStarredArticlesOperation(account: account, caller: caller, log: log)
|
||||||
//
|
syncStarred.addDependency(getCollections)
|
||||||
// let syncSaved = FeedlyCompoundOperation {
|
syncStarred.addDependency(mirrorCollectionsAsFolders)
|
||||||
//
|
syncStarred.addDependency(createFeedsOperation)
|
||||||
// let saved = FeedlyTagResourceId.saved(for: user)
|
|
||||||
// let getSavedStream = FeedlyGetStreamOperation(account: account,
|
|
||||||
// resource: saved,
|
|
||||||
// caller: caller,
|
|
||||||
// newerThan: newerThan)
|
|
||||||
// getSavedStream.delegate = self
|
|
||||||
//
|
|
||||||
// getSavedStream.addDependency(getCollections)
|
|
||||||
// getSavedStream.addDependency(mirrorCollectionsAsFolders)
|
|
||||||
// getSavedStream.addDependency(createFeedsOperation)
|
|
||||||
//
|
|
||||||
// let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account,
|
|
||||||
// streamProvider: getSavedStream,
|
|
||||||
// log: log)
|
|
||||||
// organiseByFeed.delegate = self
|
|
||||||
// organiseByFeed.addDependency(getSavedStream)
|
|
||||||
//
|
|
||||||
// let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
|
|
||||||
// organisedItemsProvider: organiseByFeed,
|
|
||||||
// log: log)
|
|
||||||
// updateAccount.delegate = self
|
|
||||||
// updateAccount.addDependency(organiseByFeed)
|
|
||||||
//
|
|
||||||
// // refresh stream entries status
|
|
||||||
//
|
|
||||||
// return [getSavedStream, organiseByFeed]
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// operationQueue.addOperation(syncSaved)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -128,18 +104,22 @@ final class FeedlySyncStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
completionOperation.addDependency(sendArticleStatuses)
|
||||||
completionOperation.addDependency(getCollections)
|
completionOperation.addDependency(getCollections)
|
||||||
completionOperation.addDependency(mirrorCollectionsAsFolders)
|
completionOperation.addDependency(mirrorCollectionsAsFolders)
|
||||||
completionOperation.addDependency(createFeedsOperation)
|
completionOperation.addDependency(createFeedsOperation)
|
||||||
completionOperation.addDependency(getCollectionStreams)
|
completionOperation.addDependency(getCollectionStreams)
|
||||||
|
completionOperation.addDependency(syncStarred)
|
||||||
|
|
||||||
finalOperation = completionOperation
|
finalOperation = completionOperation
|
||||||
startSyncCompletionHandler = completionHandler
|
startSyncCompletionHandler = completionHandler
|
||||||
|
|
||||||
let minimumOperations = [getCollections,
|
let minimumOperations = [sendArticleStatuses,
|
||||||
|
getCollections,
|
||||||
mirrorCollectionsAsFolders,
|
mirrorCollectionsAsFolders,
|
||||||
createFeedsOperation,
|
createFeedsOperation,
|
||||||
getCollectionStreams,
|
getCollectionStreams,
|
||||||
|
syncStarred,
|
||||||
completionOperation]
|
completionOperation]
|
||||||
|
|
||||||
operationQueue.addOperations(minimumOperations, waitUntilFinished: false)
|
operationQueue.addOperations(minimumOperations, waitUntilFinished: false)
|
||||||
|
@ -175,7 +155,6 @@ extension FeedlySyncStrategy: FeedlyRequestStreamsOperationDelegate {
|
||||||
// Once the articles are in the account, ensure they have the correct status
|
// Once the articles are in the account, ensure they have the correct status
|
||||||
let ensureUnreadOperation = FeedlyRefreshStreamEntriesStatusOperation(account: account,
|
let ensureUnreadOperation = FeedlyRefreshStreamEntriesStatusOperation(account: account,
|
||||||
entryProvider: streamOperation,
|
entryProvider: streamOperation,
|
||||||
articleStatusCoordinator: articleStatusCoordinator,
|
|
||||||
log: log)
|
log: log)
|
||||||
|
|
||||||
ensureUnreadOperation.delegate = self
|
ensureUnreadOperation.delegate = self
|
||||||
|
|
|
@ -31,7 +31,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||||
|
|
||||||
let allFeeds = organisedItemsProvider.allFeeds
|
let allFeeds = organisedItemsProvider.allFeeds
|
||||||
|
|
||||||
// os_log(.debug, log: log, "Begin updating %i feeds in collection \"%@\"", allFeeds.count, organisedItemsProvider.collection.label)
|
os_log(.debug, log: log, "Begin updating %i feeds for \"%@\"", allFeeds.count, organisedItemsProvider.providerName)
|
||||||
|
|
||||||
var feedIDsAndItems = [String: Set<ParsedItem>]()
|
var feedIDsAndItems = [String: Set<ParsedItem>]()
|
||||||
for feed in allFeeds {
|
for feed in allFeeds {
|
||||||
|
@ -41,7 +41,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||||
feedIDsAndItems[feed.feedID] = items
|
feedIDsAndItems[feed.feedID] = items
|
||||||
}
|
}
|
||||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) {
|
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) {
|
||||||
// os_log(.debug, log: self.log, "Finished updating feeds in collection \"%@\"", self.organisedItemsProvider.collection.label)
|
os_log(.debug, log: self.log, "Finished updating feeds for \"%@\"", self.organisedItemsProvider.providerName)
|
||||||
self.didFinish()
|
self.didFinish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue