Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto update #7

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions UpdateManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// UpdateManager.swift

import Foundation
import UserNotifications
import UIKit

class UpdateManager {

static let shared = UpdateManager()

private init() {}

func checkForUpdates(completion: (() -> Void)? = nil) {
guard let url = URL(string: "https://api.github.com/repos/JJTech0130/ValidationRelay/releases/latest") else {
print("Invalid URL")
completion?()
return
}

var request = URLRequest(url: url)
request.setValue("application/vnd.github.v3+json", forHTTPHeaderField: "Accept")

let task = URLSession.shared.dataTask(with: request) { data, response, error in
defer { completion?() }
guard let data = data, error == nil else {
print("Error fetching update info: \(error?.localizedDescription ?? "Unknown error")")
return
}

do {
let decoder = JSONDecoder()
let releaseInfo = try decoder.decode(GitHubRelease.self, from: data)
self.handleUpdate(version: releaseInfo.tag_name)
} catch {
print("Error parsing JSON: \(error.localizedDescription)")
}
}

task.resume()
}

private func handleUpdate(version: String) {
// Compare the version with the current app version
guard let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
print("Unable to get current app version")
return
}

if version.compare(currentVersion, options: .numeric) == .orderedDescending {
print("New version available: \(version)")
DispatchQueue.main.async {
self.notifyUserAboutUpdate(version: version)
}
} else {
print("App is up to date")
}
}

private func notifyUserAboutUpdate(version: String) {
let content = UNMutableNotificationContent()
content.title = "Update Available"
content.body = "A new version (\(version)) is available. Tap to update."
content.sound = .default
content.categoryIdentifier = "UPDATE_CATEGORY"

// Set up the notification action
let updateAction = UNNotificationAction(identifier: "UPDATE_NOW", title: "Update Now", options: [.foreground])
let category = UNNotificationCategory(identifier: "UPDATE_CATEGORY", actions: [updateAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])

let request = UNNotificationRequest(identifier: "UpdateAvailable", content: content, trigger: nil)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}

func downloadAndUpdate() {
guard let url = URL(string: "https://github.com/JJTech0130/ValidationRelay/releases/latest/download/ValidationRelay.ipa") else {
print("Invalid download URL")
return
}

let task = URLSession.shared.downloadTask(with: url) { location, response, error in
guard let location = location, error == nil else {
print("Error downloading update: \(error?.localizedDescription ?? "Unknown error")")
return
}

do {
let fileManager = FileManager.default
let destinationURL = fileManager.temporaryDirectory.appendingPathComponent("ValidationRelay.ipa")

if fileManager.fileExists(atPath: destinationURL.path) {
try fileManager.removeItem(at: destinationURL)
}

try fileManager.moveItem(at: location, to: destinationURL)

self.installUpdate(at: destinationURL)
} catch {
print("Error handling downloaded file: \(error.localizedDescription)")
}
}

task.resume()
}

private func installUpdate(at url: URL) {
let rootHelperPath = "/usr/libexec/trollstore-helper"
let args = ["install", url.path]

var pid: pid_t = 0
let argv: [UnsafeMutablePointer<CChar>?] = [rootHelperPath.withCString(strdup)] + args.map { $0.withCString(strdup) } + [nil]

let result = posix_spawn(&pid, rootHelperPath, nil, nil, argv, nil)

if result == 0 {
print("Update installation started")
} else {
print("Error starting update installation: \(result)")
}

for arg in argv where arg != nil {
free(arg)
}
}

// Background Fetch Support -WIP

func performBackgroundFetch(completion: @escaping (UIBackgroundFetchResult) -> Void) {
checkForUpdates {
// Assuming updates are checked and handled
completion(.newData)
}
}
}

// GitHubRelease Model - WIP

struct GitHubRelease: Decodable {
let tag_name: String
}
32 changes: 20 additions & 12 deletions ValidationRelay/Info.plist
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
<!-- Info.plist -->

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Keep ValidationRelay alive in the background</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Keep ValidationRelay alive in the background</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Keep ValidationRelay alive in the background</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>processing</string>
</array>
<!-- Location Usage Descriptions -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Keep ValidationRelay alive in the background</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Keep ValidationRelay alive in the background</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Keep ValidationRelay alive in the background</string>

<!-- Background Modes -->
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>processing</string>
<string>fetch</string>
<string>remote-notification</string>
</array>


</dict>
</plist>
38 changes: 26 additions & 12 deletions ValidationRelay/ValidationRelayApp.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
//
// ValidationRelayApp.swift
// ValidationRelay
//
// Created by James Gill on 3/24/24.
//
// AppDelegate.swift

import SwiftUI
import UIKit
import UserNotifications

@main
struct ValidationRelayApp: App {
var body: some Scene {
WindowGroup {
ContentView(relayConnectionManager: RelayConnectionManager())
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
return true
}

// Handle background fetch
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
UpdateManager.shared.performBackgroundFetch { result in
completionHandler(result)
}
}

// Handle notification actions
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
if response.actionIdentifier == "UPDATE_NOW" {
UpdateManager.shared.downloadAndUpdate()
}
completionHandler()
}

// Handle notifications while app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// Present the notification as a banner and play a sound
completionHandler([.banner, .sound])
}
}