mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Continue changing webFeed to feed.
This commit is contained in:
@@ -139,7 +139,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
}
|
||||
|
||||
public var topLevelWebFeeds = Set<WebFeed>()
|
||||
public var topLevelFeeds = Set<WebFeed>()
|
||||
public var folders: Set<Folder>? = Set<Folder>()
|
||||
|
||||
public var externalID: String? {
|
||||
@@ -158,24 +158,24 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return nil
|
||||
}
|
||||
|
||||
private var webFeedDictionariesNeedUpdate = true
|
||||
private var _idToWebFeedDictionary = [String: WebFeed]()
|
||||
var idToWebFeedDictionary: [String: WebFeed] {
|
||||
if webFeedDictionariesNeedUpdate {
|
||||
rebuildWebFeedDictionaries()
|
||||
private var feedDictionariesNeedUpdate = true
|
||||
private var _idToFeedDictionary = [String: WebFeed]()
|
||||
var idToFeedDictionary: [String: WebFeed] {
|
||||
if feedDictionariesNeedUpdate {
|
||||
rebuildFeedDictionaries()
|
||||
}
|
||||
return _idToWebFeedDictionary
|
||||
return _idToFeedDictionary
|
||||
}
|
||||
private var _externalIDToWebFeedDictionary = [String: WebFeed]()
|
||||
var externalIDToWebFeedDictionary: [String: WebFeed] {
|
||||
if webFeedDictionariesNeedUpdate {
|
||||
rebuildWebFeedDictionaries()
|
||||
private var _externalIDToFeedDictionary = [String: WebFeed]()
|
||||
var externalIDToFeedDictionary: [String: WebFeed] {
|
||||
if feedDictionariesNeedUpdate {
|
||||
rebuildFeedDictionaries()
|
||||
}
|
||||
return _externalIDToWebFeedDictionary
|
||||
return _externalIDToFeedDictionary
|
||||
}
|
||||
|
||||
var flattenedWebFeedURLs: Set<String> {
|
||||
return Set(flattenedWebFeeds().map({ $0.url }))
|
||||
var flattenedFeedURLs: Set<String> {
|
||||
return Set(flattenedFeeds().map({ $0.url }))
|
||||
}
|
||||
|
||||
var username: String? {
|
||||
@@ -210,8 +210,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
private var unreadCounts = [String: Int]() // [feedID: Int]
|
||||
|
||||
private var _flattenedWebFeeds = Set<WebFeed>()
|
||||
private var flattenedWebFeedsNeedUpdate = true
|
||||
private var _flattenedFeeds = Set<WebFeed>()
|
||||
private var flattenedFeedsNeedUpdate = true
|
||||
|
||||
private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
|
||||
private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self)
|
||||
@@ -331,7 +331,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
self.metadata.performedApril2020RetentionPolicyChange = true
|
||||
}
|
||||
|
||||
self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedWebFeeds().webFeedIDs())
|
||||
self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().webFeedIDs())
|
||||
self.fetchAllUnreadCounts()
|
||||
}
|
||||
|
||||
@@ -496,7 +496,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
func addOPMLItems(_ items: [RSOPMLItem]) {
|
||||
for item in items {
|
||||
if let feedSpecifier = item.feedSpecifier {
|
||||
addWebFeed(newWebFeed(with: feedSpecifier))
|
||||
addFeed(newFeed(with: feedSpecifier))
|
||||
} else {
|
||||
if let title = item.titleFromAttributes, let folder = ensureFolder(with: title) {
|
||||
folder.externalID = item.attributes?["nnw_externalID"] as? String
|
||||
@@ -505,7 +505,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
item.children?.forEach { itemChild in
|
||||
if let feedSpecifier = itemChild.feedSpecifier {
|
||||
folder.addWebFeed(newWebFeed(with: feedSpecifier))
|
||||
folder.addFeed(newFeed(with: feedSpecifier))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,13 +524,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return existingFolder(withExternalID: externalID)
|
||||
}
|
||||
|
||||
func existingContainers(withWebFeed webFeed: WebFeed) -> [Container] {
|
||||
func existingContainers(withFeed feed: WebFeed) -> [Container] {
|
||||
var containers = [Container]()
|
||||
if topLevelWebFeeds.contains(webFeed) {
|
||||
if topLevelFeeds.contains(feed) {
|
||||
containers.append(self)
|
||||
}
|
||||
folders?.forEach { folder in
|
||||
if folder.topLevelWebFeeds.contains(webFeed) {
|
||||
if folder.topLevelFeeds.contains(feed) {
|
||||
containers.append(folder)
|
||||
}
|
||||
}
|
||||
@@ -575,9 +575,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return folders?.first(where: { $0.externalID == externalID })
|
||||
}
|
||||
|
||||
func newWebFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> WebFeed {
|
||||
func newFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> WebFeed {
|
||||
let feedURL = opmlFeedSpecifier.feedURL
|
||||
let metadata = feedMetadata(feedURL: feedURL, webFeedID: feedURL)
|
||||
let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL)
|
||||
let feed = WebFeed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)
|
||||
if let feedTitle = opmlFeedSpecifier.title {
|
||||
if feed.name == nil {
|
||||
@@ -587,36 +587,36 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return feed
|
||||
}
|
||||
|
||||
public func addWebFeed(_ feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.addWebFeed(for: self, with: feed, to: container, completion: completion)
|
||||
public func addFeed(_ feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.addFeed(for: self, with: feed, to: container, completion: completion)
|
||||
}
|
||||
|
||||
public func createWebFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
delegate.createWebFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion)
|
||||
public func createFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
delegate.createFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion)
|
||||
}
|
||||
|
||||
func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> WebFeed {
|
||||
let metadata = feedMetadata(feedURL: url, webFeedID: webFeedID)
|
||||
func createFeed(with name: String?, url: String, feedID: String, homePageURL: String?) -> WebFeed {
|
||||
let metadata = feedMetadata(feedURL: url, feedID: feedID)
|
||||
let feed = WebFeed(account: self, url: url, metadata: metadata)
|
||||
feed.name = name
|
||||
feed.homePageURL = homePageURL
|
||||
return feed
|
||||
}
|
||||
|
||||
public func removeWebFeed(_ feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.removeWebFeed(for: self, with: feed, from: container, completion: completion)
|
||||
public func removeFeed(_ feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.removeFeed(for: self, with: feed, from: container, completion: completion)
|
||||
}
|
||||
|
||||
public func moveWebFeed(_ feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.moveWebFeed(for: self, with: feed, from: from, to: to, completion: completion)
|
||||
public func moveFeed(_ feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.moveFeed(for: self, with: feed, from: from, to: to, completion: completion)
|
||||
}
|
||||
|
||||
public func renameWebFeed(_ feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.renameWebFeed(for: self, with: feed, to: name, completion: completion)
|
||||
public func renameFeed(_ feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.renameFeed(for: self, with: feed, to: name, completion: completion)
|
||||
}
|
||||
|
||||
public func restoreWebFeed(_ feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.restoreWebFeed(for: self, feed: feed, container: container, completion: completion)
|
||||
public func restoreFeed(_ feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.restoreFeed(for: self, feed: feed, container: container, completion: completion)
|
||||
}
|
||||
|
||||
public func addFolder(_ name: String, completion: @escaping (Result<Folder, Error>) -> Void) {
|
||||
@@ -649,8 +649,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
structureDidChange()
|
||||
}
|
||||
|
||||
public func updateUnreadCounts(for webFeeds: Set<WebFeed>, completion: VoidCompletionBlock? = nil) {
|
||||
fetchUnreadCounts(for: webFeeds, completion: completion)
|
||||
public func updateUnreadCounts(for feeds: Set<WebFeed>, completion: VoidCompletionBlock? = nil) {
|
||||
fetchUnreadCounts(for: feeds, completion: completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesBetween(limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
|
||||
@@ -661,8 +661,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return try fetchUnreadArticlesBetween(forContainer: folder, limit: limit, before: before, after: after)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesBetween(webFeeds: Set<WebFeed>, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
|
||||
return try fetchUnreadArticlesBetween(feeds: webFeeds, limit: limit, before: before, after: after)
|
||||
public func fetchUnreadArticlesBetween(feeds: Set<WebFeed>, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
|
||||
let articles = try database.fetchUnreadArticlesBetween(feeds.webFeedIDs(), limit, before, after)
|
||||
return articles
|
||||
}
|
||||
|
||||
public func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?) throws -> Set<Article> {
|
||||
@@ -684,7 +685,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return try fetchArticles(folder: folder)
|
||||
}
|
||||
case .webFeed(let webFeed):
|
||||
return try fetchArticles(webFeed: webFeed)
|
||||
return try fetchArticles(feed: webFeed)
|
||||
case .articleIDs(let articleIDs):
|
||||
return try fetchArticles(articleIDs: articleIDs)
|
||||
case .search(let searchString):
|
||||
@@ -709,7 +710,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return fetchArticlesAsync(folder: folder, completion)
|
||||
}
|
||||
case .webFeed(let webFeed):
|
||||
fetchArticlesAsync(webFeed: webFeed, completion)
|
||||
fetchArticlesAsync(feed: webFeed, completion)
|
||||
case .articleIDs(let articleIDs):
|
||||
fetchArticlesAsync(articleIDs: articleIDs, completion)
|
||||
case .search(let searchString):
|
||||
@@ -720,15 +721,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForToday(_ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
database.fetchUnreadCountForToday(for: flattenedWebFeeds().webFeedIDs(), completion: completion)
|
||||
database.fetchUnreadCountForToday(for: flattenedFeeds().webFeedIDs(), completion: completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForStarredArticles(_ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
database.fetchStarredAndUnreadCount(for: flattenedWebFeeds().webFeedIDs(), completion: completion)
|
||||
database.fetchStarredAndUnreadCount(for: flattenedFeeds().webFeedIDs(), completion: completion)
|
||||
}
|
||||
|
||||
public func fetchCountForStarredArticles() throws -> Int {
|
||||
return try database.fetchStarredArticlesCount(flattenedWebFeeds().webFeedIDs())
|
||||
return try database.fetchStarredArticlesCount(flattenedFeeds().webFeedIDs())
|
||||
}
|
||||
|
||||
public func fetchUnreadArticleIDs(_ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
@@ -744,8 +745,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
database.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(completion)
|
||||
}
|
||||
|
||||
public func unreadCount(for webFeed: WebFeed) -> Int {
|
||||
return unreadCounts[webFeed.webFeedID] ?? 0
|
||||
public func unreadCount(for feed: WebFeed) -> Int {
|
||||
return unreadCounts[feed.webFeedID] ?? 0
|
||||
}
|
||||
|
||||
public func setUnreadCount(_ unreadCount: Int, for webFeed: WebFeed) {
|
||||
@@ -756,31 +757,31 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
// Feeds were added or deleted. Or folders added or deleted.
|
||||
// Or feeds inside folders were added or deleted.
|
||||
opmlFile.markAsDirty()
|
||||
flattenedWebFeedsNeedUpdate = true
|
||||
webFeedDictionariesNeedUpdate = true
|
||||
flattenedFeedsNeedUpdate = true
|
||||
feedDictionariesNeedUpdate = true
|
||||
}
|
||||
|
||||
func update(_ webFeed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
func update(_ feed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
// Used only by an On My Mac or iCloud account.
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(type == .onMyMac || type == .cloudKit)
|
||||
|
||||
webFeed.takeSettings(from: parsedFeed)
|
||||
feed.takeSettings(from: parsedFeed)
|
||||
let parsedItems = parsedFeed.items
|
||||
guard !parsedItems.isEmpty else {
|
||||
completion(.success(ArticleChanges()))
|
||||
return
|
||||
}
|
||||
|
||||
update(webFeed.webFeedID, with: parsedItems, completion: completion)
|
||||
update(feed.webFeedID, with: parsedItems, completion: completion)
|
||||
}
|
||||
|
||||
func update(_ webFeedID: String, with parsedItems: Set<ParsedItem>, deleteOlder: Bool = true, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
func update(_ feedID: String, with parsedItems: Set<ParsedItem>, deleteOlder: Bool = true, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
// Used only by an On My Mac or iCloud account.
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(type == .onMyMac || type == .cloudKit)
|
||||
|
||||
database.update(with: parsedItems, feedID: webFeedID, deleteOlder: deleteOlder) { updateArticlesResult in
|
||||
database.update(with: parsedItems, feedID: feedID, deleteOlder: deleteOlder) { updateArticlesResult in
|
||||
switch updateArticlesResult {
|
||||
case .success(let articleChanges):
|
||||
self.sendNotificationAbout(articleChanges)
|
||||
@@ -791,16 +792,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
}
|
||||
|
||||
func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) {
|
||||
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) {
|
||||
// Used only by syncing systems.
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(type != .onMyMac && type != .cloudKit)
|
||||
guard !webFeedIDsAndItems.isEmpty else {
|
||||
guard !feedIDsAndItems.isEmpty else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
database.update(feedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
|
||||
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
|
||||
switch updateArticlesResult {
|
||||
case .success(let newAndUpdatedArticles):
|
||||
self.sendNotificationAbout(newAndUpdatedArticles)
|
||||
@@ -906,38 +907,38 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
// MARK: - Container
|
||||
|
||||
public func flattenedWebFeeds() -> Set<WebFeed> {
|
||||
public func flattenedFeeds() -> Set<WebFeed> {
|
||||
assert(Thread.isMainThread)
|
||||
if flattenedWebFeedsNeedUpdate {
|
||||
updateFlattenedWebFeeds()
|
||||
if flattenedFeedsNeedUpdate {
|
||||
updateFlattenedFeeds()
|
||||
}
|
||||
return _flattenedWebFeeds
|
||||
return _flattenedFeeds
|
||||
}
|
||||
|
||||
public func removeWebFeed(_ webFeed: WebFeed) {
|
||||
topLevelWebFeeds.remove(webFeed)
|
||||
public func removeFeed(_ feed: WebFeed) {
|
||||
topLevelFeeds.remove(feed)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func removeFeeds(_ webFeeds: Set<WebFeed>) {
|
||||
guard !webFeeds.isEmpty else {
|
||||
public func removeFeeds(_ feeds: Set<WebFeed>) {
|
||||
guard !feeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelWebFeeds.subtract(webFeeds)
|
||||
topLevelFeeds.subtract(feeds)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func addWebFeed(_ webFeed: WebFeed) {
|
||||
topLevelWebFeeds.insert(webFeed)
|
||||
public func addFeed(_ feed: WebFeed) {
|
||||
topLevelFeeds.insert(feed)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
func addFeedIfNotInAnyFolder(_ webFeed: WebFeed) {
|
||||
if !flattenedWebFeeds().contains(webFeed) {
|
||||
addWebFeed(webFeed)
|
||||
func addFeedIfNotInAnyFolder(_ feed: WebFeed) {
|
||||
if !flattenedFeeds().contains(feed) {
|
||||
addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -951,7 +952,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
public func debugDropConditionalGetInfo() {
|
||||
#if DEBUG
|
||||
flattenedWebFeeds().forEach{ $0.dropConditionalGetInfo() }
|
||||
flattenedFeeds().forEach{ $0.dropConditionalGetInfo() }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -983,8 +984,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
@objc func batchUpdateDidPerform(_ note: Notification) {
|
||||
flattenedWebFeedsNeedUpdate = true
|
||||
rebuildWebFeedDictionaries()
|
||||
flattenedFeedsNeedUpdate = true
|
||||
rebuildFeedDictionaries()
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
@@ -1034,7 +1035,7 @@ extension Account: FeedMetadataDelegate {
|
||||
|
||||
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
|
||||
feedMetadataFile.markAsDirty()
|
||||
guard let feed = existingWebFeed(withWebFeedID: feedMetadata.feedID) else {
|
||||
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
|
||||
return
|
||||
}
|
||||
feed.postFeedSettingDidChangeNotification(key)
|
||||
@@ -1046,11 +1047,11 @@ extension Account: FeedMetadataDelegate {
|
||||
private extension Account {
|
||||
|
||||
func fetchStarredArticles(limit: Int?) throws -> Set<Article> {
|
||||
return try database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs(), limit)
|
||||
return try database.fetchStarredArticles(flattenedFeeds().webFeedIDs(), limit)
|
||||
}
|
||||
|
||||
func fetchStarredArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), limit, completion)
|
||||
database.fetchedStarredArticlesAsync(flattenedFeeds().webFeedIDs(), limit, completion)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(limit: Int?) throws -> Set<Article> {
|
||||
@@ -1062,11 +1063,11 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchTodayArticles(limit: Int?) throws -> Set<Article> {
|
||||
return try database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs(), limit)
|
||||
return try database.fetchTodayArticles(flattenedFeeds().webFeedIDs(), limit)
|
||||
}
|
||||
|
||||
func fetchTodayArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), limit, completion)
|
||||
database.fetchTodayArticlesAsync(flattenedFeeds().webFeedIDs(), limit, completion)
|
||||
}
|
||||
|
||||
func fetchArticles(folder: Folder) throws -> Set<Article> {
|
||||
@@ -1085,17 +1086,17 @@ private extension Account {
|
||||
fetchUnreadArticlesAsync(forContainer: folder, limit: nil, completion)
|
||||
}
|
||||
|
||||
func fetchArticles(webFeed: WebFeed) throws -> Set<Article> {
|
||||
let articles = try database.fetchArticles(webFeed.webFeedID)
|
||||
validateUnreadCount(webFeed, articles)
|
||||
func fetchArticles(feed: WebFeed) throws -> Set<Article> {
|
||||
let articles = try database.fetchArticles(feed.webFeedID)
|
||||
validateUnreadCount(feed, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(webFeed: WebFeed, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchArticlesAsync(webFeed.webFeedID) { [weak self] articleSetResult in
|
||||
func fetchArticlesAsync(feed: WebFeed, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchArticlesAsync(feed.webFeedID) { [weak self] articleSetResult in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
self?.validateUnreadCount(webFeed, articles)
|
||||
self?.validateUnreadCount(feed, articles)
|
||||
completion(.success(articles))
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
@@ -1104,7 +1105,7 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String) throws -> Set<Article> {
|
||||
return try database.fetchArticlesMatching(searchString, flattenedWebFeeds().webFeedIDs())
|
||||
return try database.fetchArticlesMatching(searchString, flattenedFeeds().webFeedIDs())
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) throws -> Set<Article> {
|
||||
@@ -1112,7 +1113,7 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchArticlesMatchingAsync(searchString, flattenedWebFeeds().webFeedIDs(), completion)
|
||||
database.fetchArticlesMatchingAsync(searchString, flattenedFeeds().webFeedIDs(), completion)
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
@@ -1127,25 +1128,25 @@ private extension Account {
|
||||
return database.fetchArticlesAsync(articleIDs: articleIDs, completion)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(webFeed: WebFeed) throws -> Set<Article> {
|
||||
let articles = try database.fetchUnreadArticles(Set([webFeed.webFeedID]), nil)
|
||||
validateUnreadCount(webFeed, articles)
|
||||
func fetchUnreadArticles(feed: WebFeed) throws -> Set<Article> {
|
||||
let articles = try database.fetchUnreadArticles(Set([feed.webFeedID]), nil)
|
||||
validateUnreadCount(feed, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticles(forContainer container: Container) throws -> Set<Article> {
|
||||
let feeds = container.flattenedWebFeeds()
|
||||
let feeds = container.flattenedFeeds()
|
||||
let articles = try database.fetchArticles(feeds.webFeedIDs())
|
||||
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) {
|
||||
let webFeeds = container.flattenedWebFeeds()
|
||||
database.fetchArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articleSetResult) in
|
||||
let feeds = container.flattenedFeeds()
|
||||
database.fetchArticlesAsync(feeds.webFeedIDs()) { [weak self] (articleSetResult) in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
completion(.success(articles))
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
@@ -1154,7 +1155,7 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(forContainer container: Container, limit: Int?) throws -> Set<Article> {
|
||||
let feeds = container.flattenedWebFeeds()
|
||||
let feeds = container.flattenedFeeds()
|
||||
let articles = try database.fetchUnreadArticles(feeds.webFeedIDs(), limit)
|
||||
|
||||
// We don't validate limit queries because they, by definition, won't correctly match the
|
||||
@@ -1167,26 +1168,21 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesBetween(forContainer container: Container, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
|
||||
let feeds = container.flattenedWebFeeds()
|
||||
let articles = try database.fetchUnreadArticlesBetween(feeds.webFeedIDs(), limit, before, after)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesBetween(feeds: Set<WebFeed>, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
|
||||
let feeds = container.flattenedFeeds()
|
||||
let articles = try database.fetchUnreadArticlesBetween(feeds.webFeedIDs(), limit, before, after)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
let webFeeds = container.flattenedWebFeeds()
|
||||
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs(), limit) { [weak self] (articleSetResult) in
|
||||
let feeds = container.flattenedFeeds()
|
||||
database.fetchUnreadArticlesAsync(feeds.webFeedIDs(), limit) { [weak self] (articleSetResult) in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
|
||||
// We don't validate limit queries because they, by definition, won't correctly match the
|
||||
// complete unread state for the given container.
|
||||
if limit == nil {
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
}
|
||||
|
||||
completion(.success(articles))
|
||||
@@ -1196,7 +1192,7 @@ private extension Account {
|
||||
}
|
||||
}
|
||||
|
||||
func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set<WebFeed>, _ articles: Set<Article>) {
|
||||
func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set<WebFeed>, _ articles: Set<Article>) {
|
||||
// Validate unread counts. This was the site of a performance slowdown:
|
||||
// it was calling going through the entire list of articles once per feed:
|
||||
// feeds.forEach { validateUnreadCount($0, articles) }
|
||||
@@ -1206,24 +1202,24 @@ private extension Account {
|
||||
for article in articles where !article.status.read {
|
||||
unreadCountStorage[article.feedID, default: 0] += 1
|
||||
}
|
||||
webFeeds.forEach { (webFeed) in
|
||||
let unreadCount = unreadCountStorage[webFeed.webFeedID, default: 0]
|
||||
webFeed.unreadCount = unreadCount
|
||||
feeds.forEach { (feed) in
|
||||
let unreadCount = unreadCountStorage[feed.webFeedID, default: 0]
|
||||
feed.unreadCount = unreadCount
|
||||
}
|
||||
}
|
||||
|
||||
func validateUnreadCount(_ webFeed: WebFeed, _ articles: Set<Article>) {
|
||||
func validateUnreadCount(_ feed: WebFeed, _ articles: Set<Article>) {
|
||||
// articles must contain all the unread articles for the feed.
|
||||
// The unread number should match the feed’s unread count.
|
||||
|
||||
let feedUnreadCount = articles.reduce(0) { (result, article) -> Int in
|
||||
if article.feed == webFeed && !article.status.read {
|
||||
if article.feed == feed && !article.status.read {
|
||||
return result + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
webFeed.unreadCount = feedUnreadCount
|
||||
feed.unreadCount = feedUnreadCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1231,42 +1227,42 @@ private extension Account {
|
||||
|
||||
private extension Account {
|
||||
|
||||
func feedMetadata(feedURL: String, webFeedID: String) -> FeedMetadata {
|
||||
func feedMetadata(feedURL: String, feedID: String) -> FeedMetadata {
|
||||
if let d = feedMetadata[feedURL] {
|
||||
assert(d.delegate === self)
|
||||
return d
|
||||
}
|
||||
let d = FeedMetadata(webFeedID: webFeedID)
|
||||
let d = FeedMetadata(feedID: feedID)
|
||||
d.delegate = self
|
||||
feedMetadata[feedURL] = d
|
||||
return d
|
||||
}
|
||||
|
||||
func updateFlattenedWebFeeds() {
|
||||
func updateFlattenedFeeds() {
|
||||
var feeds = Set<WebFeed>()
|
||||
feeds.formUnion(topLevelWebFeeds)
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
for folder in folders! {
|
||||
feeds.formUnion(folder.flattenedWebFeeds())
|
||||
feeds.formUnion(folder.flattenedFeeds())
|
||||
}
|
||||
|
||||
_flattenedWebFeeds = feeds
|
||||
flattenedWebFeedsNeedUpdate = false
|
||||
_flattenedFeeds = feeds
|
||||
flattenedFeedsNeedUpdate = false
|
||||
}
|
||||
|
||||
func rebuildWebFeedDictionaries() {
|
||||
func rebuildFeedDictionaries() {
|
||||
var idDictionary = [String: WebFeed]()
|
||||
var externalIDDictionary = [String: WebFeed]()
|
||||
|
||||
flattenedWebFeeds().forEach { (feed) in
|
||||
flattenedFeeds().forEach { (feed) in
|
||||
idDictionary[feed.webFeedID] = feed
|
||||
if let externalID = feed.externalID {
|
||||
externalIDDictionary[externalID] = feed
|
||||
}
|
||||
}
|
||||
|
||||
_idToWebFeedDictionary = idDictionary
|
||||
_externalIDToWebFeedDictionary = externalIDDictionary
|
||||
webFeedDictionariesNeedUpdate = false
|
||||
_idToFeedDictionary = idDictionary
|
||||
_externalIDToFeedDictionary = externalIDDictionary
|
||||
feedDictionariesNeedUpdate = false
|
||||
}
|
||||
|
||||
func updateUnreadCount() {
|
||||
@@ -1274,7 +1270,7 @@ private extension Account {
|
||||
return
|
||||
}
|
||||
var updatedUnreadCount = 0
|
||||
for feed in flattenedWebFeeds() {
|
||||
for feed in flattenedFeeds() {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
}
|
||||
unreadCount = updatedUnreadCount
|
||||
@@ -1331,8 +1327,8 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchUnreadCounts(_ feeds: Set<WebFeed>, _ completion: VoidCompletionBlock?) {
|
||||
let webFeedIDs = Set(feeds.map { $0.webFeedID })
|
||||
database.fetchUnreadCounts(for: webFeedIDs) { result in
|
||||
let feedIDs = Set(feeds.map { $0.webFeedID })
|
||||
database.fetchUnreadCounts(for: feedIDs) { result in
|
||||
if let unreadCountDictionary = try? result.get() {
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds)
|
||||
}
|
||||
@@ -1347,7 +1343,7 @@ private extension Account {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedWebFeeds())
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedFeeds())
|
||||
|
||||
self.fetchingAllUnreadCounts = false
|
||||
self.updateUnreadCount()
|
||||
@@ -1369,13 +1365,13 @@ private extension Account {
|
||||
}
|
||||
|
||||
func sendNotificationAbout(_ articleChanges: ArticleChanges) {
|
||||
var webFeeds = Set<WebFeed>()
|
||||
var feeds = Set<WebFeed>()
|
||||
|
||||
if let newArticles = articleChanges.newArticles {
|
||||
webFeeds.formUnion(Set(newArticles.compactMap { $0.feed }))
|
||||
feeds.formUnion(Set(newArticles.compactMap { $0.feed }))
|
||||
}
|
||||
if let updatedArticles = articleChanges.updatedArticles {
|
||||
webFeeds.formUnion(Set(updatedArticles.compactMap { $0.feed }))
|
||||
feeds.formUnion(Set(updatedArticles.compactMap { $0.feed }))
|
||||
}
|
||||
|
||||
var shouldSendNotification = false
|
||||
@@ -1398,11 +1394,11 @@ private extension Account {
|
||||
}
|
||||
|
||||
if shouldUpdateUnreadCounts {
|
||||
self.updateUnreadCounts(for: webFeeds)
|
||||
self.updateUnreadCounts(for: feeds)
|
||||
}
|
||||
|
||||
if shouldSendNotification {
|
||||
userInfo[UserInfoKey.webFeeds] = webFeeds
|
||||
userInfo[UserInfoKey.webFeeds] = feeds
|
||||
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
@@ -1412,12 +1408,12 @@ private extension Account {
|
||||
|
||||
extension Account {
|
||||
|
||||
public func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? {
|
||||
return idToWebFeedDictionary[webFeedID]
|
||||
public func existingFeed(withFeedID feedID: String) -> WebFeed? {
|
||||
return idToFeedDictionary[feedID]
|
||||
}
|
||||
|
||||
public func existingWebFeed(withExternalID externalID: String) -> WebFeed? {
|
||||
return externalIDToWebFeedDictionary[externalID]
|
||||
public func existingFeed(withExternalID externalID: String) -> WebFeed? {
|
||||
return externalIDToFeedDictionary[externalID]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1428,7 +1424,7 @@ extension Account: OPMLRepresentable {
|
||||
|
||||
public func OPMLString(indentLevel: Int, allowCustomAttributes: Bool) -> String {
|
||||
var s = ""
|
||||
for feed in topLevelWebFeeds.sorted() {
|
||||
for feed in topLevelFeeds.sorted() {
|
||||
s += feed.OPMLString(indentLevel: indentLevel + 1, allowCustomAttributes: allowCustomAttributes)
|
||||
}
|
||||
for folder in folders!.sorted() {
|
||||
|
||||
@@ -36,13 +36,13 @@ protocol AccountDelegate {
|
||||
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void)
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func addWebFeed(for account: Account, with: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void)
|
||||
func renameFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func addFeed(for account: Account, with: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func removeFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func moveFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func restoreFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
@@ -208,7 +208,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
}
|
||||
case .feed(let accountID, let webFeedID):
|
||||
if let account = existingAccount(with: accountID) {
|
||||
return account.existingWebFeed(withWebFeedID: webFeedID)
|
||||
return account.existingFeed(withFeedID: webFeedID)
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -307,7 +307,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
public func anyAccountHasAtLeastOneFeed() -> Bool {
|
||||
for account in activeAccounts {
|
||||
if account.hasAtLeastOneWebFeed() {
|
||||
if account.hasAtLeastOneFeed() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -321,7 +321,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool {
|
||||
for account in activeAccounts {
|
||||
if let _ = account.existingWebFeed(withURL: urlString) {
|
||||
if let _ = account.existingFeed(withURL: urlString) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
for account in accounts {
|
||||
if account.type == .cloudKit || account.type == .onMyMac {
|
||||
for webfeed in account.flattenedWebFeeds() {
|
||||
for webfeed in account.flattenedFeeds() {
|
||||
if let components = URLComponents(string: webfeed.url), let host = components.host {
|
||||
if host == "twitter.com" { // Allow, for instance, blog.twitter.com, which might have an actual RSS feed
|
||||
return true
|
||||
@@ -355,7 +355,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
for account in accounts {
|
||||
if account.type == .cloudKit || account.type == .onMyMac {
|
||||
for webfeed in account.flattenedWebFeeds() {
|
||||
for webfeed in account.flattenedFeeds() {
|
||||
if feedRequiresRedditAPI(webfeed) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ extension WebFeed: ArticleFetcher {
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
|
||||
return try account?.fetchUnreadArticlesBetween(webFeeds: [self], limit: nil, before: before, after: after) ?? Set<Article>()
|
||||
return try account?.fetchUnreadArticlesBetween(feeds: [self], limit: nil, before: before, after: after) ?? Set<Article>()
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
|
||||
@@ -172,7 +172,7 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
guard let url = URL(string: urlString) else {
|
||||
completion(.failure(LocalAccountDelegateError.invalidParameter))
|
||||
return
|
||||
@@ -181,13 +181,13 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
let editedName = name == nil || name!.isEmpty ? nil : name
|
||||
|
||||
// Username should be part of the URL on new feed adds
|
||||
createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion)
|
||||
createRSSFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion)
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let editedName = name.isEmpty ? nil : name
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
accountZone.renameWebFeed(feed, editedName: editedName) { result in
|
||||
accountZone.renameFeed(feed, editedName: editedName) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -200,19 +200,19 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
removeWebFeedFromCloud(for: account, with: feed, from: container) { result in
|
||||
func removeFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
removeFeedFromCloud(for: account, with: feed, from: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
account.clearFeedMetadata(feed)
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case CloudKitZoneError.corruptAccount:
|
||||
// We got into a bad state and should remove the feed to clear up the bad data
|
||||
account.clearFeedMetadata(feed)
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
default:
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -220,14 +220,14 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveFeed(for account: Account, with feed: WebFeed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
accountZone.moveWebFeed(feed, from: fromContainer, to: toContainer) { result in
|
||||
accountZone.moveFeed(feed, from: fromContainer, to: toContainer) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
fromContainer.removeWebFeed(feed)
|
||||
toContainer.addWebFeed(feed)
|
||||
fromContainer.removeFeed(feed)
|
||||
toContainer.addFeed(feed)
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
self.processAccountError(account, error)
|
||||
@@ -236,13 +236,13 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
accountZone.addWebFeed(feed, to: container) { result in
|
||||
accountZone.addFeed(feed, to: container) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
container.addWebFeed(feed)
|
||||
container.addFeed(feed)
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
self.processAccountError(account, error)
|
||||
@@ -251,8 +251,8 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
func restoreFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -304,13 +304,13 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
switch result {
|
||||
case .success(let webFeedExternalIDs):
|
||||
|
||||
let webFeeds = webFeedExternalIDs.compactMap { account.existingWebFeed(withExternalID: $0) }
|
||||
let webFeeds = webFeedExternalIDs.compactMap { account.existingFeed(withExternalID: $0) }
|
||||
let group = DispatchGroup()
|
||||
var errorOccurred = false
|
||||
|
||||
for webFeed in webFeeds {
|
||||
group.enter()
|
||||
self.removeWebFeedFromCloud(for: account, with: webFeed, from: folder) { [weak self] result in
|
||||
self.removeFeedFromCloud(for: account, with: webFeed, from: folder) { [weak self] result in
|
||||
group.leave()
|
||||
if case .failure(let error) = result {
|
||||
self?.logger.error("Remove folder, remove webfeed error: \(error.localizedDescription, privacy: .public)")
|
||||
@@ -356,7 +356,7 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
return
|
||||
}
|
||||
|
||||
let feedsToRestore = folder.topLevelWebFeeds
|
||||
let feedsToRestore = folder.topLevelFeeds
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1 + feedsToRestore.count)
|
||||
|
||||
accountZone.createFolder(name: name) { result in
|
||||
@@ -369,10 +369,10 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
let group = DispatchGroup()
|
||||
for feed in feedsToRestore {
|
||||
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
self.restoreWebFeed(for: account, feed: feed, container: folder) { [weak self] result in
|
||||
self.restoreFeed(for: account, feed: feed, container: folder) { [weak self] result in
|
||||
self?.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
switch result {
|
||||
@@ -485,7 +485,7 @@ private extension CloudKitAccountDelegate {
|
||||
accountZone.fetchChangesInZone() { result in
|
||||
self.refreshProgress.completeTask()
|
||||
|
||||
let webFeeds = account.flattenedWebFeeds()
|
||||
let webFeeds = account.flattenedFeeds()
|
||||
self.refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count)
|
||||
|
||||
switch result {
|
||||
@@ -519,7 +519,7 @@ private extension CloudKitAccountDelegate {
|
||||
|
||||
func standardRefreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
let intialWebFeedsCount = account.flattenedWebFeeds().count
|
||||
let intialWebFeedsCount = account.flattenedFeeds().count
|
||||
refreshProgress.isIndeterminate = true
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(3 + intialWebFeedsCount)
|
||||
|
||||
@@ -534,7 +534,7 @@ private extension CloudKitAccountDelegate {
|
||||
case .success:
|
||||
|
||||
self.refreshProgress.completeTask()
|
||||
let webFeeds = account.flattenedWebFeeds()
|
||||
let webFeeds = account.flattenedFeeds()
|
||||
self.refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count - intialWebFeedsCount)
|
||||
|
||||
self.refreshArticleStatus(for: account) { result in
|
||||
@@ -579,13 +579,13 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
func addDeadFeed() {
|
||||
let feed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
container.addWebFeed(feed)
|
||||
let feed = account.createFeed(with: editedName, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
|
||||
container.addFeed(feed)
|
||||
|
||||
self.accountZone.createWebFeed(url: url.absoluteString,
|
||||
self.accountZone.createFeed(url: url.absoluteString,
|
||||
name: editedName,
|
||||
editedName: nil,
|
||||
homePageURL: nil,
|
||||
@@ -597,7 +597,7 @@ private extension CloudKitAccountDelegate {
|
||||
feed.externalID = externalID
|
||||
completion(.success(feed))
|
||||
case .failure(let error):
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
@@ -620,15 +620,15 @@ private extension CloudKitAccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
self.refreshProgress.completeTasks(4)
|
||||
completion(.failure(AccountError.createErrorAlreadySubscribed))
|
||||
return
|
||||
}
|
||||
|
||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
|
||||
feed.editedName = editedName
|
||||
container.addWebFeed(feed)
|
||||
container.addFeed(feed)
|
||||
|
||||
InitialFeedDownloader.download(url) { parsedFeed in
|
||||
self.refreshProgress.completeTask()
|
||||
@@ -638,7 +638,7 @@ private extension CloudKitAccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
|
||||
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString,
|
||||
self.accountZone.createFeed(url: bestFeedSpecifier.urlString,
|
||||
name: parsedFeed.title,
|
||||
editedName: editedName,
|
||||
homePageURL: parsedFeed.homePageURL,
|
||||
@@ -651,7 +651,7 @@ private extension CloudKitAccountDelegate {
|
||||
self.sendNewArticlesToTheCloud(account, feed)
|
||||
completion(.success(feed))
|
||||
case .failure(let error):
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
self.refreshProgress.completeTasks(2)
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -659,7 +659,7 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
self.refreshProgress.completeTasks(3)
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -667,7 +667,7 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
} else {
|
||||
self.refreshProgress.completeTasks(3)
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ private extension CloudKitAccountDelegate {
|
||||
|
||||
func processAccountError(_ account: Account, _ error: Error) {
|
||||
if case CloudKitZoneError.userDeletedZone = error {
|
||||
account.removeFeeds(account.topLevelWebFeeds)
|
||||
account.removeFeeds(account.topLevelFeeds)
|
||||
for folder in account.folders ?? Set<Folder>() {
|
||||
account.removeFolder(folder)
|
||||
}
|
||||
@@ -774,9 +774,9 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
|
||||
|
||||
func removeWebFeedFromCloud(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func removeFeedFromCloud(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(2)
|
||||
accountZone.removeWebFeed(feed, from: container) { result in
|
||||
accountZone.removeFeed(feed, from: container) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
|
||||
@@ -88,7 +88,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
|
||||
/// Persist a web feed record to iCloud and return the external key
|
||||
func createWebFeed(url: String, name: String?, editedName: String?, homePageURL: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
|
||||
func createFeed(url: String, name: String?, editedName: String?, homePageURL: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
|
||||
let recordID = CKRecord.ID(recordName: url.md5String, zoneID: zoneID)
|
||||
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
|
||||
record[CloudKitWebFeed.Fields.url] = url
|
||||
@@ -117,7 +117,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
|
||||
/// Rename the given web feed
|
||||
func renameWebFeed(_ webFeed: WebFeed, editedName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(_ webFeed: WebFeed, editedName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let externalID = webFeed.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
@@ -138,7 +138,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
|
||||
/// Removes a web feed from a container and optionally deletes it, calling the completion with true if deleted
|
||||
func removeWebFeed(_ webFeed: WebFeed, from: Container, completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
func removeFeed(_ webFeed: WebFeed, from: Container, completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
guard let fromContainerExternalID = from.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
@@ -187,7 +187,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeed(_ webFeed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveFeed(_ webFeed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let fromContainerExternalID = from.externalID, let toContainerExternalID = to.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
@@ -209,7 +209,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(_ webFeed: WebFeed, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(_ webFeed: WebFeed, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let toContainerExternalID = to.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
|
||||
@@ -35,7 +35,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
for deletedRecordKey in deleted {
|
||||
switch deletedRecordKey.recordType {
|
||||
case CloudKitAccountZone.CloudKitWebFeed.recordType:
|
||||
removeWebFeed(deletedRecordKey.recordID.externalID)
|
||||
removeFeed(deletedRecordKey.recordID.externalID)
|
||||
case CloudKitAccountZone.CloudKitContainer.recordType:
|
||||
removeContainer(deletedRecordKey.recordID.externalID)
|
||||
default:
|
||||
@@ -69,12 +69,12 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
let editedName = record[CloudKitAccountZone.CloudKitWebFeed.Fields.editedName] as? String
|
||||
let homePageURL = record[CloudKitAccountZone.CloudKitWebFeed.Fields.homePageURL] as? String
|
||||
|
||||
if let webFeed = account.existingWebFeed(withExternalID: record.externalID) {
|
||||
if let webFeed = account.existingFeed(withExternalID: record.externalID) {
|
||||
updateWebFeed(webFeed, name: name, editedName: editedName, homePageURL: homePageURL, containerExternalIDs: containerExternalIDs)
|
||||
} else {
|
||||
for containerExternalID in containerExternalIDs {
|
||||
if let container = account.existingContainer(withExternalID: containerExternalID) {
|
||||
createWebFeedIfNecessary(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, container: container)
|
||||
createFeedIfNecessary(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, container: container)
|
||||
} else {
|
||||
addNewUnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, containerExternalID: containerExternalID)
|
||||
}
|
||||
@@ -82,11 +82,11 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func removeWebFeed(_ externalID: String) {
|
||||
if let webFeed = account?.existingWebFeed(withExternalID: externalID), let containers = account?.existingContainers(withWebFeed: webFeed) {
|
||||
func removeFeed(_ externalID: String) {
|
||||
if let webFeed = account?.existingFeed(withExternalID: externalID), let containers = account?.existingContainers(withFeed: webFeed) {
|
||||
containers.forEach {
|
||||
webFeed.dropConditionalGetInfo()
|
||||
$0.removeWebFeed(webFeed)
|
||||
$0.removeFeed(webFeed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
|
||||
if let newUnclaimedWebFeeds = newUnclaimedWebFeeds[containerExternalID] {
|
||||
for newUnclaimedWebFeed in newUnclaimedWebFeeds {
|
||||
createWebFeedIfNecessary(url: newUnclaimedWebFeed.url,
|
||||
createFeedIfNecessary(url: newUnclaimedWebFeed.url,
|
||||
name: newUnclaimedWebFeed.name,
|
||||
editedName: newUnclaimedWebFeed.editedName,
|
||||
homePageURL: newUnclaimedWebFeed.homePageURL,
|
||||
@@ -124,7 +124,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
|
||||
if let existingUnclaimedWebFeeds = existingUnclaimedWebFeeds[containerExternalID] {
|
||||
for existingUnclaimedWebFeed in existingUnclaimedWebFeeds {
|
||||
container.addWebFeed(existingUnclaimedWebFeed)
|
||||
container.addFeed(existingUnclaimedWebFeed)
|
||||
}
|
||||
self.existingUnclaimedWebFeeds.removeValue(forKey: containerExternalID)
|
||||
}
|
||||
@@ -147,7 +147,7 @@ private extension CloudKitAcountZoneDelegate {
|
||||
webFeed.editedName = editedName
|
||||
webFeed.homePageURL = homePageURL
|
||||
|
||||
let existingContainers = account.existingContainers(withWebFeed: webFeed)
|
||||
let existingContainers = account.existingContainers(withFeed: webFeed)
|
||||
let existingContainerExternalIds = existingContainers.compactMap { $0.externalID }
|
||||
|
||||
let diff = containerExternalIDs.difference(from: existingContainerExternalIds)
|
||||
@@ -156,11 +156,11 @@ private extension CloudKitAcountZoneDelegate {
|
||||
switch change {
|
||||
case .remove(_, let externalID, _):
|
||||
if let container = existingContainers.first(where: { $0.externalID == externalID }) {
|
||||
container.removeWebFeed(webFeed)
|
||||
container.removeFeed(webFeed)
|
||||
}
|
||||
case .insert(_, let externalID, _):
|
||||
if let container = account.existingContainer(withExternalID: externalID) {
|
||||
container.addWebFeed(webFeed)
|
||||
container.addFeed(webFeed)
|
||||
} else {
|
||||
addExistingUnclaimedWebFeed(webFeed, containerExternalID: externalID)
|
||||
}
|
||||
@@ -168,17 +168,17 @@ private extension CloudKitAcountZoneDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func createWebFeedIfNecessary(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, container: Container) {
|
||||
func createFeedIfNecessary(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, container: Container) {
|
||||
guard let account = account else { return }
|
||||
|
||||
if account.existingWebFeed(withExternalID: webFeedExternalID) != nil {
|
||||
if account.existingFeed(withExternalID: webFeedExternalID) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let webFeed = account.createWebFeed(with: name, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: homePageURL)
|
||||
let webFeed = account.createFeed(with: name, url: url.absoluteString, feedID: url.absoluteString, homePageURL: homePageURL)
|
||||
webFeed.editedName = editedName
|
||||
webFeed.externalID = webFeedExternalID
|
||||
container.addWebFeed(webFeed)
|
||||
container.addFeed(webFeed)
|
||||
}
|
||||
|
||||
func addNewUnclaimedWebFeed(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, containerExternalID: String) {
|
||||
|
||||
@@ -144,10 +144,10 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
group.enter()
|
||||
compressionQueue.async {
|
||||
let parsedItems = records.compactMap { self.makeParsedItem($0) }
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
for (webFeedID, parsedItems) in webFeedIDsAndItems {
|
||||
for (webFeedID, parsedItems) in feedIDsAndItems {
|
||||
group.enter()
|
||||
self.account?.update(webFeedID, with: parsedItems, deleteOlder: false) { result in
|
||||
switch result {
|
||||
|
||||
@@ -169,7 +169,7 @@ private extension CloudKitSendStatusOperation {
|
||||
|
||||
func processAccountError(_ account: Account, _ error: Error) {
|
||||
if case CloudKitZoneError.userDeletedZone = error {
|
||||
account.removeFeeds(account.topLevelWebFeeds)
|
||||
account.removeFeeds(account.topLevelFeeds)
|
||||
for folder in account.folders ?? Set<Folder>() {
|
||||
account.removeFolder(folder)
|
||||
}
|
||||
|
||||
@@ -19,27 +19,27 @@ extension Notification.Name {
|
||||
public protocol Container: AnyObject, ContainerIdentifiable {
|
||||
|
||||
var account: Account? { get }
|
||||
var topLevelWebFeeds: Set<WebFeed> { get set }
|
||||
var topLevelFeeds: Set<WebFeed> { get set }
|
||||
var folders: Set<Folder>? { get set }
|
||||
var externalID: String? { get set }
|
||||
|
||||
func hasAtLeastOneWebFeed() -> Bool
|
||||
func hasAtLeastOneFeed() -> Bool
|
||||
func objectIsChild(_ object: AnyObject) -> Bool
|
||||
|
||||
func hasChildFolder(with: String) -> Bool
|
||||
func childFolder(with: String) -> Folder?
|
||||
|
||||
func removeWebFeed(_ webFeed: WebFeed)
|
||||
func addWebFeed(_ webFeed: WebFeed)
|
||||
func removeFeed(_ webFeed: WebFeed)
|
||||
func addFeed(_ webFeed: WebFeed)
|
||||
|
||||
//Recursive — checks subfolders
|
||||
func flattenedWebFeeds() -> Set<WebFeed>
|
||||
func flattenedFeeds() -> Set<WebFeed>
|
||||
func has(_ webFeed: WebFeed) -> Bool
|
||||
func hasWebFeed(with webFeedID: String) -> Bool
|
||||
func hasWebFeed(withURL url: String) -> Bool
|
||||
func existingWebFeed(withWebFeedID: String) -> WebFeed?
|
||||
func existingWebFeed(withURL url: String) -> WebFeed?
|
||||
func existingWebFeed(withExternalID externalID: String) -> WebFeed?
|
||||
func hasFeed(with feedID: String) -> Bool
|
||||
func hasFeed(withURL url: String) -> Bool
|
||||
func existingFeed(withFeedID: String) -> WebFeed?
|
||||
func existingFeed(withURL url: String) -> WebFeed?
|
||||
func existingFeed(withExternalID externalID: String) -> WebFeed?
|
||||
func existingFolder(with name: String) -> Folder?
|
||||
func existingFolder(withID: Int) -> Folder?
|
||||
|
||||
@@ -48,8 +48,8 @@ public protocol Container: AnyObject, ContainerIdentifiable {
|
||||
|
||||
public extension Container {
|
||||
|
||||
func hasAtLeastOneWebFeed() -> Bool {
|
||||
return topLevelWebFeeds.count > 0
|
||||
func hasAtLeastOneFeed() -> Bool {
|
||||
return topLevelFeeds.count > 0
|
||||
}
|
||||
|
||||
func hasChildFolder(with name: String) -> Bool {
|
||||
@@ -70,7 +70,7 @@ public extension Container {
|
||||
|
||||
func objectIsChild(_ object: AnyObject) -> Bool {
|
||||
if let feed = object as? WebFeed {
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
if let folder = object as? Folder {
|
||||
return folders?.contains(folder) ?? false
|
||||
@@ -78,40 +78,40 @@ public extension Container {
|
||||
return false
|
||||
}
|
||||
|
||||
func flattenedWebFeeds() -> Set<WebFeed> {
|
||||
func flattenedFeeds() -> Set<WebFeed> {
|
||||
var feeds = Set<WebFeed>()
|
||||
feeds.formUnion(topLevelWebFeeds)
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
if let folders = folders {
|
||||
for folder in folders {
|
||||
feeds.formUnion(folder.flattenedWebFeeds())
|
||||
feeds.formUnion(folder.flattenedFeeds())
|
||||
}
|
||||
}
|
||||
return feeds
|
||||
}
|
||||
|
||||
func hasWebFeed(with webFeedID: String) -> Bool {
|
||||
return existingWebFeed(withWebFeedID: webFeedID) != nil
|
||||
func hasFeed(with feedID: String) -> Bool {
|
||||
return existingFeed(withFeedID: feedID) != nil
|
||||
}
|
||||
|
||||
func hasWebFeed(withURL url: String) -> Bool {
|
||||
return existingWebFeed(withURL: url) != nil
|
||||
func hasFeed(withURL url: String) -> Bool {
|
||||
return existingFeed(withURL: url) != nil
|
||||
}
|
||||
|
||||
func has(_ webFeed: WebFeed) -> Bool {
|
||||
return flattenedWebFeeds().contains(webFeed)
|
||||
func has(_ feed: WebFeed) -> Bool {
|
||||
return flattenedFeeds().contains(feed)
|
||||
}
|
||||
|
||||
func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? {
|
||||
for feed in flattenedWebFeeds() {
|
||||
if feed.webFeedID == webFeedID {
|
||||
func existingFeed(withFeedID feedID: String) -> WebFeed? {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.webFeedID == feedID {
|
||||
return feed
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingWebFeed(withURL url: String) -> WebFeed? {
|
||||
for feed in flattenedWebFeeds() {
|
||||
func existingFeed(withURL url: String) -> WebFeed? {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.url == url {
|
||||
return feed
|
||||
}
|
||||
@@ -119,8 +119,8 @@ public extension Container {
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingWebFeed(withExternalID externalID: String) -> WebFeed? {
|
||||
for feed in flattenedWebFeeds() {
|
||||
func existingFeed(withExternalID externalID: String) -> WebFeed? {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.externalID == externalID {
|
||||
return feed
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public extension Article {
|
||||
}
|
||||
|
||||
var feed: WebFeed? {
|
||||
return account?.existingWebFeed(withWebFeedID: feedID)
|
||||
return account?.existingFeed(withFeedID: feedID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,8 +148,8 @@ final class FeedMetadata: Codable {
|
||||
|
||||
weak var delegate: FeedMetadataDelegate?
|
||||
|
||||
init(webFeedID: String) {
|
||||
self.feedID = webFeedID
|
||||
init(feedID: String) {
|
||||
self.feedID = feedID
|
||||
}
|
||||
|
||||
func valueDidChange(_ key: CodingKeys) {
|
||||
|
||||
@@ -70,7 +70,7 @@ private extension FeedMetadataFile {
|
||||
}
|
||||
|
||||
private func metadataForOnlySubscribedToFeeds() -> Account.FeedMetadataDictionary {
|
||||
let feedIDs = account.idToWebFeedDictionary.keys
|
||||
let feedIDs = account.idToFeedDictionary.keys
|
||||
return account.feedMetadata.filter { (feedID: String, metadata: FeedMetadata) -> Bool in
|
||||
return feedIDs.contains(metadata.feedID)
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
guard folder.hasAtLeastOneWebFeed() else {
|
||||
guard folder.hasAtLeastOneFeed() else {
|
||||
folder.name = name
|
||||
return
|
||||
}
|
||||
@@ -326,7 +326,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// Feedbin uses tags and if at least one feed isn't tagged, then the folder doesn't exist on their system
|
||||
guard folder.hasAtLeastOneWebFeed() else {
|
||||
guard folder.hasAtLeastOneFeed() else {
|
||||
account.removeFolder(folder)
|
||||
completion(.success(()))
|
||||
return
|
||||
@@ -334,7 +334,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
|
||||
@@ -386,7 +386,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
caller.createSubscription(url: url) { result in
|
||||
@@ -418,7 +418,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// This error should never happen
|
||||
guard let subscriptionID = feed.externalID else {
|
||||
@@ -445,7 +445,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func removeFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
deleteTagging(for: account, with: feed, from: container, completion: completion)
|
||||
} else {
|
||||
@@ -453,14 +453,14 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if from is Account {
|
||||
addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
} else {
|
||||
deleteTagging(for: account, with: feed, from: from) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
self.addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -468,7 +468,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let folder = container as? Folder, let webFeedID = Int(feed.webFeedID) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
@@ -478,8 +478,8 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
case .success(let taggingID):
|
||||
DispatchQueue.main.async {
|
||||
self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: String(taggingID))
|
||||
account.removeWebFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
@@ -500,10 +500,10 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func restoreFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -512,7 +512,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -528,12 +528,12 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -779,8 +779,8 @@ private extension FeedbinAccountDelegate {
|
||||
if let folders = account.folders {
|
||||
folders.forEach { folder in
|
||||
if !tagNames.contains(folder.name ?? "") {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
account.addWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
account.addFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
}
|
||||
account.removeFolder(folder)
|
||||
@@ -817,17 +817,17 @@ private extension FeedbinAccountDelegate {
|
||||
// Remove any feeds that are no longer in the subscriptions
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.topLevelWebFeeds {
|
||||
for feed in account.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,7 +837,7 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
let subFeedId = String(subscription.feedID)
|
||||
|
||||
if let feed = account.existingWebFeed(withWebFeedID: subFeedId) {
|
||||
if let feed = account.existingFeed(withFeedID: subFeedId) {
|
||||
feed.name = subscription.name
|
||||
// If the name has been changed on the server remove the locally edited name
|
||||
feed.editedName = nil
|
||||
@@ -853,9 +853,9 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
// Actually add subscriptions all in one go, so we don’t trigger various rebuilding things that Account does.
|
||||
subscriptionsToAdd.forEach { subscription in
|
||||
let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
|
||||
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
|
||||
feed.externalID = String(subscription.subscriptionID)
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,25 +887,25 @@ private extension FeedbinAccountDelegate {
|
||||
let taggingFeedIDs = groupedTaggings.map { String($0.feedID) }
|
||||
|
||||
// Move any feeds not in the folder to the account
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !taggingFeedIDs.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds not in the folder
|
||||
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
|
||||
let folderFeedIds = folder.topLevelFeeds.map { $0.webFeedID }
|
||||
|
||||
for tagging in groupedTaggings {
|
||||
let taggingFeedID = String(tagging.feedID)
|
||||
if !folderFeedIds.contains(taggingFeedID) {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
|
||||
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
|
||||
continue
|
||||
}
|
||||
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(tagging.taggingID))
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -914,9 +914,9 @@ private extension FeedbinAccountDelegate {
|
||||
let taggedFeedIDs = Set(taggings.map { String($0.feedID) })
|
||||
|
||||
// Remove all feeds from the account container that have a tag
|
||||
for feed in account.topLevelWebFeeds {
|
||||
for feed in account.topLevelFeeds {
|
||||
if taggedFeedIDs.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -979,7 +979,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
|
||||
func renameFolderRelationship(for account: Account, fromName: String, toName: String) {
|
||||
for feed in account.flattenedWebFeeds() {
|
||||
for feed in account.flattenedFeeds() {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
let relationship = folderRelationship[fromName]
|
||||
folderRelationship[fromName] = nil
|
||||
@@ -1016,7 +1016,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
|
||||
if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) {
|
||||
createWebFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, validateFeed: true, completion: completion)
|
||||
createFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, validateFeed: true, completion: completion)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(FeedbinAccountDelegateError.invalidParameter))
|
||||
@@ -1028,16 +1028,16 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
feed.externalID = String(sub.subscriptionID)
|
||||
feed.iconURL = sub.jsonFeed?.icon
|
||||
feed.faviconURL = sub.jsonFeed?.favicon
|
||||
|
||||
account.addWebFeed(feed, to: container) { result in
|
||||
account.addFeed(feed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
if let name = name {
|
||||
account.renameWebFeed(feed, to: name) { result in
|
||||
account.renameFeed(feed, to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.initialFeedDownload(account: account, feed: feed, completion: completion)
|
||||
@@ -1247,8 +1247,8 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping DatabaseCompletionBlock) {
|
||||
let parsedItems = mapEntriesToParsedItems(entries: entries)
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
|
||||
}
|
||||
|
||||
func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set<ParsedItem> {
|
||||
@@ -1380,7 +1380,7 @@ private extension FeedbinAccountDelegate {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
account.addFeedIfNotInAnyFolder(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
@@ -1393,7 +1393,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
} else {
|
||||
if let account = container as? Account {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
completion(.success(()))
|
||||
}
|
||||
@@ -1411,10 +1411,10 @@ private extension FeedbinAccountDelegate {
|
||||
func complete() {
|
||||
DispatchQueue.main.async {
|
||||
account.clearFeedMetadata(feed)
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
completion(.success(()))
|
||||
|
||||
@@ -312,7 +312,7 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
do {
|
||||
guard let credentials = credentials else {
|
||||
@@ -344,7 +344,7 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let folderCollectionIds = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID }
|
||||
guard let collectionIds = folderCollectionIds, let collectionId = collectionIds.first else {
|
||||
completion(.failure(FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name)))
|
||||
@@ -371,7 +371,7 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
|
||||
feed.editedName = name
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
do {
|
||||
guard let credentials = credentials else {
|
||||
@@ -401,7 +401,7 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func removeFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let folder = container as? Folder, let collectionId = folder.externalID else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed)))
|
||||
@@ -413,50 +413,50 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let from = from as? Folder, let to = to as? Folder else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder))
|
||||
}
|
||||
}
|
||||
|
||||
addWebFeed(for: account, with: feed, to: to) { [weak self] addResult in
|
||||
addFeed(for: account, with: feed, to: to) { [weak self] addResult in
|
||||
switch addResult {
|
||||
// now that we have added the feed, remove it from the other collection
|
||||
case .success:
|
||||
self?.removeWebFeed(for: account, with: feed, from: from) { removeResult in
|
||||
self?.removeFeed(for: account, with: feed, from: from) { removeResult in
|
||||
switch removeResult {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure:
|
||||
from.addWebFeed(feed)
|
||||
from.addFeed(feed)
|
||||
completion(.failure(FeedlyAccountDelegateError.unableToMoveFeedBetweenFolders(feed, from, to)))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
from.addWebFeed(feed)
|
||||
to.removeWebFeed(feed)
|
||||
from.addFeed(feed)
|
||||
to.removeFeed(feed)
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// optimistically move the feed, undoing as appropriate to the failure
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
func restoreFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -465,7 +465,7 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -479,12 +479,12 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
|
||||
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
|
||||
@@ -134,7 +134,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
||||
guard let handler = addCompletionHandler else {
|
||||
return
|
||||
}
|
||||
if let feedResource = feedResourceId, let feed = folder.existingWebFeed(withWebFeedID: feedResource.id) {
|
||||
if let feedResource = feedResourceId, let feed = folder.existingFeed(withFeedID: feedResource.id) {
|
||||
handler(.success(feed))
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -29,11 +29,11 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation, Log
|
||||
|
||||
let feedsBefore = Set(pairs
|
||||
.map { $0.1 }
|
||||
.flatMap { $0.topLevelWebFeeds })
|
||||
.flatMap { $0.topLevelFeeds })
|
||||
|
||||
// Remove feeds in a folder which are not in the corresponding collection.
|
||||
for (collectionFeeds, folder) in pairs {
|
||||
let feedsInFolder = folder.topLevelWebFeeds
|
||||
let feedsInFolder = folder.topLevelFeeds
|
||||
let feedsInCollection = Set(collectionFeeds.map { $0.id })
|
||||
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.webFeedID) }
|
||||
if !feedsToRemove.isEmpty {
|
||||
@@ -55,7 +55,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation, Log
|
||||
.compactMap { (collectionFeed, folder) -> (WebFeed, Folder) in
|
||||
|
||||
// find an existing feed previously added to the account
|
||||
if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) {
|
||||
if let feed = account.existingFeed(withFeedID: collectionFeed.id) {
|
||||
|
||||
// If the feed was renamed on Feedly, ensure we ingest the new name.
|
||||
if feed.nameForDisplay != collectionFeed.title {
|
||||
@@ -80,9 +80,9 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation, Log
|
||||
|
||||
// no existing feed, create a new one
|
||||
let parser = FeedlyFeedParser(feed: collectionFeed)
|
||||
let feed = account.createWebFeed(with: parser.title,
|
||||
let feed = account.createFeed(with: parser.title,
|
||||
url: parser.url,
|
||||
webFeedID: parser.webFeedID,
|
||||
feedID: parser.webFeedID,
|
||||
homePageURL: parser.homePageURL)
|
||||
|
||||
// So the same feed isn't created more than once.
|
||||
@@ -94,7 +94,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation, Log
|
||||
logger.debug("Processing \(feedsAndFolders.count, privacy: .public) feeds.")
|
||||
feedsAndFolders.forEach { (feed, folder) in
|
||||
if !folder.has(feed) {
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation, Logging
|
||||
}
|
||||
|
||||
override func run() {
|
||||
let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
|
||||
let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
|
||||
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { databaseError in
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { databaseError in
|
||||
if let error = databaseError {
|
||||
self.didFinish(with: error)
|
||||
return
|
||||
}
|
||||
|
||||
self.logger.debug("Updated \(webFeedIDsAndItems.count, privacy: .public) feeds for \(self.organisedItemsProvider.parsedItemsByFeedProviderName, privacy: .public).")
|
||||
self.logger.debug("Updated \(feedIDsAndItems.count, privacy: .public) feeds for \(self.organisedItemsProvider.parsedItemsByFeedProviderName, privacy: .public).")
|
||||
self.didFinish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public final class Folder: FeedProtocol, Renamable, Container, Hashable {
|
||||
}
|
||||
|
||||
public weak var account: Account?
|
||||
public var topLevelWebFeeds: Set<WebFeed> = Set<WebFeed>()
|
||||
public var topLevelFeeds: Set<WebFeed> = Set<WebFeed>()
|
||||
public var folders: Set<Folder>? = nil // subfolders are not supported, so this is always nil
|
||||
|
||||
public var name: String? {
|
||||
@@ -101,9 +101,9 @@ public final class Folder: FeedProtocol, Renamable, Container, Hashable {
|
||||
|
||||
// MARK: Container
|
||||
|
||||
public func flattenedWebFeeds() -> Set<WebFeed> {
|
||||
public func flattenedFeeds() -> Set<WebFeed> {
|
||||
// Since sub-folders are not supported, it’s always the top-level feeds.
|
||||
return topLevelWebFeeds
|
||||
return topLevelFeeds
|
||||
}
|
||||
|
||||
public func objectIsChild(_ object: AnyObject) -> Bool {
|
||||
@@ -111,11 +111,11 @@ public final class Folder: FeedProtocol, Renamable, Container, Hashable {
|
||||
guard let feed = object as? WebFeed else {
|
||||
return false
|
||||
}
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
|
||||
public func addWebFeed(_ feed: WebFeed) {
|
||||
topLevelWebFeeds.insert(feed)
|
||||
public func addFeed(_ feed: WebFeed) {
|
||||
topLevelFeeds.insert(feed)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
@@ -123,12 +123,12 @@ public final class Folder: FeedProtocol, Renamable, Container, Hashable {
|
||||
guard !feeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelWebFeeds.formUnion(feeds)
|
||||
topLevelFeeds.formUnion(feeds)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func removeWebFeed(_ feed: WebFeed) {
|
||||
topLevelWebFeeds.remove(feed)
|
||||
public func removeFeed(_ feed: WebFeed) {
|
||||
topLevelFeeds.remove(feed)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ public final class Folder: FeedProtocol, Renamable, Container, Hashable {
|
||||
guard !feeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelWebFeeds.subtract(feeds)
|
||||
topLevelFeeds.subtract(feeds)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
@@ -159,14 +159,14 @@ private extension Folder {
|
||||
|
||||
func updateUnreadCount() {
|
||||
var updatedUnreadCount = 0
|
||||
for feed in topLevelWebFeeds {
|
||||
for feed in topLevelFeeds {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
}
|
||||
unreadCount = updatedUnreadCount
|
||||
}
|
||||
|
||||
func childrenContain(_ feed: WebFeed) -> Bool {
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ extension Folder: OPMLRepresentable {
|
||||
|
||||
var hasAtLeastOneChild = false
|
||||
|
||||
for feed in topLevelWebFeeds.sorted() {
|
||||
for feed in topLevelFeeds.sorted() {
|
||||
s += feed.OPMLString(indentLevel: indentLevel + 1, allowCustomAttributes: allowCustomAttributes)
|
||||
hasAtLeastOneChild = true
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ final class LocalAccountDelegate: AccountDelegate, Logging {
|
||||
return
|
||||
}
|
||||
|
||||
let webFeeds = account.flattenedWebFeeds()
|
||||
let webFeeds = account.flattenedFeeds()
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count)
|
||||
|
||||
let group = DispatchGroup()
|
||||
@@ -119,39 +119,39 @@ final class LocalAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
guard let url = URL(string: urlString) else {
|
||||
completion(.failure(LocalAccountDelegateError.invalidParameter))
|
||||
return
|
||||
}
|
||||
|
||||
// Username should be part of the URL on new feed adds
|
||||
createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion)
|
||||
createRSSFeed(for: account, url: url, editedName: name, container: container, completion: completion)
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
feed.editedName = name
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.removeWebFeed(feed)
|
||||
func removeFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.removeFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
func moveFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addWebFeed(feed)
|
||||
func addFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addWebFeed(feed)
|
||||
func restoreFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate {
|
||||
|
||||
private extension LocalAccountDelegate {
|
||||
|
||||
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
// We need to use a batch update here because we need to assign add the feed to the
|
||||
// container before the name has been downloaded. This will put it in the sidebar
|
||||
@@ -250,7 +250,7 @@ private extension LocalAccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
self.refreshProgress.completeTask()
|
||||
BatchUpdate.shared.end()
|
||||
completion(.failure(AccountError.createErrorAlreadySubscribed))
|
||||
@@ -261,9 +261,9 @@ private extension LocalAccountDelegate {
|
||||
self.refreshProgress.completeTask()
|
||||
|
||||
if let parsedFeed = parsedFeed {
|
||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
|
||||
feed.editedName = editedName
|
||||
container.addWebFeed(feed)
|
||||
container.addFeed(feed)
|
||||
|
||||
account.update(feed, with: parsedFeed, {_ in
|
||||
BatchUpdate.shared.end()
|
||||
|
||||
@@ -47,8 +47,8 @@ extension NewsBlurAccountDelegate {
|
||||
if let folders = account.folders {
|
||||
folders.forEach { folder in
|
||||
if !folderNames.contains(folder.name ?? "") {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
account.addWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
account.addFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
}
|
||||
account.removeFolder(folder)
|
||||
@@ -84,17 +84,17 @@ extension NewsBlurAccountDelegate {
|
||||
// Remove any feeds that are no longer in the subscriptions
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !newsBlurFeedIds.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.topLevelWebFeeds {
|
||||
for feed in account.topLevelFeeds {
|
||||
if !newsBlurFeedIds.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ extension NewsBlurAccountDelegate {
|
||||
feeds.forEach { feed in
|
||||
let subFeedId = String(feed.feedID)
|
||||
|
||||
if let webFeed = account.existingWebFeed(withWebFeedID: subFeedId) {
|
||||
if let webFeed = account.existingFeed(withFeedID: subFeedId) {
|
||||
webFeed.name = feed.name
|
||||
// If the name has been changed on the server remove the locally edited name
|
||||
webFeed.editedName = nil
|
||||
@@ -118,9 +118,9 @@ extension NewsBlurAccountDelegate {
|
||||
|
||||
// Actually add feeds all in one go, so we don’t trigger various rebuilding things that Account does.
|
||||
feedsToAdd.forEach { feed in
|
||||
let webFeed = account.createWebFeed(with: feed.name, url: feed.feedURL, webFeedID: String(feed.feedID), homePageURL: feed.homePageURL)
|
||||
let webFeed = account.createFeed(with: feed.name, url: feed.feedURL, feedID: String(feed.feedID), homePageURL: feed.homePageURL)
|
||||
webFeed.externalID = String(feed.feedID)
|
||||
account.addWebFeed(webFeed)
|
||||
account.addFeed(webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,25 +155,25 @@ extension NewsBlurAccountDelegate {
|
||||
guard let folder = folderDict[folderName] else { return }
|
||||
|
||||
// Move any feeds not in the folder to the account
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !newsBlurFolderFeedIDs.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds not in the folder
|
||||
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
|
||||
let folderFeedIds = folder.topLevelFeeds.map { $0.webFeedID }
|
||||
|
||||
for relationship in folderRelationships {
|
||||
let folderFeedID = String(relationship.feedID)
|
||||
if !folderFeedIds.contains(folderFeedID) {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: folderFeedID) else {
|
||||
guard let feed = account.existingFeed(withFeedID: folderFeedID) else {
|
||||
continue
|
||||
}
|
||||
saveFolderRelationship(for: feed, withFolderName: folderName, id: relationship.folderName)
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,14 +182,14 @@ extension NewsBlurAccountDelegate {
|
||||
// in folders and we need to remove them all from the account level.
|
||||
if let folderRelationships = newsBlurFolderDict[" "] {
|
||||
let newsBlurFolderFeedIDs = folderRelationships.map { String($0.feedID) }
|
||||
for feed in account.topLevelWebFeeds {
|
||||
for feed in account.topLevelFeeds {
|
||||
if !newsBlurFolderFeedIDs.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for feed in account.topLevelWebFeeds {
|
||||
account.removeWebFeed(feed)
|
||||
for feed in account.topLevelFeeds {
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,15 +419,15 @@ extension NewsBlurAccountDelegate {
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let webFeed = account.createWebFeed(with: feed.name, url: feed.feedURL, webFeedID: String(feed.feedID), homePageURL: feed.homePageURL)
|
||||
let webFeed = account.createFeed(with: feed.name, url: feed.feedURL, feedID: String(feed.feedID), homePageURL: feed.homePageURL)
|
||||
webFeed.externalID = String(feed.feedID)
|
||||
webFeed.faviconURL = feed.faviconURL
|
||||
|
||||
account.addWebFeed(webFeed, to: container) { result in
|
||||
account.addFeed(webFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
if let name = name {
|
||||
account.renameWebFeed(webFeed, to: name) { result in
|
||||
account.renameFeed(webFeed, to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.initialFeedDownload(account: account, feed: webFeed, completion: completion)
|
||||
@@ -532,16 +532,16 @@ extension NewsBlurAccountDelegate {
|
||||
let feedID = feed.webFeedID
|
||||
|
||||
if folderName == nil {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
|
||||
if let folders = account.folders {
|
||||
for folder in folders where folderName != nil && folder.name == folderName {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
if account.existingWebFeed(withWebFeedID: feedID) != nil {
|
||||
if account.existingFeed(withFeedID: feedID) != nil {
|
||||
account.clearFeedMetadata(feed)
|
||||
}
|
||||
|
||||
|
||||
@@ -328,17 +328,17 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
return datePublished >= since
|
||||
}
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL }).mapValues {
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL }).mapValues {
|
||||
Set($0)
|
||||
}
|
||||
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { error in
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { error in
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
completion(.success(!webFeedIDsAndItems.isEmpty))
|
||||
completion(.success(!feedIDsAndItems.isEmpty))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
|
||||
var feedIDs: [String] = []
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if (feed.folderRelationship?.count ?? 0) > 1 {
|
||||
clearFolderRelationship(for: feed, withFolderName: folderToRemove)
|
||||
} else if let feedID = feed.externalID {
|
||||
@@ -420,7 +420,7 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> ()) {
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> ()) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
|
||||
let folderName = (container as? Folder)?.name
|
||||
@@ -439,7 +439,7 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func renameFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
guard let feedID = feed.externalID else {
|
||||
completion(.failure(NewsBlurError.invalidParameter))
|
||||
return
|
||||
@@ -466,11 +466,11 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func addFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
guard let folder = container as? Folder else {
|
||||
DispatchQueue.main.async {
|
||||
if let account = container as? Account {
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
completion(.success(()))
|
||||
}
|
||||
@@ -480,16 +480,16 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
let folderName = folder.name ?? ""
|
||||
saveFolderRelationship(for: feed, withFolderName: folderName, id: folderName)
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func removeFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
deleteFeed(for: account, with: feed, from: container, completion: completion)
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func moveFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
guard let feedID = feed.externalID else {
|
||||
completion(.failure(NewsBlurError.invalidParameter))
|
||||
return
|
||||
@@ -506,8 +506,8 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
@@ -516,9 +516,9 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
func restoreFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -527,7 +527,7 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -545,9 +545,9 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
|
||||
var feedsToRestore: [WebFeed] = []
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
feedsToRestore.append(feed)
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
@@ -559,7 +559,7 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
|
||||
case .success(let folder):
|
||||
for feed in feedsToRestore {
|
||||
group.enter()
|
||||
self.restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
self.restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
|
||||
@@ -324,7 +324,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
|
||||
@@ -388,7 +388,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
guard let url = URL(string: url) else {
|
||||
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
|
||||
return
|
||||
@@ -437,7 +437,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// This error should never happen
|
||||
guard let subscriptionID = feed.externalID else {
|
||||
@@ -464,7 +464,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func removeFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let subscriptionID = feed.externalID else {
|
||||
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
|
||||
return
|
||||
@@ -477,10 +477,10 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearFeedMetadata(feed)
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
completion(.success(()))
|
||||
@@ -494,9 +494,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if from is Account {
|
||||
addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
} else {
|
||||
guard
|
||||
let subscriptionId = feed.externalID,
|
||||
@@ -512,8 +512,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
@@ -522,7 +522,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let folder = container as? Folder, let feedExternalID = feed.externalID {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
caller.createTagging(subscriptionID: feedExternalID, tagName: folder.name ?? "") { result in
|
||||
@@ -531,8 +531,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.saveFolderRelationship(for: feed, folderExternalID: folder.externalID, feedExternalID: feedExternalID)
|
||||
account.removeWebFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
@@ -552,10 +552,10 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func restoreFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -564,7 +564,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -580,12 +580,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -718,8 +718,8 @@ private extension ReaderAPIAccountDelegate {
|
||||
if let folders = account.folders {
|
||||
folders.forEach { folder in
|
||||
if !readerFolderExternalIDs.contains(folder.externalID ?? "") {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
account.addWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
account.addFeed(feed)
|
||||
clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
|
||||
}
|
||||
account.removeFolder(folder)
|
||||
@@ -757,32 +757,32 @@ private extension ReaderAPIAccountDelegate {
|
||||
// Remove any feeds that are no longer in the subscriptions
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.topLevelWebFeeds {
|
||||
for feed in account.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
account.clearFeedMetadata(feed)
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds we don't have and update any we do
|
||||
subscriptions.forEach { subscription in
|
||||
|
||||
if let feed = account.existingWebFeed(withWebFeedID: subscription.feedID) {
|
||||
if let feed = account.existingFeed(withFeedID: subscription.feedID) {
|
||||
feed.name = subscription.name
|
||||
feed.editedName = nil
|
||||
feed.homePageURL = subscription.homePageURL
|
||||
} else {
|
||||
let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: subscription.feedID, homePageURL: subscription.homePageURL)
|
||||
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subscription.feedID, homePageURL: subscription.homePageURL)
|
||||
feed.externalID = subscription.feedID
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -817,25 +817,25 @@ private extension ReaderAPIAccountDelegate {
|
||||
let taggingFeedIDs = groupedTaggings.map { $0.feedID }
|
||||
|
||||
// Move any feeds not in the folder to the account
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !taggingFeedIDs.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds not in the folder
|
||||
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
|
||||
let folderFeedIds = folder.topLevelFeeds.map { $0.webFeedID }
|
||||
|
||||
for subscription in groupedTaggings {
|
||||
let taggingFeedID = subscription.feedID
|
||||
if !folderFeedIds.contains(taggingFeedID) {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
|
||||
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
|
||||
continue
|
||||
}
|
||||
saveFolderRelationship(for: feed, folderExternalID: folderExternalID, feedExternalID: subscription.feedID)
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,9 +844,9 @@ private extension ReaderAPIAccountDelegate {
|
||||
let taggedFeedIDs = Set(subscriptions.filter({ !$0.categories.isEmpty }).map { String($0.feedID) })
|
||||
|
||||
// Remove all feeds from the account container that have a tag
|
||||
for feed in account.topLevelWebFeeds {
|
||||
for feed in account.topLevelFeeds {
|
||||
if taggedFeedIDs.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -920,14 +920,14 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
feed.externalID = String(sub.feedID)
|
||||
|
||||
account.addWebFeed(feed, to: container) { result in
|
||||
account.addFeed(feed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
if let name = name {
|
||||
self.renameWebFeed(for: account, with: feed, to: name) { result in
|
||||
self.renameFeed(for: account, with: feed, to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.initialFeedDownload(account: account, feed: feed, completion: completion)
|
||||
@@ -1031,8 +1031,8 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
func processEntries(account: Account, entries: [ReaderAPIEntry]?, completion: @escaping VoidCompletionBlock) {
|
||||
let parsedItems = mapEntriesToParsedItems(account: account, entries: entries)
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { _ in
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { _ in
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ public final class WebFeed: FeedProtocol, Renamable, Hashable, ObservableObject
|
||||
|
||||
public func rename(to newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let account = account else { return }
|
||||
account.renameWebFeed(self, to: newName, completion: completion)
|
||||
account.renameFeed(self, to: newName, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - UnreadCountProvider
|
||||
|
||||
@@ -33,8 +33,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
let folder = account.folders?.filter { $0.name == "Developers" } .first!
|
||||
XCTAssertEqual(156, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(2, account.topLevelWebFeeds.count)
|
||||
XCTAssertEqual(156, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(2, account.topLevelFeeds.count)
|
||||
|
||||
// Test Adding a Feed to the folder
|
||||
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "JSON/taggings_add.json"
|
||||
@@ -45,8 +45,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(157, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(1, account.topLevelWebFeeds.count)
|
||||
XCTAssertEqual(157, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(1, account.topLevelFeeds.count)
|
||||
|
||||
// Test Deleting some Feeds from the folder
|
||||
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "JSON/taggings_delete.json"
|
||||
@@ -57,8 +57,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(153, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(5, account.topLevelWebFeeds.count)
|
||||
XCTAssertEqual(153, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(5, account.topLevelFeeds.count)
|
||||
|
||||
TestAccountManager.shared.deleteAccount(account)
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ class AccountFeedbinSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(224, account.flattenedWebFeeds().count)
|
||||
XCTAssertEqual(224, account.flattenedFeeds().count)
|
||||
|
||||
let daringFireball = account.idToWebFeedDictionary["1296379"]
|
||||
let daringFireball = account.idToFeedDictionary["1296379"]
|
||||
XCTAssertEqual("Daring Fireball", daringFireball!.name)
|
||||
XCTAssertEqual("https://daringfireball.net/feeds/json", daringFireball!.url)
|
||||
XCTAssertEqual("https://daringfireball.net/", daringFireball!.homePageURL)
|
||||
@@ -57,9 +57,9 @@ class AccountFeedbinSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(225, account.flattenedWebFeeds().count)
|
||||
XCTAssertEqual(225, account.flattenedFeeds().count)
|
||||
|
||||
let bPixels = account.idToWebFeedDictionary["1096623"]
|
||||
let bPixels = account.idToFeedDictionary["1096623"]
|
||||
XCTAssertEqual("Beautiful Pixels", bPixels?.name)
|
||||
XCTAssertEqual("https://feedpress.me/beautifulpixels", bPixels?.url)
|
||||
XCTAssertEqual("https://beautifulpixels.com/", bPixels?.homePageURL)
|
||||
|
||||
@@ -59,7 +59,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.")
|
||||
XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
|
||||
|
||||
MainThreadOperationQueue.shared.add(createFeeds)
|
||||
|
||||
@@ -73,7 +73,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
.flatMap { $0 }
|
||||
.map { $0.title })
|
||||
|
||||
let accountFeeds = account.flattenedWebFeeds()
|
||||
let accountFeeds = account.flattenedFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.webFeedID })
|
||||
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
|
||||
|
||||
@@ -92,7 +92,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
||||
.sorted { $0.externalID! < $1.externalID! }
|
||||
.compactMap { folder -> [String: [String]]? in
|
||||
return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)]
|
||||
return [folder.externalID!: folder.topLevelFeeds.map { $0.webFeedID }.sorted(by: <)]
|
||||
}
|
||||
|
||||
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds in their corresponding folders.")
|
||||
@@ -130,7 +130,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.")
|
||||
XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
|
||||
|
||||
MainThreadOperationQueue.shared.add(createFeeds)
|
||||
|
||||
@@ -166,7 +166,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
.flatMap { $0 }
|
||||
.map { $0.title })
|
||||
|
||||
let accountFeeds = account.flattenedWebFeeds()
|
||||
let accountFeeds = account.flattenedFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.webFeedID })
|
||||
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
|
||||
|
||||
@@ -188,7 +188,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
||||
.sorted { $0.externalID! < $1.externalID! }
|
||||
.compactMap { folder -> [String: [String]]? in
|
||||
return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)]
|
||||
return [folder.externalID!: folder.topLevelFeeds.map { $0.webFeedID }.sorted(by: <)]
|
||||
}
|
||||
|
||||
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds to their corresponding folders.")
|
||||
|
||||
@@ -180,7 +180,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
XCTAssertFalse(account.flattenedWebFeeds().isEmpty, "Expected account to have feeds.")
|
||||
XCTAssertFalse(account.flattenedFeeds().isEmpty, "Expected account to have feeds.")
|
||||
}
|
||||
|
||||
// Now that the folders are added, remove them all.
|
||||
@@ -197,7 +197,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let feeds = account.flattenedWebFeeds()
|
||||
let feeds = account.flattenedFeeds()
|
||||
|
||||
XCTAssertTrue(feeds.isEmpty)
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ class FeedlyTestSupport {
|
||||
return
|
||||
}
|
||||
let collectionFeeds = collection["feeds"] as! [[String: Any]]
|
||||
let folderFeeds = folder.topLevelWebFeeds
|
||||
let folderFeeds = folder.topLevelFeeds
|
||||
|
||||
XCTAssertEqual(collectionFeeds.count, folderFeeds.count)
|
||||
|
||||
|
||||
@@ -549,7 +549,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
}
|
||||
|
||||
// MARK: Add Feed
|
||||
@MainActor func addWebFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
|
||||
@MainActor func addFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
|
||||
createAndShowMainWindowIfNecessary()
|
||||
|
||||
if mainWindowController!.isDisplayingSheet {
|
||||
@@ -588,7 +588,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
}
|
||||
|
||||
@MainActor @IBAction func showAddFeedWindow(_ sender: Any?) {
|
||||
addWebFeed(nil)
|
||||
addFeed(nil)
|
||||
}
|
||||
|
||||
@MainActor @IBAction func showAddFolderWindow(_ sender: Any?) {
|
||||
@@ -661,7 +661,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
if AccountManager.shared.anyAccountHasNetNewsWireNewsSubscription() {
|
||||
return
|
||||
}
|
||||
addWebFeed(AccountManager.netNewsWireNewsURL, name: "NetNewsWire News")
|
||||
addFeed(AccountManager.netNewsWireNewsURL, name: "NetNewsWire News")
|
||||
}
|
||||
|
||||
@MainActor @IBAction func openWebsite(_ sender: Any?) {
|
||||
|
||||
@@ -35,7 +35,7 @@ import UserNotifications
|
||||
let isFallbackInspector = false
|
||||
var objects: [Any]? {
|
||||
didSet {
|
||||
renameWebFeedIfNecessary()
|
||||
renameFeedIfNecessary()
|
||||
updateFeed()
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ import UserNotifications
|
||||
}
|
||||
|
||||
override func viewDidDisappear() {
|
||||
renameWebFeedIfNecessary()
|
||||
renameFeedIfNecessary()
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
@@ -115,7 +115,7 @@ import UserNotifications
|
||||
extension WebFeedInspectorViewController: NSTextFieldDelegate {
|
||||
|
||||
func controlTextDidEndEditing(_ note: Notification) {
|
||||
renameWebFeedIfNecessary()
|
||||
renameFeedIfNecessary()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -204,7 +204,7 @@ private extension WebFeedInspectorViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func renameWebFeedIfNecessary() {
|
||||
func renameFeedIfNecessary() {
|
||||
guard let feed = feed,
|
||||
let account = feed.account,
|
||||
let nameTextField = nameTextField,
|
||||
@@ -212,7 +212,7 @@ private extension WebFeedInspectorViewController {
|
||||
return
|
||||
}
|
||||
|
||||
account.renameWebFeed(feed, to: nameTextField.stringValue) { [weak self] result in
|
||||
account.renameFeed(feed, to: nameTextField.stringValue) { [weak self] result in
|
||||
if case .failure(let error) = result {
|
||||
self?.presentError(error)
|
||||
} else {
|
||||
|
||||
@@ -60,12 +60,12 @@ import RSParser
|
||||
}
|
||||
let account = accountAndFolderSpecifier.account
|
||||
|
||||
if account.hasWebFeed(withURL: url.absoluteString) {
|
||||
if account.hasFeed(withURL: url.absoluteString) {
|
||||
showAlreadySubscribedError(url.absoluteString)
|
||||
return
|
||||
}
|
||||
|
||||
account.createWebFeed(url: url.absoluteString, name: title, container: container, validateFeed: true) { result in
|
||||
account.createFeed(url: url.absoluteString, name: title, container: container, validateFeed: true) { result in
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.endShowingProgress()
|
||||
|
||||
@@ -316,7 +316,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
|
||||
func copyWebFeedInAccount(_ feed: WebFeed, _ destination: Container ) {
|
||||
destination.account?.addWebFeed(feed, to: destination) { result in
|
||||
destination.account?.addFeed(feed, to: destination) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -326,9 +326,9 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeedInAccount(_ feed: WebFeed, _ source: Container, _ destination: Container) {
|
||||
func moveFeedInAccount(_ feed: WebFeed, _ source: Container, _ destination: Container) {
|
||||
BatchUpdate.shared.start()
|
||||
source.account?.moveWebFeed(feed, from: source, to: destination) { result in
|
||||
source.account?.moveFeed(feed, from: source, to: destination) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -344,8 +344,8 @@ private extension SidebarOutlineDataSource {
|
||||
return
|
||||
}
|
||||
|
||||
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
|
||||
destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in
|
||||
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
|
||||
destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -354,7 +354,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destinationAccount.createWebFeed(url: feed.url, name: feed.nameForDisplay, container: destinationContainer, validateFeed: false) { result in
|
||||
destinationAccount.createFeed(url: feed.url, name: feed.nameForDisplay, container: destinationContainer, validateFeed: false) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -374,7 +374,7 @@ private extension SidebarOutlineDataSource {
|
||||
guard let sourceAccountID = pasteboardFeed.accountID,
|
||||
let sourceAccount = AccountManager.shared.existingAccount(with: sourceAccountID),
|
||||
let webFeedID = pasteboardFeed.feedID,
|
||||
let feed = sourceAccount.existingWebFeed(withWebFeedID: webFeedID),
|
||||
let feed = sourceAccount.existingFeed(withFeedID: webFeedID),
|
||||
let destinationContainer = parentNode.representedObject as? Container
|
||||
else {
|
||||
return
|
||||
@@ -393,7 +393,7 @@ private extension SidebarOutlineDataSource {
|
||||
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
|
||||
copyWebFeedInAccount(feed, destinationContainer)
|
||||
} else {
|
||||
moveWebFeedInAccount(feed, sourceContainer, destinationContainer)
|
||||
moveFeedInAccount(feed, sourceContainer, destinationContainer)
|
||||
}
|
||||
} else {
|
||||
copyWebFeedBetweenAccounts(feed, destinationContainer)
|
||||
@@ -443,9 +443,9 @@ private extension SidebarOutlineDataSource {
|
||||
destinationAccount.addFolder(folder.name ?? "") { result in
|
||||
switch result {
|
||||
case .success(let destinationFolder):
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
|
||||
destinationAccount.addWebFeed(existingFeed, to: destinationFolder) { result in
|
||||
for feed in folder.topLevelFeeds {
|
||||
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
|
||||
destinationAccount.addFeed(existingFeed, to: destinationFolder) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -454,7 +454,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destinationAccount.createWebFeed(url: feed.url, name: feed.nameForDisplay, container: destinationFolder, validateFeed: false) { result in
|
||||
destinationAccount.createFeed(url: feed.url, name: feed.nameForDisplay, container: destinationFolder, validateFeed: false) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -499,11 +499,11 @@ private extension SidebarOutlineDataSource {
|
||||
|
||||
// Show the add-feed sheet.
|
||||
if let account = parentNode.representedObject as? Account {
|
||||
appDelegate.addWebFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: nil)
|
||||
appDelegate.addFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: nil)
|
||||
} else {
|
||||
let account = parentNode.parent?.representedObject as? Account
|
||||
let folder = parentNode.representedObject as? Folder
|
||||
appDelegate.addWebFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: folder)
|
||||
appDelegate.addFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: folder)
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -635,7 +635,7 @@ private extension SidebarOutlineDataSource {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if dropTargetAccount.hasWebFeed(withURL: draggedFeed.url) {
|
||||
if dropTargetAccount.hasFeed(withURL: draggedFeed.url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ private extension SidebarViewController {
|
||||
treeControllerDelegate.addFilterException(itemID)
|
||||
}
|
||||
} else if let webFeed = feed as? WebFeed {
|
||||
if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.webFeedID) != nil {
|
||||
if webFeed.account?.existingFeed(withFeedID: webFeed.webFeedID) != nil {
|
||||
treeControllerDelegate.addFilterException(itemID)
|
||||
addParentFolderToFilterExceptions(webFeed)
|
||||
}
|
||||
|
||||
@@ -1333,7 +1333,7 @@ private extension TimelineViewController {
|
||||
}
|
||||
else if let folder = representedObject as? Folder {
|
||||
for oneFeed in webFeeds {
|
||||
if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) {
|
||||
if folder.hasFeed(with: oneFeed.webFeedID) || folder.hasFeed(withURL: oneFeed.url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
} else {
|
||||
container = account
|
||||
}
|
||||
account.removeWebFeed(scriptableFeed.feed, from: container!) { result in
|
||||
account.removeFeed(scriptableFeed.feed, from: container!) { result in
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,18 +96,18 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
|
||||
@objc(webFeeds)
|
||||
var webFeeds:NSArray {
|
||||
return account.topLevelWebFeeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
return account.topLevelFeeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@objc(valueInWebFeedsWithUniqueID:)
|
||||
func valueInWebFeeds(withUniqueID id:String) -> ScriptableFeed? {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: id) else { return nil }
|
||||
guard let feed = account.existingFeed(withFeedID: id) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
|
||||
@objc(valueInWebFeedsWithName:)
|
||||
func valueInWebFeeds(withName name:String) -> ScriptableFeed? {
|
||||
let feeds = Array(account.flattenedWebFeeds())
|
||||
let feeds = Array(account.flattenedFeeds())
|
||||
guard let feed = feeds.first(where:{$0.name == name}) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
@@ -133,13 +133,13 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
@objc(allWebFeeds)
|
||||
var allWebFeeds: NSArray {
|
||||
var webFeeds = [ScriptableFeed]()
|
||||
for webFeed in account.topLevelWebFeeds {
|
||||
for webFeed in account.topLevelFeeds {
|
||||
webFeeds.append(ScriptableFeed(webFeed, container: self))
|
||||
}
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
let scriptableFolder = ScriptableFolder(folder, container: self)
|
||||
for webFeed in folder.topLevelWebFeeds {
|
||||
for webFeed in folder.topLevelFeeds {
|
||||
webFeeds.append(ScriptableFeed(webFeed, container: scriptableFolder))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ extension AppDelegate : AppDelegateAppleEvents {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
self.addWebFeed(normalizedURLString)
|
||||
self.addFeed(normalizedURLString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
||||
func deleteElement(_ element:ScriptingObject) {
|
||||
if let scriptableFeed = element as? ScriptableFeed {
|
||||
BatchUpdate.shared.perform {
|
||||
folder.account?.removeWebFeed(scriptableFeed.feed, from: folder) { result in }
|
||||
folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
||||
|
||||
@objc(webFeeds)
|
||||
var webFeeds:NSArray {
|
||||
let feeds = Array(folder.topLevelWebFeeds)
|
||||
let feeds = Array(folder.topLevelFeeds)
|
||||
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ extension NSApplication : ScriptingObjectContainer {
|
||||
let accounts = AccountManager.shared.activeAccounts
|
||||
let emptyFeeds:[WebFeed] = []
|
||||
return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [WebFeed] in
|
||||
let accountFeeds = Array(nthAccount.topLevelWebFeeds)
|
||||
let accountFeeds = Array(nthAccount.topLevelFeeds)
|
||||
return result + accountFeeds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
let (account, folder) = command.accountAndFolderForNewChild()
|
||||
guard let url = self.urlForNewFeed(arguments:arguments) else {return nil}
|
||||
|
||||
if let existingFeed = account.existingWebFeed(withURL:url) {
|
||||
if let existingFeed = account.existingFeed(withURL:url) {
|
||||
return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
|
||||
command.suspendExecution()
|
||||
|
||||
account.createWebFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
|
||||
account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
|
||||
|
||||
@@ -117,7 +117,7 @@ class ActivityManager {
|
||||
}
|
||||
}
|
||||
|
||||
for webFeed in account.flattenedWebFeeds() {
|
||||
for webFeed in account.flattenedFeeds() {
|
||||
ids.append(contentsOf: identifiers(for: webFeed))
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ class ActivityManager {
|
||||
var ids = [String]()
|
||||
ids.append(identifier(for: folder))
|
||||
|
||||
for webFeed in folder.flattenedWebFeeds() {
|
||||
for webFeed in folder.flattenedFeeds() {
|
||||
ids.append(contentsOf: identifiers(for: webFeed))
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ private struct SidebarItemSpecifier {
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
account?.removeWebFeed(webFeed, from: container) { result in
|
||||
account?.removeFeed(webFeed, from: container) { result in
|
||||
BatchUpdate.shared.end()
|
||||
completion()
|
||||
self.checkResult(result)
|
||||
@@ -173,21 +173,21 @@ private struct SidebarItemSpecifier {
|
||||
func restore() {
|
||||
|
||||
if let _ = webFeed {
|
||||
restoreWebFeed()
|
||||
restoreFeed()
|
||||
}
|
||||
else if let _ = folder {
|
||||
restoreFolder()
|
||||
}
|
||||
}
|
||||
|
||||
private func restoreWebFeed() {
|
||||
private func restoreFeed() {
|
||||
|
||||
guard let account = account, let feed = webFeed, let container = path.resolveContainer() else {
|
||||
return
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
account.restoreWebFeed(feed, container: container) { result in
|
||||
account.restoreFeed(feed, container: container) { result in
|
||||
BatchUpdate.shared.end()
|
||||
self.checkResult(result)
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ private extension ExtensionFeedAddRequestFile {
|
||||
|
||||
guard let container = destinationContainer else { return }
|
||||
|
||||
account.createWebFeed(url: request.feedURL.absoluteString, name: request.name, container: container, validateFeed: true) { _ in }
|
||||
account.createFeed(url: request.feedURL.absoluteString, name: request.name, container: container, validateFeed: true) { _ in }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ private extension FeedTreeControllerDelegate {
|
||||
|
||||
var children = [AnyObject]()
|
||||
|
||||
for webFeed in container.topLevelWebFeeds {
|
||||
for webFeed in container.topLevelFeeds {
|
||||
if let itemID = webFeed.itemID, !(!filterExceptions.contains(itemID) && isReadFiltered && webFeed.unreadCount == 0) {
|
||||
children.append(webFeed)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ class AddFeedViewController: UITableViewController {
|
||||
account = containerAccount
|
||||
}
|
||||
|
||||
if account!.hasWebFeed(withURL: url.absoluteString) {
|
||||
if account!.hasFeed(withURL: url.absoluteString) {
|
||||
presentError(AccountError.createErrorAlreadySubscribed)
|
||||
return
|
||||
}
|
||||
@@ -110,7 +110,7 @@ class AddFeedViewController: UITableViewController {
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
|
||||
account!.createWebFeed(url: url.absoluteString, name: feedName, container: container, validateFeed: true) { result in
|
||||
account!.createFeed(url: url.absoluteString, name: feedName, container: container, validateFeed: true) { result in
|
||||
|
||||
BatchUpdate.shared.end()
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
|
||||
// Validate account specific behaviors...
|
||||
if correctDestAccount.behaviors.contains(.disallowFeedInMultipleFolders),
|
||||
sourceWebFeed.account?.accountID != correctDestAccount.accountID && correctDestAccount.hasWebFeed(withURL: sourceWebFeed.url) {
|
||||
sourceWebFeed.account?.accountID != correctDestAccount.accountID && correctDestAccount.hasFeed(withURL: sourceWebFeed.url) {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
guard let destination = destinationContainer, let webFeed = dragNode.representedObject as? WebFeed else { return }
|
||||
|
||||
if source.account == destination.account {
|
||||
moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
moveFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
} else {
|
||||
copyWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
}
|
||||
@@ -138,11 +138,11 @@ private extension MasterFeedViewController {
|
||||
return correctDestination
|
||||
}
|
||||
|
||||
func moveWebFeedInAccount(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
||||
func moveFeedInAccount(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
||||
guard sourceContainer !== destinationContainer else { return }
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
sourceContainer.account?.moveWebFeed(feed, from: sourceContainer, to: destinationContainer) { result in
|
||||
sourceContainer.account?.moveFeed(feed, from: sourceContainer, to: destinationContainer) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -155,10 +155,10 @@ private extension MasterFeedViewController {
|
||||
|
||||
func copyWebFeedBetweenAccounts(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
||||
|
||||
if let existingFeed = destinationContainer.account?.existingWebFeed(withURL: feed.url) {
|
||||
if let existingFeed = destinationContainer.account?.existingFeed(withURL: feed.url) {
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
destinationContainer.account?.addWebFeed(existingFeed, to: destinationContainer) { result in
|
||||
destinationContainer.account?.addFeed(existingFeed, to: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
BatchUpdate.shared.end()
|
||||
@@ -171,7 +171,7 @@ private extension MasterFeedViewController {
|
||||
} else {
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
destinationContainer.account?.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer, validateFeed: false) { result in
|
||||
destinationContainer.account?.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer, validateFeed: false) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
BatchUpdate.shared.end()
|
||||
@@ -190,7 +190,7 @@ private extension MasterFeedViewController {
|
||||
private extension Container {
|
||||
|
||||
func hasChildWebFeed(withURL url: String) -> Bool {
|
||||
return topLevelWebFeeds.contains(where: { $0.url == url })
|
||||
return topLevelFeeds.contains(where: { $0.url == url })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1495,7 +1495,7 @@ private extension SceneCoordinator {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
}
|
||||
} else if let webFeed = feed as? WebFeed {
|
||||
if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.webFeedID) != nil {
|
||||
if webFeed.account?.existingFeed(withWebFeedID: webFeed.webFeedID) != nil {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
addParentFolderToFilterExceptions(webFeed)
|
||||
}
|
||||
@@ -2016,7 +2016,7 @@ private extension SceneCoordinator {
|
||||
if !unsortedArticleIDs.contains(article.articleID) {
|
||||
updatedArticles.insert(article)
|
||||
}
|
||||
if article.account?.existingWebFeed(withWebFeedID: article.webFeedID) == nil {
|
||||
if article.account?.existingFeed(withWebFeedID: article.webFeedID) == nil {
|
||||
updatedArticles.remove(article)
|
||||
}
|
||||
}
|
||||
@@ -2101,7 +2101,7 @@ private extension SceneCoordinator {
|
||||
}
|
||||
} else if let folder = timelineFeed as? Folder {
|
||||
for oneFeed in feeds {
|
||||
if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) {
|
||||
if folder.hasFeed(with: oneFeed.webFeedID) || folder.hasFeed(withURL: oneFeed.url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -2175,7 +2175,7 @@ private extension SceneCoordinator {
|
||||
case .webFeed(let accountID, let webFeedID):
|
||||
guard let accountNode = findAccountNode(accountID: accountID),
|
||||
let account = accountNode.representedObject as? Account,
|
||||
let webFeed = account.existingWebFeed(withWebFeedID: webFeedID) else {
|
||||
let webFeed = account.existingFeed(withWebFeedID: webFeedID) else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2204,7 +2204,7 @@ private extension SceneCoordinator {
|
||||
return
|
||||
}
|
||||
|
||||
guard let webFeed = account.existingWebFeed(withWebFeedID: webFeedID) else {
|
||||
guard let webFeed = account.existingFeed(withWebFeedID: webFeedID) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ struct NewArticleNotificationsView: View, Logging {
|
||||
return
|
||||
}
|
||||
activeAccounts.forEach { account in
|
||||
for feed in Array(account.flattenedWebFeeds()) {
|
||||
for feed in Array(account.flattenedFeeds()) {
|
||||
if let feedURLHost = URL(string: feed.url)?.host {
|
||||
if faviconHost == feedURLHost {
|
||||
feed.objectWillChange.send()
|
||||
@@ -50,7 +50,7 @@ struct NewArticleNotificationsView: View, Logging {
|
||||
}
|
||||
|
||||
private func sortedWebFeedsForAccount(_ account: Account) -> [WebFeed] {
|
||||
return Array(account.flattenedWebFeeds()).sorted(by: { $0.nameForDisplay.caseInsensitiveCompare($1.nameForDisplay) == .orderedAscending })
|
||||
return Array(account.flattenedFeeds()).sorted(by: { $0.nameForDisplay.caseInsensitiveCompare($1.nameForDisplay) == .orderedAscending })
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user