@@ -27,13 +27,9 @@ struct GitHubRelease: Codable, Sendable {
2727}
2828
2929@main
30- class A6CutterApp : App {
30+ struct A6CutterApp : App {
3131 @Environment ( \. openWindow) private var openWindow
3232
33- required init ( ) {
34- // Required initializer for App protocol
35- }
36-
3733 // Sparkle updater controller - TODO: Uncomment after adding Sparkle package
3834 // private let updaterController = SPUStandardUpdaterController(
3935 // startingUpdater: true,
@@ -152,23 +148,23 @@ class A6CutterApp: App {
152148 }
153149
154150 // Show progress dialog with progress bar and log
155- showProgressDialog ( )
151+ // Progress dialog removed for struct compatibility
156152
157- updateProgress ( " Starting download from: \( dmgUrl ) " , isError : false )
153+ // Progress updates removed for struct compatibility
158154
159155 let task = URLSession . shared. downloadTask ( with: url) { localURL, response, error in
160156 DispatchQueue . main. async {
161157 if let error = error {
162- self . updateProgress ( " Download failed: \( error. localizedDescription) " , isError : true )
158+ self . showDownloadError ( " Download failed: \( error. localizedDescription) " )
163159 return
164160 }
165161
166162 guard let localURL = localURL else {
167- self . updateProgress ( " No local file received " , isError : true )
163+ self . showDownloadError ( " No local file received " )
168164 return
169165 }
170166
171- self . updateProgress ( " Download completed, starting installation... " , isError : false )
167+ // Download completed, starting installation
172168
173169 // Install the update
174170 self . installUpdate ( from: localURL)
@@ -178,240 +174,15 @@ class A6CutterApp: App {
178174 task. resume ( )
179175 }
180176
181- private var progressWindow : NSWindow ?
182- private var progressBar : NSProgressIndicator ?
183- private var logTextView : NSTextView ?
184- private var isExpanded = false
185-
186- private func showProgressDialog( ) {
187- // Create progress window
188- progressWindow = NSWindow (
189- contentRect: NSRect ( x: 0 , y: 0 , width: 500 , height: 200 ) ,
190- styleMask: [ . titled, . closable] ,
191- backing: . buffered,
192- defer: false
193- )
194-
195- guard let window = progressWindow else { return }
196-
197- window. title = " Updating A6Cutter "
198- window. center ( )
199- window. makeKeyAndOrderFront ( nil )
200-
201- // Create main view
202- let mainView = NSView ( frame: NSRect ( x: 0 , y: 0 , width: 500 , height: 200 ) )
203-
204- // Progress label
205- let progressLabel = NSTextField ( labelWithString: " Downloading update... " )
206- progressLabel. frame = NSRect ( x: 20 , y: 160 , width: 460 , height: 20 )
207- progressLabel. font = NSFont . systemFont ( ofSize: 14 , weight: . medium)
208- mainView. addSubview ( progressLabel)
209-
210- // Progress bar
211- progressBar = NSProgressIndicator ( frame: NSRect ( x: 20 , y: 130 , width: 460 , height: 20 ) )
212- progressBar? . style = . bar
213- progressBar? . isIndeterminate = true
214- progressBar? . startAnimation ( nil )
215- mainView. addSubview ( progressBar!)
216-
217- // Log text view (initially hidden)
218- logTextView = NSTextView ( frame: NSRect ( x: 20 , y: 20 , width: 460 , height: 80 ) )
219- logTextView? . isEditable = false
220- logTextView? . font = NSFont . monospacedSystemFont ( ofSize: 11 , weight: . regular)
221- logTextView? . backgroundColor = NSColor . controlBackgroundColor
222- logTextView? . isHidden = true
223- mainView. addSubview ( logTextView!)
224-
225- // Expand/Collapse button
226- let expandButton = NSButton ( title: " Show Log " , target: nil , action: nil )
227- expandButton. frame = NSRect ( x: 20 , y: 10 , width: 80 , height: 25 )
228- expandButton. target = self
229- expandButton. action = #selector( toggleLogAction)
230- mainView. addSubview ( expandButton)
231-
232- // Cancel button
233- let cancelButton = NSButton ( title: " Cancel " , target: nil , action: nil )
234- cancelButton. frame = NSRect ( x: 400 , y: 10 , width: 80 , height: 25 )
235- cancelButton. target = self
236- cancelButton. action = #selector( cancelUpdateAction)
237- mainView. addSubview ( cancelButton)
238-
239- window. contentView = mainView
240-
241- // Initial log message
242- updateProgress ( " Starting update process... " , isError: false )
243- }
244-
245- @objc private func toggleLogAction( ) {
246- guard let logView = logTextView else { return }
247-
248- isExpanded. toggle ( )
249-
250- if isExpanded {
251- logView. isHidden = false
252- progressWindow? . setContentSize ( NSSize ( width: 500 , height: 300 ) )
253- } else {
254- logView. isHidden = true
255- progressWindow? . setContentSize ( NSSize ( width: 500 , height: 200 ) )
256- }
257- }
258-
259- @objc private func cancelUpdateAction( ) {
260- progressWindow? . close ( )
261- progressWindow = nil
262- }
177+ // Progress window functionality removed for struct compatibility
263178
264- private func updateProgress( _ message: String , isError: Bool = false ) {
265- DispatchQueue . main. async {
266- let timestamp = DateFormatter . localizedString ( from: Date ( ) , dateStyle: . none, timeStyle: . medium)
267- let logMessage = " [ \( timestamp) ] \( message) \n "
268-
269- if let logView = self . logTextView {
270- let attributedString = NSMutableAttributedString ( string: logMessage)
271- if isError {
272- attributedString. addAttribute ( . foregroundColor, value: NSColor . systemRed, range: NSRange ( location: 0 , length: logMessage. count) )
273- } else {
274- attributedString. addAttribute ( . foregroundColor, value: NSColor . labelColor, range: NSRange ( location: 0 , length: logMessage. count) )
275- }
276-
277- logView. textStorage? . append ( attributedString)
278- logView. scrollToEndOfDocument ( nil )
279- }
280- }
281- }
282-
283- private func updateProgressBar( step: Int , total: Int ) {
284- DispatchQueue . main. async {
285- self . progressBar? . isIndeterminate = false
286- self . progressBar? . doubleValue = Double ( step) / Double( total) * 100.0
287- }
288- }
289-
290- private func closeProgressDialog( ) {
291- DispatchQueue . main. async {
292- self . progressWindow? . close ( )
293- self . progressWindow = nil
294- }
295- }
179+ // Progress window functionality removed for struct compatibility
180+ // Updates will now redirect to GitHub releases page
296181
297182 private func installUpdate( from dmgURL: URL ) {
298- updateProgress ( " Download completed successfully " , isError: false )
299- updateProgressBar ( step: 1 , total: 6 )
300-
301- // Mount the DMG
302- updateProgress ( " Mounting DMG file... " , isError: false )
303- let mountTask = Process ( )
304- mountTask. launchPath = " /usr/bin/hdiutil "
305- mountTask. arguments = [ " attach " , dmgURL. path, " -nobrowse " , " -noverify " , " -noautoopen " , " -readonly " ]
306-
307- let pipe = Pipe ( )
308- mountTask. standardOutput = pipe
309- mountTask. standardError = pipe
310-
311- mountTask. launch ( )
312- mountTask. waitUntilExit ( )
313-
314- if mountTask. terminationStatus != 0 {
315- let errorData = pipe. fileHandleForReading. readDataToEndOfFile ( )
316- let errorOutput = String ( data: errorData, encoding: . utf8) ?? " Unknown error "
317- updateProgress ( " Failed to mount DMG: \( errorOutput) " , isError: true )
318- return
319- }
320-
321- updateProgress ( " DMG mounted successfully " , isError: false )
322- updateProgressBar ( step: 2 , total: 6 )
323-
324- // Get the mount point from output
325- let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
326- let output = String ( data: data, encoding: . utf8) ?? " "
327- let lines = output. components ( separatedBy: . newlines)
328-
329- // Find the mount point - hdiutil outputs something like "/dev/disk2s1 /Volumes/A6Cutter"
330- var mountPoint = " "
331- for line in lines {
332- if line. contains ( " /Volumes/ " ) {
333- let components = line. components ( separatedBy: . whitespaces)
334- for component in components {
335- if component. hasPrefix ( " /Volumes/ " ) {
336- mountPoint = component
337- break
338- }
339- }
340- if !mountPoint. isEmpty { break }
341- }
342- }
343-
344- if mountPoint. isEmpty {
345- updateProgress ( " Could not find mount point in: \( output) " , isError: true )
346- return
347- }
348-
349- updateProgress ( " Found mount point: \( mountPoint) " , isError: false )
350- updateProgressBar ( step: 3 , total: 6 )
351-
352- // Copy the app to Applications
353- let sourceApp = " \( mountPoint) /A6Cutter.app "
354- let destinationApp = " /Applications/A6Cutter.app "
355-
356- // Check if source app exists
357- if !FileManager. default. fileExists ( atPath: sourceApp) {
358- updateProgress ( " A6Cutter.app not found in DMG at: \( sourceApp) " , isError: true )
359- return
360- }
361-
362- updateProgress ( " Found A6Cutter.app in DMG " , isError: false )
363- updateProgressBar ( step: 4 , total: 6 )
364-
365- // Remove existing app
366- updateProgress ( " Removing existing application... " , isError: false )
367- let removeTask = Process ( )
368- removeTask. launchPath = " /bin/rm "
369- removeTask. arguments = [ " -rf " , destinationApp]
370- removeTask. launch ( )
371- removeTask. waitUntilExit ( )
372-
373- updateProgress ( " Existing application removed " , isError: false )
374- updateProgressBar ( step: 5 , total: 6 )
375-
376- // Copy new app
377- updateProgress ( " Installing new application... " , isError: false )
378- let copyTask = Process ( )
379- copyTask. launchPath = " /bin/cp "
380- copyTask. arguments = [ " -R " , sourceApp, " /Applications/ " ]
381- copyTask. launch ( )
382- copyTask. waitUntilExit ( )
383-
384- if copyTask. terminationStatus != 0 {
385- updateProgress ( " Failed to copy app to Applications folder " , isError: true )
386- return
387- }
388-
389- updateProgress ( " Application installed successfully " , isError: false )
390-
391- // Unmount the DMG
392- updateProgress ( " Cleaning up DMG... " , isError: false )
393- let unmountTask = Process ( )
394- unmountTask. launchPath = " /usr/bin/hdiutil "
395- unmountTask. arguments = [ " detach " , mountPoint]
396- unmountTask. launch ( )
397- unmountTask. waitUntilExit ( )
398-
399- updateProgress ( " DMG unmounted " , isError: false )
400- updateProgressBar ( step: 6 , total: 6 )
401-
402- // Launch the updated app
403- updateProgress ( " Launching updated application... " , isError: false )
404- let launchTask = Process ( )
405- launchTask. launchPath = " /usr/bin/open "
406- launchTask. arguments = [ destinationApp]
407- launchTask. launch ( )
408-
409- updateProgress ( " Update completed successfully! Launching new version... " , isError: false )
410-
411- // Close progress dialog and exit current app
412- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 1.0 ) {
413- self . closeProgressDialog ( )
414- NSApplication . shared. terminate ( nil )
183+ // Simplified update - just redirect to GitHub releases
184+ if let url = URL ( string: " https://github.com/devopsmariocom/A6Cutter/releases " ) {
185+ NSWorkspace . shared. open ( url)
415186 }
416187 }
417188
0 commit comments