Merge pull request #1392 from kielgillard/master
Search Feedly for an appropriate feed when creating a Web Feed.
This commit is contained in:
commit
526052a094
@ -74,7 +74,6 @@
|
|||||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */; };
|
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */; };
|
||||||
844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844B2980210CE3BF004020B3 /* RSWeb.framework */; };
|
844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844B2980210CE3BF004020B3 /* RSWeb.framework */; };
|
||||||
8469F81C1F6DD15E0084783E /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935101F62486800CEBD24 /* Account.swift */; };
|
8469F81C1F6DD15E0084783E /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935101F62486800CEBD24 /* Account.swift */; };
|
||||||
846CA1882392349E00B55117 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846CA1872392349E00B55117 /* SyncDatabase.framework */; };
|
|
||||||
846E77451F6EF9B900A165E2 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419740D1F6DD25F006346C4 /* Container.swift */; };
|
846E77451F6EF9B900A165E2 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419740D1F6DD25F006346C4 /* Container.swift */; };
|
||||||
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742C1F6DDE84006346C4 /* LocalAccountDelegate.swift */; };
|
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742C1F6DDE84006346C4 /* LocalAccountDelegate.swift */; };
|
||||||
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */; };
|
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */; };
|
||||||
@ -127,6 +126,7 @@
|
|||||||
9E7299D9235062A200DAEFB7 /* FeedlyResourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */; };
|
9E7299D9235062A200DAEFB7 /* FeedlyResourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */; };
|
||||||
9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */; };
|
9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */; };
|
||||||
9E784EC0237E8BE100099B1B /* FeedlyLogoutOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */; };
|
9E784EC0237E8BE100099B1B /* FeedlyLogoutOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */; };
|
||||||
|
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 */; };
|
9E7F88AC235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7F88AB235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift */; };
|
||||||
9E7F88AE235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7F88AD235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift */; };
|
9E7F88AE235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7F88AD235FBB11009AB9DF /* FeedlyGetStreamContentsOperationTests.swift */; };
|
||||||
9E84DC472359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E84DC462359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift */; };
|
9E84DC472359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E84DC462359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift */; };
|
||||||
@ -140,6 +140,9 @@
|
|||||||
9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */; };
|
9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */; };
|
||||||
9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */; };
|
9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */; };
|
||||||
9EA643CF2391D3560018A28C /* FeedlyAddExistingFeedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA643CE2391D3550018A28C /* FeedlyAddExistingFeedOperation.swift */; };
|
9EA643CF2391D3560018A28C /* FeedlyAddExistingFeedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA643CE2391D3550018A28C /* FeedlyAddExistingFeedOperation.swift */; };
|
||||||
|
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 */; };
|
||||||
9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */; };
|
9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */; };
|
||||||
9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */; };
|
9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */; };
|
||||||
9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */; };
|
9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */; };
|
||||||
@ -293,7 +296,6 @@
|
|||||||
844B297C2106C7EC004020B3 /* WebFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeed.swift; sourceTree = "<group>"; };
|
844B297C2106C7EC004020B3 /* WebFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeed.swift; sourceTree = "<group>"; };
|
||||||
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = "<group>"; };
|
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = "<group>"; };
|
||||||
844B2980210CE3BF004020B3 /* RSWeb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSWeb.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
844B2980210CE3BF004020B3 /* RSWeb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSWeb.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
846CA1872392349E00B55117 /* SyncDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SyncDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
846E77531F6F00E300A165E2 /* AccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
|
846E77531F6F00E300A165E2 /* AccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
|
||||||
848934F61F62484F00CEBD24 /* Account.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Account.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
848934F61F62484F00CEBD24 /* Account.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Account.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
848934FA1F62484F00CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
848934FA1F62484F00CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@ -347,6 +349,7 @@
|
|||||||
9E7299D8235062A200DAEFB7 /* FeedlyResourceProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyResourceProviding.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>"; };
|
9E784EBD237E890600099B1B /* FeedlyLogoutOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyLogoutOperation.swift; sourceTree = "<group>"; };
|
||||||
9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyLogoutOperationTests.swift; sourceTree = "<group>"; };
|
9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyLogoutOperationTests.swift; sourceTree = "<group>"; };
|
||||||
|
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>"; };
|
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>"; };
|
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 /* FeedlySyncUnreadStatusesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncUnreadStatusesOperation.swift; sourceTree = "<group>"; };
|
||||||
@ -360,6 +363,9 @@
|
|||||||
9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAccountAuthorizationOperation.swift; sourceTree = "<group>"; };
|
9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAccountAuthorizationOperation.swift; sourceTree = "<group>"; };
|
||||||
9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegate.swift; sourceTree = "<group>"; };
|
9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegate.swift; sourceTree = "<group>"; };
|
||||||
9EA643CE2391D3550018A28C /* FeedlyAddExistingFeedOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyAddExistingFeedOperation.swift; sourceTree = "<group>"; };
|
9EA643CE2391D3550018A28C /* FeedlyAddExistingFeedOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyAddExistingFeedOperation.swift; sourceTree = "<group>"; };
|
||||||
|
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>"; };
|
||||||
9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCollection.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>"; };
|
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>"; };
|
9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyEntry.swift; sourceTree = "<group>"; };
|
||||||
@ -663,6 +669,8 @@
|
|||||||
9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */,
|
9E1773DA234593CF0056A5A8 /* FeedlyResourceIdTests.swift */,
|
||||||
9E0260CA236FF99A00D122D3 /* FeedlyRefreshAccessTokenOperationTests.swift */,
|
9E0260CA236FF99A00D122D3 /* FeedlyRefreshAccessTokenOperationTests.swift */,
|
||||||
9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */,
|
9E784EBF237E8BE100099B1B /* FeedlyLogoutOperationTests.swift */,
|
||||||
|
9EA643D823945CE00018A28C /* FeedlyAddNewFeedOperationTests.swift */,
|
||||||
|
9E79F7732395C9EF0031DB98 /* feedly-add-new-feed */,
|
||||||
9E5ABE99236BE6BC00B5DE9F /* feedly-1-initial */,
|
9E5ABE99236BE6BC00B5DE9F /* feedly-1-initial */,
|
||||||
9EC804E4236C1A7F0057CFCB /* feedly-2-changestatuses */,
|
9EC804E4236C1A7F0057CFCB /* feedly-2-changestatuses */,
|
||||||
9EC804E6236C1BA60057CFCB /* feedly-3-changestatusesagain */,
|
9EC804E6236C1BA60057CFCB /* feedly-3-changestatusesagain */,
|
||||||
@ -697,6 +705,7 @@
|
|||||||
children = (
|
children = (
|
||||||
9E1D1554233431A600F4944C /* FeedlyOperation.swift */,
|
9E1D1554233431A600F4944C /* FeedlyOperation.swift */,
|
||||||
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */,
|
9EF35F79234E830E003AE2AE /* FeedlyCompoundOperation.swift */,
|
||||||
|
9EA643D2239305680018A28C /* FeedlySearchOperation.swift */,
|
||||||
9E7299D623505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift */,
|
9E7299D623505E9600DAEFB7 /* FeedlyAddFeedToCollectionOperation.swift */,
|
||||||
9EB1D575238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift */,
|
9EB1D575238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift */,
|
||||||
9EA643CE2391D3550018A28C /* FeedlyAddExistingFeedOperation.swift */,
|
9EA643CE2391D3550018A28C /* FeedlyAddExistingFeedOperation.swift */,
|
||||||
@ -737,6 +746,7 @@
|
|||||||
9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */,
|
9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */,
|
||||||
9E1773D22345700E0056A5A8 /* FeedlyLink.swift */,
|
9E1773D22345700E0056A5A8 /* FeedlyLink.swift */,
|
||||||
9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */,
|
9E1773D6234575AB0056A5A8 /* FeedlyTag.swift */,
|
||||||
|
9EA643D4239306AC0018A28C /* FeedlyFeedsSearchResponse.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -927,6 +937,7 @@
|
|||||||
9EC804E5236C1A7F0057CFCB /* feedly-2-changestatuses in Resources */,
|
9EC804E5236C1A7F0057CFCB /* feedly-2-changestatuses in Resources */,
|
||||||
51D5875A227F630B00900287 /* tags_delete.json in Resources */,
|
51D5875A227F630B00900287 /* tags_delete.json in Resources */,
|
||||||
9EC804E7236C1BA60057CFCB /* feedly-3-changestatusesagain in Resources */,
|
9EC804E7236C1BA60057CFCB /* feedly-3-changestatusesagain in Resources */,
|
||||||
|
9E79F7742395C9F00031DB98 /* feedly-add-new-feed in Resources */,
|
||||||
9EC804EF236C20DD0057CFCB /* feedly_macintosh_initial.json in Resources */,
|
9EC804EF236C20DD0057CFCB /* feedly_macintosh_initial.json in Resources */,
|
||||||
5165D71722821C2400D9D53D /* taggings_add.json in Resources */,
|
5165D71722821C2400D9D53D /* taggings_add.json in Resources */,
|
||||||
5165D71622821C2400D9D53D /* taggings_delete.json in Resources */,
|
5165D71622821C2400D9D53D /* taggings_delete.json in Resources */,
|
||||||
@ -974,6 +985,7 @@
|
|||||||
514BF5202391B0DB00902FE8 /* SingleArticleFetcher.swift in Sources */,
|
514BF5202391B0DB00902FE8 /* SingleArticleFetcher.swift in Sources */,
|
||||||
9EC688EC232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift in Sources */,
|
9EC688EC232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift in Sources */,
|
||||||
8469F81C1F6DD15E0084783E /* Account.swift in Sources */,
|
8469F81C1F6DD15E0084783E /* Account.swift in Sources */,
|
||||||
|
9EA643D5239306AC0018A28C /* FeedlyFeedsSearchResponse.swift in Sources */,
|
||||||
9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */,
|
9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */,
|
||||||
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */,
|
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */,
|
||||||
3B826DAF2385C81C00FC1ADB /* FeedWranglerGenericResult.swift in Sources */,
|
3B826DAF2385C81C00FC1ADB /* FeedWranglerGenericResult.swift in Sources */,
|
||||||
@ -981,6 +993,7 @@
|
|||||||
9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */,
|
9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */,
|
||||||
51E5959B228C781500FCC42B /* FeedbinStarredEntry.swift in Sources */,
|
51E5959B228C781500FCC42B /* FeedbinStarredEntry.swift in Sources */,
|
||||||
846E77451F6EF9B900A165E2 /* Container.swift in Sources */,
|
846E77451F6EF9B900A165E2 /* Container.swift in Sources */,
|
||||||
|
9EA643D3239305680018A28C /* FeedlySearchOperation.swift in Sources */,
|
||||||
9E1D15532334304B00F4944C /* FeedlyGetStreamContentsOperation.swift in Sources */,
|
9E1D15532334304B00F4944C /* FeedlyGetStreamContentsOperation.swift in Sources */,
|
||||||
9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */,
|
9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */,
|
||||||
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
|
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
|
||||||
@ -1088,6 +1101,7 @@
|
|||||||
9E489E912360ED30004372EE /* FeedlyOrganiseParsedItemsByFeedOperationTests.swift in Sources */,
|
9E489E912360ED30004372EE /* FeedlyOrganiseParsedItemsByFeedOperationTests.swift in Sources */,
|
||||||
9E0260CB236FF99A00D122D3 /* FeedlyRefreshAccessTokenOperationTests.swift in Sources */,
|
9E0260CB236FF99A00D122D3 /* FeedlyRefreshAccessTokenOperationTests.swift in Sources */,
|
||||||
9E1FF8622368219B00834C24 /* TestGetPagedStreamIdsService.swift in Sources */,
|
9E1FF8622368219B00834C24 /* TestGetPagedStreamIdsService.swift in Sources */,
|
||||||
|
9EA643D923945CE00018A28C /* FeedlyAddNewFeedOperationTests.swift in Sources */,
|
||||||
9E7F88AC235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift in Sources */,
|
9E7F88AC235EDDC2009AB9DF /* FeedlyCreateFeedsForCollectionFoldersOperationTests.swift in Sources */,
|
||||||
9E03C11E235D976500FB6D9E /* FeedlyGetCollectionsOperationTests.swift in Sources */,
|
9E03C11E235D976500FB6D9E /* FeedlyGetCollectionsOperationTests.swift in Sources */,
|
||||||
9E85C8E62366FED600D0F1F7 /* TestGetStreamContentsService.swift in Sources */,
|
9E85C8E62366FED600D0F1F7 /* TestGetStreamContentsService.swift in Sources */,
|
||||||
|
@ -0,0 +1,334 @@
|
|||||||
|
//
|
||||||
|
// FeedlyAddNewFeedOperationTests.swift
|
||||||
|
// AccountTests
|
||||||
|
//
|
||||||
|
// Created by Kiel Gillard on 2/12/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import Account
|
||||||
|
import RSWeb
|
||||||
|
|
||||||
|
class FeedlyAddNewFeedOperationTests: 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var transport = TestTransport()
|
||||||
|
lazy var caller: FeedlyAPICaller = {
|
||||||
|
let caller = FeedlyAPICaller(transport: transport, api: .sandbox)
|
||||||
|
caller.credentials = support.accessToken
|
||||||
|
return caller
|
||||||
|
}()
|
||||||
|
|
||||||
|
private func getFolderByLoadingInitialContent() -> Folder? {
|
||||||
|
let subdirectory = "feedly-add-new-feed"
|
||||||
|
let provider = InitialMockResponseProvider(findingMocksIn: subdirectory)
|
||||||
|
|
||||||
|
transport.mockResponseFileUrlProvider = provider
|
||||||
|
let getCollections = FeedlyGetCollectionsOperation(service: caller, log: support.log)
|
||||||
|
|
||||||
|
let mirrorCollectionsAsFolders = FeedlyMirrorCollectionsAsFoldersOperation(account: account, collectionsProvider: getCollections, log: support.log)
|
||||||
|
mirrorCollectionsAsFolders.addDependency(getCollections)
|
||||||
|
|
||||||
|
let createFolders = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: mirrorCollectionsAsFolders, log: support.log)
|
||||||
|
createFolders.addDependency(mirrorCollectionsAsFolders)
|
||||||
|
|
||||||
|
let completionExpectation = expectation(description: "Did Finish")
|
||||||
|
createFolders.completionBlock = {
|
||||||
|
completionExpectation.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
OperationQueue.main.addOperations([getCollections, mirrorCollectionsAsFolders, createFolders], waitUntilFinished: false)
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 2)
|
||||||
|
|
||||||
|
support.checkFoldersAndFeeds(in: account, againstCollectionsAndFeedsInJSONNamed: "emptyCollections", subdirectory: subdirectory)
|
||||||
|
|
||||||
|
guard let folder = account.folders?.first else {
|
||||||
|
XCTFail("Unable to load test folder to add a feed into.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(folder.topLevelWebFeeds.count, 0)
|
||||||
|
|
||||||
|
return folder
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectationForCompletion(of progress: DownloadProgress) -> XCTestExpectation {
|
||||||
|
return expectation(forNotification: .DownloadProgressDidChange, object: progress) { notification -> Bool in
|
||||||
|
guard let progress = notification.object as? DownloadProgress else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// We want to assert the progress completes.
|
||||||
|
if progress.isComplete {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchUrl = "https://macrumors.com"
|
||||||
|
|
||||||
|
func testCancel() {
|
||||||
|
guard let folder = getFolderByLoadingInitialContent() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let progress = DownloadProgress(numberOfTasks: 0)
|
||||||
|
let _ = expectationForCompletion(of: progress)
|
||||||
|
|
||||||
|
let addNewFeed = try! FeedlyAddNewFeedOperation(account: account,
|
||||||
|
credentials: support.accessToken,
|
||||||
|
url: searchUrl,
|
||||||
|
feedName: nil,
|
||||||
|
searchService: caller,
|
||||||
|
addToCollectionService: caller,
|
||||||
|
syncUnreadIdsService: caller,
|
||||||
|
getStreamContentsService: caller,
|
||||||
|
container: folder,
|
||||||
|
progress: progress,
|
||||||
|
log: support.log)
|
||||||
|
|
||||||
|
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
|
||||||
|
let completionExpectation = expectation(description: "Did Finish")
|
||||||
|
addNewFeed.completionBlock = {
|
||||||
|
completionExpectation.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
OperationQueue.main.addOperation(addNewFeed)
|
||||||
|
|
||||||
|
XCTAssert(progress.numberRemaining > 0)
|
||||||
|
|
||||||
|
addNewFeed.cancel()
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 2)
|
||||||
|
|
||||||
|
XCTAssert(progress.isComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAddNewFeedSuccess() {
|
||||||
|
guard let folder = getFolderByLoadingInitialContent() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let progress = DownloadProgress(numberOfTasks: 0)
|
||||||
|
let _ = expectationForCompletion(of: progress)
|
||||||
|
|
||||||
|
let subdirectory = "feedly-add-new-feed"
|
||||||
|
let searchUrl = self.searchUrl
|
||||||
|
let provider = MockResponseProvider(findingMocksIn: subdirectory)
|
||||||
|
provider.searchQueryHandler = { query in
|
||||||
|
XCTAssertEqual(query, searchUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.mockResponseFileUrlProvider = provider
|
||||||
|
|
||||||
|
let addNewFeed = try! FeedlyAddNewFeedOperation(account: account,
|
||||||
|
credentials: support.accessToken,
|
||||||
|
url: searchUrl,
|
||||||
|
feedName: nil,
|
||||||
|
searchService: caller,
|
||||||
|
addToCollectionService: caller,
|
||||||
|
syncUnreadIdsService: caller,
|
||||||
|
getStreamContentsService: caller,
|
||||||
|
container: folder,
|
||||||
|
progress: progress,
|
||||||
|
log: support.log)
|
||||||
|
|
||||||
|
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
|
||||||
|
let completionExpectation = expectation(description: "Did Finish")
|
||||||
|
addNewFeed.completionBlock = {
|
||||||
|
completionExpectation.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
OperationQueue.main.addOperation(addNewFeed)
|
||||||
|
|
||||||
|
XCTAssert(progress.numberRemaining > 0)
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 2)
|
||||||
|
|
||||||
|
XCTAssert(progress.isComplete)
|
||||||
|
|
||||||
|
support.checkArticles(in: account, againstItemsInStreamInJSONNamed: "feedStream", subdirectory: subdirectory)
|
||||||
|
support.checkUnreadStatuses(in: account, againstIdsInStreamInJSONNamed: "unreadIds", subdirectory: subdirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestFeedlyAddFeedToCollectionService: FeedlyAddFeedToCollectionService {
|
||||||
|
var mockResult: Result<[FeedlyFeed], Error>?
|
||||||
|
var addFeedExpectation: XCTestExpectation?
|
||||||
|
var parameterTester: ((FeedlyFeedResourceId, String?, String) -> ())?
|
||||||
|
|
||||||
|
func addFeed(with feedId: FeedlyFeedResourceId, title: String?, toCollectionWith collectionId: String, completionHandler: @escaping (Result<[FeedlyFeed], Error>) -> ()) {
|
||||||
|
guard let result = mockResult else {
|
||||||
|
XCTFail("Missing mock result. Test may time out because the completion will not be called.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parameterTester?(feedId, title, collectionId)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completionHandler(result)
|
||||||
|
self.addFeedExpectation?.fulfill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAddNewFeedFailure() {
|
||||||
|
guard let folder = getFolderByLoadingInitialContent() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let progress = DownloadProgress(numberOfTasks: 0)
|
||||||
|
let _ = expectationForCompletion(of: progress)
|
||||||
|
|
||||||
|
let subdirectory = "feedly-add-new-feed"
|
||||||
|
let searchUrl = self.searchUrl
|
||||||
|
let feedName = "MacRumours with a \"u\" because I am Australian"
|
||||||
|
let provider = MockResponseProvider(findingMocksIn: subdirectory)
|
||||||
|
provider.searchQueryHandler = { query in
|
||||||
|
XCTAssertEqual(query, searchUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.mockResponseFileUrlProvider = provider
|
||||||
|
|
||||||
|
let service = TestFeedlyAddFeedToCollectionService()
|
||||||
|
service.mockResult = .failure(URLError(.timedOut))
|
||||||
|
service.addFeedExpectation = expectation(description: "Add New Feed Called")
|
||||||
|
service.parameterTester = { feedResource, title, collectionId in
|
||||||
|
XCTAssertEqual(feedResource.id, "feed/http://feeds.macrumors.com/MacRumors-All")
|
||||||
|
XCTAssertEqual(title, feedName)
|
||||||
|
XCTAssertEqual(collectionId, folder.externalID)
|
||||||
|
}
|
||||||
|
|
||||||
|
let addNewFeed = try! FeedlyAddNewFeedOperation(account: account,
|
||||||
|
credentials: support.accessToken,
|
||||||
|
url: searchUrl,
|
||||||
|
feedName: feedName,
|
||||||
|
searchService: caller,
|
||||||
|
addToCollectionService: service,
|
||||||
|
syncUnreadIdsService: caller,
|
||||||
|
getStreamContentsService: caller,
|
||||||
|
container: folder,
|
||||||
|
progress: progress,
|
||||||
|
log: support.log)
|
||||||
|
|
||||||
|
// If this expectation is not fulfilled, the operation is not calling `didFinish`.
|
||||||
|
let completionExpectation = expectation(description: "Did Finish")
|
||||||
|
addNewFeed.completionBlock = {
|
||||||
|
completionExpectation.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
OperationQueue.main.addOperation(addNewFeed)
|
||||||
|
|
||||||
|
XCTAssert(progress.numberRemaining > 0)
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 2)
|
||||||
|
|
||||||
|
XCTAssert(progress.isComplete)
|
||||||
|
|
||||||
|
XCTAssertEqual(folder.topLevelWebFeeds.count, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InitialMockResponseProvider: TestTransportMockResponseProviding {
|
||||||
|
|
||||||
|
let subdirectory: String
|
||||||
|
|
||||||
|
init(findingMocksIn subdirectory: String) {
|
||||||
|
self.subdirectory = subdirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockResponseFileUrl(for components: URLComponents) -> URL? {
|
||||||
|
let bundle = Bundle(for: type(of: self))
|
||||||
|
|
||||||
|
// When we get a request for the initial collections content, use these results.
|
||||||
|
if components.path.contains("/v3/collections") {
|
||||||
|
return bundle.url(forResource: "emptyCollections", withExtension: "json", subdirectory: subdirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class MockResponseProvider: TestTransportMockResponseProviding {
|
||||||
|
|
||||||
|
let subdirectory: String
|
||||||
|
|
||||||
|
init(findingMocksIn subdirectory: String) {
|
||||||
|
self.subdirectory = subdirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchQueryHandler: ((String) -> ())?
|
||||||
|
|
||||||
|
func mockResponseFileUrl(for components: URLComponents) -> URL? {
|
||||||
|
let bundle = Bundle(for: type(of: self))
|
||||||
|
|
||||||
|
let queryItems = components.queryItems ?? []
|
||||||
|
let query = queryItems.first(where: { $0.name.contains("query") })?.value
|
||||||
|
|
||||||
|
// When we get the search request, use these results.
|
||||||
|
if components.path.contains("search/feeds") {
|
||||||
|
if let query = query {
|
||||||
|
searchQueryHandler?(query)
|
||||||
|
} else {
|
||||||
|
XCTFail("`query` missing from URL query items in search request: \(components)")
|
||||||
|
}
|
||||||
|
return bundle.url(forResource: "searchResults", withExtension: "json", subdirectory: subdirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we get a request to add a feed, use these results.
|
||||||
|
if components.path.contains("/v3/collections") && components.path.contains("/feeds") {
|
||||||
|
return bundle.url(forResource: "putFeed", withExtension: "json", subdirectory: subdirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we get a request for the initial collections content, use these results.
|
||||||
|
if components.path.contains("/v3/collections") {
|
||||||
|
return bundle.url(forResource: "collections", withExtension: "json", subdirectory: subdirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
let continuation = queryItems.first(where: { $0.name.contains("continuation") })?.value
|
||||||
|
|
||||||
|
// When we get a request for unread article ids, use these results.
|
||||||
|
if components.path.contains("streams/ids") {
|
||||||
|
|
||||||
|
// if there is a continuation, return the page for it
|
||||||
|
if let continuation = continuation, let data = continuation.data(using: .utf8) {
|
||||||
|
let base64 = data.base64EncodedString() // at least base64 can be used as a path component.
|
||||||
|
return bundle.url(forResource: "unreadIds@\(base64)", withExtension: "json", subdirectory: subdirectory)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// return first page
|
||||||
|
return bundle.url(forResource: "unreadIds", withExtension: "json", subdirectory: subdirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we get a request for the contents of the feed stream, use these results.
|
||||||
|
if components.path.contains("streams/contents") {
|
||||||
|
|
||||||
|
// if there is a continuation, return the page for it
|
||||||
|
if let continuation = continuation, let data = continuation.data(using: .utf8) {
|
||||||
|
let base64 = data.base64EncodedString() // at least base64 can be used as a path component.
|
||||||
|
return bundle.url(forResource: "feedStream@\(base64)", withExtension: "json", subdirectory: subdirectory)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// return first page
|
||||||
|
return bundle.url(forResource: "feedStream", withExtension: "json", subdirectory: subdirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
@ -32,13 +32,13 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
|||||||
|
|
||||||
func testAddFeeds() {
|
func testAddFeeds() {
|
||||||
let feedsForFolderOne = [
|
let feedsForFolderOne = [
|
||||||
FeedlyFeed(feedId: "feed/1", id: "feed/1", title: "Feed One", updated: nil, website: nil),
|
FeedlyFeed(id: "feed/1", title: "Feed One", updated: nil, website: nil),
|
||||||
FeedlyFeed(feedId: "feed/2", id: "feed/2", title: "Feed Two", updated: nil, website: nil)
|
FeedlyFeed(id: "feed/2", title: "Feed Two", updated: nil, website: nil)
|
||||||
]
|
]
|
||||||
|
|
||||||
let feedsForFolderTwo = [
|
let feedsForFolderTwo = [
|
||||||
FeedlyFeed(feedId: "feed/1", id: "feed/1", title: "Feed One", updated: nil, website: nil),
|
FeedlyFeed(id: "feed/1", title: "Feed One", updated: nil, website: nil),
|
||||||
FeedlyFeed(feedId: "feed/3", id: "feed/3", title: "Feed Three", updated: nil, website: nil),
|
FeedlyFeed(id: "feed/3", title: "Feed Three", updated: nil, website: nil),
|
||||||
]
|
]
|
||||||
|
|
||||||
let folderOne: (name: String, id: String) = ("FolderOne", "folder/1")
|
let folderOne: (name: String, id: String) = ("FolderOne", "folder/1")
|
||||||
@ -66,7 +66,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
|||||||
|
|
||||||
let feedIds = Set([feedsForFolderOne, feedsForFolderTwo]
|
let feedIds = Set([feedsForFolderOne, feedsForFolderTwo]
|
||||||
.flatMap { $0 }
|
.flatMap { $0 }
|
||||||
.map { $0.feedId })
|
.map { $0.id })
|
||||||
|
|
||||||
let feedTitles = Set([feedsForFolderOne, feedsForFolderTwo]
|
let feedTitles = Set([feedsForFolderOne, feedsForFolderTwo]
|
||||||
.flatMap { $0 }
|
.flatMap { $0 }
|
||||||
@ -85,7 +85,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
|||||||
let expectedFolderAndFeedIds = namesAndFeeds
|
let expectedFolderAndFeedIds = namesAndFeeds
|
||||||
.sorted { $0.0.id < $1.0.id }
|
.sorted { $0.0.id < $1.0.id }
|
||||||
.map { folder, feeds -> [String: [String]] in
|
.map { folder, feeds -> [String: [String]] in
|
||||||
return [folder.id: feeds.map { $0.feedId }.sorted(by: <)]
|
return [folder.id: feeds.map { $0.id }.sorted(by: <)]
|
||||||
}
|
}
|
||||||
|
|
||||||
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
||||||
@ -100,16 +100,16 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
|||||||
func testRemoveFeeds() {
|
func testRemoveFeeds() {
|
||||||
let folderOne: (name: String, id: String) = ("FolderOne", "folder/1")
|
let folderOne: (name: String, id: String) = ("FolderOne", "folder/1")
|
||||||
let folderTwo: (name: String, id: String) = ("FolderTwo", "folder/2")
|
let folderTwo: (name: String, id: String) = ("FolderTwo", "folder/2")
|
||||||
let feedToRemove = FeedlyFeed(feedId: "feed/1", id: "feed/1", title: "Feed One", updated: nil, website: nil)
|
let feedToRemove = FeedlyFeed(id: "feed/1", title: "Feed One", updated: nil, website: nil)
|
||||||
|
|
||||||
var feedsForFolderOne = [
|
var feedsForFolderOne = [
|
||||||
feedToRemove,
|
feedToRemove,
|
||||||
FeedlyFeed(feedId: "feed/2", id: "feed/2", title: "Feed Two", updated: nil, website: nil)
|
FeedlyFeed(id: "feed/2", title: "Feed Two", updated: nil, website: nil)
|
||||||
]
|
]
|
||||||
|
|
||||||
var feedsForFolderTwo = [
|
var feedsForFolderTwo = [
|
||||||
feedToRemove,
|
feedToRemove,
|
||||||
FeedlyFeed(feedId: "feed/3", id: "feed/3", title: "Feed Three", updated: nil, website: nil),
|
FeedlyFeed(id: "feed/3", title: "Feed Three", updated: nil, website: nil),
|
||||||
]
|
]
|
||||||
|
|
||||||
// Add initial content.
|
// Add initial content.
|
||||||
@ -159,7 +159,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
|||||||
|
|
||||||
let feedIds = Set([feedsForFolderOne, feedsForFolderTwo]
|
let feedIds = Set([feedsForFolderOne, feedsForFolderTwo]
|
||||||
.flatMap { $0 }
|
.flatMap { $0 }
|
||||||
.map { $0.feedId })
|
.map { $0.id })
|
||||||
|
|
||||||
let feedTitles = Set([feedsForFolderOne, feedsForFolderTwo]
|
let feedTitles = Set([feedsForFolderOne, feedsForFolderTwo]
|
||||||
.flatMap { $0 }
|
.flatMap { $0 }
|
||||||
@ -181,7 +181,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
|||||||
let expectedFolderAndFeedIds = namesAndFeeds
|
let expectedFolderAndFeedIds = namesAndFeeds
|
||||||
.sorted { $0.0.id < $1.0.id }
|
.sorted { $0.0.id < $1.0.id }
|
||||||
.map { folder, feeds -> [String: [String]] in
|
.map { folder, feeds -> [String: [String]] in
|
||||||
return [folder.id: feeds.map { $0.feedId }.sorted(by: <)]
|
return [folder.id: feeds.map { $0.id }.sorted(by: <)]
|
||||||
}
|
}
|
||||||
|
|
||||||
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
||||||
|
@ -110,13 +110,13 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
|
|||||||
|
|
||||||
class CollectionsAndFeedsProvider: FeedlyCollectionProviding {
|
class CollectionsAndFeedsProvider: FeedlyCollectionProviding {
|
||||||
var feedsForCollectionOne = [
|
var feedsForCollectionOne = [
|
||||||
FeedlyFeed(feedId: "feed/1", id: "feed/1", title: "Feed One", updated: nil, website: nil),
|
FeedlyFeed(id: "feed/1", title: "Feed One", updated: nil, website: nil),
|
||||||
FeedlyFeed(feedId: "feed/2", id: "feed/2", title: "Feed Two", updated: nil, website: nil)
|
FeedlyFeed(id: "feed/2", title: "Feed Two", updated: nil, website: nil)
|
||||||
]
|
]
|
||||||
|
|
||||||
var feedsForCollectionTwo = [
|
var feedsForCollectionTwo = [
|
||||||
FeedlyFeed(feedId: "feed/1", id: "feed/1", title: "Feed One", updated: nil, website: nil),
|
FeedlyFeed(id: "feed/1", title: "Feed One", updated: nil, website: nil),
|
||||||
FeedlyFeed(feedId: "feed/3", id: "feed/3", title: "Feed Three", updated: nil, website: nil),
|
FeedlyFeed(id: "feed/3", title: "Feed Three", updated: nil, website: nil),
|
||||||
]
|
]
|
||||||
|
|
||||||
var collections: [FeedlyCollection] {
|
var collections: [FeedlyCollection] {
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
[{"customizable":true,"feeds":[{"feedId":"feed/http://feeds.macrumors.com/MacRumors-All","id":"feed/http://feeds.macrumors.com/MacRumors-All","title":"MacRumors: Mac News and Rumors - All Stories","updated":1575325551366,"velocity":54.4,"subscribers":3,"website":"https://www.macrumors.com","language":"en","description":"Apple, iPhone, iPad, Mac News and Rumors"}],"label":"Mac","created":1575325095480,"enterprise":false,"numFeeds":1,"id":"user/c665fbfb-36e1-485a-918b-90d91f2ebd01/category/da60a29f-94ba-4856-b64f-2830d9859ee8"}]
|
@ -0,0 +1 @@
|
|||||||
|
[{"customizable":true,"feeds":[],"label":"Mac","created":1575325095480,"enterprise":false,"numFeeds":0,"id":"user/c665fbfb-36e1-485a-918b-90d91f2ebd01/category/da60a29f-94ba-4856-b64f-2830d9859ee8"}]
|
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
[{"feedId":"feed/http://feeds.macrumors.com/MacRumors-All","id":"feed/http://feeds.macrumors.com/MacRumors-All","title":"MacRumors: Mac News and Rumors - All Stories","updated":1575318341631,"velocity":54.4,"subscribers":2,"website":"https://www.macrumors.com","language":"en","description":"Apple, iPhone, iPad, Mac News and Rumors"}]
|
@ -0,0 +1 @@
|
|||||||
|
{"results":[{"feedId":"feed/http://feeds.macrumors.com/MacRumors-All","score":1.1779602,"lastUpdated":1535409540000,"coverage":0.0,"averageReadTime":0.0,"coverageScore":0.0,"tagCounts":{"ios":1},"totalTagCount":1,"scheme":"u:b:o","id":"feed/http://feeds.macrumors.com/MacRumors-All","title":"MacRumors: Mac News and Rumors - All Stories","updated":1535409540000,"velocity":54.4,"subscribers":3,"website":"https://www.macrumors.com","language":"en","description":"Apple, iPhone, iPad, Mac News and Rumors","deliciousTags":["ios"]}],"queryType":"url","related":["ios"],"scheme":"u:b:o"}
|
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"ids": [
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec8b84b06:105f8:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec84a47ff:10035:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec81358b5:fd6a:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec7dc2863:f9eb:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec7a41b17:f62e:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec76d1e3f:ee16:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec76d1e3f:ee15:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec735cb51:eb40:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec6fecd41:e87d:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec6fecd41:e87c:40944486",
|
||||||
|
"i5dK0N4LYkCdW27N2hxMjfOjdU7G+cHFwqWTCuim9HU=_16ec6c7d2b2:e5c8:40944486"
|
||||||
|
]
|
||||||
|
}
|
@ -226,6 +226,55 @@ final class FeedlyAPICaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeFeed(_ feedId: String, fromCollectionWith collectionId: String, completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
||||||
|
guard let accessToken = credentials?.secret else {
|
||||||
|
return DispatchQueue.main.async {
|
||||||
|
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let encodedCollectionId = encodeForURLPath(collectionId) else {
|
||||||
|
return DispatchQueue.main.async {
|
||||||
|
completionHandler(.failure(FeedlyAccountDelegateError.unexpectedResourceId(collectionId)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let encodedFeedId = encodeForURLPath(feedId) else {
|
||||||
|
return DispatchQueue.main.async {
|
||||||
|
completionHandler(.failure(FeedlyAccountDelegateError.unexpectedResourceId(feedId)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var components = baseUrlComponents
|
||||||
|
components.percentEncodedPath = "/v3/collections/\(encodedCollectionId)/feeds/\(encodedFeedId)"
|
||||||
|
|
||||||
|
guard let url = components.url else {
|
||||||
|
fatalError("\(components) does not produce a valid URL.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "DELETE"
|
||||||
|
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||||
|
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||||
|
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: [FeedlyFeed].self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let httpResponse, _):
|
||||||
|
if httpResponse.statusCode == 200 {
|
||||||
|
completionHandler(.success(()))
|
||||||
|
} else {
|
||||||
|
completionHandler(.failure(URLError(.cannotDecodeContentData)))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FeedlyAPICaller: FeedlyAddFeedToCollectionService {
|
||||||
|
|
||||||
func addFeed(with feedId: FeedlyFeedResourceId, title: String? = nil, toCollectionWith collectionId: String, completionHandler: @escaping (Result<[FeedlyFeed], Error>) -> ()) {
|
func addFeed(with feedId: FeedlyFeedResourceId, title: String? = nil, toCollectionWith collectionId: String, completionHandler: @escaping (Result<[FeedlyFeed], Error>) -> ()) {
|
||||||
guard let accessToken = credentials?.secret else {
|
guard let accessToken = credentials?.secret else {
|
||||||
return DispatchQueue.main.async {
|
return DispatchQueue.main.async {
|
||||||
@ -278,52 +327,6 @@ final class FeedlyAPICaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFeed(_ feedId: String, fromCollectionWith collectionId: String, completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
|
||||||
guard let accessToken = credentials?.secret else {
|
|
||||||
return DispatchQueue.main.async {
|
|
||||||
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let encodedCollectionId = encodeForURLPath(collectionId) else {
|
|
||||||
return DispatchQueue.main.async {
|
|
||||||
completionHandler(.failure(FeedlyAccountDelegateError.unexpectedResourceId(collectionId)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let encodedFeedId = encodeForURLPath(feedId) else {
|
|
||||||
return DispatchQueue.main.async {
|
|
||||||
completionHandler(.failure(FeedlyAccountDelegateError.unexpectedResourceId(feedId)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var components = baseUrlComponents
|
|
||||||
components.percentEncodedPath = "/v3/collections/\(encodedCollectionId)/feeds/\(encodedFeedId)"
|
|
||||||
|
|
||||||
guard let url = components.url else {
|
|
||||||
fatalError("\(components) does not produce a valid URL.")
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = URLRequest(url: url)
|
|
||||||
request.httpMethod = "DELETE"
|
|
||||||
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
|
||||||
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
|
||||||
|
|
||||||
transport.send(request: request, resultType: [FeedlyFeed].self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let httpResponse, _):
|
|
||||||
if httpResponse.statusCode == 200 {
|
|
||||||
completionHandler(.success(()))
|
|
||||||
} else {
|
|
||||||
completionHandler(.failure(URLError(.cannotDecodeContentData)))
|
|
||||||
}
|
|
||||||
case .failure(let error):
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FeedlyAPICaller: OAuthAuthorizationCodeGrantRequesting {
|
extension FeedlyAPICaller: OAuthAuthorizationCodeGrantRequesting {
|
||||||
@ -688,6 +691,44 @@ extension FeedlyAPICaller: FeedlyMarkArticlesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension FeedlyAPICaller: FeedlySearchService {
|
||||||
|
|
||||||
|
func getFeeds(for query: String, count: Int, locale: String, completionHandler: @escaping (Result<FeedlyFeedsSearchResponse, Error>) -> ()) {
|
||||||
|
|
||||||
|
var components = baseUrlComponents
|
||||||
|
components.path = "/v3/search/feeds"
|
||||||
|
|
||||||
|
components.queryItems = [
|
||||||
|
URLQueryItem(name: "query", value: query),
|
||||||
|
URLQueryItem(name: "count", value: String(count)),
|
||||||
|
URLQueryItem(name: "locale", value: locale)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
guard let url = components.url else {
|
||||||
|
fatalError("\(components) does not produce a valid URL.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "GET"
|
||||||
|
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||||
|
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: FeedlyFeedsSearchResponse.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let (_, searchResponse)):
|
||||||
|
if let response = searchResponse {
|
||||||
|
completionHandler(.success(response))
|
||||||
|
} else {
|
||||||
|
completionHandler(.failure(URLError(.cannotDecodeContentData)))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension FeedlyAPICaller: FeedlyLogoutService {
|
extension FeedlyAPICaller: FeedlyLogoutService {
|
||||||
|
|
||||||
func logout(completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
func logout(completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
||||||
|
@ -292,12 +292,14 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
throw FeedlyAccountDelegateError.notLoggedIn
|
throw FeedlyAccountDelegateError.notLoggedIn
|
||||||
}
|
}
|
||||||
|
|
||||||
let resource = FeedlyFeedResourceId(url: url)
|
|
||||||
let addNewFeed = try FeedlyAddNewFeedOperation(account: account,
|
let addNewFeed = try FeedlyAddNewFeedOperation(account: account,
|
||||||
credentials: credentials,
|
credentials: credentials,
|
||||||
resource: resource,
|
url: url,
|
||||||
feedName: name,
|
feedName: name,
|
||||||
caller: caller,
|
searchService: caller,
|
||||||
|
addToCollectionService: caller,
|
||||||
|
syncUnreadIdsService: caller,
|
||||||
|
getStreamContentsService: caller,
|
||||||
container: container,
|
container: container,
|
||||||
progress: refreshProgress,
|
progress: refreshProgress,
|
||||||
log: log)
|
log: log)
|
||||||
@ -353,7 +355,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
let addExistingFeed = try FeedlyAddExistingFeedOperation(account: account,
|
let addExistingFeed = try FeedlyAddExistingFeedOperation(account: account,
|
||||||
credentials: credentials,
|
credentials: credentials,
|
||||||
resource: resource,
|
resource: resource,
|
||||||
caller: caller,
|
service: caller,
|
||||||
container: container,
|
container: container,
|
||||||
progress: refreshProgress,
|
progress: refreshProgress,
|
||||||
log: log)
|
log: log)
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct FeedlyFeed: Codable {
|
struct FeedlyFeed: Codable {
|
||||||
var feedId: String
|
|
||||||
var id: String
|
var id: String
|
||||||
var title: String?
|
var title: String?
|
||||||
var updated: Date?
|
var updated: Date?
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// FeedlyFeedsSearchResponse.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Kiel Gillard on 1/12/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct FeedlyFeedsSearchResponse: Decodable {
|
||||||
|
|
||||||
|
struct Feed: Decodable {
|
||||||
|
var title: String
|
||||||
|
var feedId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
var results: [Feed]
|
||||||
|
}
|
@ -22,6 +22,10 @@ struct FeedlyFeedResourceId: FeedlyResourceId {
|
|||||||
/// The location of the kind of resource a concrete type represents.
|
/// The location of the kind of resource a concrete type represents.
|
||||||
/// If the conrete type cannot strip the resource type from the Id, it should just return the Id
|
/// If the conrete type cannot strip the resource type from the Id, it should just return the Id
|
||||||
/// since the Id is a legitimate URL.
|
/// since the Id is a legitimate URL.
|
||||||
|
/// This is basically assuming Feedly prefixes source feed URLs with `feed/`.
|
||||||
|
/// It is not documented as such and could potentially change.
|
||||||
|
/// Feedly does not include the source feed URL as a separate field.
|
||||||
|
/// See https://developer.feedly.com/v3/feeds/#get-the-metadata-about-a-specific-feed
|
||||||
var url: String {
|
var url: String {
|
||||||
if let range = id.range(of: "feed/"), range.lowerBound == id.startIndex {
|
if let range = id.range(of: "feed/"), range.lowerBound == id.startIndex {
|
||||||
var mutant = id
|
var mutant = id
|
||||||
@ -57,6 +61,13 @@ struct FeedlyCategoryResourceId: FeedlyResourceId {
|
|||||||
let id = "user/\(userId)/category/global.all"
|
let id = "user/\(userId)/category/global.all"
|
||||||
return FeedlyCategoryResourceId(id: id)
|
return FeedlyCategoryResourceId(id: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All articles from all the feeds the user loves most.
|
||||||
|
static func mustRead(for userId: String) -> FeedlyCategoryResourceId {
|
||||||
|
// https://developer.feedly.com/cloud/#global-resource-ids
|
||||||
|
let id = "user/\(userId)/category/global.must"
|
||||||
|
return FeedlyCategoryResourceId(id: id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
|
|||||||
|
|
||||||
var addCompletionHandler: ((Result<Void, Error>) -> ())?
|
var addCompletionHandler: ((Result<Void, Error>) -> ())?
|
||||||
|
|
||||||
init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, caller: FeedlyAPICaller, container: Container, progress: DownloadProgress, log: OSLog) throws {
|
init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, log: OSLog) throws {
|
||||||
|
|
||||||
let validator = FeedlyFeedContainerValidator(container: container, userId: credentials.username)
|
let validator = FeedlyFeedContainerValidator(container: container, userId: credentials.username)
|
||||||
let (folder, collectionId) = try validator.getValidContainer()
|
let (folder, collectionId) = try validator.getValidContainer()
|
||||||
@ -27,7 +27,7 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
|
|||||||
|
|
||||||
self.downloadProgress = progress
|
self.downloadProgress = progress
|
||||||
|
|
||||||
let addRequest = FeedlyAddFeedToCollectionOperation(account: account, folder: folder, feedResource: resource, feedName: nil, collectionId: collectionId, caller: caller)
|
let addRequest = FeedlyAddFeedToCollectionOperation(account: account, folder: folder, feedResource: resource, feedName: nil, collectionId: collectionId, service: service)
|
||||||
addRequest.delegate = self
|
addRequest.delegate = self
|
||||||
addRequest.downloadProgress = progress
|
addRequest.downloadProgress = progress
|
||||||
self.operationQueue.addOperation(addRequest)
|
self.operationQueue.addOperation(addRequest)
|
||||||
|
@ -8,21 +8,25 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
protocol FeedlyAddFeedToCollectionService {
|
||||||
|
func addFeed(with feedId: FeedlyFeedResourceId, title: String?, toCollectionWith collectionId: String, completionHandler: @escaping (Result<[FeedlyFeed], Error>) -> ())
|
||||||
|
}
|
||||||
|
|
||||||
final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding, FeedlyResourceProviding {
|
final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding, FeedlyResourceProviding {
|
||||||
let feedName: String?
|
let feedName: String?
|
||||||
let collectionId: String
|
let collectionId: String
|
||||||
let caller: FeedlyAPICaller
|
let service: FeedlyAddFeedToCollectionService
|
||||||
let account: Account
|
let account: Account
|
||||||
let folder: Folder
|
let folder: Folder
|
||||||
let feedResource: FeedlyFeedResourceId
|
let feedResource: FeedlyFeedResourceId
|
||||||
|
|
||||||
init(account: Account, folder: Folder, feedResource: FeedlyFeedResourceId, feedName: String? = nil, collectionId: String, caller: FeedlyAPICaller) {
|
init(account: Account, folder: Folder, feedResource: FeedlyFeedResourceId, feedName: String? = nil, collectionId: String, service: FeedlyAddFeedToCollectionService) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
self.feedResource = feedResource
|
self.feedResource = feedResource
|
||||||
self.feedName = feedName
|
self.feedName = feedName
|
||||||
self.collectionId = collectionId
|
self.collectionId = collectionId
|
||||||
self.caller = caller
|
self.service = service
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]()
|
private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]()
|
||||||
@ -36,7 +40,7 @@ final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndF
|
|||||||
return didFinish()
|
return didFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
caller.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionId) { [weak self] result in
|
service.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionId) { [weak self] result in
|
||||||
guard let self = self else {
|
guard let self = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -52,7 +56,7 @@ final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndF
|
|||||||
case .success(let feedlyFeeds):
|
case .success(let feedlyFeeds):
|
||||||
feedsAndFolders = [(feedlyFeeds, folder)]
|
feedsAndFolders = [(feedlyFeeds, folder)]
|
||||||
|
|
||||||
let feedsWithCreatedFeedId = feedlyFeeds.filter { $0.feedId == resource.id }
|
let feedsWithCreatedFeedId = feedlyFeeds.filter { $0.id == resource.id }
|
||||||
|
|
||||||
if feedsWithCreatedFeedId.isEmpty {
|
if feedsWithCreatedFeedId.isEmpty {
|
||||||
didFinish(AccountError.createErrorNotFound)
|
didFinish(AccountError.createErrorNotFound)
|
||||||
|
@ -10,58 +10,56 @@ import Foundation
|
|||||||
import os.log
|
import os.log
|
||||||
import RSWeb
|
import RSWeb
|
||||||
|
|
||||||
class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate {
|
class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlySearchOperationDelegate, FeedlyCheckpointOperationDelegate {
|
||||||
private let operationQueue: OperationQueue
|
private let operationQueue: OperationQueue
|
||||||
private let folder: Folder
|
private let folder: Folder
|
||||||
private let feedResourceId: FeedlyFeedResourceId
|
private let collectionId: String
|
||||||
|
private let url: String
|
||||||
|
private let account: Account
|
||||||
|
private let credentials: Credentials
|
||||||
|
private let feedName: String?
|
||||||
|
private let addToCollectionService: FeedlyAddFeedToCollectionService
|
||||||
|
private let syncUnreadIdsService: FeedlyGetStreamIdsService
|
||||||
|
private let getStreamContentsService: FeedlyGetStreamContentsService
|
||||||
|
private let log: OSLog
|
||||||
|
|
||||||
var addCompletionHandler: ((Result<WebFeed, Error>) -> ())?
|
var addCompletionHandler: ((Result<WebFeed, Error>) -> ())?
|
||||||
|
|
||||||
init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, feedName: String?, caller: FeedlyAPICaller, container: Container, progress: DownloadProgress, log: OSLog) throws {
|
init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, container: Container, progress: DownloadProgress, log: OSLog) throws {
|
||||||
|
|
||||||
let validator = FeedlyFeedContainerValidator(container: container, userId: credentials.username)
|
let validator = FeedlyFeedContainerValidator(container: container, userId: credentials.username)
|
||||||
let (folder, collectionId) = try validator.getValidContainer()
|
(self.folder, self.collectionId) = try validator.getValidContainer()
|
||||||
|
|
||||||
self.folder = folder
|
self.url = url
|
||||||
self.feedResourceId = resource
|
|
||||||
self.operationQueue = OperationQueue()
|
self.operationQueue = OperationQueue()
|
||||||
self.operationQueue.isSuspended = true
|
self.operationQueue.isSuspended = true
|
||||||
|
self.account = account
|
||||||
|
self.credentials = credentials
|
||||||
|
self.feedName = feedName
|
||||||
|
self.addToCollectionService = addToCollectionService
|
||||||
|
self.syncUnreadIdsService = syncUnreadIdsService
|
||||||
|
self.getStreamContentsService = getStreamContentsService
|
||||||
|
self.log = log
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.downloadProgress = progress
|
self.downloadProgress = progress
|
||||||
|
|
||||||
let addRequest = FeedlyAddFeedToCollectionOperation(account: account, folder: folder, feedResource: resource, feedName: feedName, collectionId: collectionId, caller: caller)
|
let search = FeedlySearchOperation(query: url, locale: .current, service: searchService)
|
||||||
addRequest.delegate = self
|
search.delegate = self
|
||||||
addRequest.downloadProgress = progress
|
search.searchDelegate = self
|
||||||
self.operationQueue.addOperation(addRequest)
|
search.downloadProgress = progress
|
||||||
|
self.operationQueue.addOperation(search)
|
||||||
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
|
|
||||||
createFeeds.addDependency(addRequest)
|
|
||||||
createFeeds.downloadProgress = progress
|
|
||||||
self.operationQueue.addOperation(createFeeds)
|
|
||||||
|
|
||||||
let syncUnread = FeedlySyncUnreadStatusesOperation(account: account, credentials: credentials, service: caller, newerThan: nil, log: log)
|
|
||||||
syncUnread.addDependency(addRequest)
|
|
||||||
syncUnread.downloadProgress = progress
|
|
||||||
self.operationQueue.addOperation(syncUnread)
|
|
||||||
|
|
||||||
let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: resource, service: caller, newerThan: nil, log: log)
|
|
||||||
syncFeed.addDependency(syncUnread)
|
|
||||||
syncFeed.downloadProgress = progress
|
|
||||||
self.operationQueue.addOperation(syncFeed)
|
|
||||||
|
|
||||||
let finishOperation = FeedlyCheckpointOperation()
|
|
||||||
finishOperation.checkpointDelegate = self
|
|
||||||
finishOperation.downloadProgress = progress
|
|
||||||
finishOperation.addDependency(syncFeed)
|
|
||||||
self.operationQueue.addOperation(finishOperation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func cancel() {
|
override func cancel() {
|
||||||
operationQueue.cancelAllOperations()
|
operationQueue.cancelAllOperations()
|
||||||
super.cancel()
|
super.cancel()
|
||||||
|
|
||||||
didFinish()
|
didFinish()
|
||||||
|
|
||||||
|
// Operation should silently cancel.
|
||||||
|
addCompletionHandler = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func main() {
|
override func main() {
|
||||||
@ -71,6 +69,46 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
|||||||
operationQueue.isSuspended = false
|
operationQueue.isSuspended = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var feedResourceId: FeedlyFeedResourceId?
|
||||||
|
|
||||||
|
func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) {
|
||||||
|
guard !isCancelled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let first = response.results.first else {
|
||||||
|
return didFinish(AccountError.createErrorNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
let feedResourceId = FeedlyFeedResourceId(id: first.feedId)
|
||||||
|
self.feedResourceId = feedResourceId
|
||||||
|
|
||||||
|
let addRequest = FeedlyAddFeedToCollectionOperation(account: account, folder: folder, feedResource: feedResourceId, feedName: feedName, collectionId: collectionId, service: addToCollectionService)
|
||||||
|
addRequest.delegate = self
|
||||||
|
addRequest.downloadProgress = downloadProgress
|
||||||
|
self.operationQueue.addOperation(addRequest)
|
||||||
|
|
||||||
|
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
|
||||||
|
createFeeds.addDependency(addRequest)
|
||||||
|
createFeeds.downloadProgress = downloadProgress
|
||||||
|
self.operationQueue.addOperation(createFeeds)
|
||||||
|
|
||||||
|
let syncUnread = FeedlySyncUnreadStatusesOperation(account: account, credentials: credentials, service: syncUnreadIdsService, newerThan: nil, log: log)
|
||||||
|
syncUnread.addDependency(addRequest)
|
||||||
|
syncUnread.downloadProgress = downloadProgress
|
||||||
|
self.operationQueue.addOperation(syncUnread)
|
||||||
|
|
||||||
|
let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: feedResourceId, service: getStreamContentsService, newerThan: nil, log: log)
|
||||||
|
syncFeed.addDependency(syncUnread)
|
||||||
|
syncFeed.downloadProgress = downloadProgress
|
||||||
|
self.operationQueue.addOperation(syncFeed)
|
||||||
|
|
||||||
|
let finishOperation = FeedlyCheckpointOperation()
|
||||||
|
finishOperation.checkpointDelegate = self
|
||||||
|
finishOperation.downloadProgress = downloadProgress
|
||||||
|
finishOperation.addDependency(syncFeed)
|
||||||
|
self.operationQueue.addOperation(finishOperation)
|
||||||
|
}
|
||||||
|
|
||||||
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
|
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
|
||||||
addCompletionHandler?(.failure(error))
|
addCompletionHandler?(.failure(error))
|
||||||
addCompletionHandler = nil
|
addCompletionHandler = nil
|
||||||
@ -91,7 +129,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let feed = folder.existingWebFeed(withWebFeedID: feedResourceId.id) {
|
if let feedResource = feedResourceId, let feed = folder.existingWebFeed(withWebFeedID: feedResource.id) {
|
||||||
handler(.success(feed))
|
handler(.success(feed))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// FeedlySearchOperation.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Kiel Gillard on 1/12/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol FeedlySearchService: class {
|
||||||
|
func getFeeds(for query: String, count: Int, locale: String, completionHandler: @escaping (Result<FeedlyFeedsSearchResponse, Error>) -> ())
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol FeedlySearchOperationDelegate: class {
|
||||||
|
func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Single responsibility is to find one and only one feed for a given query (usually, a URL).
|
||||||
|
/// What happens when a feed is found for the URL is delegated to the `searchDelegate`.
|
||||||
|
class FeedlySearchOperation: FeedlyOperation {
|
||||||
|
let query: String
|
||||||
|
let locale: Locale
|
||||||
|
let searchService: FeedlySearchService
|
||||||
|
|
||||||
|
weak var searchDelegate: FeedlySearchOperationDelegate?
|
||||||
|
|
||||||
|
init(query: String, locale: Locale = .current, service: FeedlySearchService) {
|
||||||
|
self.query = query
|
||||||
|
self.locale = locale
|
||||||
|
self.searchService = service
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main() {
|
||||||
|
guard !isCancelled else {
|
||||||
|
didFinish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
searchService.getFeeds(for: query, count: 1, locale: locale.identifier) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let response):
|
||||||
|
assert(Thread.isMainThread)
|
||||||
|
self.searchDelegate?.feedlySearchOperation(self, didGet: response)
|
||||||
|
self.didFinish()
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
self.didFinish(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user