Fetch article contents from Feedly by ids rather than paging through streams up until the last successful sync date. Issues #1453 #1398 #1408 #1429
This commit is contained in:
parent
9a3763f57a
commit
745b5d8cb8
|
@ -689,7 +689,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
database.fetchStarredArticleIDsAsync(webFeedIDs: flattenedWebFeeds().webFeedIDs(), completion: completion)
|
||||
}
|
||||
|
||||
/// Fetch articleIDs for articles that we should have, but don’t. These articles are not userDeleted, and they are either (starred) or (unread and newer than the article cutoff date).
|
||||
/// Fetch articleIDs for articles that we should have, but don’t. These articles are not userDeleted, and they are either (starred) or (newer than the article cutoff date).
|
||||
public func fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(_ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
database.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(completion)
|
||||
}
|
||||
|
|
|
@ -99,7 +99,6 @@
|
|||
9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */; };
|
||||
9E1773D923458D590056A5A8 /* FeedlyResourceId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773D823458D590056A5A8 /* FeedlyResourceId.swift */; };
|
||||
9E1773DB234593CF0056A5A8 /* FeedlyResourceIdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */; };
|
||||
9E1AF38B2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1AF38A2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift */; };
|
||||
9E1D154D233370D800F4944C /* FeedlySyncAllOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154C233370D800F4944C /* FeedlySyncAllOperation.swift */; };
|
||||
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */; };
|
||||
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */; };
|
||||
|
@ -113,15 +112,14 @@
|
|||
9E1FF8642368EC2400834C24 /* FeedlySyncAllOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1FF8632368EC2400834C24 /* FeedlySyncAllOperationTests.swift */; };
|
||||
9E1FF8662368ED7E00834C24 /* TestMarkArticlesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1FF8652368ED7E00834C24 /* TestMarkArticlesService.swift */; };
|
||||
9E1FF8682368EE4900834C24 /* TestGetCollectionsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1FF8672368EE4900834C24 /* TestGetCollectionsService.swift */; };
|
||||
9E3CFFFD2368202000BA7365 /* FeedlySyncUnreadStatusesOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E3CFFFC2368202000BA7365 /* FeedlySyncUnreadStatusesOperationTests.swift */; };
|
||||
9E4828F223617F4A00D68691 /* FeedlySetUnreadArticlesOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4828F123617F4A00D68691 /* FeedlySetUnreadArticlesOperationTests.swift */; };
|
||||
9E44C90F23C6FF3600CCC286 /* FeedlyIngestStreamArticleIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E44C90E23C6FF3600CCC286 /* FeedlyIngestStreamArticleIdsOperation.swift */; };
|
||||
9E489E8D2360652C004372EE /* FeedlyGetStreamIdsOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E489E8C2360652C004372EE /* FeedlyGetStreamIdsOperationTests.swift */; };
|
||||
9E489E912360ED30004372EE /* FeedlyOrganiseParsedItemsByFeedOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E489E902360ED30004372EE /* FeedlyOrganiseParsedItemsByFeedOperationTests.swift */; };
|
||||
9E489E93236101FC004372EE /* FeedlyUpdateAccountFeedsWithItemsOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E489E92236101FC004372EE /* FeedlyUpdateAccountFeedsWithItemsOperationTests.swift */; };
|
||||
9E5ABE9A236BE6BD00B5DE9F /* feedly-1-initial in Resources */ = {isa = PBXBuildFile; fileRef = 9E5ABE99236BE6BC00B5DE9F /* feedly-1-initial */; };
|
||||
9E5DE60E23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5DE60D23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift */; };
|
||||
9E672394236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E672393236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift */; };
|
||||
9E672396236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E672395236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift */; };
|
||||
9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E713652233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift */; };
|
||||
9E7299D723505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7299D623505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift */; };
|
||||
9E7299D9235062A200DAEFB7 /* FeedlyResourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */; };
|
||||
9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */; };
|
||||
|
@ -129,9 +127,8 @@
|
|||
9E79F7742395C9F00031DB98 /* feedly-add-new-feed in Resources */ = {isa = PBXBuildFile; fileRef = 9E79F7732395C9EF0031DB98 /* feedly-add-new-feed */; };
|
||||
9E7F88AC235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7F88AB235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift */; };
|
||||
9E7F88AE235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7F88AD235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift */; };
|
||||
9E84DC472359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E84DC462359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift */; };
|
||||
9E84DC472359A23200D6E809 /* FeedlyIngestUnreadArticleIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E84DC462359A23200D6E809 /* FeedlyIngestUnreadArticleIdsOperation.swift */; };
|
||||
9E84DC492359A73600D6E809 /* FeedlyCheckpointOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E84DC482359A73600D6E809 /* FeedlyCheckpointOperation.swift */; };
|
||||
9E85C8E42366FE0100D0F1F7 /* FeedlySyncStarredArticlesOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8E32366FE0100D0F1F7 /* FeedlySyncStarredArticlesOperationTests.swift */; };
|
||||
9E85C8E62366FED600D0F1F7 /* TestGetStreamContentsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8E52366FED600D0F1F7 /* TestGetStreamContentsService.swift */; };
|
||||
9E85C8E82366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8E72366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift */; };
|
||||
9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8E9236700AD00D0F1F7 /* FeedlyGetEntriesOperation.swift */; };
|
||||
|
@ -143,6 +140,7 @@
|
|||
9EA643D3239305680018A28C /* FeedlySearchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA643D2239305680018A28C /* FeedlySearchOperation.swift */; };
|
||||
9EA643D5239306AC0018A28C /* FeedlyFeedsSearchResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA643D4239306AC0018A28C /* FeedlyFeedsSearchResponse.swift */; };
|
||||
9EA643D923945CE00018A28C /* FeedlyAddNewFeedOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA643D823945CE00018A28C /* FeedlyAddNewFeedOperationTests.swift */; };
|
||||
9EAADA1023C93144003A801F /* TestGetEntriesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAADA0F23C93144003A801F /* TestGetEntriesService.swift */; };
|
||||
9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */; };
|
||||
9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */; };
|
||||
9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */; };
|
||||
|
@ -150,7 +148,8 @@
|
|||
9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */; };
|
||||
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */; };
|
||||
9EB1D576238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB1D575238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift */; };
|
||||
9EC228552362C17F00766EF8 /* FeedlySetStarredArticlesOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC228542362C17F00766EF8 /* FeedlySetStarredArticlesOperationTests.swift */; };
|
||||
9EBD49C023C67602005AD5CD /* FeedlyDownloadArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD49BF23C67602005AD5CD /* FeedlyDownloadArticlesOperation.swift */; };
|
||||
9EBD49C223C67784005AD5CD /* FeedlyEntryIdentifierProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBD49C123C67784005AD5CD /* FeedlyEntryIdentifierProviding.swift */; };
|
||||
9EC228572362C7F900766EF8 /* FeedlyCheckpointOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC228562362C7F900766EF8 /* FeedlyCheckpointOperationTests.swift */; };
|
||||
9EC228592362D0EA00766EF8 /* FeedlySendArticleStatusesOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC228582362D0EA00766EF8 /* FeedlySendArticleStatusesOperationTests.swift */; };
|
||||
9EC2285B23639A6500766EF8 /* FeedlySyncStreamContentsOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC2285A23639A6500766EF8 /* FeedlySyncStreamContentsOperationTests.swift */; };
|
||||
|
@ -173,10 +172,11 @@
|
|||
9EEAE073235D01AE00E3FEE4 /* FeedlyGetStreamIdsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEAE072235D01AE00E3FEE4 /* FeedlyGetStreamIdsService.swift */; };
|
||||
9EEAE075235D01C400E3FEE4 /* FeedlyMarkArticlesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEAE074235D01C400E3FEE4 /* FeedlyMarkArticlesService.swift */; };
|
||||
9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEEF71E23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift */; };
|
||||
9EEEF7212355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEEF7202355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift */; };
|
||||
9EEEF7212355277F009E9D80 /* FeedlyIngestStarredArticleIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEEF7202355277F009E9D80 /* FeedlyIngestStarredArticleIdsOperation.swift */; };
|
||||
9EF1B10323584B4C000A486A /* FeedlySyncStreamContentsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF1B10223584B4C000A486A /* FeedlySyncStreamContentsOperation.swift */; };
|
||||
9EF1B10723590D61000A486A /* FeedlyGetStreamIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF1B10623590D61000A486A /* FeedlyGetStreamIdsOperation.swift */; };
|
||||
9EF1B10923590E93000A486A /* FeedlyStreamIds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF1B10823590E93000A486A /* FeedlyStreamIds.swift */; };
|
||||
9EF2602C23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */; };
|
||||
9EF35F7A234E830E003AE2AE /* FeedlyCompoundOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -322,7 +322,6 @@
|
|||
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>"; };
|
||||
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 /* FeedlySyncAllOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncAllOperation.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>"; };
|
||||
|
@ -336,15 +335,14 @@
|
|||
9E1FF8632368EC2400834C24 /* FeedlySyncAllOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncAllOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E1FF8652368ED7E00834C24 /* TestMarkArticlesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMarkArticlesService.swift; sourceTree = "<group>"; };
|
||||
9E1FF8672368EE4900834C24 /* TestGetCollectionsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGetCollectionsService.swift; sourceTree = "<group>"; };
|
||||
9E3CFFFC2368202000BA7365 /* FeedlySyncUnreadStatusesOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncUnreadStatusesOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E4828F123617F4A00D68691 /* FeedlySetUnreadArticlesOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySetUnreadArticlesOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E44C90E23C6FF3600CCC286 /* FeedlyIngestStreamArticleIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyIngestStreamArticleIdsOperation.swift; sourceTree = "<group>"; };
|
||||
9E489E8C2360652C004372EE /* FeedlyGetStreamIdsOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamIdsOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E489E902360ED30004372EE /* FeedlyOrganiseParsedItemsByFeedOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrganiseParsedItemsByFeedOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E489E92236101FC004372EE /* FeedlyUpdateAccountFeedsWithItemsOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyUpdateAccountFeedsWithItemsOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E5ABE99236BE6BC00B5DE9F /* feedly-1-initial */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "feedly-1-initial"; sourceTree = "<group>"; };
|
||||
9E5DE60D23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFetchIdsForMissingArticlesOperation.swift; sourceTree = "<group>"; };
|
||||
9E672393236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRefreshAccessTokenOperation.swift; sourceTree = "<group>"; };
|
||||
9E672395236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAcessTokenRefreshing.swift; sourceTree = "<group>"; };
|
||||
9E713652233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySetUnreadArticlesOperation.swift; sourceTree = "<group>"; };
|
||||
9E7299D623505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAddFeedToCollectionOperation.swift; sourceTree = "<group>"; };
|
||||
9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceProviding.swift; sourceTree = "<group>"; };
|
||||
9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyLogoutOperation.swift; sourceTree = "<group>"; };
|
||||
|
@ -352,9 +350,8 @@
|
|||
9E79F7732395C9EF0031DB98 /* feedly-add-new-feed */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "feedly-add-new-feed"; sourceTree = "<group>"; };
|
||||
9E7F88AB235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCreateFeedsForCollectionFoldersOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E7F88AD235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamContentsOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E84DC462359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncUnreadStatusesOperation.swift; sourceTree = "<group>"; };
|
||||
9E84DC462359A23200D6E809 /* FeedlyIngestUnreadArticleIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyIngestUnreadArticleIdsOperation.swift; sourceTree = "<group>"; };
|
||||
9E84DC482359A73600D6E809 /* FeedlyCheckpointOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCheckpointOperation.swift; sourceTree = "<group>"; };
|
||||
9E85C8E32366FE0100D0F1F7 /* FeedlySyncStarredArticlesOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncStarredArticlesOperationTests.swift; sourceTree = "<group>"; };
|
||||
9E85C8E52366FED600D0F1F7 /* TestGetStreamContentsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGetStreamContentsService.swift; sourceTree = "<group>"; };
|
||||
9E85C8E72366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGetPagedStreamContentsService.swift; sourceTree = "<group>"; };
|
||||
9E85C8E9236700AD00D0F1F7 /* FeedlyGetEntriesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetEntriesOperation.swift; sourceTree = "<group>"; };
|
||||
|
@ -366,6 +363,7 @@
|
|||
9EA643D2239305680018A28C /* FeedlySearchOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySearchOperation.swift; sourceTree = "<group>"; };
|
||||
9EA643D4239306AC0018A28C /* FeedlyFeedsSearchResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeedsSearchResponse.swift; sourceTree = "<group>"; };
|
||||
9EA643D823945CE00018A28C /* FeedlyAddNewFeedOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAddNewFeedOperationTests.swift; sourceTree = "<group>"; };
|
||||
9EAADA0F23C93144003A801F /* TestGetEntriesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGetEntriesService.swift; sourceTree = "<group>"; };
|
||||
9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCollection.swift; sourceTree = "<group>"; };
|
||||
9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeed.swift; sourceTree = "<group>"; };
|
||||
9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyEntry.swift; sourceTree = "<group>"; };
|
||||
|
@ -373,7 +371,8 @@
|
|||
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>"; };
|
||||
9EB1D575238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAddNewFeedOperation.swift; sourceTree = "<group>"; };
|
||||
9EC228542362C17F00766EF8 /* FeedlySetStarredArticlesOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySetStarredArticlesOperationTests.swift; sourceTree = "<group>"; };
|
||||
9EBD49BF23C67602005AD5CD /* FeedlyDownloadArticlesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyDownloadArticlesOperation.swift; sourceTree = "<group>"; };
|
||||
9EBD49C123C67784005AD5CD /* FeedlyEntryIdentifierProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyEntryIdentifierProviding.swift; sourceTree = "<group>"; };
|
||||
9EC228562362C7F900766EF8 /* FeedlyCheckpointOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCheckpointOperationTests.swift; sourceTree = "<group>"; };
|
||||
9EC228582362D0EA00766EF8 /* FeedlySendArticleStatusesOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySendArticleStatusesOperationTests.swift; sourceTree = "<group>"; };
|
||||
9EC2285A23639A6500766EF8 /* FeedlySyncStreamContentsOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncStreamContentsOperationTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -396,10 +395,11 @@
|
|||
9EEAE072235D01AE00E3FEE4 /* FeedlyGetStreamIdsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamIdsService.swift; sourceTree = "<group>"; };
|
||||
9EEAE074235D01C400E3FEE4 /* FeedlyMarkArticlesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyMarkArticlesService.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>"; };
|
||||
9EEEF7202355277F009E9D80 /* FeedlyIngestStarredArticleIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyIngestStarredArticleIdsOperation.swift; sourceTree = "<group>"; };
|
||||
9EF1B10223584B4C000A486A /* FeedlySyncStreamContentsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncStreamContentsOperation.swift; sourceTree = "<group>"; };
|
||||
9EF1B10623590D61000A486A /* FeedlyGetStreamIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamIdsOperation.swift; sourceTree = "<group>"; };
|
||||
9EF1B10823590E93000A486A /* FeedlyStreamIds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyStreamIds.swift; sourceTree = "<group>"; };
|
||||
9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetUpdatedArticleIdsOperation.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>"; };
|
||||
D511EEB6202422BB00712EC3 /* Account_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_target.xcconfig; sourceTree = "<group>"; };
|
||||
|
@ -648,6 +648,7 @@
|
|||
9E85C8E72366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift */,
|
||||
9E1FF8612368219B00834C24 /* TestGetPagedStreamIdsService.swift */,
|
||||
9E1FF8672368EE4900834C24 /* TestGetCollectionsService.swift */,
|
||||
9EAADA0F23C93144003A801F /* TestGetEntriesService.swift */,
|
||||
9E1FF8652368ED7E00834C24 /* TestMarkArticlesService.swift */,
|
||||
9E03C11B235D921400FB6D9E /* FeedlyOperationTests.swift */,
|
||||
9E03C11D235D976500FB6D9E /* FeedlyGetCollectionsOperationTests.swift */,
|
||||
|
@ -657,13 +658,9 @@
|
|||
9E489E8C2360652C004372EE /* FeedlyGetStreamIdsOperationTests.swift */,
|
||||
9E489E902360ED30004372EE /* FeedlyOrganiseParsedItemsByFeedOperationTests.swift */,
|
||||
9E489E92236101FC004372EE /* FeedlyUpdateAccountFeedsWithItemsOperationTests.swift */,
|
||||
9E4828F123617F4A00D68691 /* FeedlySetUnreadArticlesOperationTests.swift */,
|
||||
9EC228542362C17F00766EF8 /* FeedlySetStarredArticlesOperationTests.swift */,
|
||||
9EC228562362C7F900766EF8 /* FeedlyCheckpointOperationTests.swift */,
|
||||
9EC228582362D0EA00766EF8 /* FeedlySendArticleStatusesOperationTests.swift */,
|
||||
9EC2285A23639A6500766EF8 /* FeedlySyncStreamContentsOperationTests.swift */,
|
||||
9E85C8E32366FE0100D0F1F7 /* FeedlySyncStarredArticlesOperationTests.swift */,
|
||||
9E3CFFFC2368202000BA7365 /* FeedlySyncUnreadStatusesOperationTests.swift */,
|
||||
9E1FF8632368EC2400834C24 /* FeedlySyncAllOperationTests.swift */,
|
||||
9EC804E2236C18AB0057CFCB /* FeedlySyncAllMockResponseProvider.swift */,
|
||||
9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */,
|
||||
|
@ -718,16 +715,18 @@
|
|||
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */,
|
||||
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */,
|
||||
9E85C8E9236700AD00D0F1F7 /* FeedlyGetEntriesOperation.swift */,
|
||||
9E713652233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift */,
|
||||
9E1AF38A2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift */,
|
||||
9EEEF71E23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift */,
|
||||
9E84DC482359A73600D6E809 /* FeedlyCheckpointOperation.swift */,
|
||||
9EF1B10223584B4C000A486A /* FeedlySyncStreamContentsOperation.swift */,
|
||||
9EEEF7202355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift */,
|
||||
9E84DC462359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift */,
|
||||
9E44C90E23C6FF3600CCC286 /* FeedlyIngestStreamArticleIdsOperation.swift */,
|
||||
9EEEF7202355277F009E9D80 /* FeedlyIngestStarredArticleIdsOperation.swift */,
|
||||
9E84DC462359A23200D6E809 /* FeedlyIngestUnreadArticleIdsOperation.swift */,
|
||||
9EF2602B23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift */,
|
||||
9E1D154C233370D800F4944C /* FeedlySyncAllOperation.swift */,
|
||||
9E672393236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift */,
|
||||
9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */,
|
||||
9E5DE60D23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift */,
|
||||
9EBD49BF23C67602005AD5CD /* FeedlyDownloadArticlesOperation.swift */,
|
||||
);
|
||||
path = Operations;
|
||||
sourceTree = "<group>";
|
||||
|
@ -747,6 +746,7 @@
|
|||
9E1773D22345700E0056A5A8 /* FeedlyLink.swift */,
|
||||
9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */,
|
||||
9EA643D4239306AC0018A28C /* FeedlyFeedsSearchResponse.swift */,
|
||||
9EBD49C123C67784005AD5CD /* FeedlyEntryIdentifierProviding.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1000,12 +1000,12 @@
|
|||
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */,
|
||||
511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */,
|
||||
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
|
||||
9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */,
|
||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||
510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */,
|
||||
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
||||
9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */,
|
||||
9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */,
|
||||
9EBD49C223C67784005AD5CD /* FeedlyEntryIdentifierProviding.swift in Sources */,
|
||||
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
|
||||
515E4EB72324FF8C0057B0E7 /* Credentials.swift in Sources */,
|
||||
51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */,
|
||||
|
@ -1015,6 +1015,7 @@
|
|||
9E84DC492359A73600D6E809 /* FeedlyCheckpointOperation.swift in Sources */,
|
||||
9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */,
|
||||
9E1D154D233370D800F4944C /* FeedlySyncAllOperation.swift in Sources */,
|
||||
9E44C90F23C6FF3600CCC286 /* FeedlyIngestStreamArticleIdsOperation.swift in Sources */,
|
||||
844B297D2106C7EC004020B3 /* WebFeed.swift in Sources */,
|
||||
3B826DA72385C81C00FC1ADB /* FeedWranglerAuthorizationResult.swift in Sources */,
|
||||
9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */,
|
||||
|
@ -1031,10 +1032,10 @@
|
|||
9EF1B10323584B4C000A486A /* FeedlySyncStreamContentsOperation.swift in Sources */,
|
||||
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
|
||||
84B2D4D02238CD8A00498ADA /* WebFeedMetadata.swift in Sources */,
|
||||
9E84DC472359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift in Sources */,
|
||||
9E84DC472359A23200D6E809 /* FeedlyIngestUnreadArticleIdsOperation.swift in Sources */,
|
||||
9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */,
|
||||
9EEAE073235D01AE00E3FEE4 /* FeedlyGetStreamIdsService.swift in Sources */,
|
||||
9EEEF7212355277F009E9D80 /* FeedlySyncStarredArticlesOperation.swift in Sources */,
|
||||
9EEEF7212355277F009E9D80 /* FeedlyIngestStarredArticleIdsOperation.swift in Sources */,
|
||||
3BC23AB92385ECB100371CBA /* FeedWranglerSubscriptionResult.swift in Sources */,
|
||||
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */,
|
||||
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
|
||||
|
@ -1044,6 +1045,7 @@
|
|||
55203300229D5D5A009559E0 /* ReaderAPICaller.swift in Sources */,
|
||||
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */,
|
||||
9EAEC626233318400085D7C9 /* FeedlyStream.swift in Sources */,
|
||||
9E5DE60E23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift in Sources */,
|
||||
3B826DA92385C81C00FC1ADB /* FeedWranglerAPICaller.swift in Sources */,
|
||||
9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */,
|
||||
51E3EB41229AF61B00645299 /* AccountError.swift in Sources */,
|
||||
|
@ -1052,6 +1054,7 @@
|
|||
552032F8229D5D5A009559E0 /* ReaderAPIEntry.swift in Sources */,
|
||||
552032FB229D5D5A009559E0 /* ReaderAPITag.swift in Sources */,
|
||||
5165D72822835F7800D9D53D /* FeedFinder.swift in Sources */,
|
||||
9EBD49C023C67602005AD5CD /* FeedlyDownloadArticlesOperation.swift in Sources */,
|
||||
51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */,
|
||||
9E1D155B2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift in Sources */,
|
||||
552032FE229D5D5A009559E0 /* ReaderAPIAccountDelegate.swift in Sources */,
|
||||
|
@ -1079,10 +1082,10 @@
|
|||
9E1773D5234570E30056A5A8 /* FeedlyEntryParser.swift in Sources */,
|
||||
51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */,
|
||||
9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */,
|
||||
9E1AF38B2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift in Sources */,
|
||||
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
|
||||
9EF1B10723590D61000A486A /* FeedlyGetStreamIdsOperation.swift in Sources */,
|
||||
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */,
|
||||
9EF2602C23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift in Sources */,
|
||||
3B826DAA2385C81C00FC1ADB /* FeedWranglerSubscription.swift in Sources */,
|
||||
3B826DAC2385C81C00FC1ADB /* FeedWranglerAccountDelegate.swift in Sources */,
|
||||
);
|
||||
|
@ -1094,9 +1097,8 @@
|
|||
files = (
|
||||
9EC228572362C7F900766EF8 /* FeedlyCheckpointOperationTests.swift in Sources */,
|
||||
9E03C122235E62E100FB6D9E /* FeedlyTestSupport.swift in Sources */,
|
||||
9E3CFFFD2368202000BA7365 /* FeedlySyncUnreadStatusesOperationTests.swift in Sources */,
|
||||
9EAADA1023C93144003A801F /* TestGetEntriesService.swift in Sources */,
|
||||
9E784EC0237E8BE100099B1B /* FeedlyLogoutOperationTests.swift in Sources */,
|
||||
9EC228552362C17F00766EF8 /* FeedlySetStarredArticlesOperationTests.swift in Sources */,
|
||||
9E03C120235E62A500FB6D9E /* FeedlyMirrorCollectionsAsFoldersOperationTests.swift in Sources */,
|
||||
9E489E912360ED30004372EE /* FeedlyOrganiseParsedItemsByFeedOperationTests.swift in Sources */,
|
||||
9E0260CB236FF99A00D122D3 /* FeedlyRefreshAccessTokenOperationTests.swift in Sources */,
|
||||
|
@ -1117,10 +1119,8 @@
|
|||
51D5875E227F643C00900287 /* AccountFeedbinFolderSyncTest.swift in Sources */,
|
||||
9EC804E3236C18AB0057CFCB /* FeedlySyncAllMockResponseProvider.swift in Sources */,
|
||||
9E1FF8682368EE4900834C24 /* TestGetCollectionsService.swift in Sources */,
|
||||
9E4828F223617F4A00D68691 /* FeedlySetUnreadArticlesOperationTests.swift in Sources */,
|
||||
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */,
|
||||
513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */,
|
||||
9E85C8E42366FE0100D0F1F7 /* FeedlySyncStarredArticlesOperationTests.swift in Sources */,
|
||||
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */,
|
||||
5107A099227DE42E00C7C3C5 /* AccountCredentialsTest.swift in Sources */,
|
||||
9EC2285B23639A6500766EF8 /* FeedlySyncStreamContentsOperationTests.swift in Sources */,
|
||||
|
|
|
@ -28,6 +28,7 @@ class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {
|
|||
}
|
||||
|
||||
struct TestParsedItemsProvider: FeedlyParsedItemProviding {
|
||||
let parsedItemProviderName = "TestParsedItemsProvider"
|
||||
var resource: FeedlyResourceId
|
||||
var parsedEntries: Set<ParsedItem>
|
||||
}
|
||||
|
@ -51,7 +52,6 @@ class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {
|
|||
|
||||
let itemsAndFeedIds = organise.parsedItemsKeyedByFeedId
|
||||
XCTAssertEqual(itemsAndFeedIds, entries)
|
||||
XCTAssertEqual(resource.id, organise.providerName)
|
||||
}
|
||||
|
||||
func testGroupsOneEntryByFeedId() {
|
||||
|
@ -73,7 +73,6 @@ class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {
|
|||
|
||||
let itemsAndFeedIds = organise.parsedItemsKeyedByFeedId
|
||||
XCTAssertEqual(itemsAndFeedIds, entries)
|
||||
XCTAssertEqual(resource.id, organise.providerName)
|
||||
}
|
||||
|
||||
func testGroupsManyEntriesByFeedId() {
|
||||
|
@ -95,6 +94,5 @@ class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {
|
|||
|
||||
let itemsAndFeedIds = organise.parsedItemsKeyedByFeedId
|
||||
XCTAssertEqual(itemsAndFeedIds, entries)
|
||||
XCTAssertEqual(resource.id, organise.providerName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,489 +0,0 @@
|
|||
//
|
||||
// FeedlySetStarredArticlesOperationTests.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 25/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
import RSParser
|
||||
|
||||
class FeedlySetStarredArticlesOperationTests: XCTestCase {
|
||||
|
||||
private var account: Account!
|
||||
private let support = FeedlyTestSupport()
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
account = support.makeTestAccount()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
if let account = account {
|
||||
support.destroy(account)
|
||||
}
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Ensuring Unread Status By Id
|
||||
|
||||
struct TestStarredArticleProvider: FeedlyStarredEntryIdProviding {
|
||||
var entryIds: Set<String>
|
||||
}
|
||||
|
||||
func testEmptyArticleIds() {
|
||||
let testIds = Set<String>()
|
||||
let provider = TestStarredArticleProvider(entryIds: testIds)
|
||||
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertTrue(accountArticlesIDs.isEmpty)
|
||||
XCTAssertEqual(accountArticlesIDs, testIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetOneArticleIdStarred() {
|
||||
let testIds = Set<String>(["feed/0/article/0"])
|
||||
let provider = TestStarredArticleProvider(entryIds: testIds)
|
||||
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs.count, testIds.count)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetManyArticleIdsStarred() {
|
||||
let testIds = Set<String>((0..<10_000).map { "feed/0/article/\($0)" })
|
||||
let provider = TestStarredArticleProvider(entryIds: testIds)
|
||||
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs.count, testIds.count)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetSomeArticleIdsUnstarred() {
|
||||
let initialStarredIds = Set<String>((0..<1000).map { "feed/0/article/\($0)" })
|
||||
|
||||
do {
|
||||
let provider = TestStarredArticleProvider(entryIds: initialStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish Setting Initial Unreads")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let remainingStarredIds = Set(initialStarredIds.enumerated().filter { $0.offset % 2 > 0 }.map { $0.element })
|
||||
let provider = TestStarredArticleProvider(entryIds: remainingStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { remainingAccountArticlesIDsResult in
|
||||
do {
|
||||
let remainingAccountArticlesIDs = try remainingAccountArticlesIDsResult.get()
|
||||
XCTAssertEqual(remainingAccountArticlesIDs, remainingStarredIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetAllArticleIdsUnstarred() {
|
||||
let initialStarredIds = Set<String>((0..<1000).map { "feed/0/article/\($0)" })
|
||||
|
||||
do {
|
||||
let provider = TestStarredArticleProvider(entryIds: initialStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish Setting Initial Unreads")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let remainingStarredIds = Set<String>()
|
||||
let provider = TestStarredArticleProvider(entryIds: remainingStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { remainingAccountArticlesIDsResult in
|
||||
do {
|
||||
let remainingAccountArticlesIDs = try remainingAccountArticlesIDsResult.get()
|
||||
XCTAssertEqual(remainingAccountArticlesIDs, remainingStarredIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
// MARK: - Updating Article Unread Status
|
||||
|
||||
struct TestItemsByFeedProvider: FeedlyParsedItemsByFeedProviding {
|
||||
var providerName: String
|
||||
var parsedItemsKeyedByFeedId: [String: Set<ParsedItem>]
|
||||
}
|
||||
|
||||
func testSetAllArticlesStarred() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItemsAndFeeds)
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let testItems = Set(testItemsAndFeeds.flatMap { $0.value })
|
||||
let remainingStarredIds = Set(testItems.compactMap { $0.syncServiceID })
|
||||
XCTAssertEqual(testItems.count, remainingStarredIds.count, "Not every item has a value for \(\ParsedItem.syncServiceID).")
|
||||
|
||||
let provider = TestStarredArticleProvider(entryIds: remainingStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingStarredIds)
|
||||
|
||||
let idsOfStarredArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(remainingStarredIds))
|
||||
.filter { $0.status.boolStatus(forKey: .starred) == true }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfStarredArticles, remainingStarredIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetManyArticlesUnread() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItemsAndFeeds)
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let testItems = Set(testItemsAndFeeds.flatMap { $0.value })
|
||||
let unreadItems = testItems
|
||||
.enumerated()
|
||||
.filter { $0.offset % 2 > 0 }
|
||||
.map { $0.element }
|
||||
|
||||
let remainingStarredIds = Set(unreadItems.compactMap { $0.syncServiceID })
|
||||
XCTAssertEqual(unreadItems.count, remainingStarredIds.count, "Not every item has a value for \(\ParsedItem.syncServiceID).")
|
||||
|
||||
let provider = TestStarredArticleProvider(entryIds: remainingStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingStarredIds)
|
||||
|
||||
let idsOfStarredArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(remainingStarredIds))
|
||||
.filter { $0.status.boolStatus(forKey: .starred) == true }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfStarredArticles, remainingStarredIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetOneArticleUnread() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItemsAndFeeds)
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let testItems = Set(testItemsAndFeeds.flatMap { $0.value })
|
||||
// Since the test data is completely under the developer's control, not having at least one can be a programmer error.
|
||||
let remainingStarredIds = Set([testItems.compactMap { $0.syncServiceID }.first!])
|
||||
let provider = TestStarredArticleProvider(entryIds: remainingStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingStarredIds)
|
||||
|
||||
let idsOfStarredArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(remainingStarredIds))
|
||||
.filter { $0.status.boolStatus(forKey: .starred) == true }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfStarredArticles, remainingStarredIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetNoArticlesRead() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItemsAndFeeds)
|
||||
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let remainingStarredIds = Set<String>()
|
||||
let provider = TestStarredArticleProvider(entryIds: remainingStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingStarredIds)
|
||||
|
||||
let idsOfStarredArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(remainingStarredIds))
|
||||
.filter { $0.status.boolStatus(forKey: .starred) == true }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfStarredArticles, remainingStarredIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetAllArticlesAndArticleIdsWithSomeArticlesIngested() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
let someItemsAndFeeds = Dictionary(uniqueKeysWithValues: testItemsAndFeeds.enumerated().filter { $0.offset % 2 > 0 }.map { $0.element })
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: someItemsAndFeeds)
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let testItems = Set(testItemsAndFeeds.flatMap { $0.value })
|
||||
let remainingStarredIds = Set(testItems.compactMap { $0.syncServiceID })
|
||||
XCTAssertEqual(testItems.count, remainingStarredIds.count, "Not every item has a value for \(\ParsedItem.syncServiceID).")
|
||||
|
||||
let provider = TestStarredArticleProvider(entryIds: remainingStarredIds)
|
||||
let setStarred = FeedlySetStarredArticlesOperation(account: account, allStarredEntryIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingStarredIds)
|
||||
|
||||
let someTestItems = Set(someItemsAndFeeds.flatMap { $0.value })
|
||||
let someRemainingStarredIdsOfIngestedArticles = Set(someTestItems.compactMap { $0.syncServiceID })
|
||||
let idsOfStarredArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(someRemainingStarredIdsOfIngestedArticles))
|
||||
.filter { $0.status.boolStatus(forKey: .starred) == true }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfStarredArticles, someRemainingStarredIdsOfIngestedArticles)
|
||||
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking articles IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
}
|
|
@ -1,485 +0,0 @@
|
|||
//
|
||||
// FeedlySetUnreadArticlesOperationTests.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 24/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
import RSParser
|
||||
|
||||
class FeedlySetUnreadArticlesOperationTests: XCTestCase {
|
||||
|
||||
private var account: Account!
|
||||
private let support = FeedlyTestSupport()
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
account = support.makeTestAccount()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
if let account = account {
|
||||
support.destroy(account)
|
||||
}
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Ensuring Unread Status By Id
|
||||
|
||||
struct TestUnreadArticleIdProvider: FeedlyUnreadEntryIdProviding {
|
||||
var entryIds: Set<String>
|
||||
}
|
||||
|
||||
func testEmptyArticleIds() {
|
||||
let testIds = Set<String>()
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: testIds)
|
||||
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertTrue(accountArticlesIDs.isEmpty)
|
||||
XCTAssertEqual(accountArticlesIDs.count, testIds.count)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetOneArticleIdUnread() {
|
||||
let testIds = Set<String>(["feed/0/article/0"])
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: testIds)
|
||||
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs.count, testIds.count)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetManyArticleIdsUnread() {
|
||||
let testIds = Set<String>((0..<10_000).map { "feed/0/article/\($0)" })
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: testIds)
|
||||
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs.count, testIds.count)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetSomeArticleIdsRead() {
|
||||
let initialUnreadIds = Set<String>((0..<1000).map { "feed/0/article/\($0)" })
|
||||
|
||||
do {
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: initialUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish Setting Initial Unreads")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let remainingUnreadIds = Set(initialUnreadIds.enumerated().filter { $0.offset % 2 > 0 }.map { $0.element })
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: remainingUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { remainingAccountArticlesIDsResult in
|
||||
do {
|
||||
let remainingAccountArticlesIDs = try remainingAccountArticlesIDsResult.get()
|
||||
XCTAssertEqual(remainingAccountArticlesIDs, remainingUnreadIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetAllArticleIdsRead() {
|
||||
let initialUnreadIds = Set<String>((0..<1000).map { "feed/0/article/\($0)" })
|
||||
|
||||
do {
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: initialUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish Setting Initial Unreads")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let remainingUnreadIds = Set<String>()
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: remainingUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { remainingAccountArticlesIDsResult in
|
||||
do {
|
||||
let remainingAccountArticlesIDs = try remainingAccountArticlesIDsResult.get()
|
||||
XCTAssertEqual(remainingAccountArticlesIDs, remainingUnreadIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
// MARK: - Updating Article Unread Status
|
||||
|
||||
struct TestItemsByFeedProvider: FeedlyParsedItemsByFeedProviding {
|
||||
var providerName: String
|
||||
var parsedItemsKeyedByFeedId: [String: Set<ParsedItem>]
|
||||
}
|
||||
|
||||
func testSetAllArticlesUnread() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItemsAndFeeds)
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let testItems = Set(testItemsAndFeeds.flatMap { $0.value })
|
||||
let remainingUnreadIds = Set(testItems.compactMap { $0.syncServiceID })
|
||||
XCTAssertEqual(testItems.count, remainingUnreadIds.count, "Not every item has a value for \(\ParsedItem.syncServiceID).")
|
||||
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: remainingUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingUnreadIds)
|
||||
let idsOfUnreadArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(remainingUnreadIds))
|
||||
.filter { $0.status.boolStatus(forKey: .read) == false }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfUnreadArticles, remainingUnreadIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetManyArticlesUnread() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItemsAndFeeds)
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let testItems = Set(testItemsAndFeeds.flatMap { $0.value })
|
||||
let unreadItems = testItems
|
||||
.enumerated()
|
||||
.filter { $0.offset % 2 > 0 }
|
||||
.map { $0.element }
|
||||
|
||||
let remainingUnreadIds = Set(unreadItems.compactMap { $0.syncServiceID })
|
||||
XCTAssertEqual(unreadItems.count, remainingUnreadIds.count, "Not every item has a value for \(\ParsedItem.syncServiceID).")
|
||||
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: remainingUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingUnreadIds)
|
||||
|
||||
let idsOfUnreadArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(remainingUnreadIds))
|
||||
.filter { $0.status.boolStatus(forKey: .read) == false }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfUnreadArticles, remainingUnreadIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSetOneArticleUnread() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItemsAndFeeds)
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let testItems = Set(testItemsAndFeeds.flatMap { $0.value })
|
||||
// Since the test data is completely under the developer's control, not having at least one can be a programmer error.
|
||||
let remainingUnreadIds = Set([testItems.compactMap { $0.syncServiceID }.first!])
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: remainingUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingUnreadIds)
|
||||
|
||||
let idsOfUnreadArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(remainingUnreadIds))
|
||||
.filter { $0.status.boolStatus(forKey: .read) == false }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfUnreadArticles, remainingUnreadIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testSetNoArticlesRead() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItemsAndFeeds)
|
||||
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let remainingUnreadIds = Set<String>()
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: remainingUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingUnreadIds)
|
||||
|
||||
let idsOfUnreadArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(remainingUnreadIds))
|
||||
.filter { $0.status.boolStatus(forKey: .read) == false }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfUnreadArticles, remainingUnreadIds)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSetAllArticlesAndArticleIdsWithSomeArticlesIngested() {
|
||||
let testItemsAndFeeds = support.makeParsedItemTestDataFor(numberOfFeeds: 5, numberOfItemsInFeeds: 100)
|
||||
let someItemsAndFeeds = Dictionary(uniqueKeysWithValues: testItemsAndFeeds.enumerated().filter { $0.offset % 2 > 0 }.map { $0.element })
|
||||
|
||||
do {
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: someItemsAndFeeds)
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
update.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(update)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
let testItems = Set(testItemsAndFeeds.flatMap { $0.value })
|
||||
let remainingUnreadIds = Set(testItems.compactMap { $0.syncServiceID })
|
||||
XCTAssertEqual(testItems.count, remainingUnreadIds.count, "Not every item has a value for \(\ParsedItem.syncServiceID).")
|
||||
|
||||
let provider = TestUnreadArticleIdProvider(entryIds: remainingUnreadIds)
|
||||
let setUnread = FeedlySetUnreadArticlesOperation(account: account, allUnreadIdsProvider: provider, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
setUnread.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(setUnread)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetched Articles Ids")
|
||||
account.fetchUnreadArticleIDs { accountArticlesIDsResult in
|
||||
do {
|
||||
let accountArticlesIDs = try accountArticlesIDsResult.get()
|
||||
XCTAssertEqual(accountArticlesIDs, remainingUnreadIds)
|
||||
|
||||
let someTestItems = Set(someItemsAndFeeds.flatMap { $0.value })
|
||||
let someRemainingUnreadIdsOfIngestedArticles = Set(someTestItems.compactMap { $0.syncServiceID })
|
||||
let idsOfUnreadArticles = Set(try self.account
|
||||
.fetchArticles(.articleIDs(someRemainingUnreadIdsOfIngestedArticles))
|
||||
.filter { $0.status.boolStatus(forKey: .read) == false }
|
||||
.map { $0.articleID })
|
||||
|
||||
XCTAssertEqual(idsOfUnreadArticles, someRemainingUnreadIdsOfIngestedArticles)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking account articles IDs result: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,25 +57,31 @@ class FeedlySyncAllOperationTests: XCTestCase {
|
|||
getGlobalStreamContents.getStreamContentsExpectation = expectation(description: "Get Contents of global.all")
|
||||
getGlobalStreamContents.getStreamContentsExpectation?.isInverted = true
|
||||
|
||||
let getStarredContents = TestGetStreamContentsService()
|
||||
getStarredContents.getStreamContentsExpectation = expectation(description: "Get Contents of global.saved")
|
||||
getStarredContents.getStreamContentsExpectation?.isInverted = true
|
||||
let getStarredIds = TestGetStreamIdsService()
|
||||
getStarredIds.getStreamIdsExpectation = expectation(description: "Get Ids of global.saved")
|
||||
getStarredIds.getStreamIdsExpectation?.isInverted = true
|
||||
|
||||
let getEntriesService = TestGetEntriesService()
|
||||
getEntriesService.getEntriesExpectation = expectation(description: "Get Entries")
|
||||
getEntriesService.getEntriesExpectation?.isInverted = true
|
||||
|
||||
let progress = DownloadProgress(numberOfTasks: 0)
|
||||
let _ = expectationForCompletion(of: progress)
|
||||
|
||||
let container = support.makeTestDatabaseContainer()
|
||||
let syncAll = FeedlySyncAllOperation(account: account,
|
||||
credentials: support.accessToken,
|
||||
lastSuccessfulFetchStartDate: nil,
|
||||
markArticlesService: markArticlesService,
|
||||
getUnreadService: getStreamIdsService,
|
||||
getCollectionsService: getCollectionsService,
|
||||
getStreamContentsService: getGlobalStreamContents,
|
||||
getStarredArticlesService: getStarredContents,
|
||||
database: container.database,
|
||||
downloadProgress: progress,
|
||||
log: support.log)
|
||||
credentials: support.accessToken,
|
||||
lastSuccessfulFetchStartDate: nil,
|
||||
markArticlesService: markArticlesService,
|
||||
getUnreadService: getStreamIdsService,
|
||||
getCollectionsService: getCollectionsService,
|
||||
getStreamContentsService: getGlobalStreamContents,
|
||||
getStarredService: getStarredIds,
|
||||
getStreamIdsService: getStreamIdsService,
|
||||
getEntriesService: getEntriesService,
|
||||
database: container.database,
|
||||
downloadProgress: progress,
|
||||
log: support.log)
|
||||
|
||||
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
//
|
||||
// FeedlySyncStarredArticlesOperationTests.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 28/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
|
||||
class FeedlySyncStarredArticlesOperationTests: XCTestCase {
|
||||
|
||||
private var account: Account!
|
||||
private let support = FeedlyTestSupport()
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
account = support.makeTestAccount()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
if let account = account {
|
||||
support.destroy(account)
|
||||
}
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testIngestsOnePageSuccess() {
|
||||
let service = TestGetStreamContentsService()
|
||||
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678")
|
||||
let items = service.makeMockFeedlyEntryItem()
|
||||
service.mockResult = .success(FeedlyStream(id: resource.id, updated: nil, continuation: nil, items: items))
|
||||
|
||||
let getStreamContentsExpectation = expectation(description: "Did Get Page of Stream Contents")
|
||||
getStreamContentsExpectation.expectedFulfillmentCount = 1
|
||||
|
||||
service.getStreamContentsExpectation = getStreamContentsExpectation
|
||||
service.parameterTester = { serviceResource, continuation, serviceNewerThan, serviceUnreadOnly in
|
||||
XCTAssertEqual(serviceResource.id, resource.id)
|
||||
XCTAssertNil(serviceNewerThan)
|
||||
XCTAssertNil(continuation)
|
||||
XCTAssertNil(serviceUnreadOnly)
|
||||
}
|
||||
|
||||
let syncStarred = FeedlySyncStarredArticlesOperation(account: account, resource: resource, service: service, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
syncStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(syncStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let expectedArticleIds = Set(items.map { $0.id })
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { starredArticleIdsResult in
|
||||
do {
|
||||
let starredArticleIds = try starredArticleIdsResult.get()
|
||||
let missingIds = expectedArticleIds.subtracting(starredArticleIds)
|
||||
XCTAssertTrue(missingIds.isEmpty, "These article ids were not marked as starred.")
|
||||
|
||||
// Fetch articles directly because account.fetchArticles(.starred) fetches starred articles for feeds subscribed to.
|
||||
let expectedArticles = try self.account.fetchArticles(.articleIDs(expectedArticleIds))
|
||||
XCTAssertEqual(expectedArticles.count, expectedArticleIds.count, "Did not fetch all the articles.")
|
||||
|
||||
let starredArticles = try self.account.fetchArticles(.articleIDs(starredArticleIds))
|
||||
XCTAssertEqual(expectedArticleIds.count, expectedArticles.count)
|
||||
let missingArticles = expectedArticles.subtracting(starredArticles)
|
||||
XCTAssertTrue(missingArticles.isEmpty, "These articles should be starred and fetched.")
|
||||
XCTAssertEqual(expectedArticles, starredArticles)
|
||||
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking starred article IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testIngestsOnePageFailure() {
|
||||
let service = TestGetStreamContentsService()
|
||||
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678")
|
||||
|
||||
service.mockResult = .failure(URLError(.timedOut))
|
||||
|
||||
let getStreamContentsExpectation = expectation(description: "Did Get Page of Stream Contents")
|
||||
getStreamContentsExpectation.expectedFulfillmentCount = 1
|
||||
|
||||
service.getStreamContentsExpectation = getStreamContentsExpectation
|
||||
service.parameterTester = { serviceResource, continuation, serviceNewerThan, serviceUnreadOnly in
|
||||
XCTAssertEqual(serviceResource.id, resource.id)
|
||||
XCTAssertNil(serviceNewerThan)
|
||||
XCTAssertNil(continuation)
|
||||
XCTAssertNil(serviceUnreadOnly)
|
||||
}
|
||||
|
||||
let syncStarred = FeedlySyncStarredArticlesOperation(account: account, resource: resource, service: service, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
syncStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(syncStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { starredArticleIdsResult in
|
||||
do {
|
||||
let starredArticleIds = try starredArticleIdsResult.get()
|
||||
XCTAssertTrue(starredArticleIds.isEmpty)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking starred article IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testIngestsManyPagesSuccess() {
|
||||
let service = TestGetPagedStreamContentsService()
|
||||
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678")
|
||||
|
||||
let continuations = (1...10).map { "\($0)" }
|
||||
service.addAtLeastOnePage(for: resource, continuations: continuations, numberOfEntriesPerPage: 10)
|
||||
|
||||
let getStreamContentsExpectation = expectation(description: "Did Get Page of Stream Contents")
|
||||
getStreamContentsExpectation.expectedFulfillmentCount = 1 + continuations.count
|
||||
|
||||
var remainingContinuations = Set(continuations)
|
||||
let getStreamPageExpectation = expectation(description: "Did Request Page")
|
||||
getStreamPageExpectation.expectedFulfillmentCount = 1 + continuations.count
|
||||
|
||||
service.getStreamContentsExpectation = getStreamContentsExpectation
|
||||
service.parameterTester = { serviceResource, continuation, serviceNewerThan, serviceUnreadOnly in
|
||||
XCTAssertEqual(serviceResource.id, resource.id)
|
||||
XCTAssertNil(serviceNewerThan)
|
||||
XCTAssertNil(serviceUnreadOnly)
|
||||
|
||||
if let continuation = continuation {
|
||||
XCTAssertTrue(remainingContinuations.contains(continuation))
|
||||
remainingContinuations.remove(continuation)
|
||||
}
|
||||
|
||||
getStreamPageExpectation.fulfill()
|
||||
}
|
||||
|
||||
let syncStarred = FeedlySyncStarredArticlesOperation(account: account, resource: resource, service: service, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
syncStarred.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(syncStarred)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
// Find articles inserted.
|
||||
let expectedArticleIds = Set(service.pages.values.map { $0.items }.flatMap { $0 }.map { $0.id })
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchStarredArticleIDs { starredArticleIdsResult in
|
||||
do {
|
||||
let starredArticleIds = try starredArticleIdsResult.get()
|
||||
let missingIds = expectedArticleIds.subtracting(starredArticleIds)
|
||||
XCTAssertTrue(missingIds.isEmpty, "These article ids were not marked as starred.")
|
||||
|
||||
// Fetch articles directly because account.fetchArticles(.starred) fetches starred articles for feeds subscribed to.
|
||||
let expectedArticles = try self.account.fetchArticles(.articleIDs(expectedArticleIds))
|
||||
XCTAssertEqual(expectedArticles.count, expectedArticleIds.count, "Did not fetch all the articles.")
|
||||
|
||||
let starredArticles = try self.account.fetchArticles(.articleIDs(starredArticleIds))
|
||||
XCTAssertEqual(expectedArticleIds.count, expectedArticles.count)
|
||||
let missingArticles = expectedArticles.subtracting(starredArticles)
|
||||
XCTAssertTrue(missingArticles.isEmpty, "These articles should be starred and fetched.")
|
||||
XCTAssertEqual(expectedArticles, starredArticles)
|
||||
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking starred article IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
//
|
||||
// FeedlySyncUnreadStatusesOperationTests.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 29/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
|
||||
class FeedlySyncUnreadStatusesOperationTests: XCTestCase {
|
||||
|
||||
private var account: Account!
|
||||
private let support = FeedlyTestSupport()
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
account = support.makeTestAccount()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
if let account = account {
|
||||
support.destroy(account)
|
||||
}
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testIngestsOnePageSuccess() {
|
||||
let service = TestGetStreamIdsService()
|
||||
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678")
|
||||
let ids = [UUID().uuidString]
|
||||
service.mockResult = .success(FeedlyStreamIds(continuation: nil, ids: ids))
|
||||
|
||||
let getStreamIdsExpectation = expectation(description: "Did Get Page of Stream Ids")
|
||||
getStreamIdsExpectation.expectedFulfillmentCount = 1
|
||||
|
||||
service.getStreamIdsExpectation = getStreamIdsExpectation
|
||||
service.parameterTester = { serviceResource, continuation, serviceNewerThan, serviceUnreadOnly in
|
||||
XCTAssertEqual(serviceResource.id, resource.id)
|
||||
XCTAssertNil(serviceNewerThan)
|
||||
XCTAssertNil(continuation)
|
||||
XCTAssertEqual(serviceUnreadOnly, true)
|
||||
}
|
||||
|
||||
let syncUnreads = FeedlySyncUnreadStatusesOperation(account: account, resource: resource, service: service, newerThan: nil, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
syncUnreads.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(syncUnreads)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let expectedArticleIds = Set(ids)
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchUnreadArticleIDs { unreadArticleIdsResult in
|
||||
do {
|
||||
let unreadArticleIds = try unreadArticleIdsResult.get()
|
||||
let missingIds = expectedArticleIds.subtracting(unreadArticleIds)
|
||||
XCTAssertTrue(missingIds.isEmpty, "These article ids were not marked as unread.")
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking unread article IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testIngestsOnePageFailure() {
|
||||
let service = TestGetStreamIdsService()
|
||||
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678")
|
||||
|
||||
service.mockResult = .failure(URLError(.timedOut))
|
||||
|
||||
let getStreamIdsExpectation = expectation(description: "Did Get Page of Stream Contents")
|
||||
getStreamIdsExpectation.expectedFulfillmentCount = 1
|
||||
|
||||
service.getStreamIdsExpectation = getStreamIdsExpectation
|
||||
service.parameterTester = { serviceResource, continuation, serviceNewerThan, serviceUnreadOnly in
|
||||
XCTAssertEqual(serviceResource.id, resource.id)
|
||||
XCTAssertNil(serviceNewerThan)
|
||||
XCTAssertNil(continuation)
|
||||
XCTAssertEqual(serviceUnreadOnly, true)
|
||||
}
|
||||
|
||||
let syncUnreads = FeedlySyncUnreadStatusesOperation(account: account, resource: resource, service: service, newerThan: nil, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
syncUnreads.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(syncUnreads)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchUnreadArticleIDs { unreadArticleIdsResult in
|
||||
do {
|
||||
let unreadArticleIds = try unreadArticleIdsResult.get()
|
||||
XCTAssertTrue(unreadArticleIds.isEmpty)
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking unread article IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
|
||||
func testIngestsManyPagesSuccess() {
|
||||
let service = TestGetPagedStreamIdsService()
|
||||
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678")
|
||||
|
||||
let continuations = (1...10).map { "\($0)" }
|
||||
service.addAtLeastOnePage(for: resource, continuations: continuations, numberOfEntriesPerPage: 1000)
|
||||
|
||||
let getStreamIdsExpectation = expectation(description: "Did Get Page of Stream Contents")
|
||||
getStreamIdsExpectation.expectedFulfillmentCount = 1 + continuations.count
|
||||
|
||||
var remainingContinuations = Set(continuations)
|
||||
let getStreamPageExpectation = expectation(description: "Did Request Page")
|
||||
getStreamPageExpectation.expectedFulfillmentCount = 1 + continuations.count
|
||||
|
||||
service.getStreamIdsExpectation = getStreamIdsExpectation
|
||||
service.parameterTester = { serviceResource, continuation, serviceNewerThan, serviceUnreadOnly in
|
||||
XCTAssertEqual(serviceResource.id, resource.id)
|
||||
XCTAssertNil(serviceNewerThan)
|
||||
XCTAssertEqual(serviceUnreadOnly, true)
|
||||
|
||||
if let continuation = continuation {
|
||||
XCTAssertTrue(remainingContinuations.contains(continuation))
|
||||
remainingContinuations.remove(continuation)
|
||||
}
|
||||
|
||||
getStreamPageExpectation.fulfill()
|
||||
}
|
||||
|
||||
let syncUnreads = FeedlySyncUnreadStatusesOperation(account: account, resource: resource, service: service, newerThan: nil, log: support.log)
|
||||
|
||||
let completionExpectation = expectation(description: "Did Finish")
|
||||
syncUnreads.completionBlock = {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(syncUnreads)
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
// Find statuses inserted.
|
||||
let expectedArticleIds = Set(service.pages.values.map { $0.ids }.flatMap { $0 })
|
||||
let fetchIdsExpectation = expectation(description: "Fetch Article Ids")
|
||||
account.fetchUnreadArticleIDs { unreadArticleIdsResult in
|
||||
do {
|
||||
let unreadArticleIds = try unreadArticleIdsResult.get()
|
||||
let missingIds = expectedArticleIds.subtracting(unreadArticleIds)
|
||||
XCTAssertTrue(missingIds.isEmpty, "These article ids were not marked as unread.")
|
||||
fetchIdsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Error checking unread article IDs: \(error)")
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
}
|
||||
}
|
|
@ -28,14 +28,14 @@ class FeedlyUpdateAccountFeedsWithItemsOperationTests: XCTestCase {
|
|||
}
|
||||
|
||||
struct TestItemsByFeedProvider: FeedlyParsedItemsByFeedProviding {
|
||||
var providerName: String
|
||||
var parsedItemsByFeedProviderName: String
|
||||
var parsedItemsKeyedByFeedId: [String: Set<ParsedItem>]
|
||||
}
|
||||
|
||||
func testUpdateAccountWithEmptyItems() throws {
|
||||
let testItems = support.makeParsedItemTestDataFor(numberOfFeeds: 0, numberOfItemsInFeeds: 0)
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItems)
|
||||
let provider = TestItemsByFeedProvider(parsedItemsByFeedProviderName: resource.id, parsedItemsKeyedByFeedId: testItems)
|
||||
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
|
@ -59,7 +59,7 @@ class FeedlyUpdateAccountFeedsWithItemsOperationTests: XCTestCase {
|
|||
func testUpdateAccountWithOneItem() throws {
|
||||
let testItems = support.makeParsedItemTestDataFor(numberOfFeeds: 1, numberOfItemsInFeeds: 1)
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItems)
|
||||
let provider = TestItemsByFeedProvider(parsedItemsByFeedProviderName: resource.id, parsedItemsKeyedByFeedId: testItems)
|
||||
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
|
@ -87,7 +87,7 @@ class FeedlyUpdateAccountFeedsWithItemsOperationTests: XCTestCase {
|
|||
func testUpdateAccountWithManyItems() throws {
|
||||
let testItems = support.makeParsedItemTestDataFor(numberOfFeeds: 100, numberOfItemsInFeeds: 100)
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItems)
|
||||
let provider = TestItemsByFeedProvider(parsedItemsByFeedProviderName: resource.id, parsedItemsKeyedByFeedId: testItems)
|
||||
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
|
@ -115,7 +115,7 @@ class FeedlyUpdateAccountFeedsWithItemsOperationTests: XCTestCase {
|
|||
func testCancelUpdateAccount() throws {
|
||||
let testItems = support.makeParsedItemTestDataFor(numberOfFeeds: 1, numberOfItemsInFeeds: 1)
|
||||
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789")
|
||||
let provider = TestItemsByFeedProvider(providerName: resource.id, parsedItemsKeyedByFeedId: testItems)
|
||||
let provider = TestItemsByFeedProvider(parsedItemsByFeedProviderName: resource.id, parsedItemsKeyedByFeedId: testItems)
|
||||
|
||||
let update = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: provider, log: support.log)
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// TestGetEntriesService.swift
|
||||
// AccountTests
|
||||
//
|
||||
// Created by Kiel Gillard on 11/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Account
|
||||
|
||||
final class TestGetEntriesService: FeedlyGetEntriesService {
|
||||
var mockResult: Result<[FeedlyEntry], Error>?
|
||||
var getEntriesExpectation: XCTestExpectation?
|
||||
|
||||
func getEntries(for ids: Set<String>, completion: @escaping (Result<[FeedlyEntry], Error>) -> ()) {
|
||||
guard let result = mockResult else {
|
||||
XCTFail("Missing mock result. Test may time out because the completion will not be called.")
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
completion(result)
|
||||
self.getEntriesExpectation?.fulfill()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -637,7 +637,7 @@ extension FeedlyAPICaller: FeedlyGetStreamIdsService {
|
|||
}
|
||||
|
||||
queryItems.append(contentsOf: [
|
||||
URLQueryItem(name: "count", value: "1000"),
|
||||
URLQueryItem(name: "count", value: "10000"),
|
||||
URLQueryItem(name: "streamId", value: resource.id),
|
||||
])
|
||||
|
||||
|
|
|
@ -147,10 +147,6 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||
/// So if the user is using another client roughly simultaneously with this app,
|
||||
/// this app does its part to ensure the articles have a consistent status between both.
|
||||
///
|
||||
/// Feedly has no API that allows the app to fetch the identifiers of unread articles only.
|
||||
/// The only way to identify unread articles is to pull all of the article data,
|
||||
/// which is effectively equivalent of a full refresh.
|
||||
///
|
||||
/// - Parameter account: The account whose articles have a remote status.
|
||||
/// - Parameter completion: Call on the main queue.
|
||||
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
|
@ -160,18 +156,18 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||
|
||||
let group = DispatchGroup()
|
||||
|
||||
let syncUnread = FeedlySyncUnreadStatusesOperation(account: account, credentials: credentials, service: caller, newerThan: nil, log: log)
|
||||
let ingestUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: caller, newerThan: nil, log: log)
|
||||
|
||||
group.enter()
|
||||
syncUnread.completionBlock = {
|
||||
ingestUnread.completionBlock = {
|
||||
group.leave()
|
||||
|
||||
}
|
||||
|
||||
let syncStarred = FeedlySyncStarredArticlesOperation(account: account, credentials: credentials, service: caller, log: log)
|
||||
let ingestStarred = FeedlyIngestStarredArticleIdsOperation(account: account, credentials: credentials, service: caller, newerThan: nil, log: log)
|
||||
|
||||
group.enter()
|
||||
syncStarred.completionBlock = {
|
||||
ingestStarred.completionBlock = {
|
||||
group.leave()
|
||||
}
|
||||
|
||||
|
@ -179,7 +175,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||
completion(.success(()))
|
||||
}
|
||||
|
||||
operationQueue.addOperations([syncUnread, syncStarred], waitUntilFinished: false)
|
||||
operationQueue.addOperations([ingestUnread, ingestStarred], waitUntilFinished: false)
|
||||
}
|
||||
|
||||
func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// FeedlyEntryIdentifierProviding.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 9/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol FeedlyEntryIdentifierProviding: class {
|
||||
var entryIds: Set<String> { get }
|
||||
}
|
||||
|
||||
final class FeedlyEntryIdentifierProvider: FeedlyEntryIdentifierProviding {
|
||||
private (set) var entryIds: Set<String>
|
||||
|
||||
init(entryIds: Set<String> = Set()) {
|
||||
self.entryIds = entryIds
|
||||
}
|
||||
|
||||
func addEntryIds(from provider: FeedlyEntryIdentifierProviding) {
|
||||
entryIds.formUnion(provider.entryIds)
|
||||
}
|
||||
|
||||
func addEntryIds(in articleIds: [String]) {
|
||||
entryIds.formUnion(articleIds)
|
||||
}
|
||||
}
|
|
@ -92,7 +92,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
|||
createFeeds.downloadProgress = downloadProgress
|
||||
self.operationQueue.addOperation(createFeeds)
|
||||
|
||||
let syncUnread = FeedlySyncUnreadStatusesOperation(account: account, credentials: credentials, service: syncUnreadIdsService, newerThan: nil, log: log)
|
||||
let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: syncUnreadIdsService, newerThan: nil, log: log)
|
||||
syncUnread.addDependency(createFeeds)
|
||||
syncUnread.downloadProgress = downloadProgress
|
||||
self.operationQueue.addOperation(syncUnread)
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// FeedlyDownloadArticlesOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 9/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
class FeedlyDownloadArticlesOperation: FeedlyOperation {
|
||||
private let account: Account
|
||||
private let log: OSLog
|
||||
private let missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding
|
||||
private let updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding
|
||||
private let getEntriesService: FeedlyGetEntriesService
|
||||
private let operationQueue: OperationQueue
|
||||
private let finishOperation: FeedlyCheckpointOperation
|
||||
|
||||
init(account: Account, missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding, updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding, getEntriesService: FeedlyGetEntriesService, log: OSLog) {
|
||||
self.account = account
|
||||
self.operationQueue = OperationQueue()
|
||||
self.operationQueue.isSuspended = true
|
||||
self.missingArticleEntryIdProvider = missingArticleEntryIdProvider
|
||||
self.updatedArticleEntryIdProvider = updatedArticleEntryIdProvider
|
||||
self.getEntriesService = getEntriesService
|
||||
self.finishOperation = FeedlyCheckpointOperation()
|
||||
self.log = log
|
||||
|
||||
super.init()
|
||||
|
||||
self.finishOperation.checkpointDelegate = self
|
||||
self.operationQueue.addOperation(self.finishOperation)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
os_log(.debug, log: log, "Cancelling %{public}@.", self)
|
||||
operationQueue.cancelAllOperations()
|
||||
super.cancel()
|
||||
didFinish()
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
// override of cancel calls didFinish().
|
||||
return
|
||||
}
|
||||
|
||||
var articleIds = missingArticleEntryIdProvider.entryIds
|
||||
articleIds.formUnion(updatedArticleEntryIdProvider.entryIds)
|
||||
|
||||
os_log(.debug, log: log, "Requesting %{public}i articles.", articleIds.count)
|
||||
|
||||
let feedlyAPILimitBatchSize = 1000
|
||||
for articleIds in Array(articleIds).chunked(into: feedlyAPILimitBatchSize) {
|
||||
|
||||
let provider = FeedlyEntryIdentifierProvider(entryIds: Set(articleIds))
|
||||
let getEntries = FeedlyGetEntriesOperation(account: account, service: getEntriesService, provider: provider, log: log)
|
||||
getEntries.delegate = self
|
||||
self.operationQueue.addOperation(getEntries)
|
||||
|
||||
let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account,
|
||||
parsedItemProvider: getEntries,
|
||||
log: log)
|
||||
organiseByFeed.delegate = self
|
||||
organiseByFeed.addDependency(getEntries)
|
||||
self.operationQueue.addOperation(organiseByFeed)
|
||||
|
||||
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
|
||||
organisedItemsProvider: organiseByFeed,
|
||||
log: log)
|
||||
|
||||
updateAccount.delegate = self
|
||||
updateAccount.addDependency(organiseByFeed)
|
||||
self.operationQueue.addOperation(updateAccount)
|
||||
|
||||
finishOperation.addDependency(updateAccount)
|
||||
}
|
||||
|
||||
operationQueue.isSuspended = false
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedlyDownloadArticlesOperation: FeedlyCheckpointOperationDelegate {
|
||||
|
||||
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) {
|
||||
didFinish()
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedlyDownloadArticlesOperation: FeedlyOperationDelegate {
|
||||
|
||||
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
|
||||
assert(Thread.isMainThread)
|
||||
os_log(.debug, log: log, "%{public}@ failed with error: %{public}@.", operation, error as NSError)
|
||||
cancel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// FeedlyFetchIdsForMissingArticlesOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 7/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
|
||||
private let account: Account
|
||||
private let log: OSLog
|
||||
|
||||
private(set) var entryIds = Set<String>()
|
||||
|
||||
init(account: Account, log: OSLog) {
|
||||
self.account = account
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in
|
||||
switch result {
|
||||
case .success(let articleIds):
|
||||
self.entryIds.formUnion(articleIds)
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,15 +8,16 @@
|
|||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSParser
|
||||
|
||||
/// Single responsibility is to get full entries for the entry identifiers.
|
||||
final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding {
|
||||
final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding {
|
||||
let account: Account
|
||||
let service: FeedlyGetEntriesService
|
||||
let provider: FeedlyEntryIdenifierProviding
|
||||
let provider: FeedlyEntryIdentifierProviding
|
||||
let log: OSLog
|
||||
|
||||
init(account: Account, service: FeedlyGetEntriesService, provider: FeedlyEntryIdenifierProviding, log: OSLog) {
|
||||
init(account: Account, service: FeedlyGetEntriesService, provider: FeedlyEntryIdentifierProviding, log: OSLog) {
|
||||
self.account = account
|
||||
self.service = service
|
||||
self.provider = provider
|
||||
|
@ -25,6 +26,33 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding {
|
|||
|
||||
private (set) var entries = [FeedlyEntry]()
|
||||
|
||||
private var storedParsedEntries: Set<ParsedItem>?
|
||||
|
||||
var parsedEntries: Set<ParsedItem> {
|
||||
if let entries = storedParsedEntries {
|
||||
return entries
|
||||
}
|
||||
|
||||
let parsed = Set(entries.compactMap {
|
||||
FeedlyEntryParser(entry: $0).parsedItemRepresentation
|
||||
})
|
||||
|
||||
if parsed.count != entries.count {
|
||||
let entryIds = Set(entries.map { $0.id })
|
||||
let parsedIds = Set(parsed.map { $0.uniqueID })
|
||||
let difference = entryIds.subtracting(parsedIds)
|
||||
os_log(.debug, log: log, "%{public}@ dropping articles with ids: %{public}@.", self, difference)
|
||||
}
|
||||
|
||||
storedParsedEntries = parsed
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
var parsedItemProviderName: String {
|
||||
return name ?? String(describing: Self.self)
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
|
|
|
@ -15,7 +15,7 @@ protocol FeedlyEntryProviding {
|
|||
}
|
||||
|
||||
protocol FeedlyParsedItemProviding {
|
||||
var resource: FeedlyResourceId { get }
|
||||
var parsedItemProviderName: String { get }
|
||||
var parsedEntries: Set<ParsedItem> { get }
|
||||
}
|
||||
|
||||
|
@ -32,8 +32,8 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
|
|||
|
||||
let resourceProvider: FeedlyResourceProviding
|
||||
|
||||
var resource: FeedlyResourceId {
|
||||
return resourceProvider.resource
|
||||
var parsedItemProviderName: String {
|
||||
return resourceProvider.resource.id
|
||||
}
|
||||
|
||||
var entries: [FeedlyEntry] {
|
||||
|
|
|
@ -9,17 +9,12 @@
|
|||
import Foundation
|
||||
import os.log
|
||||
|
||||
protocol FeedlyEntryIdenifierProviding: class {
|
||||
var resource: FeedlyResourceId { get }
|
||||
var entryIds: Set<String> { get }
|
||||
}
|
||||
|
||||
protocol FeedlyGetStreamIdsOperationDelegate: class {
|
||||
func feedlyGetStreamIdsOperation(_ operation: FeedlyGetStreamIdsOperation, didGet streamIds: FeedlyStreamIds)
|
||||
}
|
||||
|
||||
/// Single responsibility is to get the stream ids from Feedly.
|
||||
final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdenifierProviding, FeedlyUnreadEntryIdProviding {
|
||||
final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
|
||||
|
||||
var entryIds: Set<String> {
|
||||
guard let ids = streamIds?.ids else {
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// FeedlyGetUpdatedArticleIdsOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 11/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
/// Single responsibility is to identify articles that have changed since a particular date.
|
||||
///
|
||||
/// Typically, it pages through the article ids of the global.all stream.
|
||||
/// When all the article ids are collected, it is the responsibility of another operation to download them when appropriate.
|
||||
class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let newerThan: Date?
|
||||
private let log: OSLog
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.newerThan = newerThan
|
||||
self.log = log
|
||||
}
|
||||
|
||||
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
let all = FeedlyCategoryResourceId.Global.all(for: credentials.username)
|
||||
self.init(account: account, resource: all, service: service, newerThan: newerThan, log: log)
|
||||
}
|
||||
|
||||
var entryIds: Set<String> {
|
||||
return storedUpdatedArticleIds
|
||||
}
|
||||
|
||||
private var storedUpdatedArticleIds = Set<String>()
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
getStreamIds(nil)
|
||||
}
|
||||
|
||||
private func getStreamIds(_ continuation: String?) {
|
||||
guard let date = newerThan else {
|
||||
os_log(.debug, log: log, "No date provided so everything must be new (nothing is updated).")
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
service.getStreamIds(for: resource, continuation: continuation, newerThan: date, unreadOnly: nil, completion: didGetStreamIds(_:))
|
||||
}
|
||||
|
||||
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let streamIds):
|
||||
storedUpdatedArticleIds.formUnion(streamIds.ids)
|
||||
|
||||
guard let continuation = streamIds.continuation else {
|
||||
os_log(.debug, log: log, "%{public}i articles updated since last successful sync start date.", storedUpdatedArticleIds.count)
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
getStreamIds(continuation)
|
||||
|
||||
case .failure(let error):
|
||||
didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// FeedlyIngestStarredArticleIdsOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 15/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
/// Single responsibility is to clone locally the remote starred article state.
|
||||
///
|
||||
/// Typically, it pages through the article ids of the global.saved stream.
|
||||
/// When all the article ids are collected, a status is created for each.
|
||||
/// The article ids previously marked as starred but not collected become unstarred.
|
||||
/// So this operation has side effects *for the entire account* it operates on.
|
||||
final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let entryIdsProvider: FeedlyEntryIdentifierProvider
|
||||
private let log: OSLog
|
||||
|
||||
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
let resource = FeedlyTagResourceId.Global.saved(for: credentials.username)
|
||||
self.init(account: account, resource: resource, service: service, newerThan: newerThan, log: log)
|
||||
}
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.entryIdsProvider = FeedlyEntryIdentifierProvider()
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
getStreamIds(nil)
|
||||
}
|
||||
|
||||
private func getStreamIds(_ continuation: String?) {
|
||||
service.getStreamIds(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil, completion: didGetStreamIds(_:))
|
||||
}
|
||||
|
||||
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let streamIds):
|
||||
|
||||
entryIdsProvider.addEntryIds(in: streamIds.ids)
|
||||
|
||||
guard let continuation = streamIds.continuation else {
|
||||
updateStarredStatuses()
|
||||
return
|
||||
}
|
||||
|
||||
getStreamIds(continuation)
|
||||
|
||||
case .failure(let error):
|
||||
didFinish(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStarredStatuses() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
account.fetchStarredArticleIDs { result in
|
||||
switch result {
|
||||
case .success(let localStarredArticleIDs):
|
||||
self.processStarredArticleIDs(localStarredArticleIDs)
|
||||
|
||||
case .failure(let error):
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processStarredArticleIDs(_ localStarredArticleIDs: Set<String>) {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
let remoteStarredArticleIDs = entryIdsProvider.entryIds
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
final class StarredStatusResults {
|
||||
var markAsStarredError: Error?
|
||||
var markAsUnstarredError: Error?
|
||||
}
|
||||
|
||||
let results = StarredStatusResults()
|
||||
|
||||
group.enter()
|
||||
account.markAsStarred(remoteStarredArticleIDs) { error in
|
||||
results.markAsStarredError = error
|
||||
group.leave()
|
||||
}
|
||||
|
||||
let deltaUnstarredArticleIDs = localStarredArticleIDs.subtracting(remoteStarredArticleIDs)
|
||||
group.enter()
|
||||
account.markAsUnstarred(deltaUnstarredArticleIDs) { error in
|
||||
results.markAsUnstarredError = error
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let markingError = results.markAsStarredError ?? results.markAsUnstarredError
|
||||
guard let error = markingError else {
|
||||
self.didFinish()
|
||||
return
|
||||
}
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// FeedlyIngestStreamArticleIdsOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 9/1/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
/// Single responsibility is to ensure a status exists for every article id the user might be interested in.
|
||||
///
|
||||
/// Typically, it pages through the article ids of the global.all stream.
|
||||
/// As the article ids are collected, a default read status is created for each.
|
||||
/// So this operation has side effects *for the entire account* it operates on.
|
||||
class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let log: OSLog
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, log: OSLog) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.log = log
|
||||
}
|
||||
|
||||
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamIdsService, log: OSLog) {
|
||||
let all = FeedlyCategoryResourceId.Global.all(for: credentials.username)
|
||||
self.init(account: account, resource: all, service: service, log: log)
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
getStreamIds(nil)
|
||||
}
|
||||
|
||||
private func getStreamIds(_ continuation: String?) {
|
||||
service.getStreamIds(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil, completion: didGetStreamIds(_:))
|
||||
}
|
||||
|
||||
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let streamIds):
|
||||
account.createStatusesIfNeeded(articleIDs: Set(streamIds.ids)) { databaseError in
|
||||
|
||||
if let error = databaseError {
|
||||
self.didFinish(error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let continuation = streamIds.continuation else {
|
||||
os_log(.debug, log: self.log, "Reached end of stream for %@", self.resource.id)
|
||||
self.didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
self.getStreamIds(continuation)
|
||||
}
|
||||
case .failure(let error):
|
||||
didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// FeedlyIngestUnreadArticleIdsOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 18/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSParser
|
||||
|
||||
/// Single responsibility is to clone locally the remote unread article state.
|
||||
///
|
||||
/// Typically, it pages through the unread article ids of the global.all stream.
|
||||
/// When all the unread article ids are collected, a status is created for each.
|
||||
/// The article ids previously marked as unread but not collected become read.
|
||||
/// So this operation has side effects *for the entire account* it operates on.
|
||||
final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let entryIdsProvider: FeedlyEntryIdentifierProvider
|
||||
private let log: OSLog
|
||||
|
||||
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
let resource = FeedlyCategoryResourceId.Global.all(for: credentials.username)
|
||||
self.init(account: account, resource: resource, service: service, newerThan: newerThan, log: log)
|
||||
}
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.entryIdsProvider = FeedlyEntryIdentifierProvider()
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
getStreamIds(nil)
|
||||
}
|
||||
|
||||
private func getStreamIds(_ continuation: String?) {
|
||||
service.getStreamIds(for: resource, continuation: continuation, newerThan: nil, unreadOnly: true, completion: didGetStreamIds(_:))
|
||||
}
|
||||
|
||||
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let streamIds):
|
||||
|
||||
entryIdsProvider.addEntryIds(in: streamIds.ids)
|
||||
|
||||
guard let continuation = streamIds.continuation else {
|
||||
updateUnreadStatuses()
|
||||
return
|
||||
}
|
||||
|
||||
getStreamIds(continuation)
|
||||
|
||||
case .failure(let error):
|
||||
didFinish(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateUnreadStatuses() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
account.fetchUnreadArticleIDs { result in
|
||||
switch result {
|
||||
case .success(let localUnreadArticleIDs):
|
||||
self.processUnreadArticleIDs(localUnreadArticleIDs)
|
||||
|
||||
case .failure(let error):
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func processUnreadArticleIDs(_ localUnreadArticleIDs: Set<String>) {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
let remoteUnreadArticleIDs = entryIdsProvider.entryIds
|
||||
let group = DispatchGroup()
|
||||
|
||||
final class ReadStatusResults {
|
||||
var markAsUnreadError: Error?
|
||||
var markAsReadError: Error?
|
||||
}
|
||||
|
||||
let results = ReadStatusResults()
|
||||
|
||||
group.enter()
|
||||
account.markAsUnread(remoteUnreadArticleIDs) { error in
|
||||
results.markAsUnreadError = error
|
||||
group.leave()
|
||||
}
|
||||
|
||||
let articleIDsToMarkRead = localUnreadArticleIDs.subtracting(remoteUnreadArticleIDs)
|
||||
group.enter()
|
||||
account.markAsRead(articleIDsToMarkRead) { error in
|
||||
results.markAsReadError = error
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let markingError = results.markAsReadError ?? results.markAsUnreadError
|
||||
guard let error = markingError else {
|
||||
self.didFinish()
|
||||
return
|
||||
}
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import RSParser
|
|||
import os.log
|
||||
|
||||
protocol FeedlyParsedItemsByFeedProviding {
|
||||
var providerName: String { get }
|
||||
var parsedItemsByFeedProviderName: String { get }
|
||||
var parsedItemsKeyedByFeedId: [String: Set<ParsedItem>] { get }
|
||||
}
|
||||
|
||||
|
@ -21,15 +21,15 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
|||
private let parsedItemProvider: FeedlyParsedItemProviding
|
||||
private let log: OSLog
|
||||
|
||||
var parsedItemsByFeedProviderName: String {
|
||||
return name ?? String(describing: Self.self)
|
||||
}
|
||||
|
||||
var parsedItemsKeyedByFeedId: [String : Set<ParsedItem>] {
|
||||
assert(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type.
|
||||
return itemsKeyedByFeedId
|
||||
}
|
||||
|
||||
var providerName: String {
|
||||
return parsedItemProvider.resource.id
|
||||
}
|
||||
|
||||
private var itemsKeyedByFeedId = [String: Set<ParsedItem>]()
|
||||
|
||||
init(account: Account, parsedItemProvider: FeedlyParsedItemProviding, log: OSLog) {
|
||||
|
@ -61,7 +61,7 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
|
|||
guard !isCancelled else { return }
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemProvider.resource.id)
|
||||
os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemProvider.parsedItemProviderName)
|
||||
|
||||
itemsKeyedByFeedId = dict
|
||||
}
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
//
|
||||
// FeedlySetStarredArticlesOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 14/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
protocol FeedlyStarredEntryIdProviding {
|
||||
var entryIds: Set<String> { get }
|
||||
}
|
||||
|
||||
/// Single responsibility is to associate a starred status for ingested and remote
|
||||
/// articles identfied by the provided identifiers *for the entire account.*
|
||||
final class FeedlySetStarredArticlesOperation: FeedlyOperation {
|
||||
private let account: Account
|
||||
private let allStarredEntryIdsProvider: FeedlyStarredEntryIdProviding
|
||||
private let log: OSLog
|
||||
|
||||
init(account: Account, allStarredEntryIdsProvider: FeedlyStarredEntryIdProviding, log: OSLog) {
|
||||
self.account = account
|
||||
self.allStarredEntryIdsProvider = allStarredEntryIdsProvider
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
account.fetchStarredArticleIDs { result in
|
||||
switch result {
|
||||
case .success(let localStarredArticleIDs):
|
||||
self.processStarredArticleIDs(localStarredArticleIDs)
|
||||
|
||||
case .failure(let error):
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension FeedlySetStarredArticlesOperation {
|
||||
|
||||
func processStarredArticleIDs(_ localStarredArticleIDs: Set<String>) {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
let remoteStarredArticleIDs = allStarredEntryIdsProvider.entryIds
|
||||
guard !remoteStarredArticleIDs.isEmpty else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
final class StarredStatusResults {
|
||||
var markAsStarredError: Error?
|
||||
var markAsUnstarredError: Error?
|
||||
}
|
||||
|
||||
let results = StarredStatusResults()
|
||||
|
||||
group.enter()
|
||||
account.markAsStarred(remoteStarredArticleIDs) { error in
|
||||
results.markAsStarredError = error
|
||||
group.leave()
|
||||
}
|
||||
|
||||
let deltaUnstarredArticleIDs = localStarredArticleIDs.subtracting(remoteStarredArticleIDs)
|
||||
group.enter()
|
||||
account.markAsUnstarred(deltaUnstarredArticleIDs) { error in
|
||||
results.markAsUnstarredError = error
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let markingError = results.markAsStarredError ?? results.markAsUnstarredError
|
||||
guard let error = markingError else {
|
||||
self.didFinish()
|
||||
return
|
||||
}
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
//
|
||||
// FeedlySetUnreadArticlesOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 25/9/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
protocol FeedlyUnreadEntryIdProviding {
|
||||
var entryIds: Set<String> { get }
|
||||
}
|
||||
|
||||
/// Single responsibility is to associate a read status for ingested and remote articles
|
||||
/// where the provided article identifers identify the unread articles *for the entire account.*
|
||||
final class FeedlySetUnreadArticlesOperation: FeedlyOperation {
|
||||
private let account: Account
|
||||
private let allUnreadIdsProvider: FeedlyUnreadEntryIdProviding
|
||||
private let log: OSLog
|
||||
|
||||
init(account: Account, allUnreadIdsProvider: FeedlyUnreadEntryIdProviding, log: OSLog) {
|
||||
self.account = account
|
||||
self.allUnreadIdsProvider = allUnreadIdsProvider
|
||||
self.log = log
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
account.fetchUnreadArticleIDs { result in
|
||||
switch result {
|
||||
case .success(let localUnreadArticleIDs):
|
||||
self.processUnreadArticleIDs(localUnreadArticleIDs)
|
||||
|
||||
case .failure(let error):
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension FeedlySetUnreadArticlesOperation {
|
||||
|
||||
private func processUnreadArticleIDs(_ localUnreadArticleIDs: Set<String>) {
|
||||
guard !isCancelled else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
let remoteUnreadArticleIDs = allUnreadIdsProvider.entryIds
|
||||
guard !remoteUnreadArticleIDs.isEmpty else {
|
||||
didFinish()
|
||||
return
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
final class ReadStatusResults {
|
||||
var markAsUnreadError: Error?
|
||||
var markAsReadError: Error?
|
||||
}
|
||||
|
||||
let results = ReadStatusResults()
|
||||
|
||||
group.enter()
|
||||
account.markAsUnread(remoteUnreadArticleIDs) { error in
|
||||
results.markAsUnreadError = error
|
||||
group.leave()
|
||||
}
|
||||
|
||||
let articleIDsToMarkRead = localUnreadArticleIDs.subtracting(remoteUnreadArticleIDs)
|
||||
group.enter()
|
||||
account.markAsRead(articleIDsToMarkRead) { error in
|
||||
results.markAsReadError = error
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let markingError = results.markAsReadError ?? results.markAsUnreadError
|
||||
guard let error = markingError else {
|
||||
self.didFinish()
|
||||
return
|
||||
}
|
||||
self.didFinish(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,19 @@ final class FeedlySyncAllOperation: FeedlyOperation {
|
|||
|
||||
var syncCompletionHandler: ((Result<Void, Error>) -> ())?
|
||||
|
||||
init(account: Account, credentials: Credentials, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredArticlesService: FeedlyGetStreamContentsService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) {
|
||||
/// These requests to Feedly determine which articles to download:
|
||||
/// 1. The set of all article ids we might need or show.
|
||||
/// 2. The set of all unread article ids we might need or show (a subset of 1).
|
||||
/// 3. The set of all article ids changed since the last sync (a subset of 1).
|
||||
/// 4. The set of all starred article ids.
|
||||
///
|
||||
/// On the response for 1, create statuses for each article id.
|
||||
/// On the response for 2, create unread statuses for each article id and mark as read those no longer in the response.
|
||||
/// On the response for 4, create starred statuses for each article id and mark as unstarred those no longer in the response.
|
||||
///
|
||||
/// Download articles for statuses at the union of those statuses without its corresponding article and those included in 3 (changed since last successful sync).
|
||||
///
|
||||
init(account: Account, credentials: Credentials, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredService: FeedlyGetStreamIdsService, getStreamIdsService: FeedlyGetStreamIdsService, getEntriesService: FeedlyGetEntriesService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) {
|
||||
self.syncUUID = UUID()
|
||||
self.log = log
|
||||
self.operationQueue = OperationQueue()
|
||||
|
@ -54,48 +66,66 @@ final class FeedlySyncAllOperation: FeedlyOperation {
|
|||
createFeedsOperation.addDependency(mirrorCollectionsAsFolders)
|
||||
self.operationQueue.addOperation(createFeedsOperation)
|
||||
|
||||
let getAllArticleIds = FeedlyIngestStreamArticleIdsOperation(account: account, credentials: credentials, service: getStreamIdsService, log: log)
|
||||
getAllArticleIds.delegate = self
|
||||
getAllArticleIds.downloadProgress = downloadProgress
|
||||
getAllArticleIds.addDependency(createFeedsOperation)
|
||||
self.operationQueue.addOperation(getAllArticleIds)
|
||||
|
||||
// Get each page of unread article ids in the global.all stream for the last 31 days (nil = Feedly API default).
|
||||
let getUnread = FeedlySyncUnreadStatusesOperation(account: account, credentials: credentials, service: getUnreadService, newerThan: nil, log: log)
|
||||
let getUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: getUnreadService, newerThan: nil, log: log)
|
||||
getUnread.delegate = self
|
||||
getUnread.addDependency(createFeedsOperation)
|
||||
getUnread.addDependency(getAllArticleIds)
|
||||
getUnread.downloadProgress = downloadProgress
|
||||
self.operationQueue.addOperation(getUnread)
|
||||
|
||||
// Get each page of the global.all stream until we get either the content from the last sync or the last 31 days.
|
||||
let getStreamContents = FeedlySyncStreamContentsOperation(account: account, credentials: credentials, service: getStreamContentsService, newerThan: lastSuccessfulFetchStartDate, log: log)
|
||||
getStreamContents.delegate = self
|
||||
getStreamContents.downloadProgress = downloadProgress
|
||||
getStreamContents.addDependency(getUnread)
|
||||
self.operationQueue.addOperation(getStreamContents)
|
||||
// Get each page of the article ids which have been update since the last successful fetch start date.
|
||||
// If the date is nil, this operation provides an empty set (everything is new, nothing is updated).
|
||||
let getUpdated = FeedlyGetUpdatedArticleIdsOperation(account: account, credentials: credentials, service: getStreamIdsService, newerThan: lastSuccessfulFetchStartDate, log: log)
|
||||
getUpdated.delegate = self
|
||||
getUpdated.downloadProgress = downloadProgress
|
||||
getUpdated.addDependency(createFeedsOperation)
|
||||
self.operationQueue.addOperation(getUpdated)
|
||||
|
||||
// Get each and every starred article.
|
||||
let syncStarred = FeedlySyncStarredArticlesOperation(account: account, credentials: credentials, service: getStarredArticlesService, log: log)
|
||||
syncStarred.delegate = self
|
||||
syncStarred.downloadProgress = downloadProgress
|
||||
syncStarred.addDependency(createFeedsOperation)
|
||||
self.operationQueue.addOperation(syncStarred)
|
||||
// Get each page of the article ids for starred articles.
|
||||
let getStarred = FeedlyIngestStarredArticleIdsOperation(account: account, credentials: credentials, service: getStarredService, newerThan: nil, log: log)
|
||||
getStarred.delegate = self
|
||||
getStarred.downloadProgress = downloadProgress
|
||||
getStarred.addDependency(createFeedsOperation)
|
||||
self.operationQueue.addOperation(getStarred)
|
||||
|
||||
// Now all the possible article ids we need have a status, fetch the article ids for missing articles.
|
||||
let getMissingIds = FeedlyFetchIdsForMissingArticlesOperation(account: account, log: log)
|
||||
getMissingIds.delegate = self
|
||||
getMissingIds.downloadProgress = downloadProgress
|
||||
getMissingIds.addDependency(getAllArticleIds)
|
||||
getMissingIds.addDependency(getUnread)
|
||||
getMissingIds.addDependency(getStarred)
|
||||
getMissingIds.addDependency(getUpdated)
|
||||
self.operationQueue.addOperation(getMissingIds)
|
||||
|
||||
// Download all the missing and updated articles
|
||||
let downloadMissingArticles = FeedlyDownloadArticlesOperation(account: account,
|
||||
missingArticleEntryIdProvider: getMissingIds,
|
||||
updatedArticleEntryIdProvider: getUpdated,
|
||||
getEntriesService: getEntriesService,
|
||||
log: log)
|
||||
downloadMissingArticles.delegate = self
|
||||
downloadMissingArticles.downloadProgress = downloadProgress
|
||||
downloadMissingArticles.addDependency(getMissingIds)
|
||||
downloadMissingArticles.addDependency(getUpdated)
|
||||
self.operationQueue.addOperation(downloadMissingArticles)
|
||||
|
||||
// Once this operation's dependencies, their dependencies etc finish, we can finish.
|
||||
let finishOperation = FeedlyCheckpointOperation()
|
||||
finishOperation.checkpointDelegate = self
|
||||
finishOperation.downloadProgress = downloadProgress
|
||||
finishOperation.addDependency(getStreamContents)
|
||||
finishOperation.addDependency(syncStarred)
|
||||
|
||||
finishOperation.addDependency(downloadMissingArticles)
|
||||
self.operationQueue.addOperation(finishOperation)
|
||||
}
|
||||
|
||||
convenience init(account: Account, credentials: Credentials, caller: FeedlyAPICaller, database: SyncDatabase, lastSuccessfulFetchStartDate: Date?, downloadProgress: DownloadProgress, log: OSLog) {
|
||||
|
||||
let newerThan: Date? = {
|
||||
if let date = lastSuccessfulFetchStartDate {
|
||||
return date
|
||||
} else {
|
||||
return Calendar.current.date(byAdding: .day, value: -31, to: Date())
|
||||
}
|
||||
}()
|
||||
|
||||
self.init(account: account, credentials: credentials, lastSuccessfulFetchStartDate: newerThan, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredArticlesService: caller, database: database, downloadProgress: downloadProgress, log: log)
|
||||
self.init(account: account, credentials: credentials, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredService: caller, getStreamIdsService: caller, getEntriesService: caller, database: database, downloadProgress: downloadProgress, log: log)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
//
|
||||
// FeedlySyncStarredArticlesOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 15/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSParser
|
||||
|
||||
final class FeedlySyncStarredArticlesOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate {
|
||||
private let account: Account
|
||||
private let operationQueue: OperationQueue
|
||||
private let service: FeedlyGetStreamContentsService
|
||||
private let log: OSLog
|
||||
|
||||
private let setStatuses: FeedlySetStarredArticlesOperation
|
||||
private let finishOperation: FeedlyCheckpointOperation
|
||||
|
||||
/// Buffers every starred/saved entry from every page.
|
||||
private class StarredEntryProvider: FeedlyEntryProviding, FeedlyStarredEntryIdProviding, FeedlyParsedItemProviding {
|
||||
var resource: FeedlyResourceId
|
||||
|
||||
private(set) var parsedEntries = Set<ParsedItem>()
|
||||
private(set) var entries = [FeedlyEntry]()
|
||||
|
||||
init(resource: FeedlyResourceId) {
|
||||
self.resource = resource
|
||||
}
|
||||
|
||||
func addEntries(from provider: FeedlyEntryProviding & FeedlyParsedItemProviding) {
|
||||
entries.append(contentsOf: provider.entries)
|
||||
parsedEntries.formUnion(provider.parsedEntries)
|
||||
}
|
||||
|
||||
var entryIds: Set<String> {
|
||||
return Set(entries.map { $0.id })
|
||||
}
|
||||
}
|
||||
|
||||
private let entryProvider: StarredEntryProvider
|
||||
|
||||
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, log: OSLog) {
|
||||
let saved = FeedlyTagResourceId.Global.saved(for: credentials.username)
|
||||
self.init(account: account, resource: saved, service: service, log: log)
|
||||
}
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, log: OSLog) {
|
||||
self.account = account
|
||||
self.service = service
|
||||
self.operationQueue = OperationQueue()
|
||||
self.operationQueue.isSuspended = true
|
||||
self.finishOperation = FeedlyCheckpointOperation()
|
||||
self.log = log
|
||||
|
||||
let provider = StarredEntryProvider(resource: resource)
|
||||
self.entryProvider = provider
|
||||
self.setStatuses = FeedlySetStarredArticlesOperation(account: account,
|
||||
allStarredEntryIdsProvider: provider,
|
||||
log: log)
|
||||
|
||||
super.init()
|
||||
|
||||
let getFirstPage = FeedlyGetStreamContentsOperation(account: account,
|
||||
resource: resource,
|
||||
service: service,
|
||||
newerThan: nil,
|
||||
log: log)
|
||||
|
||||
let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account,
|
||||
parsedItemProvider: provider,
|
||||
log: log)
|
||||
|
||||
let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account,
|
||||
organisedItemsProvider: organiseByFeed,
|
||||
log: log)
|
||||
|
||||
getFirstPage.delegate = self
|
||||
getFirstPage.streamDelegate = self
|
||||
|
||||
setStatuses.addDependency(getFirstPage)
|
||||
setStatuses.delegate = self
|
||||
|
||||
organiseByFeed.addDependency(setStatuses)
|
||||
organiseByFeed.delegate = self
|
||||
|
||||
updateAccount.addDependency(organiseByFeed)
|
||||
updateAccount.delegate = self
|
||||
|
||||
finishOperation.checkpointDelegate = self
|
||||
finishOperation.addDependency(updateAccount)
|
||||
|
||||
let operations = [getFirstPage, setStatuses, organiseByFeed, updateAccount, finishOperation]
|
||||
operationQueue.addOperations(operations, waitUntilFinished: false)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
os_log(.debug, log: log, "Canceling sync starred articles")
|
||||
operationQueue.cancelAllOperations()
|
||||
super.cancel()
|
||||
didFinish()
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
// override of cancel calls didFinish().
|
||||
return
|
||||
}
|
||||
|
||||
operationQueue.isSuspended = false
|
||||
}
|
||||
|
||||
func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) {
|
||||
guard !isCancelled else {
|
||||
os_log(.debug, log: log, "Cancelled starred stream contents for %@", stream.id)
|
||||
return
|
||||
}
|
||||
|
||||
entryProvider.addEntries(from: operation)
|
||||
os_log(.debug, log: log, "Collecting %i items from %@", stream.items.count, stream.id)
|
||||
|
||||
guard let continuation = stream.continuation else {
|
||||
return
|
||||
}
|
||||
|
||||
let nextPageOperation = FeedlyGetStreamContentsOperation(account: operation.account,
|
||||
resource: operation.resource,
|
||||
service: operation.service,
|
||||
continuation: continuation,
|
||||
newerThan: operation.newerThan,
|
||||
log: log)
|
||||
nextPageOperation.delegate = self
|
||||
nextPageOperation.streamDelegate = self
|
||||
|
||||
setStatuses.addDependency(nextPageOperation)
|
||||
operationQueue.addOperation(nextPageOperation)
|
||||
}
|
||||
|
||||
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) {
|
||||
didFinish()
|
||||
}
|
||||
|
||||
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
|
||||
os_log(.debug, log: log, "%{public}@ failing and cancelling other operations because %{public}@.", operation, error.localizedDescription)
|
||||
operationQueue.cancelAllOperations()
|
||||
didFinish(error)
|
||||
}
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
//
|
||||
// FeedlySyncUnreadStatusesOperation.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Kiel Gillard on 18/10/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSParser
|
||||
|
||||
/// Makes one or more requests to get the complete set of unread article ids to update the status of those articles *for the entire account.*
|
||||
final class FeedlySyncUnreadStatusesOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamIdsOperationDelegate, FeedlyCheckpointOperationDelegate {
|
||||
private let account: Account
|
||||
private let resource: FeedlyResourceId
|
||||
private let operationQueue: OperationQueue
|
||||
private let service: FeedlyGetStreamIdsService
|
||||
private let log: OSLog
|
||||
|
||||
/// Buffers every unread article id from every page of the resource's stream.
|
||||
private class UnreadEntryIdsProvider: FeedlyUnreadEntryIdProviding {
|
||||
let resource: FeedlyResourceId
|
||||
private(set) var entryIds = Set<String>()
|
||||
|
||||
init(resource: FeedlyResourceId) {
|
||||
self.resource = resource
|
||||
}
|
||||
|
||||
func addEntryIds(from provider: FeedlyEntryIdenifierProviding) {
|
||||
entryIds.formUnion(provider.entryIds)
|
||||
}
|
||||
}
|
||||
|
||||
private let unreadEntryIdsProvider: UnreadEntryIdsProvider
|
||||
private let setStatuses: FeedlySetUnreadArticlesOperation
|
||||
|
||||
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
let resource = FeedlyCategoryResourceId.Global.all(for: credentials.username)
|
||||
self.init(account: account, resource: resource, service: service, newerThan: newerThan, log: log)
|
||||
}
|
||||
|
||||
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) {
|
||||
self.account = account
|
||||
self.resource = resource
|
||||
self.service = service
|
||||
self.operationQueue = OperationQueue()
|
||||
self.operationQueue.isSuspended = true
|
||||
self.log = log
|
||||
|
||||
let provider = UnreadEntryIdsProvider(resource: resource)
|
||||
self.unreadEntryIdsProvider = provider
|
||||
self.setStatuses = FeedlySetUnreadArticlesOperation(account: account,
|
||||
allUnreadIdsProvider: unreadEntryIdsProvider,
|
||||
log: log)
|
||||
|
||||
super.init()
|
||||
|
||||
let getFirstPageOfUnreadIds = FeedlyGetStreamIdsOperation(account: account,
|
||||
resource: resource,
|
||||
service: service,
|
||||
newerThan: nil,
|
||||
unreadOnly: true,
|
||||
log: log)
|
||||
|
||||
getFirstPageOfUnreadIds.delegate = self
|
||||
getFirstPageOfUnreadIds.streamIdsDelegate = self
|
||||
|
||||
setStatuses.addDependency(getFirstPageOfUnreadIds)
|
||||
setStatuses.delegate = self
|
||||
|
||||
let finishOperation = FeedlyCheckpointOperation()
|
||||
finishOperation.checkpointDelegate = self
|
||||
finishOperation.addDependency(setStatuses)
|
||||
|
||||
let operations = [getFirstPageOfUnreadIds, setStatuses, finishOperation]
|
||||
operationQueue.addOperations(operations, waitUntilFinished: false)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
os_log(.debug, log: log, "Canceling sync unread statuses")
|
||||
operationQueue.cancelAllOperations()
|
||||
super.cancel()
|
||||
didFinish()
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !isCancelled else {
|
||||
// override of cancel calls didFinish().
|
||||
return
|
||||
}
|
||||
|
||||
operationQueue.isSuspended = false
|
||||
}
|
||||
|
||||
func feedlyGetStreamIdsOperation(_ operation: FeedlyGetStreamIdsOperation, didGet streamIds: FeedlyStreamIds) {
|
||||
guard !isCancelled else {
|
||||
os_log(.debug, log: log, "Cancelled unread stream ids.")
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Collecting %i unread article ids from %@", streamIds.ids.count, resource.id)
|
||||
unreadEntryIdsProvider.addEntryIds(from: operation)
|
||||
|
||||
guard let continuation = streamIds.continuation else {
|
||||
return
|
||||
}
|
||||
|
||||
let nextPageOperation = FeedlyGetStreamIdsOperation(account: operation.account,
|
||||
resource: operation.resource,
|
||||
service: operation.service,
|
||||
continuation: continuation,
|
||||
newerThan: operation.newerThan,
|
||||
unreadOnly: operation.unreadOnly,
|
||||
log: log)
|
||||
nextPageOperation.delegate = self
|
||||
nextPageOperation.streamIdsDelegate = self
|
||||
|
||||
setStatuses.addDependency(nextPageOperation)
|
||||
operationQueue.addOperation(nextPageOperation)
|
||||
}
|
||||
|
||||
func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) {
|
||||
didFinish()
|
||||
}
|
||||
|
||||
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
|
||||
operationQueue.cancelAllOperations()
|
||||
didFinish(error)
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
|||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", webFeedIDsAndItems.count, self.organisedItemsProvider.providerName)
|
||||
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", webFeedIDsAndItems.count, self.organisedItemsProvider.parsedItemsByFeedProviderName)
|
||||
self.didFinish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,7 +176,7 @@ public final class ArticlesDatabase {
|
|||
articlesTable.fetchStarredArticleIDsAsync(webFeedIDs, completion)
|
||||
}
|
||||
|
||||
/// Fetch articleIDs for articles that we should have, but don’t. These articles are not userDeleted, and they are either (starred) or (unread and newer than the article cutoff date).
|
||||
/// Fetch articleIDs for articles that we should have, but don’t. These articles are not userDeleted, and they are either (starred) or (newer than the article cutoff date).
|
||||
public func fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(_ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
articlesTable.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(completion)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue