Skip to content

Commit 0458ad1

Browse files
committed
Improved on launche behavior, did some cleaning
1 parent 1c48d71 commit 0458ad1

13 files changed

Lines changed: 120 additions & 132 deletions

File tree

NumWorksMac/App/AppController.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ final class AppController: NSObject, NSWindowDelegate {
2121
func start() {
2222
print("[App] start()")
2323
OnLaunch.ensureAppSupportExists()
24-
let hasSim = OnLaunch.hasInstalledSimulator()
24+
let simulatorVersion = EpsilonVersions.currentSimulatorVersionString()
25+
let hasSim = simulatorVersion != "00.00.00"
2526
if hasSim {
26-
print("[App] simulator detected in Application Support")
27+
print("[App] simulator detected in Application Support version=\(simulatorVersion)")
2728
}
2829

2930
updateMenuBarIcon()
@@ -38,7 +39,7 @@ final class AppController: NSObject, NSWindowDelegate {
3839
}
3940

4041
if !hasSim {
41-
print("[App] no installed simulator → keeping menu bar icon, blocking calculator window")
42+
print("[App] no valid simulator → version=\(simulatorVersion) → keeping menu bar icon, blocking calculator window")
4243
Preferences.shared.isAppVisible = false
4344
OnLaunch.requestRequiredSimulatorUpdater()
4445
return

NumWorksMac/App/AppLifecycle.swift

Whitespace-only changes.

NumWorksMac/Core/OnLaunch.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ enum OnLaunch {
2727
}
2828

2929
static func hasInstalledSimulator() -> Bool {
30-
SimulatorPaths.installedHTMLURLIfPresent() != nil
30+
EpsilonVersions.currentSimulatorVersionString() != "00.00.00"
3131
}
3232

3333
static func requestRequiredSimulatorUpdater() {
@@ -94,10 +94,7 @@ enum OnLaunch {
9494
do {
9595
print("[OnLaunch] checking epsilon update")
9696

97-
// If we are using the bundled simulator, ensure the installed-version marker is cleared.
98-
SimulatorUpdater.clearInstalledSimulatorVersionIfUsingBundled()
99-
100-
let current = SimulatorUpdater.installedSimulatorVersionString()
97+
let current = EpsilonVersions.currentSimulatorVersionString()
10198
let report = try await EpsilonUpdateChecker.checkLatest(currentVersionString: current)
10299

103100
epsilonNeedsUpdate = report.needsUpdate
Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,68 @@
11
import Foundation
22

33
enum EpsilonVersions {
4-
static let bundledSimulator = "00.00.0"
4+
5+
struct SimulatorVersion: Comparable {
6+
let major: Int
7+
let minor: Int
8+
let patch: Int
9+
10+
var normalizedString: String {
11+
"\(String(format: "%02d", major)).\(String(format: "%02d", minor)).\(String(format: "%02d", patch))"
12+
}
13+
14+
static func < (lhs: SimulatorVersion, rhs: SimulatorVersion) -> Bool {
15+
if lhs.major != rhs.major { return lhs.major < rhs.major }
16+
if lhs.minor != rhs.minor { return lhs.minor < rhs.minor }
17+
return lhs.patch < rhs.patch
18+
}
19+
}
20+
21+
/// Returns the currently detected simulator version.
22+
/// - "NN.NN.NN" if a valid versioned simulator file is detected in Application Support.
23+
/// - "00.00.00" if no valid simulator file is detected.
24+
static func currentSimulatorVersionString() -> String {
25+
bestDetectedSimulator()?.version.normalizedString ?? "00.00.00"
26+
}
27+
28+
/// Returns the URL of the best detected simulator HTML file, or nil if none are valid.
29+
static func bestSimulatorHTMLURL() -> URL? {
30+
bestDetectedSimulator()?.url
31+
}
32+
33+
// MARK: - Detection
34+
35+
private static func bestDetectedSimulator() -> (version: SimulatorVersion, url: URL)? {
36+
let candidates = SimulatorPaths.simulatorHTMLCandidates()
37+
var best: (SimulatorVersion, URL)? = nil
38+
39+
for url in candidates {
40+
guard let v = parseSimulatorVersion(from: url.lastPathComponent) else { continue }
41+
if let currentBest = best {
42+
if v > currentBest.0 { best = (v, url) }
43+
} else {
44+
best = (v, url)
45+
}
46+
}
47+
48+
return best.map { (version: $0.0, url: $0.1) }
49+
}
50+
51+
/// Accepts filenames like: numworks-simulator-X.Y.Z.html where X/Y/Z are integers.
52+
/// Normalization to NN.NN.NN is done via `SimulatorVersion.normalizedString`.
53+
private static func parseSimulatorVersion(from filename: String) -> SimulatorVersion? {
54+
let prefix = "numworks-simulator-"
55+
let suffix = ".html"
56+
guard filename.hasPrefix(prefix), filename.hasSuffix(suffix) else { return nil }
57+
58+
let start = filename.index(filename.startIndex, offsetBy: prefix.count)
59+
let end = filename.index(filename.endIndex, offsetBy: -suffix.count)
60+
let core = String(filename[start..<end])
61+
62+
let parts = core.split(separator: ".")
63+
guard parts.count == 3 else { return nil }
64+
guard let major = Int(parts[0]), let minor = Int(parts[1]), let patch = Int(parts[2]) else { return nil }
65+
66+
return SimulatorVersion(major: major, minor: minor, patch: patch)
67+
}
568
}

NumWorksMac/Core/Simulator/SimulatorPaths.swift

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import Foundation
22

33
/// Centralized paths for the offline NumWorks simulator assets.
44
///
5-
/// Design goals:
6-
/// - The app ships with a bundled default HTML (Resources/DefaultSimulator/numworks-simulator.html)
7-
/// - If a newer simulator is downloaded, it lives in Application Support/<bundle-id>/Simulator/current
8-
/// - The app prefers the downloaded simulator when present, otherwise falls back to the bundled default
5+
/// Notes:
6+
/// - The app does not ship with any bundled simulator.
7+
/// - Downloaded simulator assets live in Application Support/<bundle-id>/Simulator/current
8+
/// - Versioning and selection logic live in `EpsilonVersions`.
99

1010
enum SimulatorPaths {
1111

@@ -34,39 +34,25 @@ enum SimulatorPaths {
3434
try simulatorDirectory().appendingPathComponent("current", isDirectory: true)
3535
}
3636

37-
// MARK: - Entry HTML
37+
// MARK: - Simulator entry HTML helpers
3838

39-
/// Bundled default simulator HTML inside the app.
40-
static func bundledHTMLURL() -> URL {
41-
guard let url = Bundle.main.url(
42-
forResource: "numworks-simulator",
43-
withExtension: "html",
44-
subdirectory: "DefaultSimulator"
45-
) else {
46-
fatalError("Missing bundled DefaultSimulator/numworks-simulator.html")
47-
}
48-
return url
39+
/// Build the expected simulator HTML URL for a given normalized version string (NN.NN.NN).
40+
static func simulatorHTMLURL(version: String) throws -> URL {
41+
try currentSimulatorDirectory().appendingPathComponent("numworks-simulator-\(version).html", isDirectory: false)
4942
}
5043

51-
/// Downloaded simulator HTML if present in Application Support.
52-
static func installedHTMLURLIfPresent() -> URL? {
44+
/// List all HTML files directly in the current simulator directory.
45+
/// Selection/parsing logic is handled elsewhere.
46+
static func simulatorHTMLCandidates() -> [URL] {
5347
do {
54-
let url = try currentSimulatorDirectory().appendingPathComponent("numworks-simulator.html", isDirectory: false)
55-
if FileManager.default.fileExists(atPath: url.path) {
56-
return url
57-
}
58-
return nil
48+
let dir = try currentSimulatorDirectory()
49+
let items = (try? FileManager.default.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil)) ?? []
50+
return items.filter { $0.pathExtension.lowercased() == "html" }
5951
} catch {
60-
return nil
52+
return []
6153
}
6254
}
6355

64-
/// URL the app should load.
65-
/// Prefer downloaded simulator; fall back to bundled default.
66-
static func urlToLoad() -> URL {
67-
installedHTMLURLIfPresent() ?? bundledHTMLURL()
68-
}
69-
7056
// MARK: - Ensure directories exist (for updater)
7157

7258
static func ensureDirectoriesExist() throws {

NumWorksMac/Core/Simulator/SimulatorRuntime.swift

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,21 @@ import Foundation
22

33
/// Resolves which simulator HTML to load.
44
///
5-
/// Behavior:
6-
/// - If an installed simulator exists in Application Support (Simulator/current/numworks-simulator.html), load that.
7-
/// - Otherwise, load the bundled DefaultSimulator directly from the app bundle.
8-
///
9-
/// The bundled default is never copied into Application Support.
5+
/// Notes:
6+
/// - The app does not ship with any bundled simulator.
7+
/// - A simulator is considered valid only if `EpsilonVersions` can detect a versioned
8+
/// `numworks-simulator-X.Y.Z.html` file in Application Support.
109
final class SimulatorRuntime {
1110

1211
enum RuntimeError: Error {
13-
case bundledHTMLNotFound
12+
case noValidSimulatorInstalled
1413
}
1514

1615
/// Returns the URL that WKWebView should load.
1716
func urlToLoad() throws -> URL {
18-
if let installed = installedSimulatorHTMLURL(), FileManager.default.fileExists(atPath: installed.path) {
19-
return installed
20-
}
21-
22-
guard let bundled = bundledDefaultHTMLURL() else {
23-
throw RuntimeError.bundledHTMLNotFound
17+
guard let url = EpsilonVersions.bestSimulatorHTMLURL() else {
18+
throw RuntimeError.noValidSimulatorInstalled
2419
}
25-
return bundled
26-
}
27-
28-
// MARK: - Installed simulator
29-
30-
private func installedSimulatorHTMLURL() -> URL? {
31-
let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
32-
guard let base else { return nil }
33-
34-
let id = Bundle.main.bundleIdentifier ?? "NumworksApplication"
35-
let currentDir = base
36-
.appendingPathComponent(id, isDirectory: true)
37-
.appendingPathComponent("Simulator", isDirectory: true)
38-
.appendingPathComponent("current", isDirectory: true)
39-
40-
return currentDir.appendingPathComponent("numworks-simulator.html", isDirectory: false)
41-
}
42-
43-
// MARK: - Bundled default
44-
45-
private func bundledDefaultHTMLURL() -> URL? {
46-
Bundle.main.url(forResource: "numworks-simulator", withExtension: "html", subdirectory: "DefaultSimulator")
47-
?? Bundle.main.url(forResource: "numworks-simulator", withExtension: "html")
20+
return url
4821
}
4922
}

NumWorksMac/Core/Updaters/EpsilonUpdateChecker.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct EpsilonUpdateChecker {
1515
case couldNotExtractRemoteVersion(URL)
1616
}
1717

18-
static func check(remoteURL: URL, currentVersionString: String = EpsilonVersions.bundledSimulator) throws -> Report {
18+
static func check(remoteURL: URL, currentVersionString: String) throws -> Report {
1919
guard let current = SemVer(currentVersionString) else {
2020
throw Error.invalidCurrentVersion(currentVersionString)
2121
}
@@ -81,7 +81,7 @@ struct EpsilonUpdateChecker {
8181
return best.url
8282
}
8383

84-
static func checkLatest(currentVersionString: String = EpsilonVersions.bundledSimulator) async throws -> Report {
84+
static func checkLatest(currentVersionString: String) async throws -> Report {
8585
let remoteURL = try await fetchLatestRemoteURL()
8686
return try check(remoteURL: remoteURL, currentVersionString: currentVersionString)
8787
}

NumWorksMac/Core/Updaters/SimulatorUpdater.swift

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ final class SimulatorUpdater: NSObject, ObservableObject, URLSessionDownloadDele
1919
@Published var isPresented = false
2020
@Published var phase: Phase = .idle
2121

22-
@Published var fromVersion: String = EpsilonVersions.bundledSimulator
22+
@Published var fromVersion: String = EpsilonVersions.currentSimulatorVersionString()
2323
@Published var toVersion: String = ""
2424

2525
@Published var progress: Double = 0
@@ -57,7 +57,7 @@ final class SimulatorUpdater: NSObject, ObservableObject, URLSessionDownloadDele
5757
restartTimer = nil
5858
restartSecondsRemaining = 0
5959
self.remoteURL = remoteURL
60-
self.fromVersion = currentInstalledVersion
60+
self.fromVersion = EpsilonVersions.currentSimulatorVersionString()
6161
self.toVersion = remoteVersion
6262
setProgress(0)
6363
self.phase = .prompt
@@ -74,7 +74,7 @@ final class SimulatorUpdater: NSObject, ObservableObject, URLSessionDownloadDele
7474
restartTimer = nil
7575
restartSecondsRemaining = 0
7676
self.remoteURL = remoteURL
77-
self.fromVersion = currentInstalledVersion
77+
self.fromVersion = EpsilonVersions.currentSimulatorVersionString()
7878
self.toVersion = remoteVersion
7979
setProgress(0)
8080
self.phase = .prompt
@@ -134,10 +134,8 @@ final class SimulatorUpdater: NSObject, ObservableObject, URLSessionDownloadDele
134134
try? fm.removeItem(at: currentDir)
135135
}
136136
try fm.moveItem(at: stagingDir, to: currentDir)
137-
try ensureSimulatorEntryHTML(in: currentDir)
138-
139-
setInstalledVersion(toVersion)
140-
print("[SimulatorUpdater] installed version set to \(toVersion)")
137+
let normalizedTo = try normalizeVersionString(toVersion)
138+
try ensureSimulatorEntryHTML(in: currentDir, version: normalizedTo)
141139

142140
// cleanup staged artifacts
143141
try? fm.removeItem(at: zip)
@@ -232,7 +230,7 @@ final class SimulatorUpdater: NSObject, ObservableObject, URLSessionDownloadDele
232230

233231
// Added per update prompting fixes and cleanup
234232
nonisolated static func installedSimulatorVersionString() -> String {
235-
UserDefaults.standard.string(forKey: "installedSimulatorVersion") ?? EpsilonVersions.bundledSimulator
233+
EpsilonVersions.currentSimulatorVersionString()
236234
}
237235

238236
nonisolated static func hasInstalledSimulatorInAppSupport() -> Bool {
@@ -331,14 +329,6 @@ final class SimulatorUpdater: NSObject, ObservableObject, URLSessionDownloadDele
331329

332330
// MARK: - Paths + versions
333331

334-
private var currentInstalledVersion: String {
335-
UserDefaults.standard.string(forKey: "installedSimulatorVersion") ?? EpsilonVersions.bundledSimulator
336-
}
337-
338-
private func setInstalledVersion(_ v: String) {
339-
UserDefaults.standard.set(v, forKey: "installedSimulatorVersion")
340-
}
341-
342332
private var appSupportDir: URL {
343333
let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
344334
let id = Bundle.main.bundleIdentifier ?? "NumworksApplication"
@@ -363,9 +353,9 @@ final class SimulatorUpdater: NSObject, ObservableObject, URLSessionDownloadDele
363353

364354
// MARK: - Install helpers
365355

366-
private func ensureSimulatorEntryHTML(in currentDir: URL) throws {
356+
private func ensureSimulatorEntryHTML(in currentDir: URL, version: String) throws {
367357
let fm = FileManager.default
368-
let desired = currentDir.appendingPathComponent("numworks-simulator.html", isDirectory: false)
358+
let desired = currentDir.appendingPathComponent("numworks-simulator-\(version).html", isDirectory: false)
369359
if fm.fileExists(atPath: desired.path) { return }
370360

371361
let index = currentDir.appendingPathComponent("index.html", isDirectory: false)
@@ -382,6 +372,15 @@ final class SimulatorUpdater: NSObject, ObservableObject, URLSessionDownloadDele
382372
}
383373
}
384374

375+
private func normalizeVersionString(_ v: String) throws -> String {
376+
let parts = v.split(separator: ".")
377+
guard parts.count == 3 else { throw NSError(domain: "SimulatorUpdater", code: 1) }
378+
guard let major = Int(parts[0]), let minor = Int(parts[1]), let patch = Int(parts[2]) else {
379+
throw NSError(domain: "SimulatorUpdater", code: 2)
380+
}
381+
return String(format: "%02d.%02d.%02d", major, minor, patch)
382+
}
383+
385384
private func unzip(zipFile: URL, to dest: URL) throws {
386385
let p = Process()
387386
p.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")

NumWorksMac/UI/Calculator/CalculatorWebView.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ struct CalculatorWebView: NSViewRepresentable {
1818
webView.setValue(false, forKey: "drawsBackground")
1919
webView.navigationDelegate = context.coordinator
2020

21-
let url = try! runtime.urlToLoad()
22-
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
21+
do {
22+
let url = try runtime.urlToLoad()
23+
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
24+
} catch {
25+
print("[CalculatorWebView] no valid simulator to load: \(error)")
26+
}
2327
return webView
2428
}
2529

NumWorksMac/UI/MenuBar/MenuBarPopoverView.swift

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)