Skip to content

Commit 18ec8e4

Browse files
Fix SwiftUI struct requirement - convert A6CutterApp from class to struct
- Change A6CutterApp from class to struct to comply with SwiftUI requirements - Remove progress window functionality that was incompatible with struct approach - Simplify update mechanism to redirect to GitHub releases page - Remove @objc methods and mutating property issues - Fix 'apps must be value types' fatal error - App now builds and runs successfully with proper version injection
1 parent f6b46ee commit 18ec8e4

File tree

13 files changed

+304
-244
lines changed

13 files changed

+304
-244
lines changed

A6Cutter/A6CutterApp.swift

Lines changed: 12 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -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

A6Cutter/Info.plist

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313
<key>CFBundlePackageType</key>
1414
<string>APPL</string>
1515
<key>CFBundleShortVersionString</key>
16-
<string>v1.0.5</string>
16+
<string>v1.0.26</string>
1717
<key>CFBundleSignature</key>
1818
<string>????</string>
1919
<key>CFBundleVersion</key>
20-
<string>123</string>
20+
<string>f6b46ee</string>
2121
<key>GitHash</key>
22-
<string>2c1ce603afaf1396186c613cbf1ec4b60745587f</string>
22+
<string>f6b46eee13b732d5d9caee15898a9d20ad68297e</string>
2323
<key>LSMinimumSystemVersion</key>
2424
<string>14.0</string>
2525
<key>NSHumanReadableCopyright</key>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>BuildMachineOSBuild</key>
6+
<string>25A362</string>
7+
<key>CFBundleDevelopmentRegion</key>
8+
<string>en</string>
9+
<key>CFBundleExecutable</key>
10+
<string>A6Cutter</string>
11+
<key>CFBundleIconFile</key>
12+
<string>AppIcon</string>
13+
<key>CFBundleIconName</key>
14+
<string>AppIcon</string>
15+
<key>CFBundleIdentifier</key>
16+
<string>Vejlupek-s.r.o..A6Cutter</string>
17+
<key>CFBundleInfoDictionaryVersion</key>
18+
<string>6.0</string>
19+
<key>CFBundleName</key>
20+
<string>A6Cutter</string>
21+
<key>CFBundlePackageType</key>
22+
<string>APPL</string>
23+
<key>CFBundleShortVersionString</key>
24+
<string>1.0</string>
25+
<key>CFBundleSupportedPlatforms</key>
26+
<array>
27+
<string>MacOSX</string>
28+
</array>
29+
<key>CFBundleVersion</key>
30+
<string>1</string>
31+
<key>DTCompiler</key>
32+
<string>com.apple.compilers.llvm.clang.1_0</string>
33+
<key>DTPlatformBuild</key>
34+
<string>25A352</string>
35+
<key>DTPlatformName</key>
36+
<string>macosx</string>
37+
<key>DTPlatformVersion</key>
38+
<string>26.0</string>
39+
<key>DTSDKBuild</key>
40+
<string>25A352</string>
41+
<key>DTSDKName</key>
42+
<string>macosx26.0</string>
43+
<key>DTXcode</key>
44+
<string>2601</string>
45+
<key>DTXcodeBuild</key>
46+
<string>17A400</string>
47+
<key>LSMinimumSystemVersion</key>
48+
<string>26.0</string>
49+
</dict>
50+
</plist>
1.14 MB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
APPL????
94.2 KB
Binary file not shown.
1.41 MB
Binary file not shown.

0 commit comments

Comments
 (0)