From b5d7ce8e8c26b38a3052b3a423e4db2d23591ac0 Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Sat, 19 Oct 2019 12:31:47 -0500 Subject: [PATCH 01/13] Disable "Add Folder" button when the text field is empty --- Mac/Base.lproj/AddFolderSheet.xib | 34 +++++++++++-------- .../AddFolder/AddFolderWindowController.swift | 12 +++++-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/Mac/Base.lproj/AddFolderSheet.xib b/Mac/Base.lproj/AddFolderSheet.xib index 169d02cdf..b56a31726 100644 --- a/Mac/Base.lproj/AddFolderSheet.xib +++ b/Mac/Base.lproj/AddFolderSheet.xib @@ -1,47 +1,51 @@ - + - + - + + - + - + - + - - + + - - + + - + + + + - - + + @@ -49,7 +53,7 @@ - + @@ -64,7 +68,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift deleted file mode 100644 index cdbda991c..000000000 --- a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// ExportOPMLWindowController.swift -// NetNewsWire -// -// Created by Maurice Parker on 5/1/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import AppKit -import Account - -class ExportOPMLWindowController: NSWindowController { - - @IBOutlet weak var accountPopUpButton: NSPopUpButton! - private weak var hostWindow: NSWindow? - - convenience init() { - self.init(windowNibName: NSNib.Name("ExportOPMLSheet")) - } - - override func windowDidLoad() { - accountPopUpButton.removeAllItems() - - let menu = NSMenu() - accountPopUpButton.menu = menu - - for oneAccount in AccountManager.shared.sortedAccounts { - - let oneMenuItem = NSMenuItem() - oneMenuItem.title = oneAccount.nameForDisplay - oneMenuItem.representedObject = oneAccount - menu.addItem(oneMenuItem) - - if oneAccount.accountID == AppDefaults.exportOPMLAccountID { - accountPopUpButton.select(oneMenuItem) - } - - } - } - - // MARK: API - - func runSheetOnWindow(_ hostWindow: NSWindow) { - - self.hostWindow = hostWindow - - if AccountManager.shared.accounts.count == 1 { - let account = AccountManager.shared.accounts.first! - exportOPML(account: account) - } else { - hostWindow.beginSheet(window!) - } - - } - - // MARK: Actions - - @IBAction func cancel(_ sender: Any) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) - } - - @IBAction func exportOPML(_ sender: Any) { - - guard let menuItem = accountPopUpButton.selectedItem else { - return - } - - let account = menuItem.representedObject as! Account - AppDefaults.exportOPMLAccountID = account.accountID - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) - exportOPML(account: account) - - } - - func exportOPML(account: Account) { - - let panel = NSSavePanel() - panel.allowedFileTypes = ["opml"] - panel.allowsOtherFileTypes = false - panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML") - panel.title = NSLocalizedString("Export OPML", comment: "Export OPML") - panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML") - panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML") - panel.isExtensionHidden = false - - let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) - panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" - - panel.beginSheetModal(for: hostWindow!) { result in - if result == NSApplication.ModalResponse.OK, let url = panel.url { - DispatchQueue.main.async { - let filename = url.lastPathComponent - let opmlString = OPMLExporter.OPMLString(with: account, title: filename) - do { - try opmlString.write(to: url, atomically: true, encoding: String.Encoding.utf8) - } - catch let error as NSError { - NSApplication.shared.presentError(error) - } - } - } - } - - } - -} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index c24b1e5d4..1a9fa9f4e 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -22,9 +22,8 @@ 5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */; }; 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; }; 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */; }; - 5144EA3D227A37AF00D19003 /* ExportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3C227A37AF00D19003 /* ExportOPMLSheet.xib */; }; 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; }; - 5144EA43227A380F00D19003 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */; }; + 5144EA43227A380F00D19003 /* ExportOPMLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLController.swift */; }; 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; }; 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 51543685228F6753005E1CDF /* DetailAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51543684228F6753005E1CDF /* DetailAccountViewController.swift */; }; @@ -313,6 +312,8 @@ 84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; }; 84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; + B24C4F7B235D39D40000B924 /* ExportOPMLAccessoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */; }; + B24C4F85235D39F30000B924 /* ExportOPMLAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */; }; D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */; }; @@ -685,9 +686,8 @@ 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAddLocal.xib; sourceTree = ""; }; 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddLocalWindowController.swift; sourceTree = ""; }; 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImportOPMLSheet.xib; sourceTree = ""; }; - 5144EA3C227A37AF00D19003 /* ExportOPMLSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportOPMLSheet.xib; sourceTree = ""; }; 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportOPMLWindowController.swift; sourceTree = ""; }; - 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLWindowController.swift; sourceTree = ""; }; + 5144EA42227A380F00D19003 /* ExportOPMLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLController.swift; sourceTree = ""; }; 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = ""; }; 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = ""; }; 51543684228F6753005E1CDF /* DetailAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailAccountViewController.swift; sourceTree = ""; }; @@ -925,6 +925,8 @@ 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; + B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLAccessoryViewController.swift; sourceTree = ""; }; + B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportOPMLAccessoryView.xib; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; D553737C20186C1F006D8857 /* Article+Scriptability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Article+Scriptability.swift"; sourceTree = ""; }; @@ -1035,8 +1037,9 @@ children = ( 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */, 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */, - 5144EA3C227A37AF00D19003 /* ExportOPMLSheet.xib */, - 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */, + 5144EA42227A380F00D19003 /* ExportOPMLController.swift */, + B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */, + B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */, ); path = OPML; sourceTree = ""; @@ -1989,12 +1992,12 @@ ORGANIZATIONNAME = "Ranchero Software"; TargetAttributes = { 6581C73220CED60000F4AD34 = { - DevelopmentTeam = M8L2WTLA8W; - ProvisioningStyle = Manual; + DevelopmentTeam = M72QZ9W58G; + ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = M8L2WTLA8W; + DevelopmentTeam = M72QZ9W58G; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -2010,8 +2013,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M8L2WTLA8W; - ProvisioningStyle = Manual; + DevelopmentTeam = M72QZ9W58G; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -2020,7 +2023,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 9C84TZ7Q6Z; + DevelopmentTeam = M72QZ9W58G; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2286,9 +2289,9 @@ 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */, 84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */, + B24C4F85235D39F30000B924 /* ExportOPMLAccessoryView.xib in Resources */, 8459D0F92355794C0050076F /* NNW3OpenPanelAccessoryView.xib in Resources */, 84C9FC8222629E4800D921D6 /* Preferences.storyboard in Resources */, - 5144EA3D227A37AF00D19003 /* ExportOPMLSheet.xib in Resources */, 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */, 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */, 8483630B2262A3F000DA1D35 /* RenameSheet.xib in Resources */, @@ -2497,6 +2500,7 @@ 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */, + B24C4F7B235D39D40000B924 /* ExportOPMLAccessoryViewController.swift in Sources */, 84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */, 84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */, 84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */, @@ -2507,7 +2511,7 @@ 849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */, 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */, 84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */, - 5144EA43227A380F00D19003 /* ExportOPMLWindowController.swift in Sources */, + 5144EA43227A380F00D19003 /* ExportOPMLController.swift in Sources */, 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */, 84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */, D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */, From a762052264b2597c0059a89cc2af882405df0997 Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Mon, 21 Oct 2019 16:31:33 -0500 Subject: [PATCH 04/13] Don't use block-based notification observing --- Mac/MainWindow/OPML/ExportOPMLController.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Mac/MainWindow/OPML/ExportOPMLController.swift b/Mac/MainWindow/OPML/ExportOPMLController.swift index f5c671fcb..2888ff986 100644 --- a/Mac/MainWindow/OPML/ExportOPMLController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLController.swift @@ -9,7 +9,9 @@ import AppKit import Account -struct ExportOPMLController { +class ExportOPMLController { + + weak var savePanel: NSSavePanel? func runSheetOnWindow(_ hostWindow: NSWindow) { @@ -24,11 +26,11 @@ struct ExportOPMLController { panel.isExtensionHidden = false panel.accessoryView = accessoryViewController.view - let observer = NotificationCenter.default.addObserver(forName: .ExportOPMLSelectedAccountDidChange, object: nil, queue: OperationQueue.main) { notification in - self.updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController) - } + NotificationCenter.default.addObserver(self, selector: #selector(selectedAccountDidChange(_:)), name: .ExportOPMLSelectedAccountDidChange, object: accessoryViewController) updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController, force: true) + + savePanel = panel panel.beginSheetModal(for: hostWindow) { result in if result == NSApplication.ModalResponse.OK, let url = panel.url { @@ -45,7 +47,7 @@ struct ExportOPMLController { } } - NotificationCenter.default.removeObserver(observer) + NotificationCenter.default.removeObserver(self) } } @@ -59,4 +61,10 @@ struct ExportOPMLController { panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" } + + @objc private func selectedAccountDidChange(_ notification: Notification) { + if let savePanel = savePanel, let accessoryViewController = notification.object as? ExportOPMLAccessoryViewController { + self.updateNameFieldStringValueIfAppropriate(savePanel: savePanel, from: accessoryViewController) + } + } } From e7278017d991d986a7e6adefcce1e339abca7759 Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Mon, 21 Oct 2019 17:17:07 -0500 Subject: [PATCH 05/13] Forgo notifications entirely, and use a delegate instead --- .../OPML/ExportOPMLAccessoryViewController.swift | 11 ++++++----- Mac/MainWindow/OPML/ExportOPMLController.swift | 12 ++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift index 639d88ec6..6e94a5206 100644 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift @@ -12,13 +12,15 @@ import Account class ExportOPMLAccessoryViewController: NSViewController { @IBOutlet weak var accountPopUpButton: NSPopUpButton! + weak var delegate: ExportOPMLAccessoryViewControllerDelegate? var selectedAccount: Account? { accountPopUpButton.selectedItem?.representedObject as? Account } - init() { + init(delegate: ExportOPMLAccessoryViewControllerDelegate) { super.init(nibName: "ExportOPMLAccessoryView", bundle: nil) + self.delegate = delegate } // MARK: - NSViewController @@ -48,11 +50,10 @@ class ExportOPMLAccessoryViewController: NSViewController { } @IBAction func accountSelected(_ popUpButton: NSPopUpButton) { - NotificationCenter.default.post(name: .ExportOPMLSelectedAccountDidChange, object: self) + delegate!.selectedAccountDidChange(self) } } -extension Notification.Name { - static let ExportOPMLSelectedAccountDidChange = Notification.Name(rawValue: "SelectedAccountDidChange") - +protocol ExportOPMLAccessoryViewControllerDelegate: class { + func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) } diff --git a/Mac/MainWindow/OPML/ExportOPMLController.swift b/Mac/MainWindow/OPML/ExportOPMLController.swift index 2888ff986..87530df3a 100644 --- a/Mac/MainWindow/OPML/ExportOPMLController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLController.swift @@ -9,13 +9,13 @@ import AppKit import Account -class ExportOPMLController { +class ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { weak var savePanel: NSSavePanel? func runSheetOnWindow(_ hostWindow: NSWindow) { - let accessoryViewController = ExportOPMLAccessoryViewController() + let accessoryViewController = ExportOPMLAccessoryViewController(delegate: self) let panel = NSSavePanel() panel.allowedFileTypes = ["opml"] panel.allowsOtherFileTypes = false @@ -26,8 +26,6 @@ class ExportOPMLController { panel.isExtensionHidden = false panel.accessoryView = accessoryViewController.view - NotificationCenter.default.addObserver(self, selector: #selector(selectedAccountDidChange(_:)), name: .ExportOPMLSelectedAccountDidChange, object: accessoryViewController) - updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController, force: true) savePanel = panel @@ -46,8 +44,6 @@ class ExportOPMLController { } } } - - NotificationCenter.default.removeObserver(self) } } @@ -62,8 +58,8 @@ class ExportOPMLController { } - @objc private func selectedAccountDidChange(_ notification: Notification) { - if let savePanel = savePanel, let accessoryViewController = notification.object as? ExportOPMLAccessoryViewController { + internal func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) { + if let savePanel = savePanel { self.updateNameFieldStringValueIfAppropriate(savePanel: savePanel, from: accessoryViewController) } } From 37e77a9596433859fe108ee32aa2db88eae0184c Mon Sep 17 00:00:00 2001 From: Nate Weaver Date: Mon, 21 Oct 2019 17:24:15 -0500 Subject: [PATCH 06/13] Make delegate-less init() fail since a delegate is currently required --- Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift index 6e94a5206..3c7779198 100644 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift @@ -23,7 +23,9 @@ class ExportOPMLAccessoryViewController: NSViewController { self.delegate = delegate } - // MARK: - NSViewController + init() { + preconditionFailure("init() without delegate not implemented by design.") + } required init?(coder: NSCoder) { preconditionFailure("ExportOPMLAccessoryView.init(coder) not implemented by design.") From 06b0e35739ec10196381f5c8d596864e30c1b57a Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 23 Oct 2019 22:00:14 -0700 Subject: [PATCH 07/13] Move Protocol declaration to top of ExportOPMLAccessoryViewController.swift. As critical API, it needs to be easy to spot. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create separation extensions — one for protocol conformance, one for private methods — ExportOPMLController. Make minor code formatting change: instead of { return } — which is hard to set a breakpoint on — move the return to its own line. Remove an internal access qualifier, since internal is default and implied. --- .../ExportOPMLAccessoryViewController.swift | 7 ++-- .../OPML/ExportOPMLController.swift | 37 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift index 3c7779198..d558d7da0 100644 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift @@ -9,6 +9,10 @@ import AppKit import Account +protocol ExportOPMLAccessoryViewControllerDelegate: class { + func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) +} + class ExportOPMLAccessoryViewController: NSViewController { @IBOutlet weak var accountPopUpButton: NSPopUpButton! @@ -56,6 +60,3 @@ class ExportOPMLAccessoryViewController: NSViewController { } } -protocol ExportOPMLAccessoryViewControllerDelegate: class { - func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) -} diff --git a/Mac/MainWindow/OPML/ExportOPMLController.swift b/Mac/MainWindow/OPML/ExportOPMLController.swift index 87530df3a..eb3fae3b0 100644 --- a/Mac/MainWindow/OPML/ExportOPMLController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLController.swift @@ -9,7 +9,7 @@ import AppKit import Account -class ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { +class ExportOPMLController { weak var savePanel: NSSavePanel? @@ -33,7 +33,9 @@ class ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { panel.beginSheetModal(for: hostWindow) { result in if result == NSApplication.ModalResponse.OK, let url = panel.url { DispatchQueue.main.async { - guard let account = accessoryViewController.selectedAccount else { return } + guard let account = accessoryViewController.selectedAccount else { + return + } let filename = url.lastPathComponent let opmlString = OPMLExporter.OPMLString(with: account, title: filename) do { @@ -45,22 +47,31 @@ class ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { } } } - } +} - private func updateNameFieldStringValueIfAppropriate(savePanel panel: NSSavePanel, from accessoryViewController: ExportOPMLAccessoryViewController, force: Bool = false) { +extension ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { - if !force && !panel.nameFieldStringValue.hasPrefix("Subscriptions-") { return } - - guard let account = accessoryViewController.selectedAccount else { return } - let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) - panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" - - } - - internal func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) { + func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) { if let savePanel = savePanel { self.updateNameFieldStringValueIfAppropriate(savePanel: savePanel, from: accessoryViewController) } } } + +private extension ExportOPMLController { + + func updateNameFieldStringValueIfAppropriate(savePanel panel: NSSavePanel, from accessoryViewController: ExportOPMLAccessoryViewController, force: Bool = false) { + + if !force && !panel.nameFieldStringValue.hasPrefix("Subscriptions-") { + return + } + + guard let account = accessoryViewController.selectedAccount else { + return + } + let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) + panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" + + } +} From 6f29497ec89daedebecf76bb6569e06e66143257 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 23 Oct 2019 22:27:08 -0700 Subject: [PATCH 08/13] Move NSTextFieldDelegate conformance to separate extension. Handle the optional nil case in controlTextDidChange. --- .../AddFolder/AddFolderWindowController.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift index 5cec5174e..2a0153582 100644 --- a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift +++ b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift @@ -10,7 +10,7 @@ import AppKit import Articles import Account -class AddFolderWindowController : NSWindowController, NSTextFieldDelegate { +class AddFolderWindowController : NSWindowController { @IBOutlet var folderNameTextField: NSTextField! @IBOutlet var accountPopupButton: NSPopUpButton! @@ -79,20 +79,23 @@ class AddFolderWindowController : NSWindowController, NSTextFieldDelegate { // MARK: Actions @IBAction func cancel(_ sender: Any?) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) } @IBAction func addFolder(_ sender: Any?) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) } +} - // MARK: Text Field Delegate +// MARK: Text Field Delegate + +extension AddFolderWindowController: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { - if let value = (obj.object as? NSTextField)?.stringValue { - addFolderButton.isEnabled = !value.isEmpty + guard let folderName = (obj.object as? NSTextField)?.stringValue else { + addFolderButton.isEnabled = false + return } + addFolderButton.isEnabled = !folderName.isEmpty } } From 1c06eb27e8d197131647bcd876b8de4eb3178d11 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 23 Oct 2019 22:33:21 -0700 Subject: [PATCH 09/13] Create separate private extension. Make misc. code formatting changes to match current thinking. --- .../AddFolder/AddFolderWindowController.swift | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift index 2a0153582..c572ce1b3 100644 --- a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift +++ b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift @@ -15,17 +15,15 @@ class AddFolderWindowController : NSWindowController { @IBOutlet var folderNameTextField: NSTextField! @IBOutlet var accountPopupButton: NSPopUpButton! @IBOutlet var addFolderButton: NSButton! - var hostWindow: NSWindow? + private var hostWindow: NSWindow? convenience init() { - self.init(windowNibName: NSNib.Name("AddFolderSheet")) } - // MARK: API + // MARK: - API func runSheetOnWindow(_ w: NSWindow) { - hostWindow = w hostWindow!.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) -> Void in @@ -35,7 +33,7 @@ class AddFolderWindowController : NSWindowController { } } - // MARK: NSViewController + // MARK: - NSViewController override func windowDidLoad() { let preferredAccountID = AppDefaults.addFolderAccountID @@ -54,40 +52,21 @@ class AddFolderWindowController : NSWindowController { if oneAccount.accountID == preferredAccountID { accountPopupButton.select(oneMenuItem) } - } } - // MARK: Private - - private func addFolderIfNeeded() { - guard let menuItem = accountPopupButton.selectedItem else { - return - } - - let account = menuItem.representedObject as! Account - AppDefaults.addFolderAccountID = account.accountID - - let folderName = self.folderNameTextField.stringValue - if folderName.isEmpty { - return - } - - account.ensureFolder(with: folderName) - } - - // MARK: Actions + // MARK: - Actions @IBAction func cancel(_ sender: Any?) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) + hostWindow!.endSheet(window!, returnCode: .cancel) } @IBAction func addFolder(_ sender: Any?) { - hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) + hostWindow!.endSheet(window!, returnCode: .OK) } } -// MARK: Text Field Delegate +// MARK: - Text Field Delegate extension AddFolderWindowController: NSTextFieldDelegate { @@ -99,3 +78,24 @@ extension AddFolderWindowController: NSTextFieldDelegate { addFolderButton.isEnabled = !folderName.isEmpty } } + +// MARK: - Private + +private extension AddFolderWindowController { + + private func addFolderIfNeeded() { + guard let menuItem = accountPopupButton.selectedItem else { + return + } + + let account = menuItem.representedObject as! Account + AppDefaults.addFolderAccountID = account.accountID + + let folderName = self.folderNameTextField.stringValue + if folderName.isEmpty { + return + } + + account.ensureFolder(with: folderName) + } +} From f8376b807a08b3d729d70c698172136c9dc34090 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 24 Oct 2019 21:12:58 -0700 Subject: [PATCH 10/13] Restore two-step process for OPML exporting. --- Mac/AppDelegate.swift | 4 +- .../OPML/ExportOPMLAccessoryView.xib | 83 -------------- .../ExportOPMLAccessoryViewController.swift | 62 ---------- .../OPML/ExportOPMLController.swift | 77 ------------- Mac/MainWindow/OPML/ExportOPMLSheet.xib | 104 +++++++++++++++++ .../OPML/ExportOPMLWindowController.swift | 106 ++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 32 +++--- 7 files changed, 226 insertions(+), 242 deletions(-) delete mode 100644 Mac/MainWindow/OPML/ExportOPMLAccessoryView.xib delete mode 100644 Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift delete mode 100644 Mac/MainWindow/OPML/ExportOPMLController.swift create mode 100644 Mac/MainWindow/OPML/ExportOPMLSheet.xib create mode 100644 Mac/MainWindow/OPML/ExportOPMLWindowController.swift diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index f971da1ed..73519e152 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -57,7 +57,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, private var addFeedController: AddFeedController? private var addFolderWindowController: AddFolderWindowController? private var importOPMLController: ImportOPMLWindowController? - private var exportOPMLController: ExportOPMLController? + private var exportOPMLController: ExportOPMLWindowController? private var keyboardShortcutsWindowController: WebViewWindowController? private var inspectorWindowController: InspectorWindowController? private var crashReportWindowController: CrashReportWindowController? // For testing only @@ -438,7 +438,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, return } - exportOPMLController = ExportOPMLController() + exportOPMLController = ExportOPMLWindowController() exportOPMLController?.runSheetOnWindow(mainWindowController!.window!) } diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryView.xib b/Mac/MainWindow/OPML/ExportOPMLAccessoryView.xib deleted file mode 100644 index 49376a471..000000000 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryView.xib +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Choose the account with the subscriptions you’d like to export. Subscriptions are exported in the standard OPML format, which most RSS readers can import. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift b/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift deleted file mode 100644 index d558d7da0..000000000 --- a/Mac/MainWindow/OPML/ExportOPMLAccessoryViewController.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// ExportOPMLAccessoryViewController.swift -// NetNewsWire -// -// Created by Nate Weaver on 2019-10-20. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import AppKit -import Account - -protocol ExportOPMLAccessoryViewControllerDelegate: class { - func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) -} - -class ExportOPMLAccessoryViewController: NSViewController { - - @IBOutlet weak var accountPopUpButton: NSPopUpButton! - weak var delegate: ExportOPMLAccessoryViewControllerDelegate? - - var selectedAccount: Account? { - accountPopUpButton.selectedItem?.representedObject as? Account - } - - init(delegate: ExportOPMLAccessoryViewControllerDelegate) { - super.init(nibName: "ExportOPMLAccessoryView", bundle: nil) - self.delegate = delegate - } - - init() { - preconditionFailure("init() without delegate not implemented by design.") - } - - required init?(coder: NSCoder) { - preconditionFailure("ExportOPMLAccessoryView.init(coder) not implemented by design.") - } - - override func viewDidLoad() { - accountPopUpButton.removeAllItems() - - let menu = NSMenu() - accountPopUpButton.menu = menu - - for oneAccount in AccountManager.shared.sortedAccounts { - - let oneMenuItem = NSMenuItem() - oneMenuItem.title = oneAccount.nameForDisplay - oneMenuItem.representedObject = oneAccount - menu.addItem(oneMenuItem) - - if oneAccount.accountID == AppDefaults.exportOPMLAccountID { - accountPopUpButton.select(oneMenuItem) - } - - } - } - - @IBAction func accountSelected(_ popUpButton: NSPopUpButton) { - delegate!.selectedAccountDidChange(self) - } -} - diff --git a/Mac/MainWindow/OPML/ExportOPMLController.swift b/Mac/MainWindow/OPML/ExportOPMLController.swift deleted file mode 100644 index eb3fae3b0..000000000 --- a/Mac/MainWindow/OPML/ExportOPMLController.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// ExportOPMLController.swift -// NetNewsWire -// -// Created by Maurice Parker on 5/1/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import AppKit -import Account - -class ExportOPMLController { - - weak var savePanel: NSSavePanel? - - func runSheetOnWindow(_ hostWindow: NSWindow) { - - let accessoryViewController = ExportOPMLAccessoryViewController(delegate: self) - let panel = NSSavePanel() - panel.allowedFileTypes = ["opml"] - panel.allowsOtherFileTypes = false - panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML") - panel.title = NSLocalizedString("Export OPML", comment: "Export OPML") - panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML") - panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML") - panel.isExtensionHidden = false - panel.accessoryView = accessoryViewController.view - - updateNameFieldStringValueIfAppropriate(savePanel: panel, from: accessoryViewController, force: true) - - savePanel = panel - - panel.beginSheetModal(for: hostWindow) { result in - if result == NSApplication.ModalResponse.OK, let url = panel.url { - DispatchQueue.main.async { - guard let account = accessoryViewController.selectedAccount else { - return - } - let filename = url.lastPathComponent - let opmlString = OPMLExporter.OPMLString(with: account, title: filename) - do { - try opmlString.write(to: url, atomically: true, encoding: .utf8) - } - catch let error as NSError { - NSApplication.shared.presentError(error) - } - } - } - } - } -} - -extension ExportOPMLController: ExportOPMLAccessoryViewControllerDelegate { - - func selectedAccountDidChange(_ accessoryViewController: ExportOPMLAccessoryViewController) { - if let savePanel = savePanel { - self.updateNameFieldStringValueIfAppropriate(savePanel: savePanel, from: accessoryViewController) - } - } -} - -private extension ExportOPMLController { - - func updateNameFieldStringValueIfAppropriate(savePanel panel: NSSavePanel, from accessoryViewController: ExportOPMLAccessoryViewController, force: Bool = false) { - - if !force && !panel.nameFieldStringValue.hasPrefix("Subscriptions-") { - return - } - - guard let account = accessoryViewController.selectedAccount else { - return - } - let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) - panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" - - } -} diff --git a/Mac/MainWindow/OPML/ExportOPMLSheet.xib b/Mac/MainWindow/OPML/ExportOPMLSheet.xib new file mode 100644 index 000000000..e6bc9f85b --- /dev/null +++ b/Mac/MainWindow/OPML/ExportOPMLSheet.xib @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Choose the account with the subscriptions you’d like to export. Subscriptions are exported in the standard OPML format, which most RSS readers can import. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift new file mode 100644 index 000000000..cdbda991c --- /dev/null +++ b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift @@ -0,0 +1,106 @@ +// +// ExportOPMLWindowController.swift +// NetNewsWire +// +// Created by Maurice Parker on 5/1/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import AppKit +import Account + +class ExportOPMLWindowController: NSWindowController { + + @IBOutlet weak var accountPopUpButton: NSPopUpButton! + private weak var hostWindow: NSWindow? + + convenience init() { + self.init(windowNibName: NSNib.Name("ExportOPMLSheet")) + } + + override func windowDidLoad() { + accountPopUpButton.removeAllItems() + + let menu = NSMenu() + accountPopUpButton.menu = menu + + for oneAccount in AccountManager.shared.sortedAccounts { + + let oneMenuItem = NSMenuItem() + oneMenuItem.title = oneAccount.nameForDisplay + oneMenuItem.representedObject = oneAccount + menu.addItem(oneMenuItem) + + if oneAccount.accountID == AppDefaults.exportOPMLAccountID { + accountPopUpButton.select(oneMenuItem) + } + + } + } + + // MARK: API + + func runSheetOnWindow(_ hostWindow: NSWindow) { + + self.hostWindow = hostWindow + + if AccountManager.shared.accounts.count == 1 { + let account = AccountManager.shared.accounts.first! + exportOPML(account: account) + } else { + hostWindow.beginSheet(window!) + } + + } + + // MARK: Actions + + @IBAction func cancel(_ sender: Any) { + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) + } + + @IBAction func exportOPML(_ sender: Any) { + + guard let menuItem = accountPopUpButton.selectedItem else { + return + } + + let account = menuItem.representedObject as! Account + AppDefaults.exportOPMLAccountID = account.accountID + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) + exportOPML(account: account) + + } + + func exportOPML(account: Account) { + + let panel = NSSavePanel() + panel.allowedFileTypes = ["opml"] + panel.allowsOtherFileTypes = false + panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML") + panel.title = NSLocalizedString("Export OPML", comment: "Export OPML") + panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML") + panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML") + panel.isExtensionHidden = false + + let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) + panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" + + panel.beginSheetModal(for: hostWindow!) { result in + if result == NSApplication.ModalResponse.OK, let url = panel.url { + DispatchQueue.main.async { + let filename = url.lastPathComponent + let opmlString = OPMLExporter.OPMLString(with: account, title: filename) + do { + try opmlString.write(to: url, atomically: true, encoding: String.Encoding.utf8) + } + catch let error as NSError { + NSApplication.shared.presentError(error) + } + } + } + } + + } + +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 1a9fa9f4e..571fbf842 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; }; 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */; }; 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; }; - 5144EA43227A380F00D19003 /* ExportOPMLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLController.swift */; }; 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; }; 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 51543685228F6753005E1CDF /* DetailAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51543684228F6753005E1CDF /* DetailAccountViewController.swift */; }; @@ -231,6 +230,8 @@ 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; }; 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849C64671ED37A5D003D8FC0 /* Assets.xcassets */; }; + 849C78902362AAFC009A71E4 /* ExportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */; }; + 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */; }; 849EE70F203919360082A1EA /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE70E203919360082A1EA /* AppAssets.swift */; }; 849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */; }; 84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; @@ -312,8 +313,6 @@ 84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; }; 84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; - B24C4F7B235D39D40000B924 /* ExportOPMLAccessoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */; }; - B24C4F85235D39F30000B924 /* ExportOPMLAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */; }; D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */; }; @@ -687,7 +686,6 @@ 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddLocalWindowController.swift; sourceTree = ""; }; 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImportOPMLSheet.xib; sourceTree = ""; }; 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportOPMLWindowController.swift; sourceTree = ""; }; - 5144EA42227A380F00D19003 /* ExportOPMLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLController.swift; sourceTree = ""; }; 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = ""; }; 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = ""; }; 51543684228F6753005E1CDF /* DetailAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailAccountViewController.swift; sourceTree = ""; }; @@ -850,6 +848,8 @@ 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; 849C64671ED37A5D003D8FC0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 849C64711ED37A5D003D8FC0 /* NetNewsWireTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NetNewsWireTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ExportOPMLSheet.xib; sourceTree = ""; }; + 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportOPMLWindowController.swift; sourceTree = ""; }; 849EE70E203919360082A1EA /* AppAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServicePickerDelegate.swift; sourceTree = ""; }; 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToMicroBlogCommand.swift; sourceTree = ""; }; @@ -925,8 +925,6 @@ 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; - B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLAccessoryViewController.swift; sourceTree = ""; }; - B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportOPMLAccessoryView.xib; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; D553737C20186C1F006D8857 /* Article+Scriptability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Article+Scriptability.swift"; sourceTree = ""; }; @@ -1037,9 +1035,8 @@ children = ( 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */, 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */, - 5144EA42227A380F00D19003 /* ExportOPMLController.swift */, - B24C4F7A235D39D40000B924 /* ExportOPMLAccessoryViewController.swift */, - B24C4F84235D39F30000B924 /* ExportOPMLAccessoryView.xib */, + 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */, + 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */, ); path = OPML; sourceTree = ""; @@ -1992,12 +1989,12 @@ ORGANIZATIONNAME = "Ranchero Software"; TargetAttributes = { 6581C73220CED60000F4AD34 = { - DevelopmentTeam = M72QZ9W58G; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = M8L2WTLA8W; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -2013,8 +2010,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M72QZ9W58G; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -2023,7 +2020,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = 9C84TZ7Q6Z; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2289,8 +2286,8 @@ 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */, 84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */, - B24C4F85235D39F30000B924 /* ExportOPMLAccessoryView.xib in Resources */, 8459D0F92355794C0050076F /* NNW3OpenPanelAccessoryView.xib in Resources */, + 849C78902362AAFC009A71E4 /* ExportOPMLSheet.xib in Resources */, 84C9FC8222629E4800D921D6 /* Preferences.storyboard in Resources */, 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */, 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */, @@ -2479,6 +2476,7 @@ 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */, 845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */, 84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */, + 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */, 84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */, 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */, 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, @@ -2500,7 +2498,6 @@ 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */, - B24C4F7B235D39D40000B924 /* ExportOPMLAccessoryViewController.swift in Sources */, 84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */, 84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */, 84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */, @@ -2511,7 +2508,6 @@ 849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */, 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */, 84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */, - 5144EA43227A380F00D19003 /* ExportOPMLController.swift in Sources */, 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */, 84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */, D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */, From eec008ca5b9a281cf9d0d5a3d4a4715d94482413 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 24 Oct 2019 21:41:20 -0700 Subject: [PATCH 11/13] =?UTF-8?q?Remove=20Info.plist=20from=20NetNewsWire?= =?UTF-8?q?=20Tests=20copy=20files=20phase,=20since=20it=20shouldn?= =?UTF-8?q?=E2=80=99t=20be=20there.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NetNewsWire.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 571fbf842..28526c805 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -309,7 +309,6 @@ 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; }; 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; }; 84F9EAF6213660A100CF2DE4 /* NetNewsWireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE3213660A100CF2DE4 /* NetNewsWireTests.swift */; }; - 84F9EAF7213660A100CF2DE4 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 84F9EAE4213660A100CF2DE4 /* Info.plist */; }; 84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; }; 84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; @@ -2307,7 +2306,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 84F9EAF7213660A100CF2DE4 /* Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; From cd4135bf9a9dfe6b418eeab26a9ab0a3a79a370f Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 24 Oct 2019 21:48:24 -0700 Subject: [PATCH 12/13] Remove old, comment-out update method in ArticlesTable. --- .../ArticlesDatabase/ArticlesTable.swift | 59 ------------------- 1 file changed, 59 deletions(-) diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index d9bf36e7c..418e53a1a 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -272,65 +272,6 @@ final class ArticlesTable: DatabaseTable { } } -// func update(_ feedID: String, _ parsedItems: Set, _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) { -// if parsedItems.isEmpty { -// completion(nil, nil) -// return -// } -// -// // 1. Ensure statuses for all the incoming articles. -// // 2. Create incoming articles with parsedItems. -// // 3. Ignore incoming articles that are userDeleted || (!starred and really old) -// // 4. Fetch all articles for the feed. -// // 5. Create array of Articles not in database and save them. -// // 6. Create array of updated Articles and save what’s changed. -// // 7. Call back with new and updated Articles. -// // 8. Update search index. -// -// let articleIDs = Set(parsedItems.map { $0.articleID }) -// -// self.queue.update { (database) in -// let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1 -// assert(statusesDictionary.count == articleIDs.count) -// -// let allIncomingArticles = Article.articlesWithParsedItems(parsedItems, self.accountID, feedID, statusesDictionary) //2 -// if allIncomingArticles.isEmpty { -// self.callUpdateArticlesCompletionBlock(nil, nil, completion) -// return -// } -// -// let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3 -// if incomingArticles.isEmpty { -// self.callUpdateArticlesCompletionBlock(nil, nil, completion) -// return -// } -// -// let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database) //4 -// let fetchedArticlesDictionary = fetchedArticles.dictionary() -// -// let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5 -// let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6 -// -// self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7 -// -// // 8. Update search index. -// var articlesToIndex = Set
() -// if let newArticles = newArticles { -// articlesToIndex.formUnion(newArticles) -// } -// if let updatedArticles = updatedArticles { -// articlesToIndex.formUnion(updatedArticles) -// } -// let articleIDs = articlesToIndex.articleIDs() -// if articleIDs.isEmpty { -// return -// } -// DispatchQueue.main.async { -// self.searchTable.ensureIndexedArticles(for: articleIDs) -// } -// } -// } - func ensureStatuses(_ articleIDs: Set, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool) { self.queue.update { (database) in let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, defaultRead, database) From 3354d5a56972803a3f55fb4840124b7f7114d6b6 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 24 Oct 2019 22:28:26 -0700 Subject: [PATCH 13/13] Delete articles and statuses from feeds no longer subscribed-to. At startup. Fix #899. --- Frameworks/Account/Account.swift | 1 + .../ArticlesDatabase/ArticlesDatabase.swift | 9 ++++++ .../ArticlesDatabase/ArticlesTable.swift | 29 +++++++++++++++++++ .../ArticlesDatabase/StatusesTable.swift | 6 ++++ 4 files changed, 45 insertions(+) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index b4b1ff707..4c9258c83 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -254,6 +254,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, pullObjectsFromDisk() DispatchQueue.main.async { + self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().feedIDs()) self.fetchAllUnreadCounts() } diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index 5aa003d30..e14dac4de 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -152,6 +152,15 @@ public final class ArticlesDatabase { public func emptyCaches() { articlesTable.emptyCaches() } + + // MARK: - Cleanup + + // These are to be used only at startup. These are to prevent the database from growing forever. + + /// Calls the various clean-up functions. + public func cleanupDatabaseAtStartup(subscribedToFeedIDs: Set) { + articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToFeedIDs) + } } // MARK: - Private diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index 418e53a1a..a0b6edbd2 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -423,6 +423,31 @@ final class ArticlesTable: DatabaseTable { self.databaseArticlesCache = [String: DatabaseArticle]() } } + + // MARK: - Cleanup + + /// Delete articles from feeds that are no longer in the current set of subscribed-to feeds. + /// This deletes from the articles and articleStatuses tables, + /// and, via a trigger, it also deletes from the search index. + func deleteArticlesNotInSubscribedToFeedIDs(_ feedIDs: Set) { + if feedIDs.isEmpty { + return + } + queue.run { (database) in + let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! + let sql = "select articleID from articles where feedID not in \(placeholders);" + let parameters = Array(feedIDs) as [Any] + guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else { + return + } + let articleIDs = resultSet.mapToSet{ $0.string(forColumn: DatabaseKey.articleID) } + if articleIDs.isEmpty { + return + } + self.removeArticles(articleIDs, database) + self.statusesTable.removeStatuses(articleIDs, database) + } + } } // MARK: - Private @@ -730,6 +755,10 @@ private extension ArticlesTable { // Drop Articles that we can ignore. return Set(articles.filter{ !statusIndicatesArticleIsIgnorable($0.status) }) } + + func removeArticles(_ articleIDs: Set, _ database: FMDatabase) { + deleteRowsWhere(key: DatabaseKey.articleID, equalsAnyValue: Array(articleIDs), in: database) + } } private extension Set where Element == ParsedItem { diff --git a/Frameworks/ArticlesDatabase/StatusesTable.swift b/Frameworks/ArticlesDatabase/StatusesTable.swift index fa5a741ab..65615c1b0 100644 --- a/Frameworks/ArticlesDatabase/StatusesTable.swift +++ b/Frameworks/ArticlesDatabase/StatusesTable.swift @@ -134,6 +134,12 @@ final class StatusesTable: DatabaseTable { return d } + + // MARK: - Cleanup + + func removeStatuses(_ articleIDs: Set, _ database: FMDatabase) { + deleteRowsWhere(key: DatabaseKey.articleID, equalsAnyValue: Array(articleIDs), in: database) + } } // MARK: - Private