Files
Brewer/Brewer/Model/Homebrew.swift
2024-07-10 11:40:17 +02:00

249 lines
4.9 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?
var 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:
false
}
}
func uninstall(_ fullToken: String) async -> Bool {
self.isLoading = true
self.data = nil
let task = Task { [weak self] in
do {
try shell(
"/opt/homebrew/bin/brew uninstall \(fullToken)"
)
} 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:
false
}
}
func update(_ 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 upgrade \(name); echo $?"
)
.trimmingCharacters(in: .whitespacesAndNewlines)
self?.isLoading = false
return res == "0"
} catch {
self?.errorMessage = error.localizedDescription
}
self?.isLoading = false
return false
}
return switch await task.result {
case .success(let success):
success
case .failure:
false
}
}
}