From d58bf5137a56440189e38b762456543ff492fcb1 Mon Sep 17 00:00:00 2001 From: Nindi Gill Date: Mon, 26 Sep 2022 13:56:25 +1000 Subject: [PATCH] Add support for custom cache directories --- Mist.xcodeproj/project.pbxproj | 4 ++ Mist/Helpers/InstallerCreator.swift | 9 +-- Mist/Helpers/TaskManager.swift | 9 +-- Mist/Views/Components/PathControl.swift | 27 +++++++++ Mist/Views/List/InstallerListRow.swift | 2 + .../SettingsInstallersCacheView.swift | 60 +++++++++++++++---- .../Settings/SettingsInstallersView.swift | 5 +- 7 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 Mist/Views/Components/PathControl.swift diff --git a/Mist.xcodeproj/project.pbxproj b/Mist.xcodeproj/project.pbxproj index 47cd0ac..a5fc849 100644 --- a/Mist.xcodeproj/project.pbxproj +++ b/Mist.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 390451E1285740E800E0B563 /* Sequence+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451E0285740E800E0B563 /* Sequence+Extension.swift */; }; 390451E528574F0000E0B563 /* Catalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451E428574F0000E0B563 /* Catalog.swift */; }; 390451E72857510C00E0B563 /* TextTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451E62857510B00E0B563 /* TextTag.swift */; }; + 39148CFC28DD55B300011FF5 /* PathControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39148CFB28DD55B300011FF5 /* PathControl.swift */; }; 39252A77285A849F00956C74 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252A76285A849F00956C74 /* AppDelegate.swift */; }; 39252A79285A85AF00956C74 /* SettingsInstallersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252A78285A85AF00956C74 /* SettingsInstallersView.swift */; }; 39252A7B285AC50400956C74 /* SettingsDiskImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252A7A285AC50400956C74 /* SettingsDiskImagesView.swift */; }; @@ -175,6 +176,7 @@ 390451E0285740E800E0B563 /* Sequence+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Extension.swift"; sourceTree = ""; }; 390451E428574F0000E0B563 /* Catalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Catalog.swift; sourceTree = ""; }; 390451E62857510B00E0B563 /* TextTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextTag.swift; sourceTree = ""; }; + 39148CFB28DD55B300011FF5 /* PathControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathControl.swift; sourceTree = ""; }; 39252A76285A849F00956C74 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39252A78285A85AF00956C74 /* SettingsInstallersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInstallersView.swift; sourceTree = ""; }; 39252A7A285AC50400956C74 /* SettingsDiskImagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiskImagesView.swift; sourceTree = ""; }; @@ -484,6 +486,7 @@ 39252AA4285C463A00956C74 /* DynamicTextView.swift */, 39252A86285ACE9C00956C74 /* FooterText.swift */, 39252AA0285C2A1600956C74 /* PaddedDivider.swift */, + 39148CFB28DD55B300011FF5 /* PathControl.swift */, 39252A84285ACDC800956C74 /* ResetToDefaultButton.swift */, 390451C92856F1D300E0B563 /* ScaledImage.swift */, 390451CB2856F23100E0B563 /* ScaledSystemImage.swift */, @@ -756,6 +759,7 @@ 390451CA2856F1D300E0B563 /* ScaledImage.swift in Sources */, 39252A95285BF83D00956C74 /* MistTask.swift in Sources */, 39CF56272861E10F006FB5D2 /* Codesigner.swift in Sources */, + 39148CFC28DD55B300011FF5 /* PathControl.swift in Sources */, 3935F4892866C68000760AB0 /* DownloadSectionHeaderView.swift in Sources */, 39252AB5285C706000956C74 /* URL+Extension.swift in Sources */, 39CF56352862D4BF006FB5D2 /* FileCompressor.swift in Sources */, diff --git a/Mist/Helpers/InstallerCreator.swift b/Mist/Helpers/InstallerCreator.swift index f2a7fb1..7bf48d0 100644 --- a/Mist/Helpers/InstallerCreator.swift +++ b/Mist/Helpers/InstallerCreator.swift @@ -14,11 +14,12 @@ struct InstallerCreator { /// Creates a recently downloaded macOS Installer. /// /// - Parameters: - /// - installer: The selected macOS Installer that was downloaded. - /// - mountPoint: The URL of the directory mount point. + /// - installer: The selected macOS Installer that was downloaded. + /// - mountPoint: The URL of the directory mount point. + /// - cacheDirectory: The cache directory storing all macOS Installer components. /// /// - Throws: A `MistError` if the downloaded macOS Installer fails to generate. - static func create(_ installer: Installer, mountPoint: URL) async throws { + static func create(_ installer: Installer, mountPoint: URL, cacheDirectory: String) async throws { guard let url: URL = URL(string: installer.distributionURL) else { throw MistError.invalidURL(installer.distributionURL) @@ -26,7 +27,7 @@ struct InstallerCreator { try await DirectoryRemover.remove(installer.temporaryInstallerURL) - let cacheDirectoryURL: URL = URL(fileURLWithPath: .cacheDirectory) + let cacheDirectoryURL: URL = URL(fileURLWithPath: cacheDirectory) let distributionURL: URL = cacheDirectoryURL.appendingPathComponent(installer.id).appendingPathComponent(url.lastPathComponent) var argumentsArrays: [[String]] = [ ["installer", "-pkg", distributionURL.path, "-target", mountPoint.path] diff --git a/Mist/Helpers/TaskManager.swift b/Mist/Helpers/TaskManager.swift index cf2f8cd..45a05fa 100644 --- a/Mist/Helpers/TaskManager.swift +++ b/Mist/Helpers/TaskManager.swift @@ -137,6 +137,7 @@ class TaskManager: ObservableObject { destination destinationURL: URL?, exports: [InstallerExportType], cacheDownloads: Bool, + cacheDirectory: String, retries: Int, delay retryDelay: Int, applicationFilename: String, @@ -150,7 +151,7 @@ class TaskManager: ObservableObject { packageSigningIdentity: String ) throws -> [(section: MistTaskSection, tasks: [MistTask])] { var taskGroups: [(section: MistTaskSection, tasks: [MistTask])] = [] - let cacheDirectoryURL: URL = URL(fileURLWithPath: .cacheDirectory).appendingPathComponent(installer.id) + let cacheDirectoryURL: URL = URL(fileURLWithPath: cacheDirectory).appendingPathComponent(installer.id) let temporaryDirectoryURL: URL = URL(fileURLWithPath: .temporaryDirectory) let mountPointURL: URL = URL(fileURLWithPath: "/Volumes/\(installer.id) Temp") @@ -165,7 +166,7 @@ class TaskManager: ObservableObject { ), ( section: .setup, - tasks: installTasks(for: installer, temporaryDirectory: temporaryDirectoryURL, mountPoint: mountPointURL) + tasks: installTasks(for: installer, temporaryDirectory: temporaryDirectoryURL, mountPoint: mountPointURL, cacheDirectory: cacheDirectory) ) ] @@ -252,7 +253,7 @@ class TaskManager: ObservableObject { return tasks } - private static func installTasks(for installer: Installer, temporaryDirectory temporaryDirectoryURL: URL, mountPoint mountPointURL: URL) -> [MistTask] { + private static func installTasks(for installer: Installer, temporaryDirectory temporaryDirectoryURL: URL, mountPoint mountPointURL: URL, cacheDirectory: String) -> [MistTask] { let imageURL: URL = temporaryDirectoryURL.appendingPathComponent("\(installer.id) Temp.dmg") let mountPointURL: URL = URL(fileURLWithPath: "/Volumes/\(installer.id) Temp") @@ -268,7 +269,7 @@ class TaskManager: ObservableObject { try await DiskImageMounter.mount(imageURL, mountPoint: mountPointURL) }, MistTask(type: .create, description: "macOS Installer in Disk Image") { - try await InstallerCreator.create(installer, mountPoint: mountPointURL) + try await InstallerCreator.create(installer, mountPoint: mountPointURL, cacheDirectory: cacheDirectory) } ] } diff --git a/Mist/Views/Components/PathControl.swift b/Mist/Views/Components/PathControl.swift new file mode 100644 index 0000000..bf8b0ec --- /dev/null +++ b/Mist/Views/Components/PathControl.swift @@ -0,0 +1,27 @@ +// +// PathControl.swift +// Mist +// +// Created by Nindi Gill on 23/9/2022. +// + +import SwiftUI + +struct PathControl: NSViewRepresentable { + + @Binding var path: String + + func makeNSView(context: Context) -> NSPathControl { + NSPathControl() + } + + func updateNSView(_ nsView: NSPathControl, context: Context) { + nsView.url = URL(fileURLWithPath: path) + } +} + +struct PathControl_Previews: PreviewProvider { + static var previews: some View { + PathControl(path: .constant(.cacheDirectory)) + } +} diff --git a/Mist/Views/List/InstallerListRow.swift b/Mist/Views/List/InstallerListRow.swift index ef50235..4754baa 100644 --- a/Mist/Views/List/InstallerListRow.swift +++ b/Mist/Views/List/InstallerListRow.swift @@ -9,6 +9,7 @@ import SwiftUI struct InstallerListRow: View { @AppStorage("cacheDownloads") private var cacheDownloads: Bool = false + @AppStorage("cacheDirectory") private var cacheDirectory: String = .cacheDirectory @AppStorage("applicationFilename") private var applicationFilename: String = .applicationFilenameTemplate @AppStorage("diskImageFilename") private var diskImageFilename: String = .diskImageFilenameTemplate @AppStorage("diskImageSign") private var diskImageSign: Bool = false @@ -83,6 +84,7 @@ struct InstallerListRow: View { destination: openPanel.url, exports: exports, cacheDownloads: cacheDownloads, + cacheDirectory: cacheDirectory, retries: retries, delay: retryDelay, applicationFilename: applicationFilename, diff --git a/Mist/Views/Settings/SettingsInstallersCacheView.swift b/Mist/Views/Settings/SettingsInstallersCacheView.swift index 2f302f9..ae0066c 100644 --- a/Mist/Views/Settings/SettingsInstallersCacheView.swift +++ b/Mist/Views/Settings/SettingsInstallersCacheView.swift @@ -8,29 +8,44 @@ import SwiftUI struct SettingsInstallersCacheView: View { - @Binding var enabled: Bool + @Binding var cacheDownloads: Bool + @Binding var cacheDirectory: String @State private var cacheSize: String = "" @State private var buttonClicked: Bool = false + @State private var openPanel: NSOpenPanel = NSOpenPanel() var body: some View { - HStack { - VStack(alignment: .leading) { - Toggle(isOn: $enabled) { - Text("Cache downloads") + VStack(alignment: .leading) { + HStack(alignment: .firstTextBaseline) { + VStack(alignment: .leading) { + Toggle(isOn: $cacheDownloads) { + Text("Cache downloads") + } + FooterText("Speed up future operations by caching a local copy of macOS Installer files.") } - FooterText("Speed up future operations by caching a local copy of macOS Installer files.") + Spacer() + Button("Select...") { + selectCacheDirectory() + } + .disabled(!cacheDownloads) } - Spacer() - VStack { + PathControl(path: $cacheDirectory) + .disabled(true) + HStack(alignment: .firstTextBaseline) { + FooterText("Cache directory currently contains \(cacheSize) of data.") + Spacer() Button("Empty Cache...") { buttonClicked.toggle() } - FooterText(cacheSize) } + .disabled(!cacheDownloads) } .onAppear { getCacheSize() } + .onChange(of: cacheDirectory) { _ in + getCacheSize() + } .alert(isPresented: $buttonClicked) { Alert( title: Text("Empty Cache Directory?"), @@ -41,9 +56,28 @@ struct SettingsInstallersCacheView: View { } } + private func selectCacheDirectory() { + openPanel.prompt = "Select" + openPanel.canCreateDirectories = true + openPanel.canChooseFiles = false + openPanel.canChooseDirectories = true + openPanel.resolvesAliases = true + openPanel.allowsMultipleSelection = false + openPanel.isAccessoryViewDisclosed = true + + let response: NSApplication.ModalResponse = openPanel.runModal() + + guard response == .OK, + let url: URL = openPanel.url else { + return + } + + cacheDirectory = url.path + } + private func getCacheSize() { - let url: URL = URL(fileURLWithPath: .cacheDirectory) + let url: URL = URL(fileURLWithPath: cacheDirectory) var isDirectory: ObjCBool = false do { @@ -61,10 +95,10 @@ struct SettingsInstallersCacheView: View { private func emptyCache() { do { - let paths: [String] = try FileManager.default.contentsOfDirectory(atPath: .cacheDirectory) + let paths: [String] = try FileManager.default.contentsOfDirectory(atPath: cacheDirectory) for path in paths { - let url: URL = URL(fileURLWithPath: .cacheDirectory + "/" + path) + let url: URL = URL(fileURLWithPath: cacheDirectory + "/" + path) try FileManager.default.removeItem(at: url) } } catch { @@ -75,6 +109,6 @@ struct SettingsInstallersCacheView: View { struct SettingsInstallersCacheView_Previews: PreviewProvider { static var previews: some View { - SettingsInstallersCacheView(enabled: .constant(true)) + SettingsInstallersCacheView(cacheDownloads: .constant(true), cacheDirectory: .constant(.cacheDirectory)) } } diff --git a/Mist/Views/Settings/SettingsInstallersView.swift b/Mist/Views/Settings/SettingsInstallersView.swift index 7e10112..e9cf193 100644 --- a/Mist/Views/Settings/SettingsInstallersView.swift +++ b/Mist/Views/Settings/SettingsInstallersView.swift @@ -9,9 +9,11 @@ import SwiftUI struct SettingsInstallersView: View { @AppStorage("cacheDownloads") private var cacheDownloads: Bool = false + @AppStorage("cacheDirectory") private var cacheDirectory: String = .cacheDirectory @State private var catalogRows: [CatalogRow] = [] @State private var selectedCatalogRow: CatalogRow? private let cacheDownloadsDefault: Bool = false + private let cacheDirectoryDefault: String = .cacheDirectory private var defaultCatalogRows: [CatalogRow] = Catalog.urls.map { CatalogRow(url: $0) } private let imageName: String = "Installer" private let title: String = "Installers" @@ -21,7 +23,7 @@ struct SettingsInstallersView: View { VStack(alignment: .leading) { SettingsHeaderView(imageName: imageName, title: title, description: description, fade: .constant(false)) PaddedDivider() - SettingsInstallersCacheView(enabled: $cacheDownloads) + SettingsInstallersCacheView(cacheDownloads: $cacheDownloads, cacheDirectory: $cacheDirectory) PaddedDivider() SettingsInstallersCatalogsView(catalogRows: $catalogRows, selectedCatalogRow: $selectedCatalogRow) PaddedDivider() @@ -50,6 +52,7 @@ struct SettingsInstallersView: View { private func reset() { cacheDownloads = cacheDownloadsDefault + cacheDirectory = cacheDirectoryDefault catalogRows = defaultCatalogRows } }