Merge branch 'ios-candidate' of https://github.com/Ranchero-Software/NetNewsWire into ios-candidate

This commit is contained in:
Maurice Parker 2020-01-17 10:54:21 -07:00
commit b7368c2c90
17 changed files with 76 additions and 111 deletions

View File

@ -45,7 +45,7 @@ class FeedlySyncStreamContentsOperationTests: XCTestCase {
XCTAssertNil(serviceUnreadOnly) XCTAssertNil(serviceUnreadOnly)
} }
let syncStreamContents = FeedlySyncStreamContentsOperation(account: account, resource: resource, service: service, newerThan: newerThan, log: support.log) let syncStreamContents = FeedlySyncStreamContentsOperation(account: account, resource: resource, service: service, isPagingEnabled: true, newerThan: newerThan, log: support.log)
let completionExpectation = expectation(description: "Did Finish") let completionExpectation = expectation(description: "Did Finish")
syncStreamContents.completionBlock = { _ in syncStreamContents.completionBlock = { _ in
@ -79,7 +79,7 @@ class FeedlySyncStreamContentsOperationTests: XCTestCase {
XCTAssertNil(serviceUnreadOnly) XCTAssertNil(serviceUnreadOnly)
} }
let syncStreamContents = FeedlySyncStreamContentsOperation(account: account, resource: resource, service: service, newerThan: newerThan, log: support.log) let syncStreamContents = FeedlySyncStreamContentsOperation(account: account, resource: resource, service: service, isPagingEnabled: true, newerThan: newerThan, log: support.log)
let completionExpectation = expectation(description: "Did Finish") let completionExpectation = expectation(description: "Did Finish")
syncStreamContents.completionBlock = { _ in syncStreamContents.completionBlock = { _ in
@ -120,7 +120,7 @@ class FeedlySyncStreamContentsOperationTests: XCTestCase {
getStreamPageExpectation.fulfill() getStreamPageExpectation.fulfill()
} }
let syncStreamContents = FeedlySyncStreamContentsOperation(account: account, resource: resource, service: service, newerThan: newerThan, log: support.log) let syncStreamContents = FeedlySyncStreamContentsOperation(account: account, resource: resource, service: service, isPagingEnabled: true, newerThan: newerThan, log: support.log)
let completionExpectation = expectation(description: "Did Finish") let completionExpectation = expectation(description: "Did Finish")
syncStreamContents.completionBlock = { _ in syncStreamContents.completionBlock = { _ in

View File

@ -10,7 +10,6 @@ import Foundation
struct FeedlyFeedContainerValidator { struct FeedlyFeedContainerValidator {
var container: Container var container: Container
var userId: String?
func getValidContainer() throws -> (Folder, String) { func getValidContainer() throws -> (Folder, String) {
guard let folder = container as? Folder else { guard let folder = container as? Folder else {
@ -21,16 +20,6 @@ struct FeedlyFeedContainerValidator {
throw FeedlyAccountDelegateError.addFeedInvalidFolder(folder) throw FeedlyAccountDelegateError.addFeedInvalidFolder(folder)
} }
guard let userId = userId else {
throw FeedlyAccountDelegateError.notLoggedIn
}
let uncategorized = FeedlyCategoryResourceId.Global.uncategorized(for: userId)
guard collectionId != uncategorized.id else {
throw FeedlyAccountDelegateError.addFeedInvalidFolder(folder)
}
return (folder, collectionId) return (folder, collectionId)
} }
} }

View File

@ -9,6 +9,6 @@
import Foundation import Foundation
struct FeedlyCategory: Decodable { struct FeedlyCategory: Decodable {
var label: String let label: String
var id: String let id: String
} }

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
struct FeedlyCollection: Codable { struct FeedlyCollection: Codable {
var feeds: [FeedlyFeed] let feeds: [FeedlyFeed]
var label: String let label: String
var id: String let id: String
} }

View File

@ -8,84 +8,58 @@
import Foundation import Foundation
enum Direction: String, Codable {
case leftToRight = "ltr"
case rightToLeft = "rtl"
}
struct FeedlyEntry: Decodable { struct FeedlyEntry: Decodable {
/// the unique, immutable ID for this particular article. /// the unique, immutable ID for this particular article.
var id: String let id: String
/// the articles title. This string does not contain any HTML markup. /// the articles title. This string does not contain any HTML markup.
var title: String? let title: String?
struct Content: Codable { struct Content: Decodable {
var content: String?
var direction: Direction? enum Direction: String, Decodable {
case leftToRight = "ltr"
case rightToLeft = "rtl"
}
let content: String?
let direction: Direction?
} }
/// This object typically has two values: content for the content itself, and direction (ltr for left-to-right, rtl for right-to-left). The content itself contains sanitized HTML markup. /// This object typically has two values: content for the content itself, and direction (ltr for left-to-right, rtl for right-to-left). The content itself contains sanitized HTML markup.
var content: Content? let content: Content?
/// content object the article summary. See the content object above. /// content object the article summary. See the content object above.
var summary: Content? let summary: Content?
/// the authors name /// the authors name
var author: String? let author: String?
/// the immutable timestamp, in ms, when this article was processed by the feedly Cloud servers. /// the immutable timestamp, in ms, when this article was processed by the feedly Cloud servers.
var crawled: Date let crawled: Date
/// the timestamp, in ms, when this article was re-processed and updated by the feedly Cloud servers. /// the timestamp, in ms, when this article was re-processed and updated by the feedly Cloud servers.
var recrawled: Date? let recrawled: Date?
/// the timestamp, in ms, when this article was published, as reported by the RSS feed (often inaccurate).
// var published: Date
/// the timestamp, in ms, when this article was updated, as reported by the RSS feed
// var updated: Date?
/// the feed from which this article was crawled. If present, streamId will contain the feed id, title will contain the feed title, and htmlUrl will contain the feeds website. /// the feed from which this article was crawled. If present, streamId will contain the feed id, title will contain the feed title, and htmlUrl will contain the feeds website.
var origin: FeedlyOrigin? let origin: FeedlyOrigin?
/// Used to help find the URL to visit an article on a web site. /// Used to help find the URL to visit an article on a web site.
/// See https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ /// See https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ
var canonical: [FeedlyLink]? let canonical: [FeedlyLink]?
/// a list of alternate links for this article. Each link object contains a media type and a URL. Typically, a single object is present, with a link to the original web page. /// a list of alternate links for this article. Each link object contains a media type and a URL. Typically, a single object is present, with a link to the original web page.
var alternate: [FeedlyLink]? let alternate: [FeedlyLink]?
//
// // var origin:
// // Optional origin object the feed from which this article was crawled. If present, streamId will contain the feed id, title will contain the feed title, and htmlUrl will contain the feeds website.
// var keywords: [String]?
//
// /// 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?
//
/// 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
//
/// 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: [FeedlyTag]?
//
/// a list of category objects (id and label) that the user associated with the feed of this entry. This value is only returned if an Authorization header is provided.
var categories: [FeedlyCategory]?
//
// /// an indicator of how popular this entry is. The higher the number, the more readers have read, saved or shared this particular entry.
// var engagement: Int?
//
// /// Timestamp for tagged articles, contains the timestamp when the article was tagged by the user. This will only be returned when the entry is returned through the streams API.
// var actionTimestamp: Date?
//
/// A list of media links (videos, images, sound etc) provided by the feed. Some entries do not have a summary or content, only a collection of media links.
var enclosure: [FeedlyLink]?
//
// /// The article fingerprint. This value might change if the article is updated.
// var fingerprint: String
// originId /// 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.
// string the unique id of this post in the RSS feed (not necessarily a URL!) let unread: Bool
// sid
// Optional string an internal search id. /// 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.
let tags: [FeedlyTag]?
/// a list of category objects (id and label) that the user associated with the feed of this entry. This value is only returned if an Authorization header is provided.
let categories: [FeedlyCategory]?
/// A list of media links (videos, images, sound etc) provided by the feed. Some entries do not have a summary or content, only a collection of media links.
let enclosure: [FeedlyLink]?
} }

View File

@ -11,7 +11,7 @@ import Articles
import RSParser import RSParser
struct FeedlyEntryParser { struct FeedlyEntryParser {
var entry: FeedlyEntry let entry: FeedlyEntry
var id: String { var id: String {
return entry.id return entry.id

View File

@ -9,8 +9,8 @@
import Foundation import Foundation
struct FeedlyFeed: Codable { struct FeedlyFeed: Codable {
var id: String let id: String
var title: String? let title: String?
var updated: Date? let updated: Date?
var website: String? let website: String?
} }

View File

@ -11,9 +11,9 @@ import Foundation
struct FeedlyFeedsSearchResponse: Decodable { struct FeedlyFeedsSearchResponse: Decodable {
struct Feed: Decodable { struct Feed: Decodable {
var title: String let title: String
var feedId: String let feedId: String
} }
var results: [Feed] let results: [Feed]
} }

View File

@ -9,10 +9,10 @@
import Foundation import Foundation
struct FeedlyLink: Decodable { struct FeedlyLink: Decodable {
var href: String let href: String
/// The mime type of the resource located by `href`. /// The mime type of the resource located by `href`.
/// When `nil`, it's probably a web page? /// When `nil`, it's probably a web page?
/// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ
var type: String? let type: String?
} }

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
struct FeedlyOrigin: Decodable { struct FeedlyOrigin: Decodable {
var title: String? let title: String?
var streamId: String? let streamId: String?
var htmlUrl: String? let htmlUrl: String?
} }

View File

@ -17,7 +17,7 @@ protocol FeedlyResourceId {
/// The Feed Resource is documented here: https://developer.feedly.com/cloud/ /// The Feed Resource is documented here: https://developer.feedly.com/cloud/
struct FeedlyFeedResourceId: FeedlyResourceId { struct FeedlyFeedResourceId: FeedlyResourceId {
var id: String let id: String
/// 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
@ -45,7 +45,7 @@ extension FeedlyFeedResourceId {
} }
struct FeedlyCategoryResourceId: FeedlyResourceId { struct FeedlyCategoryResourceId: FeedlyResourceId {
var id: String let id: String
enum Global { enum Global {
@ -72,7 +72,7 @@ struct FeedlyCategoryResourceId: FeedlyResourceId {
} }
struct FeedlyTagResourceId: FeedlyResourceId { struct FeedlyTagResourceId: FeedlyResourceId {
var id: String let id: String
enum Global { enum Global {

View File

@ -9,16 +9,16 @@
import Foundation import Foundation
struct FeedlyStream: Decodable { struct FeedlyStream: Decodable {
var id: String let id: String
/// Of the most recent entry for this stream (regardless of continuation, newerThan, etc). /// Of the most recent entry for this stream (regardless of continuation, newerThan, etc).
var updated: Date? let updated: Date?
/// Optional string the continuation id to pass to the next stream call, for pagination. /// the continuation id to pass to the next stream call, for pagination.
/// This id guarantees that no entry will be duplicated in a stream (meaning, there is no need to de-duplicate entries returned by this call). /// This id guarantees that no entry will be duplicated in a stream (meaning, there is no need to de-duplicate entries returned by this call).
/// If this value is not returned, it means the end of the stream has been reached. /// If this value is not returned, it means the end of the stream has been reached.
var continuation: String? let continuation: String?
var items: [FeedlyEntry] let items: [FeedlyEntry]
var isStreamEnd: Bool { var isStreamEnd: Bool {
return continuation == nil return continuation == nil

View File

@ -9,8 +9,8 @@
import Foundation import Foundation
struct FeedlyStreamIds: Decodable { struct FeedlyStreamIds: Decodable {
var continuation: String? let continuation: String?
var ids: [String] let ids: [String]
var isStreamEnd: Bool { var isStreamEnd: Bool {
return continuation == nil return continuation == nil

View File

@ -9,6 +9,6 @@
import Foundation import Foundation
struct FeedlyTag: Decodable { struct FeedlyTag: Decodable {
var id: String let id: String
var label: String? let label: String?
} }

View File

@ -18,7 +18,7 @@ class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate,
init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, 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)
let (folder, collectionId) = try validator.getValidContainer() let (folder, collectionId) = try validator.getValidContainer()
self.operationQueue = MainThreadOperationQueue() self.operationQueue = MainThreadOperationQueue()

View File

@ -30,7 +30,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws { init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws {
let validator = FeedlyFeedContainerValidator(container: container, userId: credentials.username) let validator = FeedlyFeedContainerValidator(container: container)
(self.folder, self.collectionId) = try validator.getValidContainer() (self.folder, self.collectionId) = try validator.getValidContainer()
self.url = url self.url = url
@ -83,28 +83,28 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
let addRequest = FeedlyAddFeedToCollectionOperation(account: account, folder: folder, feedResource: feedResourceId, feedName: feedName, collectionId: collectionId, service: addToCollectionService) let addRequest = FeedlyAddFeedToCollectionOperation(account: account, folder: folder, feedResource: feedResourceId, feedName: feedName, collectionId: collectionId, service: addToCollectionService)
addRequest.delegate = self addRequest.delegate = self
addRequest.downloadProgress = downloadProgress addRequest.downloadProgress = downloadProgress
self.operationQueue.addOperation(addRequest) operationQueue.addOperation(addRequest)
let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log) let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: addRequest, log: log)
operationQueue.make(createFeeds, dependOn: addRequest) operationQueue.make(createFeeds, dependOn: addRequest)
createFeeds.downloadProgress = downloadProgress createFeeds.downloadProgress = downloadProgress
self.operationQueue.addOperation(createFeeds) operationQueue.addOperation(createFeeds)
let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: syncUnreadIdsService, database: database, newerThan: nil, log: log) let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, credentials: credentials, service: syncUnreadIdsService, database: database, newerThan: nil, log: log)
operationQueue.make(syncUnread, dependOn: createFeeds) operationQueue.make(syncUnread, dependOn: createFeeds)
syncUnread.downloadProgress = downloadProgress syncUnread.downloadProgress = downloadProgress
self.operationQueue.addOperation(syncUnread) operationQueue.addOperation(syncUnread)
let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: feedResourceId, service: getStreamContentsService, newerThan: nil, log: log) let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: feedResourceId, service: getStreamContentsService, isPagingEnabled: false, newerThan: nil, log: log)
operationQueue.make(syncFeed, dependOn: syncUnread) operationQueue.make(syncFeed, dependOn: syncUnread)
syncFeed.downloadProgress = downloadProgress syncFeed.downloadProgress = downloadProgress
self.operationQueue.addOperation(syncFeed) operationQueue.addOperation(syncFeed)
let finishOperation = FeedlyCheckpointOperation() let finishOperation = FeedlyCheckpointOperation()
finishOperation.checkpointDelegate = self finishOperation.checkpointDelegate = self
finishOperation.downloadProgress = downloadProgress finishOperation.downloadProgress = downloadProgress
operationQueue.make(finishOperation, dependOn: syncFeed) operationQueue.make(finishOperation, dependOn: syncFeed)
self.operationQueue.addOperation(finishOperation) operationQueue.addOperation(finishOperation)
} }
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) { func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {

View File

@ -17,13 +17,15 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
private let operationQueue = MainThreadOperationQueue() private let operationQueue = MainThreadOperationQueue()
private let service: FeedlyGetStreamContentsService private let service: FeedlyGetStreamContentsService
private let newerThan: Date? private let newerThan: Date?
private let isPagingEnabled: Bool
private let log: OSLog private let log: OSLog
private let finishOperation: FeedlyCheckpointOperation private let finishOperation: FeedlyCheckpointOperation
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, newerThan: Date?, log: OSLog) { init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, isPagingEnabled: Bool, newerThan: Date?, log: OSLog) {
self.account = account self.account = account
self.resource = resource self.resource = resource
self.service = service self.service = service
self.isPagingEnabled = isPagingEnabled
self.operationQueue.suspend() self.operationQueue.suspend()
self.newerThan = newerThan self.newerThan = newerThan
self.log = log self.log = log
@ -38,7 +40,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, newerThan: Date?, log: OSLog) { convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, newerThan: Date?, log: OSLog) {
let all = FeedlyCategoryResourceId.Global.all(for: credentials.username) let all = FeedlyCategoryResourceId.Global.all(for: credentials.username)
self.init(account: account, resource: all, service: service, newerThan: newerThan, log: log) self.init(account: account, resource: all, service: service, isPagingEnabled: true, newerThan: newerThan, log: log)
} }
override func cancel() { override func cancel() {
@ -98,7 +100,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
os_log(.debug, log: log, "Ingesting %i items from %@", stream.items.count, stream.id) os_log(.debug, log: log, "Ingesting %i items from %@", stream.items.count, stream.id)
guard let continuation = stream.continuation else { guard isPagingEnabled, let continuation = stream.continuation else {
os_log(.debug, log: log, "Reached end of stream for %@", stream.id) os_log(.debug, log: log, "Reached end of stream for %@", stream.id)
return return
} }