Skip to content

Commit

Permalink
implement self update
Browse files Browse the repository at this point in the history
  • Loading branch information
castdrian committed Jul 18, 2023
1 parent a9f1264 commit 6d0061c
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 7 deletions.
17 changes: 17 additions & 0 deletions ishare.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
3AB38D0B2A62F28700184E0D /* UploaderSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB38D0A2A62F28700184E0D /* UploaderSettings.swift */; };
3AB38D0D2A62F7C500184E0D /* RecordingSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB38D0C2A62F7C500184E0D /* RecordingSettings.swift */; };
3ADDAEDA2A659A0B00C42406 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3ADDAED92A659A0B00C42406 /* Assets.xcassets */; };
3ADDAEE32A65D00F00C42406 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = 3ADDAEE22A65D00F00C42406 /* Zip */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -53,6 +54,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3ADDAEE32A65D00F00C42406 /* Zip in Frameworks */,
3AB345BC2A5EBA5400AAEC0E /* Defaults in Frameworks */,
3A17C0792A61A20E0012F285 /* Alamofire in Frameworks */,
3AB345C22A5ED5B800AAEC0E /* KeyboardShortcuts in Frameworks */,
Expand Down Expand Up @@ -157,6 +159,7 @@
3AB345C12A5ED5B800AAEC0E /* KeyboardShortcuts */,
3A17C0782A61A20E0012F285 /* Alamofire */,
3A0679E02A648C0F0088DE6A /* SwiftyJSON */,
3ADDAEE22A65D00F00C42406 /* Zip */,
);
productName = ishare;
productReference = 3A9A9FD42A5C84B5007BA5C9 /* ishare.app */;
Expand Down Expand Up @@ -192,6 +195,7 @@
3AB345C02A5ED5B800AAEC0E /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */,
3A17C0772A61A20E0012F285 /* XCRemoteSwiftPackageReference "Alamofire" */,
3A0679DF2A648C0F0088DE6A /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
3ADDAEE12A65D00F00C42406 /* XCRemoteSwiftPackageReference "Zip" */,
);
productRefGroup = 3A9A9FD52A5C84B5007BA5C9 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -484,6 +488,14 @@
minimumVersion = 1.0.0;
};
};
3ADDAEE12A65D00F00C42406 /* XCRemoteSwiftPackageReference "Zip" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/marmelroy/Zip.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
Expand Down Expand Up @@ -512,6 +524,11 @@
package = 3AB345C02A5ED5B800AAEC0E /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */;
productName = KeyboardShortcuts;
};
3ADDAEE22A65D00F00C42406 /* Zip */ = {
isa = XCSwiftPackageProductDependency;
package = 3ADDAEE12A65D00F00C42406 /* XCRemoteSwiftPackageReference "Zip" */;
productName = Zip;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 3A9A9FCC2A5C84B4007BA5C9 /* Project object */;
Expand Down
168 changes: 163 additions & 5 deletions ishare/Util/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
// Created by Adrian Castro on 12.07.23.
//

import Zip
import SwiftUI
import Defaults
import Alamofire
import SwiftyJSON
@testable import KeyboardShortcuts

extension KeyboardShortcuts.Name {
Expand Down Expand Up @@ -90,7 +93,7 @@ func checkAppInstallation(_ app: InstalledApp) -> Bool {
let fileManager = FileManager.default
let homebrewPath = utsname.isAppleSilicon ? "/opt/homebrew/bin/brew" : "/usr/local/bin/brew"
let ffmpegPath = utsname.isAppleSilicon ? "/opt/homebrew/bin/ffmpeg" : "/usr/local/bin/ffmpeg"

return fileManager.fileExists(atPath: app == InstalledApp.HOMEBREW ? homebrewPath : ffmpegPath)
}

Expand Down Expand Up @@ -193,24 +196,179 @@ func importFile(_ url: URL, completion: @escaping (Bool, Error?) -> Void) {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let uploader = try decoder.decode(CustomUploader.self, from: data)

@Default(.savedCustomUploaders) var savedCustomUploaders
@Default(.activeCustomUploader) var activeCustomUploader
@Default(.uploadType) var uploadType

if var uploaders = savedCustomUploaders {
uploaders.remove(uploader)
uploaders.insert(uploader)
savedCustomUploaders = uploaders
} else {
savedCustomUploaders = Set([uploader])
}

activeCustomUploader = uploader
uploadType = .CUSTOM

completion(true, nil) // Success callback
} catch {
completion(false, error) // Error callback
}
}

func selfUpdate() {
guard let releasesURL = URL(string: "https://api.github.com/repos/castdrian/ishare/releases") else {
print("Invalid releases URL")
return
}

AF.request(releasesURL).responseDecodable(of: JSON.self) { response in
switch response.result {
case .success(let value):
let json = JSON(value)

// Check if the response contains any releases
guard let releases = json.array else {
print("No releases found")
return
}

// Check if there is at least one release
guard let latestRelease = releases.first else {
print("No latest release found")
return
}

// Extract the creation date of the latest release
if let createdAtString = latestRelease["created_at"].string,
let releaseCreationDate = ISO8601DateFormatter().date(from: createdAtString) {
print("Latest release creation date: \(releaseCreationDate)")

// Get the bundle's creation date
if let executableURL = Bundle.main.executableURL,
let bundleCreationDate = (try? executableURL.resourceValues(forKeys: [.creationDateKey]))?.creationDate {
print("Bundle creation date: \(bundleCreationDate)")

// Compare the dates
let comparisonResult = releaseCreationDate.compare(bundleCreationDate)

if comparisonResult == .orderedDescending {
print("Bundle is older than the latest release")
let alert = NSAlert()
alert.messageText = "Update Available"
alert.informativeText = "An update is available. Do you want to update ishare?"
alert.addButton(withTitle: "Yes")
alert.addButton(withTitle: "No")

let modalResponse = alert.runModal()
if modalResponse == .alertFirstButtonReturn {
if let assetURL = latestRelease["assets"][0]["browser_download_url"].url {
print("Latest release asset URL: \(assetURL)")
downloadAndReplaceApp(assetURL: assetURL)
} else {
print("No assets found for the latest release")
}
}
} else if comparisonResult == .orderedAscending {
print("Bundle is newer than the latest release")
showAlert(title: "Up to Date", message: "Your version of ishare is up to date.")
} else {
print("Bundle and latest release have the same creation date")
showAlert(title: "Up to Date", message: "Your version of ishare is up to date.")
}
}
} else {
print("Failed to extract release creation date")
showAlert(title: "Error", message: "Failed to extract release creation date.")
}

case .failure(let error):
print("Request failed with error: \(error)")
showAlert(title: "Error", message: "Request failed with error: \(error)")
}
}
}

func showAlert(title: String, message: String) {
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.addButton(withTitle: "OK")
alert.runModal()
}

func downloadAndReplaceApp(assetURL: URL) {
let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent("ishare.zip")

AF.download(assetURL, to: { _, _ in (destinationURL, [.removePreviousFile]) })
.response { response in
if response.error == nil {
if response.fileURL != nil {
replaceAppWithDownloadedArchive(zipURL: destinationURL)
} else {
showAlert(title: "Download Failed", message: "Failed to download the update archive.")
}
} else {
showAlert(title: "Download Failed", message: "Failed to download the update: \(response.error!.localizedDescription)")
}
}
}

func replaceAppWithDownloadedArchive(zipURL: URL) {
let fileManager = FileManager.default
let appSupportDirectoryURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let extractedAppDirectoryURL = appSupportDirectoryURL.appendingPathComponent("ishare_extracted")

do {
try fileManager.createDirectory(at: extractedAppDirectoryURL, withIntermediateDirectories: true, attributes: nil)

try Zip.unzipFile(zipURL, destination: extractedAppDirectoryURL, overwrite: true, password: nil)

let extractedAppURLs = try fileManager.contentsOfDirectory(at: extractedAppDirectoryURL, includingPropertiesForKeys: nil)
print(extractedAppURLs)
guard let extractedAppURL = extractedAppURLs.first(where: { $0.pathExtension == "app" }) else {
print("Failed to find extracted ishare.app")
showAlert(title: "Update Failed", message: "Failed to find the extracted iShare app.")
return
}

let extractedAppBundleURL = extractedAppURL.appendingPathComponent("Contents").appendingPathComponent("MacOS")
let extractedAppExecutableURL = extractedAppBundleURL.appendingPathComponent("ishare")

guard fileManager.fileExists(atPath: extractedAppExecutableURL.path) else {
print("Failed to find extracted ishare.app executable")
showAlert(title: "Update Failed", message: "Failed to find the extracted ishare app executable.")
return
}

let currentAppURL = Bundle.main.bundleURL
let currentAppBundleURL = currentAppURL.appendingPathComponent("Contents").appendingPathComponent("MacOS")
let currentAppExecutableURL = currentAppBundleURL.appendingPathComponent("ishare")


do {
try fileManager.replaceItemAt(currentAppExecutableURL, withItemAt: extractedAppExecutableURL)

showAlert(title: "Update Successful", message: "ishare has been updated successfully. The app will now restart.")

let appURL = Bundle.main.bundleURL
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.open(appURL, configuration: configuration) { _, _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
NSApplication.shared.terminate(nil)
}
}

} catch {
print("Failed to replace the current ishare.app: \(error.localizedDescription)")
showAlert(title: "Update Failed", message: "Failed to replace the current ishare app: \(error.localizedDescription)")
}

try fileManager.removeItem(at: extractedAppDirectoryURL)
} catch {
print("Failed to extract the update archive: \(error.localizedDescription)")
showAlert(title: "Update Failed", message: "Failed to extract the update archive: \(error.localizedDescription)")
}
}
8 changes: 6 additions & 2 deletions ishare/Views/MainMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ struct MainMenuView: View {
}
if let uploaders = savedCustomUploaders {
if !uploaders.isEmpty {
// doesn"t work :(
// doesn't work :(
// Picker("Custom", selection: $activeCustomUploader) {
// ForEach(CustomUploader.allCases) { uploader in
// ForEach(CustomUploader.allCases, id: \.self) { uploader in
// Text(uploader.name).tag(uploader)
// }
// }
Expand Down Expand Up @@ -111,6 +111,10 @@ struct MainMenuView: View {
)
}.keyboardShortcut("a")

Button("Check for Updates") {
selfUpdate()
}.keyboardShortcut("u")

Button("Quit") {
NSApplication.shared.terminate(nil)
}.keyboardShortcut("q")
Expand Down

0 comments on commit 6d0061c

Please sign in to comment.