Merge pull request #1058 from kielgillard/master
Send and receive unread statuses
This commit is contained in:
commit
8c3c89030e
@ -79,11 +79,12 @@
|
|||||||
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */; };
|
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */; };
|
||||||
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */; };
|
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */; };
|
||||||
9E1D15532334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15522334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift */; };
|
9E1D15532334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15522334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift */; };
|
||||||
9E1D1555233431A600F4944C /* FeedlySyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D1554233431A600F4944C /* FeedlySyncOperation.swift */; };
|
9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D1554233431A600F4944C /* FeedlyOperation.swift */; };
|
||||||
9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15562334355900F4944C /* FeedlyRequestStreamsOperation.swift */; };
|
9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D15562334355900F4944C /* FeedlyRequestStreamsOperation.swift */; };
|
||||||
9E1D155923343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */; };
|
9E1D155923343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */; };
|
||||||
9E1D155B2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */; };
|
9E1D155B2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */; };
|
||||||
9E1D155D233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */; };
|
9E1D155D233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */; };
|
||||||
|
9E713653233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */; };
|
||||||
9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */; };
|
9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.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 */; };
|
||||||
@ -91,6 +92,7 @@
|
|||||||
9EAEC626233318400085D7C9 /* FeedlyStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC625233318400085D7C9 /* FeedlyStream.swift */; };
|
9EAEC626233318400085D7C9 /* FeedlyStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC625233318400085D7C9 /* FeedlyStream.swift */; };
|
||||||
9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */; };
|
9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */; };
|
||||||
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */; };
|
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */; };
|
||||||
|
9EBC31B7233987C1002A567B /* FeedlyArticleStatusCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBC31B6233987C1002A567B /* FeedlyArticleStatusCoordinator.swift */; };
|
||||||
9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */; };
|
9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */; };
|
||||||
9EC688EC232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */; };
|
9EC688EC232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */; };
|
||||||
9EC688EE232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */; };
|
9EC688EE232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */; };
|
||||||
@ -217,11 +219,12 @@
|
|||||||
9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionsOperation.swift; sourceTree = "<group>"; };
|
9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionsOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyMirrorCollectionsAsFoldersOperation.swift; sourceTree = "<group>"; };
|
9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyMirrorCollectionsAsFoldersOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D15522334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionStreamOperation.swift; sourceTree = "<group>"; };
|
9E1D15522334304B00F4944C /* FeedlyGetCollectionStreamOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetCollectionStreamOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D1554233431A600F4944C /* FeedlySyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlySyncOperation.swift; sourceTree = "<group>"; };
|
9E1D1554233431A600F4944C /* FeedlyOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D15562334355900F4944C /* FeedlyRequestStreamsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRequestStreamsOperation.swift; sourceTree = "<group>"; };
|
9E1D15562334355900F4944C /* FeedlyRequestStreamsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRequestStreamsOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamParsedItemsOperation.swift; sourceTree = "<group>"; };
|
9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetStreamParsedItemsOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrganiseParsedItemsByFeedOperation.swift; sourceTree = "<group>"; };
|
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrganiseParsedItemsByFeedOperation.swift; sourceTree = "<group>"; };
|
||||||
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyUpdateAccountFeedsWithItemsOperation.swift; sourceTree = "<group>"; };
|
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyUpdateAccountFeedsWithItemsOperation.swift; sourceTree = "<group>"; };
|
||||||
|
9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyRefreshStreamEntriesStatusOperation.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>"; };
|
||||||
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>"; };
|
||||||
@ -229,6 +232,7 @@
|
|||||||
9EAEC625233318400085D7C9 /* FeedlyStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyStream.swift; sourceTree = "<group>"; };
|
9EAEC625233318400085D7C9 /* FeedlyStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyStream.swift; sourceTree = "<group>"; };
|
||||||
9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCategory.swift; sourceTree = "<group>"; };
|
9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCategory.swift; sourceTree = "<group>"; };
|
||||||
9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrigin.swift; sourceTree = "<group>"; };
|
9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyOrigin.swift; sourceTree = "<group>"; };
|
||||||
|
9EBC31B6233987C1002A567B /* FeedlyArticleStatusCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyArticleStatusCoordinator.swift; sourceTree = "<group>"; };
|
||||||
9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAPICaller.swift; sourceTree = "<group>"; };
|
9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyAPICaller.swift; sourceTree = "<group>"; };
|
||||||
9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedlyAccountDelegate+OAuth.swift"; sourceTree = "<group>"; };
|
9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedlyAccountDelegate+OAuth.swift"; sourceTree = "<group>"; };
|
||||||
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAuthorizationCodeGranting.swift; sourceTree = "<group>"; };
|
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAuthorizationCodeGranting.swift; sourceTree = "<group>"; };
|
||||||
@ -441,8 +445,18 @@
|
|||||||
9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */,
|
9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */,
|
||||||
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */,
|
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */,
|
||||||
9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */,
|
9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */,
|
||||||
|
9E1D1554233431A600F4944C /* FeedlyOperation.swift */,
|
||||||
|
9EBC31B6233987C1002A567B /* FeedlyArticleStatusCoordinator.swift */,
|
||||||
|
9EBC31B32338AC2E002A567B /* Models */,
|
||||||
|
9EBC31B22338AC0F002A567B /* Refresh */,
|
||||||
|
);
|
||||||
|
path = Feedly;
|
||||||
|
sourceTree = SOURCE_ROOT;
|
||||||
|
};
|
||||||
|
9EBC31B22338AC0F002A567B /* Refresh */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */,
|
9E1D154C233370D800F4944C /* FeedlySyncStrategy.swift */,
|
||||||
9E1D1554233431A600F4944C /* FeedlySyncOperation.swift */,
|
|
||||||
9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */,
|
9E1D154E233371DD00F4944C /* FeedlyGetCollectionsOperation.swift */,
|
||||||
9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */,
|
9E1D15502334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift */,
|
||||||
9E12B01F2334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift */,
|
9E12B01F2334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift */,
|
||||||
@ -451,6 +465,14 @@
|
|||||||
9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */,
|
9E1D155823343B2A00F4944C /* FeedlyGetStreamParsedItemsOperation.swift */,
|
||||||
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */,
|
9E1D155A2334423300F4944C /* FeedlyOrganiseParsedItemsByFeedOperation.swift */,
|
||||||
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */,
|
9E1D155C233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift */,
|
||||||
|
9E713652233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift */,
|
||||||
|
);
|
||||||
|
path = Refresh;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9EBC31B32338AC2E002A567B /* Models */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */,
|
9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */,
|
||||||
9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */,
|
9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */,
|
||||||
9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */,
|
9EAEC623233315F60085D7C9 /* FeedlyEntry.swift */,
|
||||||
@ -458,8 +480,8 @@
|
|||||||
9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */,
|
9EAEC62723331C350085D7C9 /* FeedlyCategory.swift */,
|
||||||
9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */,
|
9EAEC62923331EE70085D7C9 /* FeedlyOrigin.swift */,
|
||||||
);
|
);
|
||||||
path = Feedly;
|
path = Models;
|
||||||
sourceTree = SOURCE_ROOT;
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D511EEB4202422BB00712EC3 /* xcconfig */ = {
|
D511EEB4202422BB00712EC3 /* xcconfig */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
@ -657,6 +679,7 @@
|
|||||||
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
|
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
|
||||||
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */,
|
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */,
|
||||||
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
|
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
|
||||||
|
9E713653233AD63E00765C84 /* FeedlyRefreshStreamEntriesStatusOperation.swift in Sources */,
|
||||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||||
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */,
|
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */,
|
||||||
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
||||||
@ -702,8 +725,9 @@
|
|||||||
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
|
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
|
||||||
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */,
|
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */,
|
||||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
||||||
9E1D1555233431A600F4944C /* FeedlySyncOperation.swift in Sources */,
|
9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */,
|
||||||
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
|
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
|
||||||
|
9EBC31B7233987C1002A567B /* FeedlyArticleStatusCoordinator.swift in Sources */,
|
||||||
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */,
|
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -89,7 +89,7 @@ final class FeedlyAPICaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStream(for collection: FeedlyCollection, completionHandler: @escaping (Result<FeedlyStream, Error>) -> ()) {
|
func getStream(for collection: FeedlyCollection, unreadOnly: Bool = false, completionHandler: @escaping (Result<FeedlyStream, Error>) -> ()) {
|
||||||
guard let accessToken = credentials?.secret else {
|
guard let accessToken = credentials?.secret else {
|
||||||
return DispatchQueue.main.async {
|
return DispatchQueue.main.async {
|
||||||
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
||||||
@ -98,7 +98,8 @@ final class FeedlyAPICaller {
|
|||||||
var components = baseUrlComponents
|
var components = baseUrlComponents
|
||||||
components.path = "/v3/streams/contents"
|
components.path = "/v3/streams/contents"
|
||||||
components.queryItems = [
|
components.queryItems = [
|
||||||
URLQueryItem(name: "streamId", value: collection.id)
|
URLQueryItem(name: "streamId", value: collection.id),
|
||||||
|
URLQueryItem(name: "unreadOnly", value: unreadOnly ? "true" : "false")
|
||||||
]
|
]
|
||||||
|
|
||||||
guard let url = components.url else {
|
guard let url = components.url else {
|
||||||
@ -130,7 +131,33 @@ final class FeedlyAPICaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAsRead(articleIds: [String], completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
enum MarkAction {
|
||||||
|
case read
|
||||||
|
case unread
|
||||||
|
case saved
|
||||||
|
case unsaved
|
||||||
|
|
||||||
|
var actionValue: String {
|
||||||
|
switch self {
|
||||||
|
case .read:
|
||||||
|
return "markAsRead"
|
||||||
|
case .unread:
|
||||||
|
return "keepUnread"
|
||||||
|
case .saved:
|
||||||
|
return "markAsSaved"
|
||||||
|
case .unsaved:
|
||||||
|
return "markAsUnsaved"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct MarkerEntriesBody: Encodable {
|
||||||
|
let type = "entries"
|
||||||
|
var action: String
|
||||||
|
var entryIds: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
func mark(_ articleIds: Set<String>, as action: MarkAction, completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
||||||
guard let accessToken = credentials?.secret else {
|
guard let accessToken = credentials?.secret else {
|
||||||
return DispatchQueue.main.async {
|
return DispatchQueue.main.async {
|
||||||
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
||||||
@ -144,37 +171,62 @@ final class FeedlyAPICaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "POST"
|
||||||
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||||
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||||
|
|
||||||
let json: [String: Any] = [
|
|
||||||
"action": "markAsRead",
|
|
||||||
"type": "entries",
|
|
||||||
"entryIds": articleIds
|
|
||||||
]
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
request.httpBody = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
|
let body = MarkerEntriesBody(action: action.actionValue, entryIds: Array(articleIds))
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let data = try encoder.encode(body)
|
||||||
|
request.httpBody = data
|
||||||
} catch {
|
} catch {
|
||||||
return DispatchQueue.main.async {
|
return DispatchQueue.main.async {
|
||||||
completionHandler(.failure(error))
|
completionHandler(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLSession.shared.dataTask(with: request) { (data, response, error) in
|
transport.send(request: request, resultType: String.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
||||||
// let obj = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments)
|
switch result {
|
||||||
// let data = try! JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted)
|
case .success(let (httpResponse, _)):
|
||||||
// print(String(data: data, encoding: .utf8)!)
|
if httpResponse.statusCode == 200 {
|
||||||
// }.resume()
|
completionHandler(.success(()))
|
||||||
|
} else {
|
||||||
transport.send(request: request, resultType: FeedlyStream.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
completionHandler(.failure(URLError(.cannotDecodeContentData)))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func importOpml(_ opmlData: Data, completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
||||||
|
guard let accessToken = credentials?.secret else {
|
||||||
|
return DispatchQueue.main.async {
|
||||||
|
completionHandler(.failure(CredentialsError.incompleteCredentials))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var components = baseUrlComponents
|
||||||
|
components.path = "/v3/opml"
|
||||||
|
|
||||||
|
guard let url = components.url else {
|
||||||
|
fatalError("\(components) does not produce a valid URL.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.addValue("text/xml", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||||
|
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||||
|
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||||
|
request.httpBody = opmlData
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: String.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let (httpResponse, _)):
|
case .success(let (httpResponse, _)):
|
||||||
if httpResponse.statusCode == 200 {
|
if httpResponse.statusCode == 200 {
|
||||||
completionHandler(.success(()))
|
completionHandler(.success(()))
|
||||||
} else {
|
} else {
|
||||||
// tempror
|
|
||||||
completionHandler(.failure(URLError(.cannotDecodeContentData)))
|
completionHandler(.failure(URLError(.cannotDecodeContentData)))
|
||||||
}
|
}
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
|
@ -42,15 +42,12 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
|
|
||||||
var refreshProgress = DownloadProgress(numberOfTasks: 0)
|
var refreshProgress = DownloadProgress(numberOfTasks: 0)
|
||||||
|
|
||||||
private let database: SyncDatabase
|
|
||||||
private let caller: FeedlyAPICaller
|
private let caller: FeedlyAPICaller
|
||||||
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedly")
|
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedly")
|
||||||
|
private let articleStatusCoodinator: FeedlyArticleStatusCoordinator
|
||||||
|
|
||||||
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API = .default) {
|
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API = .default) {
|
||||||
|
|
||||||
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
|
|
||||||
database = SyncDatabase(databaseFilePath: databaseFilePath)
|
|
||||||
|
|
||||||
if let transport = transport {
|
if let transport = transport {
|
||||||
caller = FeedlyAPICaller(transport: transport, api: api)
|
caller = FeedlyAPICaller(transport: transport, api: api)
|
||||||
|
|
||||||
@ -73,6 +70,9 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
caller = FeedlyAPICaller(transport: session, api: api)
|
caller = FeedlyAPICaller(transport: session, api: api)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
articleStatusCoodinator = FeedlyArticleStatusCoordinator(dataFolderPath: dataFolder,
|
||||||
|
caller: caller,
|
||||||
|
log: log)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Account API
|
// MARK: Account API
|
||||||
@ -93,17 +93,59 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
||||||
os_log(.debug, log: log, "*** SKIPPING SEND ARTICLE STATUS ***")
|
// Ensure remote articles have the same status as they do locally.
|
||||||
completion()
|
articleStatusCoodinator.sendArticleStatus(for: account, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to ensure local articles have the same status as they do remotely.
|
||||||
|
/// 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 (() -> Void)) {
|
func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) {
|
||||||
os_log(.debug, log: log, "*** SKIPPING REFRESH ARTICLE STATUS ***")
|
refreshAll(for: account) { _ in
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
fatalError()
|
let data: Data
|
||||||
|
|
||||||
|
do {
|
||||||
|
data = try Data(contentsOf: opmlFile)
|
||||||
|
} catch {
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
os_log(.debug, log: log, "Begin importing OPML...")
|
||||||
|
isOPMLImportInProgress = true
|
||||||
|
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||||
|
|
||||||
|
caller.importOpml(data) { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
os_log(.debug, log: self.log, "Import OPML done.")
|
||||||
|
self.refreshProgress.completeTask()
|
||||||
|
self.isOPMLImportInProgress = false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
os_log(.debug, log: self.log, "Import OPML failed.")
|
||||||
|
self.refreshProgress.completeTask()
|
||||||
|
self.isOPMLImportInProgress = false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let wrappedError = AccountError.wrappedError(error: error, account: account)
|
||||||
|
completion(.failure(wrappedError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addFolder(for account: Account, name: String, completion: @escaping (Result<Folder, Error>) -> Void) {
|
func addFolder(for account: Account, name: String, completion: @escaping (Result<Folder, Error>) -> Void) {
|
||||||
@ -148,31 +190,21 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
|
|
||||||
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||||
|
|
||||||
let log = self.log
|
let acceptedStatuses = articleStatusCoodinator.articles(articles,
|
||||||
|
for: account,
|
||||||
|
didChangeStatus: statusKey,
|
||||||
|
flag: flag)
|
||||||
|
|
||||||
switch statusKey {
|
return acceptedStatuses
|
||||||
case .read:
|
|
||||||
let ids = articles.map { $0.articleID }
|
|
||||||
caller.markAsRead(articleIds: ids) { result in
|
|
||||||
switch result {
|
|
||||||
case .success:
|
|
||||||
account.update(articles, statusKey: statusKey, flag: flag)
|
|
||||||
case .failure(let error):
|
|
||||||
os_log(.debug, log: log, "*** SKIPPING MARKING ARTICLES READ: %@ %@ ***", error as NSError, ids)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
os_log(.debug, log: log, "*** SKIPPING STATUS UPDATE FOR ARTICLES: %@ ***", articles)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountDidInitialize(_ account: Account) {
|
func accountDidInitialize(_ account: Account) {
|
||||||
credentials = try? account.retrieveCredentials(type: .oauthAccessToken)
|
credentials = try? account.retrieveCredentials(type: .oauthAccessToken)
|
||||||
|
|
||||||
syncStrategy = FeedlySyncStrategy(account: account, caller: caller, log: log)
|
syncStrategy = FeedlySyncStrategy(account: account,
|
||||||
|
caller: caller,
|
||||||
|
articleStatusCoordinator: articleStatusCoodinator,
|
||||||
|
log: log)
|
||||||
|
|
||||||
//TODO: Figure out how other accounts get refreshed automatically.
|
//TODO: Figure out how other accounts get refreshed automatically.
|
||||||
refreshAll(for: account) { result in
|
refreshAll(for: account) { result in
|
||||||
|
125
Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift
Normal file
125
Frameworks/Account/Feedly/FeedlyArticleStatusCoordinator.swift
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
//
|
||||||
|
// FeedlyArticleStatusCoordinator.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Kiel Gillard on 24/9/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SyncDatabase
|
||||||
|
import Articles
|
||||||
|
import os.log
|
||||||
|
|
||||||
|
final class FeedlyArticleStatusCoordinator {
|
||||||
|
private let database: SyncDatabase
|
||||||
|
private let log: OSLog
|
||||||
|
private let caller: FeedlyAPICaller
|
||||||
|
|
||||||
|
init(dataFolderPath: String, caller: FeedlyAPICaller, log: OSLog) {
|
||||||
|
let databaseFilePath = (dataFolderPath as NSString).appendingPathComponent("Sync.sqlite3")
|
||||||
|
self.database = SyncDatabase(databaseFilePath: databaseFilePath)
|
||||||
|
self.log = log
|
||||||
|
self.caller = caller
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a status for a particular article locally.
|
||||||
|
func articles(_ articles: Set<Article>, for account: Account, didChangeStatus statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||||
|
|
||||||
|
let syncStatuses = articles.map { article in
|
||||||
|
return SyncStatus(articleID: article.articleID, key: statusKey, flag: flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
database.insertStatuses(syncStatuses)
|
||||||
|
os_log(.debug, log: log, "Marking %@ as %@.", articles.map { $0.title }, syncStatuses)
|
||||||
|
|
||||||
|
if database.selectPendingCount() > 100 {
|
||||||
|
sendArticleStatus(for: account)
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.update(articles, statusKey: statusKey, flag: flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensures local articles have the same status as they do remotely.
|
||||||
|
func refreshArticleStatus(for account: Account, stream: FeedlyStream, collection: FeedlyCollection, completion: @escaping (() -> Void)) {
|
||||||
|
|
||||||
|
guard let folder = account.existingFolder(with: collection.label) else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let unreadArticleIds = Set(
|
||||||
|
stream.items
|
||||||
|
.filter { $0.unread }
|
||||||
|
.map { $0.id }
|
||||||
|
)
|
||||||
|
let localArticles = folder.fetchArticles()
|
||||||
|
let localArticleIds = localArticles.articleIDs()
|
||||||
|
let readArticleIds = localArticleIds.subtracting(unreadArticleIds)
|
||||||
|
account.update(localArticles.filter { readArticleIds.contains($0.articleID) }, statusKey: .read, flag: true)
|
||||||
|
// account.ensureStatuses(readArticleIds, true, .read, true)
|
||||||
|
account.update(localArticles.filter { unreadArticleIds.contains($0.articleID) }, statusKey: .read, flag: false)
|
||||||
|
// account.ensureStatuses(unreadArticleIds, false, .read, false)
|
||||||
|
|
||||||
|
os_log(.debug, log: log, "Ensured %i UNREAD and %i read article(s) in \"%@\".", unreadArticleIds.count, readArticleIds.count, collection.label)
|
||||||
|
|
||||||
|
completion()
|
||||||
|
|
||||||
|
// TODO: starred
|
||||||
|
|
||||||
|
// group.enter()
|
||||||
|
// caller.retrieveStarredEntries() { result in
|
||||||
|
// switch result {
|
||||||
|
// case .success(let articleIDs):
|
||||||
|
// self.syncArticleStarredState(account: account, articleIDs: articleIDs)
|
||||||
|
// group.leave()
|
||||||
|
// case .failure(let error):
|
||||||
|
// os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription)
|
||||||
|
// group.leave()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensures remote articles have the same status as they do locally.
|
||||||
|
func sendArticleStatus(for account: Account, completion: (() -> Void)? = nil) {
|
||||||
|
os_log(.debug, log: log, "Sending article statuses...")
|
||||||
|
|
||||||
|
let pending = database.selectForProcessing()
|
||||||
|
|
||||||
|
let statuses: [(status: ArticleStatus.Key, flag: Bool, action: FeedlyAPICaller.MarkAction)] = [
|
||||||
|
(.read, false, .unread),
|
||||||
|
(.read, true, .read),
|
||||||
|
(.starred, true, .saved),
|
||||||
|
(.starred, false, .unsaved),
|
||||||
|
]
|
||||||
|
|
||||||
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
for pairing in statuses {
|
||||||
|
let articleIds = pending.filter { $0.key == pairing.status && $0.flag == pairing.flag }
|
||||||
|
guard !articleIds.isEmpty else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids = Set(articleIds.map { $0.articleID })
|
||||||
|
let database = self.database
|
||||||
|
group.enter()
|
||||||
|
caller.mark(ids, as: pairing.action) { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
database.deleteSelectedForProcessing(Array(ids))
|
||||||
|
case .failure:
|
||||||
|
database.resetSelectedForProcessing(Array(ids))
|
||||||
|
}
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.notify(queue: DispatchQueue.main) {
|
||||||
|
os_log(.debug, log: self.log, "Done sending article statuses.")
|
||||||
|
completion?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// FeedlySyncOperation.swift
|
// FeedlyOperation.swift
|
||||||
// Account
|
// Account
|
||||||
//
|
//
|
||||||
// Created by Kiel Gillard on 20/9/19.
|
// Created by Kiel Gillard on 20/9/19.
|
||||||
@ -8,15 +8,15 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol FeedlySyncOperationDelegate: class {
|
protocol FeedlyOperationDelegate: class {
|
||||||
func feedlySyncOperation(_ operation: FeedlySyncOperation, didFailWith error: Error)
|
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Abstract class common to all the tasks required to ingest content from Feedly into NetNewsWire.
|
/// Abstract class common to all the tasks required to ingest content from Feedly into NetNewsWire.
|
||||||
/// Each task should try to have a single responsibility so they can be easily composed with others.
|
/// Each task should try to have a single responsibility so they can be easily composed with others.
|
||||||
class FeedlySyncOperation: Operation {
|
class FeedlyOperation: Operation {
|
||||||
|
|
||||||
weak var delegate: FeedlySyncOperationDelegate?
|
weak var delegate: FeedlyOperationDelegate?
|
||||||
|
|
||||||
func didFinish() {
|
func didFinish() {
|
||||||
self.isExecutingOperation = false
|
self.isExecutingOperation = false
|
||||||
@ -25,7 +25,7 @@ class FeedlySyncOperation: Operation {
|
|||||||
|
|
||||||
func didFinish(_ error: Error) {
|
func didFinish(_ error: Error) {
|
||||||
assert(delegate != nil)
|
assert(delegate != nil)
|
||||||
delegate?.feedlySyncOperation(self, didFailWith: error)
|
delegate?.feedlyOperation(self, didFailWith: error)
|
||||||
didFinish()
|
didFinish()
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +59,8 @@ struct FeedlyEntry: Decodable {
|
|||||||
// /// an image URL for this entry. If present, “url” will contain the image URL, “width” and “height” its dimension, and “contentType” its MIME type.
|
// /// an image URL for this entry. If present, “url” will contain the image URL, “width” and “height” its dimension, and “contentType” its MIME type.
|
||||||
// var visual: Image?
|
// var visual: Image?
|
||||||
//
|
//
|
||||||
// /// was this entry read by the user? If an Authorization header is not provided, this will always return false. If an Authorization header is provided, it will reflect if the user has read this entry or not.
|
/// Was this entry read by the user? If an Authorization header is not provided, this will always return false. If an Authorization header is provided, it will reflect if the user has read this entry or not.
|
||||||
// var unread: Bool
|
var unread: Bool
|
||||||
//
|
//
|
||||||
// /// a list of tag objects (“id” and “label”) that the user added to this entry. This value is only returned if an Authorization header is provided, and at least one tag has been added. If the entry has been explicitly marked as read (not the feed itself), the “global.read” tag will be present.
|
// /// a list of tag objects (“id” and “label”) that the user added to this entry. This value is only returned if an Authorization header is provided, and at least one tag has been added. If the entry has been explicitly marked as read (not the feed itself), the “global.read” tag will be present.
|
||||||
// var tags: [Tag]?
|
// var tags: [Tag]?
|
@ -10,7 +10,7 @@ import Foundation
|
|||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
/// Single responsibility is to accurately reflect Collections and their Feeds as Folders and their Feeds.
|
/// Single responsibility is to accurately reflect Collections and their Feeds as Folders and their Feeds.
|
||||||
final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlySyncOperation {
|
final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||||
|
|
||||||
let account: Account
|
let account: Account
|
||||||
let collectionsAndFoldersProvider: FeedlyCollectionsAndFoldersProviding
|
let collectionsAndFoldersProvider: FeedlyCollectionsAndFoldersProviding
|
||||||
@ -53,12 +53,8 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlySyncOperation
|
|||||||
let metadata = FeedMetadata(feedID: url)
|
let metadata = FeedMetadata(feedID: url)
|
||||||
// TODO: More metadata
|
// TODO: More metadata
|
||||||
|
|
||||||
// Kiel, I'm commenting this out as we shouldn't be storing the name
|
|
||||||
// in the feed metadata. It should be stored in the OPML file.
|
|
||||||
// You can just set the name directly on the feed itself.
|
|
||||||
// metadata.name = collectionFeed.title
|
|
||||||
|
|
||||||
let feed = Feed(account: account, url: url, metadata: metadata)
|
let feed = Feed(account: account, url: url, metadata: metadata)
|
||||||
|
feed.name = collectionFeed.title
|
||||||
|
|
||||||
// So the same feed isn't created more than once.
|
// So the same feed isn't created more than once.
|
||||||
localFeeds.insert(feed)
|
localFeeds.insert(feed)
|
@ -14,7 +14,7 @@ protocol FeedlyCollectionStreamProviding: class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Single responsibility is to get the stream content of a Collection from Feedly.
|
/// Single responsibility is to get the stream content of a Collection from Feedly.
|
||||||
final class FeedlyGetCollectionStreamOperation: FeedlySyncOperation, FeedlyCollectionStreamProviding {
|
final class FeedlyGetCollectionStreamOperation: FeedlyOperation, FeedlyCollectionStreamProviding {
|
||||||
|
|
||||||
private(set) var collection: FeedlyCollection
|
private(set) var collection: FeedlyCollection
|
||||||
|
|
||||||
@ -30,11 +30,13 @@ final class FeedlyGetCollectionStreamOperation: FeedlySyncOperation, FeedlyColle
|
|||||||
|
|
||||||
let account: Account
|
let account: Account
|
||||||
let caller: FeedlyAPICaller
|
let caller: FeedlyAPICaller
|
||||||
|
let unreadOnly: Bool
|
||||||
|
|
||||||
init(account: Account, collection: FeedlyCollection, caller: FeedlyAPICaller) {
|
init(account: Account, collection: FeedlyCollection, caller: FeedlyAPICaller, unreadOnly: Bool = false) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
|
self.unreadOnly = unreadOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
override func main() {
|
override func main() {
|
||||||
@ -44,7 +46,7 @@ final class FeedlyGetCollectionStreamOperation: FeedlySyncOperation, FeedlyColle
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Use account metadata to get articles newer than some date.
|
//TODO: Use account metadata to get articles newer than some date.
|
||||||
caller.getStream(for: collection) { result in
|
caller.getStream(for: collection, unreadOnly: unreadOnly) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let stream):
|
case .success(let stream):
|
||||||
self.storedStream = stream
|
self.storedStream = stream
|
@ -7,20 +7,23 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import os.log
|
||||||
|
|
||||||
protocol FeedlyCollectionProviding: class {
|
protocol FeedlyCollectionProviding: class {
|
||||||
var collections: [FeedlyCollection] { get }
|
var collections: [FeedlyCollection] { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Single responsibility is to get Collections from Feedly.
|
/// Single responsibility is to get Collections from Feedly.
|
||||||
final class FeedlyGetCollectionsOperation: FeedlySyncOperation, FeedlyCollectionProviding {
|
final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProviding {
|
||||||
|
|
||||||
let caller: FeedlyAPICaller
|
let caller: FeedlyAPICaller
|
||||||
|
let log: OSLog
|
||||||
|
|
||||||
private(set) var collections = [FeedlyCollection]()
|
private(set) var collections = [FeedlyCollection]()
|
||||||
|
|
||||||
init(caller: FeedlyAPICaller) {
|
init(caller: FeedlyAPICaller, log: OSLog) {
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
|
self.log = log
|
||||||
}
|
}
|
||||||
|
|
||||||
override func main() {
|
override func main() {
|
||||||
@ -29,13 +32,17 @@ final class FeedlyGetCollectionsOperation: FeedlySyncOperation, FeedlyCollection
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os_log(.debug, log: log, "Requesting collections.")
|
||||||
|
|
||||||
caller.getCollections { result in
|
caller.getCollections { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let collections):
|
case .success(let collections):
|
||||||
|
os_log(.debug, log: self.log, "Received collections: %@.", collections.map { $0.id })
|
||||||
self.collections = collections
|
self.collections = collections
|
||||||
self.didFinish()
|
self.didFinish()
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
|
os_log(.debug, log: self.log, "Unable to request collections %@.", error as NSError)
|
||||||
self.didFinish(error)
|
self.didFinish(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ protocol FeedlyStreamParsedItemsProviding: class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Single responsibility is to model articles as ParsedItems for entries in a Collection's stream from Feedly.
|
/// Single responsibility is to model articles as ParsedItems for entries in a Collection's stream from Feedly.
|
||||||
final class FeedlyGetStreamParsedItemsOperation: FeedlySyncOperation, FeedlyStreamParsedItemsProviding {
|
final class FeedlyGetStreamParsedItemsOperation: FeedlyOperation, FeedlyStreamParsedItemsProviding {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let caller: FeedlyAPICaller
|
private let caller: FeedlyAPICaller
|
||||||
private let collectionStreamProvider: FeedlyCollectionStreamProviding
|
private let collectionStreamProvider: FeedlyCollectionStreamProviding
|
@ -7,24 +7,27 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import os.log
|
||||||
|
|
||||||
protocol FeedlyCollectionsAndFoldersProviding: class {
|
protocol FeedlyCollectionsAndFoldersProviding: class {
|
||||||
var collectionsAndFolders: [(FeedlyCollection, Folder)] { get }
|
var collectionsAndFolders: [(FeedlyCollection, Folder)] { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Single responsibility is accurately reflect Collections from Feedly as Folders.
|
/// Single responsibility is accurately reflect Collections from Feedly as Folders.
|
||||||
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlySyncOperation, FeedlyCollectionsAndFoldersProviding {
|
final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlyOperation, FeedlyCollectionsAndFoldersProviding {
|
||||||
|
|
||||||
let caller: FeedlyAPICaller
|
let caller: FeedlyAPICaller
|
||||||
let account: Account
|
let account: Account
|
||||||
let collectionsProvider: FeedlyCollectionProviding
|
let collectionsProvider: FeedlyCollectionProviding
|
||||||
|
let log: OSLog
|
||||||
|
|
||||||
private(set) var collectionsAndFolders = [(FeedlyCollection, Folder)]()
|
private(set) var collectionsAndFolders = [(FeedlyCollection, Folder)]()
|
||||||
|
|
||||||
init(account: Account, collectionsProvider: FeedlyCollectionProviding, caller: FeedlyAPICaller) {
|
init(account: Account, collectionsProvider: FeedlyCollectionProviding, caller: FeedlyAPICaller, log: OSLog) {
|
||||||
self.collectionsProvider = collectionsProvider
|
self.collectionsProvider = collectionsProvider
|
||||||
self.account = account
|
self.account = account
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
|
self.log = log
|
||||||
}
|
}
|
||||||
|
|
||||||
override func main() {
|
override func main() {
|
||||||
@ -36,21 +39,16 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlySyncOperation, Feed
|
|||||||
let collections = collectionsProvider.collections
|
let collections = collectionsProvider.collections
|
||||||
|
|
||||||
let pairs = collections.compactMap { collection -> (FeedlyCollection, Folder)? in
|
let pairs = collections.compactMap { collection -> (FeedlyCollection, Folder)? in
|
||||||
for folder in localFolders {
|
guard let folder = account.ensureFolder(with: collection.label) else {
|
||||||
if folder.name == collection.label {
|
assertionFailure("Why wasn't a folder created?")
|
||||||
return (collection, folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let newFolder = account.ensureFolder(with: collection.label) else {
|
|
||||||
assertionFailure("Try debugging why a folder could not be created.")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
folder.externalID = collection.id
|
||||||
return (collection, newFolder)
|
return (collection, folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionsAndFolders = pairs
|
collectionsAndFolders = pairs
|
||||||
|
os_log(.debug, log: log, "Ensured %i folders for %i collections.", pairs.count, collections.count)
|
||||||
|
|
||||||
// Remove folders without a corresponding collection
|
// Remove folders without a corresponding collection
|
||||||
let collectionFolders = Set(pairs.map { $0.1 })
|
let collectionFolders = Set(pairs.map { $0.1 })
|
||||||
@ -58,5 +56,7 @@ final class FeedlyMirrorCollectionsAsFoldersOperation: FeedlySyncOperation, Feed
|
|||||||
for unmatched in foldersWithoutCollections {
|
for unmatched in foldersWithoutCollections {
|
||||||
account.removeFolder(unmatched)
|
account.removeFolder(unmatched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os_log(.debug, log: log, "Removed %i folders: %@", foldersWithoutCollections.count, foldersWithoutCollections.map { $0.externalID ?? $0.nameForDisplay })
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,7 +18,7 @@ protocol FeedlyParsedItemsByFeedProviding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Single responsibility is to group articles by their feeds.
|
/// Single responsibility is to group articles by their feeds.
|
||||||
final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlySyncOperation, FeedlyParsedItemsByFeedProviding {
|
final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyParsedItemsByFeedProviding {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let parsedItemsProvider: FeedlyStreamParsedItemsProviding
|
private let parsedItemsProvider: FeedlyStreamParsedItemsProviding
|
||||||
private let log: OSLog
|
private let log: OSLog
|
@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// FeedlyRefreshStreamEntriesStatusOperation.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Kiel Gillard on 25/9/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import os.log
|
||||||
|
|
||||||
|
/// Single responsibility is to update the read status of articles stored locally with the unread status of the entries in a Collection's stream from Feedly.
|
||||||
|
final class FeedlyRefreshStreamEntriesStatusOperation: FeedlyOperation {
|
||||||
|
private let account: Account
|
||||||
|
private let collectionStreamProvider: FeedlyCollectionStreamProviding
|
||||||
|
private let log: OSLog
|
||||||
|
let articleStatusCoordinator: FeedlyArticleStatusCoordinator
|
||||||
|
|
||||||
|
init(account: Account, collectionStreamProvider: FeedlyCollectionStreamProviding, articleStatusCoordinator: FeedlyArticleStatusCoordinator, log: OSLog) {
|
||||||
|
self.account = account
|
||||||
|
self.articleStatusCoordinator = articleStatusCoordinator
|
||||||
|
self.collectionStreamProvider = collectionStreamProvider
|
||||||
|
self.log = log
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main() {
|
||||||
|
guard !isCancelled else {
|
||||||
|
didFinish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let collection = collectionStreamProvider.collection
|
||||||
|
let stream = collectionStreamProvider.stream
|
||||||
|
articleStatusCoordinator.refreshArticleStatus(for: account, stream: stream, collection: collection) {
|
||||||
|
self.didFinish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ protocol FeedlyRequestStreamsOperationDelegate: class {
|
|||||||
|
|
||||||
/// Single responsibility is to create one stream request operation for one Feedly collection.
|
/// Single responsibility is to create one stream request operation for one Feedly collection.
|
||||||
/// This is the start of the process of refreshing the entire contents of a Folder.
|
/// This is the start of the process of refreshing the entire contents of a Folder.
|
||||||
final class FeedlyRequestStreamsOperation: FeedlySyncOperation {
|
final class FeedlyRequestStreamsOperation: FeedlyOperation {
|
||||||
|
|
||||||
weak var queueDelegate: FeedlyRequestStreamsOperationDelegate?
|
weak var queueDelegate: FeedlyRequestStreamsOperationDelegate?
|
||||||
|
|
@ -14,13 +14,15 @@ final class FeedlySyncStrategy {
|
|||||||
let account: Account
|
let account: Account
|
||||||
let caller: FeedlyAPICaller
|
let caller: FeedlyAPICaller
|
||||||
let operationQueue: OperationQueue
|
let operationQueue: OperationQueue
|
||||||
|
let articleStatusCoordinator: FeedlyArticleStatusCoordinator
|
||||||
let log: OSLog
|
let log: OSLog
|
||||||
|
|
||||||
init(account: Account, caller: FeedlyAPICaller, log: OSLog) {
|
init(account: Account, caller: FeedlyAPICaller, articleStatusCoordinator: FeedlyArticleStatusCoordinator, log: OSLog) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
self.operationQueue = OperationQueue()
|
self.operationQueue = OperationQueue()
|
||||||
self.log = log
|
self.log = log
|
||||||
|
self.articleStatusCoordinator = articleStatusCoordinator
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancel() {
|
func cancel() {
|
||||||
@ -34,17 +36,19 @@ final class FeedlySyncStrategy {
|
|||||||
func startSync(completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
func startSync(completionHandler: @escaping (Result<Void, Error>) -> ()) {
|
||||||
guard operationQueue.operationCount == 0 else {
|
guard operationQueue.operationCount == 0 else {
|
||||||
os_log(.debug, log: log, "Reqeusted start sync but ignored because a sync is already in progress.")
|
os_log(.debug, log: log, "Reqeusted start sync but ignored because a sync is already in progress.")
|
||||||
|
completionHandler(.success(()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the truth is in the cloud, everything hinges of what Collections the user has.
|
// Since the truth is in the cloud, everything hinges of what Collections the user has.
|
||||||
let getCollections = FeedlyGetCollectionsOperation(caller: caller)
|
let getCollections = FeedlyGetCollectionsOperation(caller: caller, log: log)
|
||||||
getCollections.delegate = self
|
getCollections.delegate = self
|
||||||
|
|
||||||
// Ensure a folder exists for each Collection, removing Folders without a corresponding Collection.
|
// Ensure a folder exists for each Collection, removing Folders without a corresponding Collection.
|
||||||
let mirrorCollectionsAsFolders = FeedlyMirrorCollectionsAsFoldersOperation(account: account,
|
let mirrorCollectionsAsFolders = FeedlyMirrorCollectionsAsFoldersOperation(account: account,
|
||||||
collectionsProvider: getCollections,
|
collectionsProvider: getCollections,
|
||||||
caller: caller)
|
caller: caller,
|
||||||
|
log: log)
|
||||||
mirrorCollectionsAsFolders.delegate = self
|
mirrorCollectionsAsFolders.delegate = self
|
||||||
mirrorCollectionsAsFolders.addDependency(getCollections)
|
mirrorCollectionsAsFolders.addDependency(getCollections)
|
||||||
|
|
||||||
@ -94,7 +98,7 @@ final class FeedlySyncStrategy {
|
|||||||
os_log(.debug, log: log, "Sync started: %@", syncId)
|
os_log(.debug, log: log, "Sync started: %@", syncId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var finalOperation: Operation?
|
private weak var finalOperation: Operation?
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FeedlySyncStrategy: FeedlyRequestStreamsOperationDelegate {
|
extension FeedlySyncStrategy: FeedlyRequestStreamsOperationDelegate {
|
||||||
@ -127,21 +131,30 @@ extension FeedlySyncStrategy: FeedlyRequestStreamsOperationDelegate {
|
|||||||
updateOperation.delegate = self
|
updateOperation.delegate = self
|
||||||
updateOperation.addDependency(groupItemsByFeed)
|
updateOperation.addDependency(groupItemsByFeed)
|
||||||
|
|
||||||
|
// Once the articles are in the account, ensure they have the correct status
|
||||||
|
let ensureUnreadOperation = FeedlyRefreshStreamEntriesStatusOperation(account: account,
|
||||||
|
collectionStreamProvider: collectionStreamOperation,
|
||||||
|
articleStatusCoordinator: articleStatusCoordinator,
|
||||||
|
log: log)
|
||||||
|
|
||||||
|
ensureUnreadOperation.delegate = self
|
||||||
|
ensureUnreadOperation.addDependency(updateOperation)
|
||||||
|
|
||||||
// Sync completes successfully when the account has been updated with all the parsedd entries from the stream.
|
// Sync completes successfully when the account has been updated with all the parsedd entries from the stream.
|
||||||
if let operation = finalOperation {
|
if let operation = finalOperation {
|
||||||
operation.addDependency(updateOperation)
|
operation.addDependency(ensureUnreadOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
let operations = [collectionStreamOperation, parseItemsOperation, groupItemsByFeed, updateOperation]
|
let operations = [collectionStreamOperation, parseItemsOperation, groupItemsByFeed, updateOperation, ensureUnreadOperation]
|
||||||
|
|
||||||
operationQueue.addOperations(operations, waitUntilFinished: false)
|
operationQueue.addOperations(operations, waitUntilFinished: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FeedlySyncStrategy: FeedlySyncOperationDelegate {
|
extension FeedlySyncStrategy: FeedlyOperationDelegate {
|
||||||
|
|
||||||
func feedlySyncOperation(_ operation: FeedlySyncOperation, didFailWith error: Error) {
|
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
|
||||||
os_log(.debug, log: log, "**** Operation failed! **** %@", error as NSError)
|
os_log(.debug, log: log, "%@ failed so sync failed with error %@", operation, error.localizedDescription)
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
startSyncCompletionHandler?(.failure(error))
|
startSyncCompletionHandler?(.failure(error))
|
@ -11,7 +11,7 @@ import RSParser
|
|||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
/// Single responsibility is to combine the articles with their feeds for a specific account.
|
/// Single responsibility is to combine the articles with their feeds for a specific account.
|
||||||
final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlySyncOperation {
|
final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let organisedItemsProvider: FeedlyParsedItemsByFeedProviding
|
private let organisedItemsProvider: FeedlyParsedItemsByFeedProviding
|
||||||
private let log: OSLog
|
private let log: OSLog
|
||||||
@ -40,7 +40,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlySyncOperation {
|
|||||||
group.enter()
|
group.enter()
|
||||||
os_log(.debug, log: log, "Updating %i items for feed \"%@\" in collection \"%@\"", items.count, feed.nameForDisplay, organisedItemsProvider.collection.label)
|
os_log(.debug, log: log, "Updating %i items for feed \"%@\" in collection \"%@\"", items.count, feed.nameForDisplay, organisedItemsProvider.collection.label)
|
||||||
|
|
||||||
account.update(feed, parsedItems: items) {
|
account.update(feed, parsedItems: items, defaultRead: true) {
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -70,7 +70,7 @@ class AccountsFeedlyWebWindowController: NSWindowController, WKNavigationDelegat
|
|||||||
NSApplication.shared.presentError(error)
|
NSApplication.shared.presentError(error)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
NSApplication.shared.presentError(error)
|
print(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
decisionHandler(.allow)
|
decisionHandler(.allow)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user