diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..1f32a4fa1 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,58 @@ +# iOS CircleCI 2.0 configuration file +# +version: 2 +jobs: + build: + + # Specify the Xcode version to use + macos: + xcode: "10.2.1" + # https://circleci.com/docs/2.0/configuration-reference/ + + # Mac/IOS specific examples and docs under the following links: + # https://circleci.com/docs/2.0/hello-world-macos/ + + steps: + - checkout + - run: git submodule sync + - run: git submodule update --init + # Commands will execute in macOS container + # with Xcode 10.2.1 installed + - run: xcodebuild -version + #- run: + # name: get xcodebuild build options + # command: xcodebuild -help + - run: + name: get xcodebuild build settings + command: xcodebuild -showBuildSettings + + - run: + name: force wipe of any pre-existing derived data in CI + command: rm -rf /Users/distiller/Library/Developer/Xcode/DerivedData/NetNewsWire-* + + # Build the app and run tests + - run: + name: Build Mac + command: xcodebuild -workspace NetNewsWire.xcworkspace -scheme NetNewsWire -configuration Debug -showBuildTimingSummary + # NOTE(heckj): + # the -configuration Release build invokes a shell script specifically + # codesigning the Sparkle pieces with the developer 'Brent Simmons', + # so we don't try and invoke that in CI + # + + # the stuff below is from example that was using fastlane + # (and we're not using that...) so it's placeholder tidbits + # to clue me in to where I can get things for test log output + # for the CircleCI UI exposure... + + # Collect XML test results data to show in the UI, + # and save the same XML files under test-results folder + # in the Artifacts tab + #- store_test_results: + # path: test_output/report.xml + #- store_artifacts: + # path: /tmp/test-results + # destination: scan-test-results + #- store_artifacts: + # path: ~/Library/Logs/scan + # destination: scan-logs diff --git a/Appcasts/netnewswire-beta.xml b/Appcasts/netnewswire-beta.xml index 49ec6b42e..691906ae5 100755 --- a/Appcasts/netnewswire-beta.xml +++ b/Appcasts/netnewswire-beta.xml @@ -6,6 +6,58 @@ Most recent NetNewsWire changes with links to updates. en + + NetNewsWire 5.0a3 + Fixed crash happening only on macOS 10.15 beta. We owe Apple a bug report for this one.

+

Fixed a crash that could happen when finding a feed.

+

Skip showing error dialogs on automatic refreshes.

+

Immediately show the refresh progress bar when an OPML import to Feedbin starts.

+

Add ellipsis to Import from OPML and Export to OPML buttons.

+ ]]>
+ Mon, 10 Jun 2019 21:45:00 -0700 + + 10.14.4 +
+ + + + NetNewsWire 5.0a2 + Escape HTML in the title in the article view — if there’s HTML in the title, the tags should actually be displayed.

+

The Mark as Read command in the Article menu now turns into Mark as Unread at the appropriate times.

+

Feedbin syncing: send locally changed statuses before downloading statuses from the server.

+

Feedbin syncing: fix bug renaming a folder that has no feeds.

+

Feedbin syncing: fixed a bunch of accuracy and reliability issues, and a crashing bug.

+

Fixed issue where local account feed finder could lock UI in the case of an error.

+ ]]>
+ Sat, 08 Jun 2019 16:00:00 -0700 + + 10.14.4 +
+ + + + NetNewsWire 5.0a1 + NetNewsWire 5.0 has reached alpha stage! This means it has no known bugs. It surely does have bugs, though. Now it’s time for testing. (And writing the Help book. And making the website better.)

+

Fixed a crashing bug with parsing a response from Feedbin. (Totally our fault, not Feedbin’s fault.)

+

Show avatars from Micro.blog feeds with multiple authors (such as your personal timeline feed).

+

Made OPML import to the On My Mac account way faster.

+

The Today smart feed now updates when the day changes.

+

