Skip to content

Commit

Permalink
~ Rewriting of package loading function
Browse files Browse the repository at this point in the history
  • Loading branch information
buresdv committed Nov 13, 2024
1 parent 544b2e5 commit 8e78277
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 112 deletions.
108 changes: 0 additions & 108 deletions Cork/Logic/File System/File Browser/Get Contents of Folder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,114 +111,6 @@ private extension URL
return items
}

/// This function checks whether the package was installed intentionally.
/// - For Formulae, this info gets read from the install receipt
/// - Casks are always instaled intentionally
/// - Parameter versionURLs: All available versions for this package. Some packages have multiple versions installed at a time (for example, the package `xz` might have versions 1.2 and 1.3 installed at once)
/// - Returns: Indication whether this package was installed intentionally or not
func checkIfPackageWasInstalledIntentionally(_ versionURLs: [URL]) async throws -> Bool
{
guard let localPackagePath = versionURLs.first
else
{
throw PackageLoadingError.failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-load-version-to-check-from-available-versions"))
}

guard localPackagePath.lastPathComponent != "Cellar"
else
{
AppConstants.shared.logger.error("The last path component of the requested URL is the package container folder itself - perhaps a misconfigured package folder? Tried to load URL \(localPackagePath)")

throw PackageLoadingError.failedWhileLoadingPackages(failureReason: String(localized: "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.formulae"))
}

guard localPackagePath.lastPathComponent != "Caskroom"
else
{
AppConstants.shared.logger.error("The last path component of the requested URL is the package container folder itself - perhaps a misconfigured package folder? Tried to load URL \(localPackagePath)")

throw PackageLoadingError.failedWhileLoadingPackages(failureReason: String(localized: "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.casks"))
}

if path.contains("Cellar")
{
let localPackageInfoJSONPath: URL = localPackagePath.appendingPathComponent("INSTALL_RECEIPT.json", conformingTo: .json)
if FileManager.default.fileExists(atPath: localPackageInfoJSONPath.path)
{
struct InstallRecepitParser: Codable
{
let installedOnRequest: Bool
}

let decoder: JSONDecoder = {
let decoder: JSONDecoder = .init()
decoder.keyDecodingStrategy = .convertFromSnakeCase

return decoder
}()

do
{
let installReceiptContents: Data = try .init(contentsOf: localPackageInfoJSONPath)

do
{
return try decoder.decode(InstallRecepitParser.self, from: installReceiptContents).installedOnRequest
}
catch let installReceiptParsingError
{
AppConstants.shared.logger.error("Failed to decode install receipt for package \(self.lastPathComponent, privacy: .public) with error \(installReceiptParsingError.localizedDescription, privacy: .public)")

throw PackageLoadingError.failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-decode-installa-receipt-\(installReceiptParsingError.localizedDescription)"))
}
}
catch let installReceiptLoadingError
{
AppConstants.shared.logger.error("Failed to load contents of install receipt for package \(self.lastPathComponent, privacy: .public) with error \(installReceiptLoadingError.localizedDescription, privacy: .public)")
throw PackageLoadingError.failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-convert-contents-of-install-receipt-to-data-\(installReceiptLoadingError.localizedDescription)"))
}
}
else
{ /// There's no install receipt for this package - silently fail and return that the packagw was not installed intentionally
// TODO: Add a setting like "Strictly check for errors" that would instead throw an error here

AppConstants.shared.logger.error("There appears to be no install receipt for package \(localPackageInfoJSONPath.lastPathComponent, privacy: .public)")

let shouldStrictlyCheckForHomebrewErrors: Bool = UserDefaults.standard.bool(forKey: "strictlyCheckForHomebrewErrors")

if shouldStrictlyCheckForHomebrewErrors
{
throw PackageLoadingError.failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.missing-install-receipt"))
}
else
{
return false
}
}
}
else if path.contains("Caskroom")
{
return true
}
else
{
throw PackageLoadingError.failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.unexpected-folder-name"))
}
}

/// Determine a package's type type from its URL
var packageType: PackageType
{
if path.contains("Cellar")
{
return .formula
}
else
{
return .cask
}
}

/// Get URLs to a package's versions
var packageVersionURLs: [URL]?
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//
// Check if Package Was Installed Intentionally.swift
// Cork
//
// Created by David Bureš on 13.11.2024.
//

import Foundation
import CorkShared

