@@ -128,16 +128,127 @@ struct A6CutterApp: App {
128128 alert. messageText = " Update Available "
129129 alert. informativeText = " A6Cutter \( latestVersion) is available. You currently have version \( currentVersion) . \n \n \( releaseNotes) "
130130 alert. alertStyle = . informational
131- alert. addButton ( withTitle: " Download Update " )
131+ alert. addButton ( withTitle: " Download & Install " )
132132 alert. addButton ( withTitle: " Later " )
133133
134134 let response = alert. runModal ( )
135135 if response == . alertFirstButtonReturn {
136- // Open GitHub releases page
137- if let url = URL ( string: " https://github.com/devopsmariocom/A6Cutter/releases/latest " ) {
138- NSWorkspace . shared. open ( url)
136+ // Download and install the update directly
137+ downloadAndInstallUpdate ( latestVersion: latestVersion)
138+ }
139+ }
140+
141+ private func downloadAndInstallUpdate( latestVersion: String ) {
142+ // Show progress dialog
143+ let progressAlert = NSAlert ( )
144+ progressAlert. messageText = " Downloading Update "
145+ progressAlert. informativeText = " Please wait while the update is downloaded and installed... "
146+ progressAlert. alertStyle = . informational
147+ progressAlert. addButton ( withTitle: " Cancel " )
148+ progressAlert. runModal ( )
149+
150+ // Download the DMG from GitHub releases
151+ let dmgUrl = " https://github.com/devopsmariocom/A6Cutter/releases/download/ \( latestVersion) /A6Cutter- \( latestVersion) .dmg "
152+
153+ guard let url = URL ( string: dmgUrl) else {
154+ showDownloadError ( " Invalid download URL " )
155+ return
156+ }
157+
158+ let task = URLSession . shared. downloadTask ( with: url) { localURL, response, error in
159+ DispatchQueue . main. async {
160+ if let error = error {
161+ self . showDownloadError ( " Download failed: \( error. localizedDescription) " )
162+ return
163+ }
164+
165+ guard let localURL = localURL else {
166+ self . showDownloadError ( " No local file received " )
167+ return
168+ }
169+
170+ // Install the update
171+ self . installUpdate ( from: localURL)
139172 }
140173 }
174+
175+ task. resume ( )
176+ }
177+
178+ private func installUpdate( from dmgURL: URL ) {
179+ // Mount the DMG
180+ let mountTask = Process ( )
181+ mountTask. launchPath = " /usr/bin/hdiutil "
182+ mountTask. arguments = [ " attach " , dmgURL. path, " -nobrowse " , " -noverify " , " -noautoopen " ]
183+
184+ let pipe = Pipe ( )
185+ mountTask. standardOutput = pipe
186+ mountTask. standardError = pipe
187+
188+ mountTask. launch ( )
189+ mountTask. waitUntilExit ( )
190+
191+ if mountTask. terminationStatus != 0 {
192+ showDownloadError ( " Failed to mount DMG " )
193+ return
194+ }
195+
196+ // Get the mount point
197+ let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
198+ let output = String ( data: data, encoding: . utf8) ?? " "
199+ let lines = output. components ( separatedBy: . newlines)
200+ let mountPoint = lines. first { $0. contains ( " /Volumes/ " ) } ? . trimmingCharacters ( in: . whitespacesAndNewlines) ?? " "
201+
202+ if mountPoint. isEmpty {
203+ showDownloadError ( " Could not find mount point " )
204+ return
205+ }
206+
207+ // Copy the app to Applications
208+ let sourceApp = " \( mountPoint) /A6Cutter.app "
209+ let destinationApp = " /Applications/A6Cutter.app "
210+
211+ let copyTask = Process ( )
212+ copyTask. launchPath = " /bin/rm "
213+ copyTask. arguments = [ " -rf " , destinationApp]
214+ copyTask. launch ( )
215+ copyTask. waitUntilExit ( )
216+
217+ let moveTask = Process ( )
218+ moveTask. launchPath = " /bin/cp "
219+ moveTask. arguments = [ " -R " , sourceApp, " /Applications/ " ]
220+ moveTask. launch ( )
221+ moveTask. waitUntilExit ( )
222+
223+ if moveTask. terminationStatus != 0 {
224+ showDownloadError ( " Failed to install update " )
225+ return
226+ }
227+
228+ // Unmount the DMG
229+ let unmountTask = Process ( )
230+ unmountTask. launchPath = " /usr/bin/hdiutil "
231+ unmountTask. arguments = [ " detach " , mountPoint]
232+ unmountTask. launch ( )
233+ unmountTask. waitUntilExit ( )
234+
235+ // Launch the updated app
236+ let launchTask = Process ( )
237+ launchTask. launchPath = " /usr/bin/open "
238+ launchTask. arguments = [ destinationApp]
239+ launchTask. launch ( )
240+
241+ // Exit current app
242+ NSApplication . shared. terminate ( nil )
243+ }
244+
245+ private func showDownloadError( _ message: String ) {
246+ let alert = NSAlert ( )
247+ alert. messageText = " Update Failed "
248+ alert. informativeText = message
249+ alert. alertStyle = . warning
250+ alert. addButton ( withTitle: " OK " )
251+ alert. runModal ( )
141252 }
142253
143254 private func showNoUpdatesDialog( ) {
0 commit comments