You can now drag and drop in the sidebar between accounts.

+

Made the default file name for OPML exports “Subscriptions-[accountName].opml”

+

Add explanation text to Account preferences for the Name field. (It’s just a display name and doesn’t affect authentication.)

+

Fixed several bugs with Feedbin syncing — it’s now more reliable. (We know of no remaining sync bugs, though of course there might be some.)

+

Added a placeholder web page for the Help book.

+

New app icon! But it might take a while for your Mac to notice and put in the Dock. (I wish we could speed that up, but it’s out of our control.)

+ ]]>
+ Fri, 31 May 2019 20:30:00 -0700 + + 10.14.4 +
+ + NetNewsWire 5.0d17 ")?.lowerBound { - return Int(link[lowerBound..") { + return Int(partialLink[partialLink.startIndex..) -> Void) { + guard folder.hasAtLeastOneFeed() else { + folder.name = name + return + } + caller.renameTag(oldName: folder.name ?? "", newName: name) { result in switch result { case .success: DispatchQueue.main.async { + self.renameFolderRelationship(for: account, fromName: folder.name ?? "", toName: name) folder.name = name completion(.success(())) } @@ -269,16 +280,44 @@ final class FeedbinAccountDelegate: AccountDelegate { let group = DispatchGroup() for feed in folder.topLevelFeeds { - group.enter() - removeFeed(for: account, with: feed, from: folder) { result in - group.leave() - switch result { - case .success: - break - case .failure(let error): - os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) + + if feed.folderRelationship?.count ?? 0 > 1 { + + if let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] { + group.enter() + caller.deleteTagging(taggingID: feedTaggingID) { result in + group.leave() + switch result { + case .success: + DispatchQueue.main.async { + self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") + } + case .failure(let error): + os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) + } + } } + + } else { + + if let subscriptionID = feed.subscriptionID { + group.enter() + caller.deleteSubscription(subscriptionID: subscriptionID) { result in + group.leave() + switch result { + case .success: + DispatchQueue.main.async { + account.clearFeedMetadata(feed) + } + case .failure(let error): + os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) + } + } + + } + } + } group.notify(queue: DispatchQueue.main) { @@ -347,7 +386,6 @@ final class FeedbinAccountDelegate: AccountDelegate { if feed.folderRelationship?.count ?? 0 > 1 { deleteTagging(for: account, with: feed, from: container, completion: completion) } else { - account.clearFeedMetadata(feed) deleteSubscription(for: account, with: feed, from: container, completion: completion) } } @@ -399,12 +437,23 @@ final class FeedbinAccountDelegate: AccountDelegate { func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { - createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in - switch result { - case .success: - completion(.success(())) - case .failure(let error): - completion(.failure(error)) + if let existingFeed = account.existingFeed(withURL: feed.url) { + account.addFeed(existingFeed, to: container) { result in + switch result { + case .success: + completion(.success(())) + case .failure(let error): + completion(.failure(error)) + } + } + } else { + createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in + switch result { + case .success: + completion(.success(())) + case .failure(let error): + completion(.failure(error)) + } } } @@ -412,22 +461,27 @@ final class FeedbinAccountDelegate: AccountDelegate { func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { - account.addFolder(folder) let group = DispatchGroup() for feed in folder.topLevelFeeds { + folder.topLevelFeeds.remove(feed) + group.enter() - addFeed(for: account, with: feed, to: folder) { result in - if account.topLevelFeeds.contains(feed) { - account.removeFeed(feed) - } + restoreFeed(for: account, feed: feed, container: folder) { result in group.leave() + switch result { + case .success: + break + case .failure(let error): + os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription) + } } } group.notify(queue: DispatchQueue.main) { + account.addFolder(folder) completion(.success(())) } @@ -502,6 +556,7 @@ private extension FeedbinAccountDelegate { if let result = importResult, result.complete { os_log(.debug, log: self.log, "Checking status of OPML import successfully completed.") timer.invalidate() + self.refreshProgress.completeTask() self.opmlImportInProgress = false DispatchQueue.main.async { completion(.success(())) @@ -510,6 +565,7 @@ private extension FeedbinAccountDelegate { case .failure(let error): os_log(.debug, log: self.log, "Import OPML check failed.") timer.invalidate() + self.refreshProgress.completeTask() self.opmlImportInProgress = false DispatchQueue.main.async { completion(.failure(error)) @@ -647,7 +703,10 @@ private extension FeedbinAccountDelegate { DispatchQueue.main.sync { if let feed = account.idToFeedDictionary[subFeedId] { feed.name = subscription.name + // If the name has been changed on the server remove the locally edited name + feed.editedName = nil feed.homePageURL = subscription.homePageURL + feed.subscriptionID = String(subscription.subscriptionID) } else { let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subFeedId, homePageURL: subscription.homePageURL) feed.subscriptionID = String(subscription.subscriptionID) @@ -792,6 +851,17 @@ private extension FeedbinAccountDelegate { } + func renameFolderRelationship(for account: Account, fromName: String, toName: String) { + for feed in account.flattenedFeeds() { + if var folderRelationship = feed.folderRelationship { + let relationship = folderRelationship[fromName] + folderRelationship[fromName] = nil + folderRelationship[toName] = relationship + feed.folderRelationship = folderRelationship + } + } + } + func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = nil @@ -1158,6 +1228,7 @@ private extension FeedbinAccountDelegate { switch result { case .success: DispatchQueue.main.async { + account.clearFeedMetadata(feed) account.removeFeed(feed) if let folders = account.folders { for folder in folders { diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 36fee4e59..a4eb4ca0e 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -124,12 +124,12 @@ final class LocalAccountDelegate: AccountDelegate { } - case .failure(let error): - completion(.failure(error)) + case .failure: + completion(.failure(AccountError.createErrorNotFound)) } } - + } func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { diff --git a/Mac/Base.lproj/Main.storyboard b/Mac/Base.lproj/Main.storyboard index 83e29b174..fe81915b1 100644 --- a/Mac/Base.lproj/Main.storyboard +++ b/Mac/Base.lproj/Main.storyboard @@ -413,7 +413,7 @@ - + diff --git a/Mac/ErrorHandler.swift b/Mac/ErrorHandler.swift index f0258a7e2..018da1a63 100644 --- a/Mac/ErrorHandler.swift +++ b/Mac/ErrorHandler.swift @@ -8,11 +8,18 @@ import AppKit import Account +import os.log struct ErrorHandler { + private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Account") + public static func present(_ error: Error) { NSApplication.shared.presentError(error) } + public static func log(_ error: Error) { + os_log(.error, log: self.log, "%@", error.localizedDescription) + } + } diff --git a/Mac/MainWindow/AddFeed/AddFeedController.swift b/Mac/MainWindow/AddFeed/AddFeedController.swift index 7475be1b3..3760dbe88 100644 --- a/Mac/MainWindow/AddFeed/AddFeedController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedController.swift @@ -62,7 +62,10 @@ class AddFeedController: AddFeedWindowControllerDelegate { account.createFeed(url: url.absoluteString, name: title, container: container) { result in - self.endShowingProgress() + DispatchQueue.main.async { + self.endShowingProgress() + } + BatchUpdate.shared.end() switch result { diff --git a/Mac/MainWindow/OPML/ExportOPMLSheet.xib b/Mac/MainWindow/OPML/ExportOPMLSheet.xib index 8e7aab95a..e6bc9f85b 100644 --- a/Mac/MainWindow/OPML/ExportOPMLSheet.xib +++ b/Mac/MainWindow/OPML/ExportOPMLSheet.xib @@ -18,12 +18,12 @@ - - + + - + 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. @@ -40,7 +40,7 @@ - + @@ -54,8 +54,8 @@