enum IntentionalInstallationDiscoveryError: Error
{
/// The function could not determine the most relevant version of the package to read the recepit from
case failedToDetermineMostRelevantVersion(packageURL: URL)

/// The installation receipt is there, but cannot be read due to permission issues
case failedToReadInstallationRecepit(packageURL: URL)

/// The installation receipt could be read, but not parsed
case failedToParseInstallationReceipt(packageURL: URL)

/// The installation receipt is missing completely
case installationReceiptMissingCompletely(packageURL: URL)

/// The provided `URL` has an unexpected form
case unexpectedFolderName(packageURL: URL)
}

extension URL
{
/// This function checks whether the package was installed intentionally.
/// - For Formulae, this info gets read from the install receipt
/// - Casks are always instaled intentionally
/// - Parameter versionURLs: All available versions for this package. Some packages have multiple versions installed at a time (for example, the package `xz` might have versions 1.2 and 1.3 installed at once)
/// - Returns: Indication whether this package was installed intentionally or not
func checkIfPackageWasInstalledIntentionally(versionURLs: [URL]) async throws(IntentionalInstallationDiscoveryError) -> Bool
{

// TODO: Convert this so it uses the most recent version instead of a random one
guard let localPackagePath = versionURLs.first
else
{
throw .failedToDetermineMostRelevantVersion(packageURL: self)

//throw .failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-load-version-to-check-from-available-versions"))
}

if path.contains("Cellar")
{
let localPackageInfoJSONPath: URL = localPackagePath.appendingPathComponent("INSTALL_RECEIPT.json", conformingTo: .json)
if FileManager.default.fileExists(atPath: localPackageInfoJSONPath.path)
{
struct InstallRecepitParser: Codable
{
let installedOnRequest: Bool
}

let decoder: JSONDecoder = {
let decoder: JSONDecoder = .init()
decoder.keyDecodingStrategy = .convertFromSnakeCase

return decoder
}()

do
{
let installReceiptContents: Data = try .init(contentsOf: localPackageInfoJSONPath)

do
{
return try decoder.decode(InstallRecepitParser.self, from: installReceiptContents).installedOnRequest
}
catch let installReceiptParsingError
{
AppConstants.shared.logger.error("Failed to decode install receipt for package \(self.lastPathComponent, privacy: .public) with error \(installReceiptParsingError.localizedDescription, privacy: .public)")

throw IntentionalInstallationDiscoveryError.failedToParseInstallationReceipt(packageURL: self)

//throw PackageLoadingError.failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-decode-installa-receipt-\(installReceiptParsingError.localizedDescription)"))
}
}
catch let installReceiptLoadingError
{
AppConstants.shared.logger.error("Failed to load contents of install receipt for package \(self.lastPathComponent, privacy: .public) with error \(installReceiptLoadingError.localizedDescription, privacy: .public)")

throw .failedToReadInstallationRecepit(packageURL: self)

//throw .failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-convert-contents-of-install-receipt-to-data-\(installReceiptLoadingError.localizedDescription)"))
}
}
else
{ /// There's no install receipt for this package - silently fail and return that the packagw was not installed intentionally
// TODO: Add a setting like "Strictly check for errors" that would instead throw an error here

AppConstants.shared.logger.error("There appears to be no install receipt for package \(localPackageInfoJSONPath.lastPathComponent, privacy: .public)")

let shouldStrictlyCheckForHomebrewErrors: Bool = UserDefaults.standard.bool(forKey: "strictlyCheckForHomebrewErrors")

if shouldStrictlyCheckForHomebrewErrors
{
throw .installationReceiptMissingCompletely(packageURL: self)

//throw .failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.missing-install-receipt"))
}
else
{
return false
}
}
}
else if path.contains("Caskroom")
{
return true
}
else
{
throw .unexpectedFolderName(packageURL: self)
//throw .failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.unexpected-folder-name"))
}
}
}
28 changes: 28 additions & 0 deletions Cork/Logic/Package Loading/Helper Logic/Filter Symlinks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Filter Symlinks.swift
// Cork
//
// Created by David Bureš on 13.11.2024.
//

import Foundation

extension [URL]
{
/// Filter out all symlinks from an array of URLs
var withoutSymlinks: [URL]
{
return self.filter
{ url in
/// If the existence of a symlink cannot be verified, be safe and return `false`
guard let isSymlink = url.isSymlink()
else
{
return false
}

/// `isSymlink` is `true` for a symlink. Therefore, if we want to filter out symlinks, we have to return the opposite of `true`, which is `false`
return !isSymlink
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Get Package Type from URL.swift
// Cork
//
// Created by David Bureš on 13.11.2024.
//

import Foundation

extension URL
{
/// Determine a package's type type from its URL
var packageType: PackageType
{
if self.pathComponents.contains("Cellar")
{
return .formula
}
else
{
return .cask
}
}
}
Loading

0 comments on commit 8e78277

Please sign in to comment.