Continue removing feed provider and extension point references.
@@ -181,12 +181,7 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
|
||||
let editedName = name == nil || name!.isEmpty ? nil : name
|
||||
|
||||
// Username should be part of the URL on new feed adds
|
||||
if let feedProvider = FeedProviderManager.shared.best(for: urlComponents) {
|
||||
createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: editedName, container: container, feedProvider: feedProvider, completion: completion)
|
||||
} else {
|
||||
createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion)
|
||||
}
|
||||
|
||||
createRSSWebFeed(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) {
|
||||
@@ -570,123 +565,19 @@ private extension CloudKitAccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func combinedRefresh(_ account: Account, _ webFeeds: Set<WebFeed>, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
var refresherWebFeeds = Set<WebFeed>()
|
||||
let group = DispatchGroup()
|
||||
var feedProviderError: Error? = nil
|
||||
|
||||
for webFeed in webFeeds {
|
||||
if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components) {
|
||||
group.enter()
|
||||
feedProvider.refresh(webFeed) { result in
|
||||
switch result {
|
||||
case .success(let parsedItems):
|
||||
|
||||
account.update(webFeed.webFeedID, with: parsedItems) { result in
|
||||
switch result {
|
||||
case .success(let articleChanges):
|
||||
self.storeArticleChanges(new: articleChanges.newArticles, updated: articleChanges.updatedArticles, deleted: articleChanges.deletedArticles) {
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
}
|
||||
case .failure(let error):
|
||||
self.logger.error("CloudKit Feed refresh update error: \(error.localizedDescription, privacy: .public)")
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
}
|
||||
|
||||
}
|
||||
func combinedRefresh(_ account: Account, _ webFeeds: Set<WebFeed>, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
case .failure(let error):
|
||||
self.logger.error("CloudKit Feed refresh error: \(error.localizedDescription, privacy: .public)")
|
||||
feedProviderError = error
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refresherWebFeeds.insert(webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
group.enter()
|
||||
refresher.refreshFeeds(refresherWebFeeds) {
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
if let error = feedProviderError {
|
||||
completion(.failure(error))
|
||||
} else {
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
let group = DispatchGroup()
|
||||
|
||||
}
|
||||
group.enter()
|
||||
refresher.refreshFeeds(webFeeds) {
|
||||
group.leave()
|
||||
}
|
||||
|
||||
func createProviderWebFeed(for account: Account, urlComponents: URLComponents, editedName: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(5)
|
||||
|
||||
feedProvider.metaData(urlComponents) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
|
||||
case .success(let metaData):
|
||||
|
||||
guard let urlString = urlComponents.url?.absoluteString else {
|
||||
self.refreshProgress.completeTasks(4)
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
return
|
||||
}
|
||||
|
||||
self.accountZone.createWebFeed(url: urlString, name: metaData.name, editedName: editedName, homePageURL: metaData.homePageURL, container: container) { result in
|
||||
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success(let externalID):
|
||||
|
||||
let feed = account.createWebFeed(with: metaData.name, url: urlString, webFeedID: urlString, homePageURL: metaData.homePageURL)
|
||||
feed.editedName = editedName
|
||||
feed.externalID = externalID
|
||||
container.addWebFeed(feed)
|
||||
|
||||
feedProvider.refresh(feed) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success(let parsedItems):
|
||||
|
||||
account.update(urlString, with: parsedItems) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.sendNewArticlesToTheCloud(account, feed)
|
||||
self.refreshProgress.clear()
|
||||
completion(.success(feed))
|
||||
case .failure(let error):
|
||||
self.refreshProgress.completeTasks(2)
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.refreshProgress.completeTasks(3)
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
self.refreshProgress.completeTasks(4)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.refreshProgress.completeTasks(4)
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
|
||||
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
|
||||
@@ -41,57 +41,29 @@ final class LocalAccountDelegate: AccountDelegate, Logging {
|
||||
completion()
|
||||
}
|
||||
|
||||
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard refreshProgress.isComplete else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard refreshProgress.isComplete else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
var refresherWebFeeds = Set<WebFeed>()
|
||||
let webFeeds = account.flattenedWebFeeds()
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count)
|
||||
let webFeeds = account.flattenedWebFeeds()
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count)
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
group.enter()
|
||||
refresher?.refreshFeeds(webFeeds) {
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
self.refreshProgress.clear()
|
||||
account.metadata.lastArticleFetchEndTime = Date()
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
var feedProviderError: Error? = nil
|
||||
|
||||
for webFeed in webFeeds {
|
||||
if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components) {
|
||||
group.enter()
|
||||
feedProvider.refresh(webFeed) { result in
|
||||
switch result {
|
||||
case .success(let parsedItems):
|
||||
account.update(webFeed.webFeedID, with: parsedItems) { _ in
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
}
|
||||
case .failure(let error):
|
||||
self.logger.error("Feed Provided refresh error: \(error.localizedDescription, privacy: .public)")
|
||||
feedProviderError = error
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refresherWebFeeds.insert(webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
group.enter()
|
||||
refresher?.refreshFeeds(refresherWebFeeds) {
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
self.refreshProgress.clear()
|
||||
account.metadata.lastArticleFetchEndTime = Date()
|
||||
if let error = feedProviderError {
|
||||
completion(.failure(error))
|
||||
} else {
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func syncArticleStatus(for account: Account, completion: ((Result<Void, Error>) -> Void)? = nil) {
|
||||
completion?(.success(()))
|
||||
@@ -154,11 +126,7 @@ final class LocalAccountDelegate: AccountDelegate, Logging {
|
||||
}
|
||||
|
||||
// Username should be part of the URL on new feed adds
|
||||
if let feedProvider = FeedProviderManager.shared.best(for: urlComponents) {
|
||||
createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: name, container: container, feedProvider: feedProvider, completion: completion)
|
||||
} else {
|
||||
createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion)
|
||||
}
|
||||
createRSSWebFeed(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) {
|
||||
@@ -263,44 +231,6 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate {
|
||||
|
||||
private extension LocalAccountDelegate {
|
||||
|
||||
func createProviderWebFeed(for account: Account, urlComponents: URLComponents, editedName: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(2)
|
||||
|
||||
feedProvider.metaData(urlComponents) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
|
||||
case .success(let metaData):
|
||||
|
||||
guard let urlString = urlComponents.url?.absoluteString else {
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
return
|
||||
}
|
||||
|
||||
let feed = account.createWebFeed(with: metaData.name, url: urlString, webFeedID: urlString, homePageURL: metaData.homePageURL)
|
||||
feed.editedName = editedName
|
||||
container.addWebFeed(feed)
|
||||
|
||||
feedProvider.refresh(feed) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success(let parsedItems):
|
||||
account.update(urlString, with: parsedItems) { _ in
|
||||
completion(.success(feed))
|
||||
}
|
||||
case .failure(let error):
|
||||
self.refreshProgress.clear()
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.refreshProgress.clear()
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createRSSWebFeed(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
|
||||
|
||||
@@ -152,15 +152,10 @@ public final class WebFeed: Feed, Renamable, Hashable, ObservableObject {
|
||||
|
||||
public var isArticleExtractorAlwaysOn: Bool? {
|
||||
get {
|
||||
if isFeedProvider == true { return false } // not an option for FeedProviders
|
||||
return metadata.isArticleExtractorAlwaysOn
|
||||
metadata.isArticleExtractorAlwaysOn
|
||||
}
|
||||
set {
|
||||
if isFeedProvider == true {
|
||||
metadata.isArticleExtractorAlwaysOn = false
|
||||
return
|
||||
}
|
||||
metadata.isArticleExtractorAlwaysOn = newValue
|
||||
metadata.isArticleExtractorAlwaysOn = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,21 +230,6 @@ public final class WebFeed: Feed, Renamable, Hashable, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Feed Provider
|
||||
public var isFeedProvider: Bool {
|
||||
get {
|
||||
guard let webfeedURL = URL(string: url),
|
||||
let components = URLComponents(url: webfeedURL, resolvingAgainstBaseURL: false) else {
|
||||
return false
|
||||
}
|
||||
|
||||
if FeedProviderManager.shared.best(for: components) == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NotificationDisplayName
|
||||
public var notificationDisplayName: String {
|
||||
#if os(macOS)
|
||||
|
||||
@@ -72,14 +72,14 @@ struct AppAssets {
|
||||
return NSImage(systemSymbolName: "wind", accessibilityDescription: nil)!
|
||||
}()
|
||||
|
||||
static var extensionPointMarsEdit: RSImage = {
|
||||
return RSImage(named: "extensionPointMarsEdit")!
|
||||
static var marsEditIcon: RSImage = {
|
||||
return RSImage(named: "MarsEditIcon")!
|
||||
}()
|
||||
|
||||
static var extensionPointMicroblog: RSImage = {
|
||||
return RSImage(named: "extensionPointMicroblog")!
|
||||
|
||||
static var microblogIcon: RSImage = {
|
||||
return RSImage(named: "MicroblogIcon")!
|
||||
}()
|
||||
|
||||
|
||||
static var faviconTemplateImage: RSImage = {
|
||||
return RSImage(named: "faviconTemplateImage")!
|
||||
}()
|
||||
|
||||
@@ -141,7 +141,7 @@ private extension WebFeedInspectorViewController {
|
||||
windowTitle = feed?.nameForDisplay ?? NSLocalizedString("window.title.feed-inspector", comment: "Feed Inspector")
|
||||
view.needsLayout = true
|
||||
if let webfeed = feed {
|
||||
webfeed.isFeedProvider ? (isReaderViewAlwaysOnCheckBox?.isEnabled = false) : (isReaderViewAlwaysOnCheckBox?.isEnabled = true)
|
||||
isReaderViewAlwaysOnCheckBox?.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1192,12 +1192,7 @@ private extension MainWindowController {
|
||||
}
|
||||
|
||||
if let webfeed = currentTimelineViewController?.selectedArticles.first?.webFeed {
|
||||
if webfeed.isFeedProvider {
|
||||
toolbarButton.isEnabled = false
|
||||
return false
|
||||
} else {
|
||||
toolbarButton.isEnabled = true
|
||||
}
|
||||
toolbarButton.isEnabled = true
|
||||
}
|
||||
|
||||
guard let state = articleExtractor?.state else {
|
||||
|
||||
@@ -304,19 +304,15 @@ private extension SidebarViewController {
|
||||
}
|
||||
menu.addItem(notificationMenuItem)
|
||||
|
||||
let articleExtractorText = NSLocalizedString("button.title.always-use-reader-view", comment: "Always Use Reader View")
|
||||
let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), webFeed)
|
||||
|
||||
if !webFeed.isFeedProvider {
|
||||
let articleExtractorText = NSLocalizedString("button.title.always-use-reader-view", comment: "Always Use Reader View")
|
||||
let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), webFeed)
|
||||
|
||||
if webFeed.isArticleExtractorAlwaysOn == nil || webFeed.isArticleExtractorAlwaysOn! == false {
|
||||
articleExtractorMenuItem.state = .off
|
||||
} else {
|
||||
articleExtractorMenuItem.state = .on
|
||||
}
|
||||
menu.addItem(articleExtractorMenuItem)
|
||||
if webFeed.isArticleExtractorAlwaysOn == nil || webFeed.isArticleExtractorAlwaysOn! == false {
|
||||
articleExtractorMenuItem.state = .off
|
||||
} else {
|
||||
articleExtractorMenuItem.state = .on
|
||||
}
|
||||
|
||||
menu.addItem(articleExtractorMenuItem)
|
||||
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "micro.blog.24.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "micro.blog.48.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
||||
@@ -118,31 +118,7 @@ public final class WebFeedIconDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let components = URLComponents(string: feed.url), let feedProvider = FeedProviderManager.shared.best(for: components) {
|
||||
guard !urlsInProgress.contains(feed.url) else {
|
||||
return nil
|
||||
}
|
||||
urlsInProgress.insert(feed.url)
|
||||
|
||||
feedProvider.iconURL(components) { result in
|
||||
self.urlsInProgress.remove(feed.url)
|
||||
switch result {
|
||||
case .success(let feedProviderURL):
|
||||
self.feedURLToIconURLCache[feed.url] = feedProviderURL
|
||||
self.feedURLToIconURLCacheDirty = true
|
||||
self.icon(forURL: feedProviderURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
}
|
||||
}
|
||||
case .failure:
|
||||
checkFeedIconURL()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkFeedIconURL()
|
||||
}
|
||||
checkFeedIconURL()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -71,8 +71,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
let documentThemesFolderPath = String(documentThemesFolder.suffix(from: documentAccountsFolder.index(documentThemesFolder.startIndex, offsetBy: 7)))
|
||||
ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentThemesFolderPath)
|
||||
|
||||
FeedProviderManager.shared.delegate = ExtensionPointManager.shared
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -210,11 +210,7 @@ class ArticleViewController: UIViewController, MainControllerIdentifiable, Loggi
|
||||
appearanceBarButtonItem.isEnabled = true
|
||||
|
||||
let permalinkPresent = article.preferredLink != nil
|
||||
var isFeedProvider = false
|
||||
if let webfeed = article.webFeed {
|
||||
isFeedProvider = webfeed.isFeedProvider
|
||||
}
|
||||
articleExtractorButton.isEnabled = permalinkPresent && !AppDefaults.shared.isDeveloperBuild && !isFeedProvider
|
||||
articleExtractorButton.isEnabled = permalinkPresent && !AppDefaults.shared.isDeveloperBuild
|
||||
actionBarButtonItem.isEnabled = permalinkPresent
|
||||
|
||||
if article.status.read {
|
||||
|
||||
@@ -31,14 +31,6 @@ struct WebFeedInspectorView: View {
|
||||
Toggle(isOn: Binding(get: { webFeed.isNotifyAboutNewArticles ?? false }, set: { webFeed.isNotifyAboutNewArticles = $0 })) {
|
||||
Text("toggle.title.notify-about-new-articles", comment: "New Article Notifications")
|
||||
}
|
||||
|
||||
if webFeed.isFeedProvider == false {
|
||||
Toggle(isOn: Binding(
|
||||
get: { webFeed.isArticleExtractorAlwaysOn ?? false },
|
||||
set: { webFeed.isArticleExtractorAlwaysOn = $0 })) {
|
||||
Text("toggle.title.always-show-reader-view", comment: "Always Show Reader View")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("label.text.home-page", comment: "Home Page")) {
|
||||
|
||||