Skip to content

Commit 5a42bae

Browse files
mbeczynskiclaude
andcommitted
fix(budget): read the Time Machine quota in the unit tmutil prints
tmutil formats the quota for the user's region whatever LC_ALL says: the 2450 GB quota set on 27.10.2026 came out as `2,45 TB`, the parser read only "<number> GB", and vm-status kept asking to set a quota that was set. Units GB/TB/MB and a decimal comma are read now, and the match allows for the rounding of what tmutil prints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AU1MhxJW1e9oTB2XvjX8WT
1 parent b9dc0d3 commit 5a42bae

3 files changed

Lines changed: 35 additions & 4 deletions

File tree

‎mac-app/Sources/CloudMachineCore/MachineBudget.swift‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,9 @@ public enum MachineBudget {
283283
/// Whether the Time Machine quota matches the limit. `nil` quota = none set.
284284
public static func quotaMatches(currentQuotaGB: Double?, limitGB: Int) -> Bool {
285285
guard let currentQuotaGB else { return false }
286-
return Int(currentQuotaGB.rounded()) == timeMachineQuotaGB(forLimitGB: limitGB)
286+
// tmutil rounds what it prints (`2,45 TB` = anything from 2445 to 2455).
287+
let wanted = Double(timeMachineQuotaGB(forLimitGB: limitGB))
288+
return abs(currentQuotaGB - wanted) <= max(1, wanted * 0.005)
287289
}
288290

289291
/// One status line: usage against the limit, or why there is none.

‎mac-app/Sources/CloudMachineCore/TimeMachineStatus.swift‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,7 @@ public enum TimeMachineStatus {
252252
case "URL": block.url = value
253253
case "Mount Point": block.mountPoint = value
254254
case "ID": block.id = value
255-
case "Quota":
256-
let number = value.split(separator: " ").first.map(String.init) ?? value
257-
block.quotaGB = Double(number)
255+
case "Quota": block.quotaGB = quotaGB(value)
258256
default: break
259257
}
260258
current = block
@@ -263,6 +261,23 @@ public enum TimeMachineStatus {
263261
return result
264262
}
265263

264+
/// `Quota` as tmutil prints it, in GB. tmutil formats it for the user's
265+
/// region whatever `LC_ALL` says: 2450 GB came out as `2,45 TB` on
266+
/// 27.10.2026, and reading only "<number> GB" took that for no quota.
267+
/// Decimal units, as macOS shows sizes. `nil` = not a size.
268+
static func quotaGB(_ value: String) -> Double? {
269+
let parts = value.split(separator: " ")
270+
guard let first = parts.first,
271+
let number = Double(first.replacingOccurrences(of: ",", with: "."))
272+
else { return nil }
273+
switch parts.count > 1 ? parts[1].uppercased() : "GB" {
274+
case "TB": return number * 1000
275+
case "GB": return number
276+
case "MB": return number / 1000
277+
default: return nil
278+
}
279+
}
280+
266281
/// The registered destinations; `nil` = tmutil DID NOT ANSWER, `[]` = it
267282
/// answered that there are none.
268283
public static func destinationsReading() async -> [Destination]? {

‎mac-app/Tests/CloudMachineAppTests/TimeMachineStatusTests.swift‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,18 @@ final class TimeMachineStatusTests: XCTestCase {
145145
XCTAssertEqual(TimeMachineStatus.destinations(destinationInfoOutput: answer!), [])
146146
}
147147
}
148+
149+
/// 27.10.2026: tmutil printed the 2450 GB quota as `2,45 TB` (Polish
150+
/// region), and the status kept asking to set a quota that was set.
151+
func testQuotaIsReadInTheUnitTmutilPrints() {
152+
XCTAssertEqual(TimeMachineStatus.quotaGB("300 GB"), 300)
153+
XCTAssertEqual(TimeMachineStatus.quotaGB("2,45 TB"), 2450)
154+
XCTAssertEqual(TimeMachineStatus.quotaGB("2.45 TB"), 2450)
155+
XCTAssertNil(TimeMachineStatus.quotaGB("none"))
156+
let parsed = TimeMachineStatus.destinations(
157+
destinationInfoOutput: "Name : CloudMachine\nQuota : 2,45 TB\n")
158+
XCTAssertEqual(parsed.first?.quotaGB, 2450)
159+
XCTAssertTrue(MachineBudget.quotaMatches(currentQuotaGB: 2450, limitGB: 3500))
160+
XCTAssertFalse(MachineBudget.quotaMatches(currentQuotaGB: 2000, limitGB: 3500))
161+
}
148162
}

0 commit comments

Comments
 (0)