1import AppKit23@MainActor4protocol WallpaperManaging {5 func download(from url: URL) async throws -> URL6 func setDesktopImage(from fileURL: URL, screen: NSScreen?) throws7 func saveToFolder(from sourceURL: URL, to folderURL: URL) throws8}910@MainActor11protocol DesktopImageSetting {12 func setDesktopImageURL(13 _ url: URL, for screen: NSScreen, options: [NSWorkspace.DesktopImageOptionKey: Any]14 ) throws15}1617extension NSWorkspace: DesktopImageSetting {}1819class WallpaperManager: WallpaperManaging {20 static let shared = WallpaperManager()2122 private let session: URLSession23 private let desktopSetter: @MainActor (URL, NSScreen) throws -> Void24 private let mainScreen: () -> NSScreen?25 private let firstScreen: () -> NSScreen?2627 init(28 session: URLSession = .shared,29 desktopSetter: (@MainActor (URL, NSScreen) throws -> Void)? = nil,30 workspace: DesktopImageSetting = NSWorkspace.shared,31 mainScreen: @escaping () -> NSScreen? = { NSScreen.main },32 firstScreen: @escaping () -> NSScreen? = { NSScreen.screens.first }33 ) {34 self.session = session35 self.desktopSetter =36 desktopSetter ?? { url, screen in37 try workspace.setDesktopImageURL(38 url, for: screen, options: [:]39 )40 }41 self.mainScreen = mainScreen42 self.firstScreen = firstScreen43 }4445 46 47 48 49 50 51 52 func download(from url: URL) async throws -> URL {53 let (data, response) = try await session.data(from: url)54 guard let response = response as? HTTPURLResponse,55 (200...299).contains(response.statusCode)56 else {57 throw WallhavenError.apiError("Wallpaper download failed")58 }5960 let fileURL = Self.makeTempURL(for: url)61 try data.write(to: fileURL)62 return fileURL63 }6465 66 67 68 69 70 static func makeTempURL(for url: URL) -> URL {71 let fileExtension = url.pathExtension.isEmpty ? "img" : url.pathExtension72 let stem = UUID().uuidString73 let filename = "haven-\(stem).\(fileExtension)"74 return FileManager.default.temporaryDirectory.appendingPathComponent(filename)75 }7677 78 @MainActor79 func setDesktopImage(from fileURL: URL, screen: NSScreen? = nil) throws {80 if let screen = screen {81 try desktopSetter(fileURL, screen)82 return83 }84 85 86 87 88 89 let target: NSScreen90 if let main = mainScreen() {91 target = main92 } else if let first = firstScreen() {93 target = first94 } else {95 throw WallhavenError.apiError("No screen is available to set the desktop image")96 }97 try desktopSetter(fileURL, target)98 }99100 101 102 103 104 105 106 107 108 109 func saveToFolder(from sourceURL: URL, to folderURL: URL) throws {110 111 try FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true)112113 let destinationURL = folderURL.appendingPathComponent(sourceURL.lastPathComponent)114115 if FileManager.default.fileExists(atPath: destinationURL.path) {116 let sourceData = try Data(contentsOf: sourceURL)117 let existingData = try Data(contentsOf: destinationURL)118 if sourceData == existingData {119 return120 }121 try FileManager.default.removeItem(at: destinationURL)122 }123124 try FileManager.default.copyItem(at: sourceURL, to: destinationURL)125 }126127 128 @MainActor129 func setWallpaper(from url: URL, screen: NSScreen? = nil) async throws {130 let localURL = try await download(from: url)131 try setDesktopImage(from: localURL, screen: screen)132 }133}