From afd952fbc2212047e71b99c57d79488cf7319466 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Mon, 20 Sep 2021 09:36:09 +0800 Subject: [PATCH] refactors iOS theme downloads --- .../AppDelegate+Scriptability.swift | 1 - NetNewsWire.xcodeproj/project.pbxproj | 6 + .../ArticleTheme+Notifications.swift | 15 +++ iOS/SceneDelegate.swift | 104 +++++++++++------- 4 files changed, 85 insertions(+), 41 deletions(-) create mode 100644 Shared/ArticleStyles/ArticleTheme+Notifications.swift diff --git a/Mac/Scriptability/AppDelegate+Scriptability.swift b/Mac/Scriptability/AppDelegate+Scriptability.swift index 81eba91c2..b8ad7ee3d 100644 --- a/Mac/Scriptability/AppDelegate+Scriptability.swift +++ b/Mac/Scriptability/AppDelegate+Scriptability.swift @@ -60,7 +60,6 @@ extension AppDelegate : AppDelegateAppleEvents { try? FileManager.default.createDirectory(at: downloadDirectory, withIntermediateDirectories: true, attributes: nil) let tmpFileName = UUID().uuidString + ".zip" downloadDirectory.appendPathComponent("\(tmpFileName)") - if location == nil { return } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 3ba5cf4f7..80ff1568c 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 1701E1E725689D1E009453D8 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1701E1E625689D1E009453D8 /* Localized.swift */; }; 1704053424E5985A00A00787 /* SceneNavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1704053324E5985A00A00787 /* SceneNavigationModel.swift */; }; 1704053524E5985A00A00787 /* SceneNavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1704053324E5985A00A00787 /* SceneNavigationModel.swift */; }; + 17071EF026F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */; }; + 17071EF126F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */; }; 1710B9132552354E00679C0D /* AddAccountHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B9122552354E00679C0D /* AddAccountHelpView.swift */; }; 1710B9142552354E00679C0D /* AddAccountHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B9122552354E00679C0D /* AddAccountHelpView.swift */; }; 1710B929255246F900679C0D /* EnableExtensionPointHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */; }; @@ -1539,6 +1541,7 @@ 1701E1B62568983D009453D8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 1701E1E625689D1E009453D8 /* Localized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = ""; }; 1704053324E5985A00A00787 /* SceneNavigationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneNavigationModel.swift; sourceTree = ""; }; + 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ArticleTheme+Notifications.swift"; sourceTree = ""; }; 1710B9122552354E00679C0D /* AddAccountHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountHelpView.swift; sourceTree = ""; }; 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointHelpView.swift; sourceTree = ""; }; 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesModel.swift; sourceTree = ""; }; @@ -3459,6 +3462,7 @@ 849A97871ED9ECEF007D329B /* ArticleTheme.swift */, 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */, 179D280C26F73D83003B2E0A /* ArticleThemePlist.swift */, + 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */, ); name = "Article Styles"; path = Shared/ArticleStyles; @@ -5625,6 +5629,7 @@ C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */, 17D7586F2679C21800B17787 /* OnePasswordExtension.m in Sources */, 51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */, + 17071EF126F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, @@ -5679,6 +5684,7 @@ 844B5B5B1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift in Sources */, 842E45DD1ED8C54B000A8B52 /* Browser.swift in Sources */, 84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */, + 17071EF026F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */, 8444C8F21FED81840051386C /* OPMLExporter.swift in Sources */, 849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */, 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */, diff --git a/Shared/ArticleStyles/ArticleTheme+Notifications.swift b/Shared/ArticleStyles/ArticleTheme+Notifications.swift new file mode 100644 index 000000000..08337aab4 --- /dev/null +++ b/Shared/ArticleStyles/ArticleTheme+Notifications.swift @@ -0,0 +1,15 @@ +// +// ArticleTheme+Notifications.swift +// ArticleTheme+Notifications +// +// Created by Stuart Breckenridge on 20/09/2021. +// Copyright © 2021 Ranchero Software. All rights reserved. +// + +import Foundation + +extension Notification.Name { + static let didBeginDownloadingTheme = Notification.Name("didBeginDownloadingTheme") + static let didEndDownloadingTheme = Notification.Name("didEndDownloadingTheme") + static let didEndDownloadingThemeWithError = Notification.Name("didEndDownloadingThemeWithError") +} diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index 3b850f22a..6658c3e55 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -11,7 +11,7 @@ import UserNotifications import Account import Zip -class SceneDelegate: UIResponder, UIWindowSceneDelegate, URLSessionDownloadDelegate { +class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? var coordinator = SceneCoordinator() @@ -179,9 +179,29 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, URLSessionDownloadDeleg if let providedThemeURL = queryItems.first(where: { $0.name == "url" })?.value { if let themeURL = URL(string: providedThemeURL) { let request = URLRequest(url: themeURL) - let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) - let downloadTask = session.downloadTask(with: request) - downloadTask.resume() + + DispatchQueue.main.async { + NotificationCenter.default.post(name: .didBeginDownloadingTheme, object: nil) + } + let task = URLSession.shared.downloadTask(with: request) { [weak self] location, response, error in + guard let self = self, let location = location else { return } + self.createDownloadDirectoryIfRequired() + do { + let movedFileLocation = try self.moveTheme(from: location) + let unzippedFileLocation = try self.unzipFile(at: movedFileLocation) + let renamedFileLocation = try self.renameFileToThemeName(at: unzippedFileLocation) + DispatchQueue.main.async { + NotificationCenter.default.post(name: .didEndDownloadingTheme, object: nil) + self.coordinator.importTheme(filename: renamedFileLocation.path) + } + } catch { + DispatchQueue.main.async { + NotificationCenter.default.post(name: .didEndDownloadingThemeWithError, object: nil, userInfo: ["error" : error]) + self.showAlert(error) + } + } + } + task.resume() } else { print("No theme URL") return @@ -193,45 +213,49 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, URLSessionDownloadDeleg } } - // MARK: - URLSessionDownloadDelegate - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { - var downloadDirectory = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first! + // MARK: - Theme Downloader + private func createDownloadDirectoryIfRequired() { + let downloadDirectory = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first! try? FileManager.default.createDirectory(at: downloadDirectory, withIntermediateDirectories: true, attributes: nil) - let tmpFileName = UUID().uuidString + ".zip" - downloadDirectory.appendPathComponent("\(tmpFileName)") - - do { - try FileManager.default.moveItem(at: location, to: downloadDirectory) - - var unzippedDir = downloadDirectory - unzippedDir = unzippedDir.deletingLastPathComponent() - unzippedDir.appendPathComponent("newtheme.nnwtheme") - - try Zip.unzipFile(downloadDirectory, destination: unzippedDir, overwrite: true, password: nil, progress: nil, fileOutputHandler: nil) - try FileManager.default.removeItem(at: downloadDirectory) - - let decoder = PropertyListDecoder() - let plistURL = URL(fileURLWithPath: unzippedDir.appendingPathComponent("Info.plist").path) - - let data = try Data(contentsOf: plistURL) - let plist = try decoder.decode(ArticleThemePlist.self, from: data) - - // rename - var renamedUnzippedDir = unzippedDir.deletingLastPathComponent() - renamedUnzippedDir.appendPathComponent(plist.name + ".nnwtheme") - if FileManager.default.fileExists(atPath: renamedUnzippedDir.path) { - try FileManager.default.removeItem(at: renamedUnzippedDir) - } - try FileManager.default.moveItem(at: unzippedDir, to: renamedUnzippedDir) - DispatchQueue.main.async { - self.coordinator.importTheme(filename: renamedUnzippedDir.path) - } - } catch { - print(error) - } } + private func moveTheme(from location: URL) throws -> URL { + var downloadDirectory = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first! + let tmpFileName = UUID().uuidString + ".zip" + downloadDirectory.appendPathComponent("\(tmpFileName)") + try FileManager.default.moveItem(at: location, to: downloadDirectory) + return downloadDirectory + } + + private func unzipFile(at location: URL) throws -> URL { + var unzippedDir = location.deletingLastPathComponent() + unzippedDir.appendPathComponent("newtheme.nnwtheme") + try Zip.unzipFile(location, destination: unzippedDir, overwrite: true, password: nil, progress: nil, fileOutputHandler: nil) + try FileManager.default.removeItem(at: location) + return unzippedDir + } + + private func renameFileToThemeName(at location: URL) throws -> URL { + let decoder = PropertyListDecoder() + let plistURL = URL(fileURLWithPath: location.appendingPathComponent("Info.plist").path) + let data = try Data(contentsOf: plistURL) + let plist = try decoder.decode(ArticleThemePlist.self, from: data) + var renamedUnzippedDir = location.deletingLastPathComponent() + renamedUnzippedDir.appendPathComponent(plist.name + ".nnwtheme") + if FileManager.default.fileExists(atPath: renamedUnzippedDir.path) { + try FileManager.default.removeItem(at: renamedUnzippedDir) + } + try FileManager.default.moveItem(at: location, to: renamedUnzippedDir) + return renamedUnzippedDir + } + + private func showAlert(_ error: Error) { + let alert = UIAlertController(title: NSLocalizedString("Error", comment: "Error"), + message: error.localizedDescription, + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("Dismiss", comment: "Dismiss"), style: .cancel, handler: nil)) + self.window?.rootViewController?.present(alert, animated: true, completion: nil) + } } private extension SceneDelegate {