Files
Brewer/Brewer/Model/Homebrew.swift
2024-07-09 20:35:00 +02:00

225 lines
4.4 KiB
Swift

//
// Cask.swift
// Brewer
//
// Created by Cédric MAS on 08/07/2024.
//
import Foundation
enum ShellError: Error {
case emptyOutput
}
@discardableResult
func shell(_ command: String) throws -> String {
let task = Process()
task.executableURL = URL(filePath: "/bin/bash")
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardError = pipe
task.standardOutput = pipe
try task.run()
if let data = try pipe.fileHandleForReading.readToEnd(),
let output = String(data: data, encoding: .utf8)
{
return output
}
throw ShellError.emptyOutput
}
struct Cask: Codable {
let token: String
let fullToken: String
let tap: String
let name: [String]
let desc: String
let homepage: String
let url: String
let version: String
let installed: String?
let outdated: Bool
enum CodingKeys: String, CodingKey {
case token, tap, name, desc, homepage, url, version, installed, outdated
case fullToken = "full_token"
}
}
struct Formulae: Codable {
struct Version: Codable {
let stable: String
let head: String?
let bottle: Bool
enum CodingKeys: String, CodingKey {
case stable
case head, bottle
}
}
let name: String
let fullName: String
let tap: String
let oldNames: [String]
let aliases: [String]
let versionedFormulae: [String]
let desc: String
let license: String?
let homepage: String
let versions: Version
let outdated: Bool
enum CodingKeys: String, CodingKey {
case name
case tap
case aliases
case desc
case license
case homepage
case versions
case outdated
case fullName = "full_name"
case oldNames = "oldnames"
case versionedFormulae = "versioned_formulae"
}
}
struct InfoResponse: Codable {
let formulae: [Formulae]
let casks: [Cask]
enum CodingKeys: String, CodingKey {
case casks
case formulae
}
}
@Observable
class Homebrew {
var data: InfoResponse?
var isLoading = false
var errorMessage: String?
func getInfo(on query: String) {
self.isLoading = true
self.errorMessage = nil
self.data = nil
Task { [weak self] in
do {
let res = try shell(
"/opt/homebrew/bin/brew info --json=v2 \(query)")
if let data = res.data(using: .utf8) {
let output = try JSONDecoder().decode(
InfoResponse.self, from: data)
self?.data = output
}
} catch {
self?.errorMessage = error.localizedDescription
}
self?.isLoading = false
}
}
func getInstalled() {
self.isLoading = true
self.data = nil
self.errorMessage = nil
Task { [weak self] in
do {
let res = try shell(
"/opt/homebrew/bin/brew info --json=v2 --installed")
if let data = res.data(using: .utf8) {
let output = try JSONDecoder().decode(
InfoResponse.self, from: data)
self?.data = output
}
} catch {
self?.errorMessage = error.localizedDescription
}
self?.isLoading = false
}
}
func isDownloaded(_ name: String) async -> Bool {
self.isLoading = true
self.errorMessage = nil
self.data = nil
let task = Task { [weak self] in
do {
let res = try shell(
"/opt/homebrew/bin/brew list -1 \(name) >/dev/null 2>&1; echo $?"
)
.trimmingCharacters(in: .whitespacesAndNewlines)
self?.isLoading = false
return res == "0"
} catch {
self?.errorMessage = error.localizedDescription
}
return false
}
switch await task.result {
case .success(let success):
return success
case .failure(let fail):
return false
}
}
func install(_ fullToken: String, isCask: Bool = false) async -> Bool {
self.isLoading = true
self.data = nil
let task = Task { [weak self] in
do {
try shell(
"/opt/homebrew/bin/brew install \(isCask ? "--cask" : "") \(fullToken)"
)
} catch {
self?.errorMessage = error.localizedDescription
}
self?.isLoading = false
return await self?.isDownloaded(fullToken) ?? false
}
return switch await task.result {
case .success(let success):
success
case .failure(let fail):
false
}
}
func uninstall(_ fullToken: String) async -> Bool {
self.isLoading = true
self.data = nil
let task = Task { [weak self] in
do {
let res =
try shell(
"/opt/homebrew/bin/brew uninstall \(fullToken); echo $?"
)
.trimmingCharacters(in: .whitespacesAndNewlines)
} catch {
self?.errorMessage = error.localizedDescription
}
self?.isLoading = false
return await !(self?.isDownloaded(fullToken) ?? true)
}
return switch await task.result {
case .success(let success):
success
case .failure(let failure):
false
}
}
}