2017-05-27 19:43:27 +02:00
|
|
|
|
//
|
|
|
|
|
// AddFeedController.swift
|
|
|
|
|
// Evergreen
|
|
|
|
|
//
|
|
|
|
|
// Created by Brent Simmons on 8/28/16.
|
2017-05-29 22:17:58 +02:00
|
|
|
|
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
2017-05-27 19:43:27 +02:00
|
|
|
|
//
|
|
|
|
|
|
2018-02-03 07:51:32 +01:00
|
|
|
|
import AppKit
|
2017-05-27 19:43:27 +02:00
|
|
|
|
import RSCore
|
|
|
|
|
import RSTree
|
2017-09-17 21:22:15 +02:00
|
|
|
|
import Data
|
2017-05-27 19:43:27 +02:00
|
|
|
|
import RSFeedFinder
|
2017-09-17 21:34:10 +02:00
|
|
|
|
import Account
|
2017-12-03 02:47:08 +01:00
|
|
|
|
import RSParser
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
// Run add-feed sheet.
|
|
|
|
|
// If it returns with URL and optional name,
|
|
|
|
|
// run FeedFinder plus modal progress window.
|
|
|
|
|
// If FeedFinder returns feed,
|
|
|
|
|
// add feed.
|
|
|
|
|
// Else,
|
|
|
|
|
// display error sheet.
|
|
|
|
|
|
|
|
|
|
class AddFeedController: AddFeedWindowControllerDelegate, FeedFinderDelegate {
|
|
|
|
|
|
2017-10-22 00:56:01 +02:00
|
|
|
|
private let hostWindow: NSWindow
|
|
|
|
|
private var addFeedWindowController: AddFeedWindowController?
|
|
|
|
|
private var userEnteredURL: URL?
|
|
|
|
|
private var userEnteredFolder: Folder?
|
|
|
|
|
private var userEnteredTitle: String?
|
|
|
|
|
private var userEnteredAccount: Account?
|
|
|
|
|
private var foundFeedURLString: String?
|
|
|
|
|
private var titleFromFeed: String?
|
|
|
|
|
private var feedFinder: FeedFinder?
|
|
|
|
|
private var isFindingFeed = false
|
|
|
|
|
private var bestFeedSpecifier: FeedSpecifier?
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
init(hostWindow: NSWindow) {
|
|
|
|
|
|
|
|
|
|
self.hostWindow = hostWindow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func showAddFeedSheet(_ urlString: String?, _ name: String?) {
|
|
|
|
|
|
|
|
|
|
let folderTreeControllerDelegate = FolderTreeControllerDelegate()
|
|
|
|
|
|
2017-09-24 21:24:44 +02:00
|
|
|
|
let rootNode = Node(representedObject: AccountManager.shared.localAccount, parent: nil)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
rootNode.canHaveChildNodes = true
|
|
|
|
|
let folderTreeController = TreeController(delegate: folderTreeControllerDelegate, rootNode: rootNode)
|
|
|
|
|
|
|
|
|
|
addFeedWindowController = AddFeedWindowController(urlString: urlString ?? urlStringFromPasteboard, name: name, folderTreeController: folderTreeController, delegate: self)
|
|
|
|
|
addFeedWindowController!.runSheetOnWindow(hostWindow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: AddFeedWindowControllerDelegate
|
|
|
|
|
|
2017-10-22 00:04:59 +02:00
|
|
|
|
func addFeedWindowController(_: AddFeedWindowController, userEnteredURL url: URL, userEnteredTitle title: String?, container: Container) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2017-09-17 21:54:08 +02:00
|
|
|
|
closeAddFeedSheet(NSApplication.ModalResponse.OK)
|
2017-10-22 00:56:01 +02:00
|
|
|
|
|
|
|
|
|
guard let accountAndFolderSpecifier = accountAndFolderFromContainer(container) else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let account = accountAndFolderSpecifier.account
|
|
|
|
|
let folder = accountAndFolderSpecifier.folder
|
|
|
|
|
|
|
|
|
|
if account.hasFeed(withURL: url.absoluteString) {
|
|
|
|
|
showAlreadySubscribedError(url.absoluteString)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userEnteredAccount = account
|
|
|
|
|
userEnteredURL = url
|
|
|
|
|
userEnteredFolder = folder
|
|
|
|
|
userEnteredTitle = title
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
findFeed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController) {
|
|
|
|
|
|
2017-09-17 21:54:08 +02:00
|
|
|
|
closeAddFeedSheet(NSApplication.ModalResponse.cancel)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: FeedFinderDelegate
|
|
|
|
|
|
|
|
|
|
public func feedFinder(_ feedFinder: FeedFinder, didFindFeeds feedSpecifiers: Set<FeedSpecifier>) {
|
|
|
|
|
|
|
|
|
|
isFindingFeed = false
|
|
|
|
|
endShowingProgress()
|
|
|
|
|
|
|
|
|
|
if let error = feedFinder.initialDownloadError {
|
|
|
|
|
if feedFinder.initialDownloadStatusCode == 404 {
|
|
|
|
|
showNoFeedsErrorMessage()
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
showInitialDownloadError(error)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers) else {
|
|
|
|
|
showNoFeedsErrorMessage()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.bestFeedSpecifier = bestFeedSpecifier
|
|
|
|
|
self.foundFeedURLString = bestFeedSpecifier.urlString
|
|
|
|
|
|
|
|
|
|
if let url = URL(string: bestFeedSpecifier.urlString) {
|
|
|
|
|
|
2017-12-03 02:47:08 +01:00
|
|
|
|
InitialFeedDownloader.download(url) { (parsedFeed) in
|
|
|
|
|
self.titleFromFeed = parsedFeed?.title
|
|
|
|
|
self.addFeedIfPossible(parsedFeed)
|
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Shouldn't happen.
|
|
|
|
|
showNoFeedsErrorMessage()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private extension AddFeedController {
|
|
|
|
|
|
|
|
|
|
var urlStringFromPasteboard: String? {
|
|
|
|
|
get {
|
2017-09-17 21:54:08 +02:00
|
|
|
|
if let urlString = NSPasteboard.rs_urlString(from: NSPasteboard.general) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
return urlString.rs_normalizedURL()
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-22 00:56:01 +02:00
|
|
|
|
struct AccountAndFolderSpecifier {
|
|
|
|
|
let account: Account
|
|
|
|
|
let folder: Folder?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func accountAndFolderFromContainer(_ container: Container) -> AccountAndFolderSpecifier? {
|
|
|
|
|
|
|
|
|
|
if let account = container as? Account {
|
|
|
|
|
return AccountAndFolderSpecifier(account: account, folder: nil)
|
|
|
|
|
}
|
2017-10-22 01:37:40 +02:00
|
|
|
|
if let folder = container as? Folder, let account = folder.account {
|
|
|
|
|
return AccountAndFolderSpecifier(account: account, folder: folder)
|
2017-10-22 00:56:01 +02:00
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-17 21:54:08 +02:00
|
|
|
|
func closeAddFeedSheet(_ returnCode: NSApplication.ModalResponse) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
if let sheetWindow = addFeedWindowController?.window {
|
|
|
|
|
hostWindow.endSheet(sheetWindow, returnCode: returnCode)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-12-03 02:47:08 +01:00
|
|
|
|
func addFeedIfPossible(_ parsedFeed: ParsedFeed?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
// Add feed if not already subscribed-to.
|
|
|
|
|
|
2017-10-22 00:56:01 +02:00
|
|
|
|
guard let account = userEnteredAccount else {
|
|
|
|
|
assertionFailure("Expected account.")
|
2017-05-27 19:43:27 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
guard let feedURLString = foundFeedURLString else {
|
2017-10-22 00:56:01 +02:00
|
|
|
|
assertionFailure("Expected feedURLString.")
|
2017-05-27 19:43:27 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-17 22:07:55 +02:00
|
|
|
|
if account.hasFeed(withURL: feedURLString) {
|
2017-10-22 00:56:01 +02:00
|
|
|
|
showAlreadySubscribedError(feedURLString)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-22 00:56:01 +02:00
|
|
|
|
guard let feed = account.createFeed(with: titleFromFeed, editedName: userEnteredTitle, url: feedURLString) else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-03 02:47:08 +01:00
|
|
|
|
if let parsedFeed = parsedFeed {
|
|
|
|
|
account.update(feed, with: parsedFeed, {})
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-22 00:56:01 +02:00
|
|
|
|
if account.addFeed(feed, to: userEnteredFolder) {
|
2017-12-18 21:34:07 +01:00
|
|
|
|
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Find Feeds
|
|
|
|
|
|
|
|
|
|
func findFeed() {
|
|
|
|
|
|
|
|
|
|
guard let url = userEnteredURL else {
|
2017-10-22 00:56:01 +02:00
|
|
|
|
assertionFailure("Expected userEnteredURL.")
|
2017-05-27 19:43:27 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isFindingFeed = true
|
|
|
|
|
feedFinder = FeedFinder(url: url, delegate: self)
|
|
|
|
|
|
|
|
|
|
beginShowingProgress()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Errors
|
|
|
|
|
|
2017-10-22 00:56:01 +02:00
|
|
|
|
func showAlreadySubscribedError(_ urlString: String) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
let alert = NSAlert()
|
|
|
|
|
alert.alertStyle = .informational
|
|
|
|
|
alert.messageText = NSLocalizedString("Already subscribed", comment: "Feed finder")
|
|
|
|
|
alert.informativeText = NSLocalizedString("Can’t add this feed because you’ve already subscribed to it.", comment: "Feed finder")
|
|
|
|
|
|
|
|
|
|
alert.beginSheetModal(for: hostWindow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func showInitialDownloadError(_ error: Error) {
|
|
|
|
|
|
|
|
|
|
let alert = NSAlert()
|
|
|
|
|
alert.alertStyle = .informational
|
|
|
|
|
alert.messageText = NSLocalizedString("Download Error", comment: "Feed finder")
|
|
|
|
|
|
|
|
|
|
let formatString = NSLocalizedString("Can’t add this feed because of a download error: “%@”", comment: "Feed finder")
|
|
|
|
|
let errorText = NSString.localizedStringWithFormat(formatString as NSString, error.localizedDescription)
|
|
|
|
|
alert.informativeText = errorText as String
|
|
|
|
|
|
|
|
|
|
alert.beginSheetModal(for: hostWindow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func showNoFeedsErrorMessage() {
|
|
|
|
|
|
|
|
|
|
let alert = NSAlert()
|
|
|
|
|
alert.alertStyle = .informational
|
|
|
|
|
alert.messageText = NSLocalizedString("Feed not found", comment: "Feed finder")
|
|
|
|
|
alert.informativeText = NSLocalizedString("Can’t add a feed because no feed was found.", comment: "Feed finder")
|
|
|
|
|
|
|
|
|
|
alert.beginSheetModal(for: hostWindow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Progress
|
|
|
|
|
|
|
|
|
|
func beginShowingProgress() {
|
|
|
|
|
|
|
|
|
|
runIndeterminateProgressWithMessage(NSLocalizedString("Finding feed…", comment:"Feed finder"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func endShowingProgress() {
|
|
|
|
|
|
|
|
|
|
stopIndeterminateProgress()
|
|
|
|
|
hostWindow.makeKeyAndOrderFront(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|