From b69b34ea14ec455c584c1dce736691379befe3d0 Mon Sep 17 00:00:00 2001 From: GrĂ©goire DuchĂȘne Date: Sat, 10 Jan 2026 14:25:40 +0000 Subject: Make sleep inhibition more structured --- .gitignore | 1 + Sources/SleepInhibition.swift | 42 +++++++++++++++++++++++++++++++++ Sources/SleepInhibitor.swift | 48 -------------------------------------- Sources/Watcher.swift | 54 ++++++++++++++----------------------------- 4 files changed, 60 insertions(+), 85 deletions(-) create mode 100644 Sources/SleepInhibition.swift delete mode 100644 Sources/SleepInhibitor.swift diff --git a/.gitignore b/.gitignore index 24e5b0a..a915b32 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .build +caffeinate-downloads diff --git a/Sources/SleepInhibition.swift b/Sources/SleepInhibition.swift new file mode 100644 index 0000000..0d9a05e --- /dev/null +++ b/Sources/SleepInhibition.swift @@ -0,0 +1,42 @@ +#if canImport(IOKit.pwr_mgt) +import IOKit.pwr_mgt + +func withSleepInhibited(operation: () async throws -> Void) async throws { + let assertionID = try { + var assertionID = IOPMAssertionID(0) + let ioReturn = IOPMAssertionCreateWithDescription( + kIOPMAssertionTypePreventUserIdleSystemSleep as CFString, + "caffeinate-downloads" as CFString, + "There are files being downloaded" as CFString, + nil, nil, 0, nil, + &assertionID + ) + guard ioReturn == kIOReturnSuccess else { + throw SleepInhibitionError("failed to inhibit sleep", ioReturn) + } + return assertionID + }() + + do { + try await operation() + } catch is CancellationError { + } + + let ioReturn = IOPMAssertionRelease(assertionID) + guard ioReturn == kIOReturnSuccess else { + throw SleepInhibitionError("failed to allow sleep", ioReturn) + } +} + +struct SleepInhibitionError: CustomStringConvertible, Error { + let description: String + + init(_ description: String, _ code: IOReturn) { + if let code = mach_error_string(code) { + self.description = "\(description): \(String(cString: code))" + } else { + self.description = "\(description): \(code)" + } + } +} +#endif diff --git a/Sources/SleepInhibitor.swift b/Sources/SleepInhibitor.swift deleted file mode 100644 index 0cfe895..0000000 --- a/Sources/SleepInhibitor.swift +++ /dev/null @@ -1,48 +0,0 @@ -import IOKit.pwr_mgt - -actor SleepInhibitor { - var assertionID = IOPMAssertionID?.none - var isInhibitingSleep: Bool { self.assertionID != nil } - - func create(name: String, details: String) throws { - guard self.assertionID == nil else { - return - } - - var assertionID = IOPMAssertionID(0) - let ioReturn = IOPMAssertionCreateWithDescription( - kIOPMAssertionTypePreventUserIdleSystemSleep as CFString, - name as CFString, - details as CFString, - nil, nil, 0, nil, - &assertionID - ) - guard ioReturn == kIOReturnSuccess else { - throw SleepInhibitorError(ioReturn: ioReturn) - } - self.assertionID = assertionID - } - - func release() throws { - guard let assertionID = self.assertionID else { - return - } - - let ioReturn = IOPMAssertionRelease(assertionID) - guard ioReturn == kIOReturnSuccess else { - throw SleepInhibitorError(ioReturn: ioReturn) - } - self.assertionID = nil - } -} - -struct SleepInhibitorError: CustomStringConvertible, Error { - let ioReturn: IOReturn - - var description: String { - guard let cString = mach_error_string(self.ioReturn) else { - return self.ioReturn.description - } - return String(cString: cString) - } -} diff --git a/Sources/Watcher.swift b/Sources/Watcher.swift index 3b08efd..75fefdf 100644 --- a/Sources/Watcher.swift +++ b/Sources/Watcher.swift @@ -7,49 +7,29 @@ struct Watcher: Service { let logger: Logger let suffix: String - func run() async { - let sleepInhibitor = SleepInhibitor() + func foundDownloads() async throws -> Bool { + try await FileSystem.shared.withDirectoryHandle(atPath: self.directory) { + try await $0.listContents().contains { + $0.name.string.hasSuffix(self.suffix) + } + } + } + func run() async throws { while !Task.isCancelled { - let isDownloading: Bool do { - isDownloading = try await FileSystem.shared.withDirectoryHandle(atPath: self.directory) { - try await $0.listContents().contains { - $0.name.string.hasSuffix(self.suffix) - } + while try await !foundDownloads() { + try await Task.sleep(for: .seconds(30)) } - } catch is CancellationError { - return - } catch { - self.logger.error("Failed to check directory: \(error)") - return - } - switch (isDownloading, await sleepInhibitor.isInhibitingSleep) { - case (true, false): - self.logger.debug("Ongoing downloads found, inhibiting sleep") - do { - try await sleepInhibitor.create( - name: "caffeinate-downloads", - details: "There are files being downloaded" - ) - } catch { - self.logger.error("Failed to create assertion: \(error)") + try await withSleepInhibited { + self.logger.debug("Ongoing downloads found, inhibiting sleep") + while try await foundDownloads() { + try await Task.sleep(for: .seconds(30)) + } + self.logger.debug("No ongoing downloads found, allowing sleep") } - - case (false, true): - self.logger.debug("No ongoing downloads found, allowing sleep") - do { - try await sleepInhibitor.release() - } catch { - self.logger.error("Failed to release assertion: \(error)") - } - - default: - break - } - - guard (try? await Task.sleep(for: .seconds(30))) != nil else { + } catch is CancellationError { return } } -- cgit v1.2.3-70-g09d2