diff --git a/bigdl b/bigdl index c6ae2d8..3dd8b70 100644 Binary files a/bigdl and b/bigdl differ diff --git a/fast_info.go b/fast_info.go new file mode 100644 index 0000000..71b04e6 --- /dev/null +++ b/fast_info.go @@ -0,0 +1,87 @@ +// fast_info.go // this file implements the functionality of 'f_info' //> +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +// fast_BinaryInfo represents the structure of binary information. +type fast_BinaryInfo struct { + Description string `json:"description"` + Name string `json:"name"` + Architecture string `json:"architecture"` + Version string `json:"version"` + Updated string `json:"updated"` + Size string `json:"size"` + SHA string `json:"sha"` + Source string `json:"source"` +} + +// fast_showBinaryInfo fetches a binary's information from RMetadataURL and prints it. +func fast_showBinaryInfo(binaryName string, validatedArch string) { + response, err := http.Get(RMetadataURL) + if err != nil { + fmt.Printf("Error fetching metadata: %v\n", err) + return + } + defer response.Body.Close() + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + fmt.Printf("Error reading metadata: %v\n", err) + return + } + + var metadata map[string][]fast_BinaryInfo + if err := json.Unmarshal(body, &metadata); err != nil { + fmt.Printf("Error decoding metadata: %v\n", err) + return + } + + binaries, exists := metadata["packages"] + if !exists { + fmt.Println("Invalid metadata format. No fields found. Check fast_info.go:44:31") + return + } + + var found bool + var nonMatchingArchCount int + + for _, bin := range binaries { + if bin.Name == binaryName && bin.Architecture == validatedArch { + // Print the binary information + fmt.Printf("Name: %s\n", bin.Name) + fmt.Printf("Description: %s\n", bin.Description) + if bin.Version != "" { + fmt.Printf("Version: %s\n", bin.Version) + } + if bin.Updated != "" { + fmt.Printf("Updated: %s\n", bin.Updated) + } + if bin.Size != "" { + fmt.Printf("Size: %s\n", bin.Size) + } + if bin.SHA != "" { + fmt.Printf("SHA: %s\n", bin.SHA) + } + fmt.Printf("Source: %s\n", bin.Source) + found = true + break + } else if bin.Name == binaryName { + // Binary found but not for the system's architecture + nonMatchingArchCount++ + } + } + + // If binary not found for the system's architecture + if !found { + if nonMatchingArchCount > 0 { + fmt.Printf("Binary '%s' found, but not for the system's architecture.\n", binaryName) + } else { + fmt.Printf("Info for the requested binary ('%s') not found in the metadata.json file. Please contribute to %s.\n", binaryName, RMetadataURL) + } + } +} diff --git a/findURL.go b/findURL.go index d90eed1..287025c 100644 --- a/findURL.go +++ b/findURL.go @@ -1,5 +1,4 @@ -// findURL.go // This file implements the findURL function // - +// findURL.go // This file implements the findURL function //> package main import ( diff --git a/fsearch.go b/fsearch.go index 81a79d6..a961db0 100644 --- a/fsearch.go +++ b/fsearch.go @@ -1,5 +1,4 @@ -// fsearch.go // this file implements the search option - +// fsearch.go // this file implements the search option //> package main import ( @@ -30,17 +29,29 @@ func fSearch(searchTerm string, desiredArch string) { return } - var metadata map[string][]BinaryInfo - if err := json.Unmarshal(body, &metadata); err != nil { + // Define a struct to match the JSON structure from RMetadataURL + type Binary struct { + Name string `json:"name"` + Description string `json:"description"` + // Include other fields if needed + } + + type RMetadata struct { + Binaries []Binary `json:"packages"` + } + + // Unmarshal the description as an RMetadata object + var rMetadata RMetadata + if err := json.Unmarshal(body, &rMetadata); err != nil { fmt.Println("Failed to decode metadata.") return } // Filter binaries based on the search term and architecture searchResultsSet := make(map[string]struct{}) // Use a set to keep track of unique entries - for _, bin := range metadata["binaries"] { - if strings.Contains(strings.ToLower(bin.Name+bin.Description), strings.ToLower(searchTerm)) { - entry := fmt.Sprintf("%s - %s", bin.Name, bin.Description) + for _, pkg := range rMetadata.Binaries { + if strings.Contains(strings.ToLower(pkg.Name+pkg.Description), strings.ToLower(searchTerm)) { + entry := fmt.Sprintf("%s - %s", pkg.Name, pkg.Description) searchResultsSet[entry] = struct{}{} // Add the entry to the set } } @@ -50,7 +61,7 @@ func fSearch(searchTerm string, desiredArch string) { fmt.Printf("No matching binaries found for '%s'.\n", searchTerm) return } else if len(searchResultsSet) > 90 { - fmt.Printf("Too many matching binaries found for '%s'.\n", searchTerm) + fmt.Printf("Too many matching binaries (+90. [Limit defined in fsearch.go:63:36,37]) found for '%s'.\n", searchTerm) return } @@ -69,11 +80,11 @@ func fSearch(searchTerm string, desiredArch string) { cmd.Stdin = os.Stdin out, err := cmd.Output() if err != nil { - return 80 // Default to 80 columns if unable to get terminal width + return 80 // Default to 80 columns if unable to get terminal width } width, err := strconv.Atoi(strings.TrimSpace(string(out))) if err != nil { - return 80 // Default to 80 columns if unable to convert width to integer + return 80 // Default to 80 columns if unable to convert width to integer } return width } @@ -103,7 +114,7 @@ func fSearch(searchTerm string, desiredArch string) { } // Calculate available space for description - availableSpace := getTerminalWidth() - len(prefix) - len(name) - 4 // 4 accounts for space around ' - ' + availableSpace := getTerminalWidth() - len(prefix) - len(name) - 4 // 4 accounts for space around ' - ' // Truncate the description if it exceeds the available space if len(description) > availableSpace { diff --git a/helperFunctions.go b/helperFunctions.go index c508bd1..dfd9245 100644 --- a/helperFunctions.go +++ b/helperFunctions.go @@ -1,4 +1,4 @@ -// helperFunctions.go // This file contains commonly used functions // +// helperFunctions.go // This file contains commonly used functions //> package main diff --git a/info.go b/info.go index 5b0f2b7..65dc500 100644 --- a/info.go +++ b/info.go @@ -1,5 +1,4 @@ -// info.go // this file implements the functionality of 'info' - +// info.go // this file implements the functionality of 'info' //> package main import ( @@ -12,11 +11,13 @@ import ( // BinaryInfo represents the structure of binary information. type BinaryInfo struct { Name string `json:"Name"` + Description string `json:"Description"` Repo string `json:"Repo"` + ModTime string `json:"ModTime"` Size string `json:"Size"` SHA256 string `json:"SHA256"` B3SUM string `json:"B3SUM"` - Description string `json:"Description"` + Source string `json:"Source"` } // BinaryMetadata represents the structure of the metadata for a binary. @@ -24,84 +25,66 @@ type BinaryMetadata struct { Binaries []BinaryInfo `json:"binaries"` } -// showBinaryInfo fetches binary information from MetadataURLs and prints it. -func showBinaryInfo(binaryName string) { +// getBinaryInfo fetches binary information from MetadataURLs and returns it as a BinaryInfo struct. +func getBinaryInfo(binaryName string) (*BinaryInfo, error) { for i, metadataURL := range MetadataURLs { - if i >= 2 { // TODO: Correctly unmarshal Github's REST API's "contents" endpoint. + if i >= 2 { // TODO: Correctly unmarshal Github's REST API's "contents" endpoint. In order not to do this ugly thing. break } - response, err := http.Get(metadataURL) if err != nil { - fmt.Printf("Error fetching metadata from %s: %v\n", metadataURL, err) - continue + return nil, fmt.Errorf("error fetching metadata from %s: %v", metadataURL, err) } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { - fmt.Printf("Error reading metadata from %s: %v\n", metadataURL, err) - continue + return nil, fmt.Errorf("error reading metadata from %s: %v", metadataURL, err) } var metadata []BinaryInfo if err := json.Unmarshal(body, &metadata); err != nil { - fmt.Printf("Error decoding metadata from %s: %v\n", metadataURL, err) - continue + return nil, fmt.Errorf("error decoding metadata from %s: %v", metadataURL, err) } for _, bin := range metadata { if bin.Name == binaryName { - fmt.Printf("Binary Name: %s\n", bin.Name) - if bin.Repo != "" { - fmt.Printf("Repo: %s\n", bin.Repo) - } - if bin.Size != "" { - fmt.Printf("Size: %s\n", bin.Size) - } - if bin.SHA256 != "" { - fmt.Printf("SHA256: %s\n", bin.SHA256) - } - if bin.B3SUM != "" { - fmt.Printf("B3SUM: %s\n", bin.B3SUM) - } - // Fetch the description from RMetadataURL response, err = http.Get(RMetadataURL) if err != nil { - fmt.Printf("Error fetching description from %s: %v\n", RMetadataURL, err) - return + return nil, fmt.Errorf("error fetching description from %s: %v", RMetadataURL, err) } defer response.Body.Close() body, err = ioutil.ReadAll(response.Body) if err != nil { - fmt.Printf("Error reading description from %s: %v\n", RMetadataURL, err) - return + return nil, fmt.Errorf("error reading description from %s: %v", RMetadataURL, err) } - // Unmarshal the description as a BinaryMetadata object - var binaryMetadata BinaryMetadata - if err := json.Unmarshal(body, &binaryMetadata); err != nil { - fmt.Printf("Error decoding description from %s: %v\n", RMetadataURL, err) - return + // Unmarshal the description as a map with a slice of BinaryInfo objects + var descriptionMetadata map[string][]BinaryInfo + if err := json.Unmarshal(body, &descriptionMetadata); err != nil { + return nil, fmt.Errorf("error decoding description from %s: %v", RMetadataURL, err) } - // Find the binary in the metadata and set the description - for _, binInfo := range binaryMetadata.Binaries { + // TODO: Once Github repos are supported, optionally detect METADATA.json files in the repos. + binaries, exists := descriptionMetadata["packages"] + if !exists { + return nil, fmt.Errorf("invalid description metadata format. No 'packages' field found.") + } + + // Find the binary with the matching name and set its description + for _, binInfo := range binaries { if binInfo.Name == binaryName { bin.Description = binInfo.Description break } } - if bin.Description != "" { - fmt.Printf("Description: %s\n", bin.Description) - } - return + return &bin, nil } } } - fmt.Printf("Info for the requested binary ('%s') not found in the metadata.json files. Please contribute to the repositories.\n", binaryName) + return nil, fmt.Errorf("info for the requested binary ('%s') not found in the metadata.json files", binaryName) } diff --git a/install.go b/install.go index c632ba3..6f5ae69 100644 --- a/install.go +++ b/install.go @@ -1,5 +1,4 @@ // install.go // This file implements the install functionality // - package main import ( @@ -8,19 +7,22 @@ import ( "path/filepath" ) -func installCommand(binaryName string, args []string) { +func installCommand(binaryName string, args []string) error { installDir := os.Getenv("INSTALL_DIR") if len(args) > 0 && args[0] != "" { installDir = args[0] } if installDir == "" { - installDir = filepath.Join(os.Getenv("HOME"), ".local", "bin") + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("Error: Failed to get user's Home directory: %w", err) + } + installDir = filepath.Join(homeDir, ".local", "bin") } if err := os.MkdirAll(installDir, os.ModePerm); err != nil { - fmt.Printf("Error creating installation directory: %v\n", err) - os.Exit(1) + return fmt.Errorf("Error: Could not create installation directory: %v", err) } installPath := filepath.Join(installDir, binaryName) @@ -32,23 +34,20 @@ func installCommand(binaryName string, args []string) { fmt.Printf("Using cached file: %s\n", cachedFile) // Copy the cached file to the install path if err := copyFile(cachedFile, installPath); err != nil { - fmt.Printf("Error copying cached file: %v\n", err) - os.Exit(1) + return fmt.Errorf("Error: Could not copy cached file: %v", err) } } else { // If the cached file does not exist, download the binary url, err := findURL(binaryName) if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) + return fmt.Errorf("Error: %v", err) } if err := fetchBinaryFromURL(url, installPath); err != nil { - fmt.Printf("Error installing binary: %v\n", err) - os.Exit(1) + return fmt.Errorf("Error: Could not install binary: %v", err) } } // Use the escape sequence for newline directly - fmt.Printf("Installation complete: %s\n", installPath) - fmt.Println() // Adding a newline for proper PS1 behavior + fmt.Printf("Installation complete: %s \n", installPath) + return nil } diff --git a/listBinaries.go b/listBinaries.go index 5fea5c9..157c477 100644 --- a/listBinaries.go +++ b/listBinaries.go @@ -1,4 +1,4 @@ -// listBinaries.go // This file implements the listBinaries function // +// listBinaries.go // This file implements the listBinaries function //> package main @@ -7,14 +7,13 @@ import ( "fmt" "io/ioutil" "net/http" - "os" "path/filepath" "sort" "strings" ) // listBinariesCommand fetches and lists binary names from the given URL. -func listBinaries() { +func listBinaries() ([]string, error) { var allBinaries []string // Fetch binaries from each metadata URL @@ -22,21 +21,18 @@ func listBinaries() { // Fetch metadata from the given URL resp, err := http.Get(url) if err != nil { - fmt.Printf("Error fetching metadata from %s: %v\n", url, err) - os.Exit(1) + return nil, fmt.Errorf("error fetching metadata from %s: %v", url, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - fmt.Printf("Failed to fetch metadata from %s. HTTP status code: %d\n", url, resp.StatusCode) - os.Exit(1) + return nil, fmt.Errorf("failed to fetch metadata from %s. HTTP status code: %d", url, resp.StatusCode) } // Read response body body, err := ioutil.ReadAll(resp.Body) if err != nil { - fmt.Printf("Failed to read response body: %v\n", err) - os.Exit(1) + return nil, fmt.Errorf("failed to read response body: %v", err) } // Unmarshal JSON @@ -45,8 +41,7 @@ func listBinaries() { NameAlt string `json:"Name"` // Consider both "name" and "Name" fields } if err := json.Unmarshal(body, &metadata); err != nil { - fmt.Printf("Failed to unmarshal metadata JSON from %s: %v\n", url, err) - os.Exit(1) + return nil, fmt.Errorf("failed to unmarshal metadata JSON from %s: %v", url, err) } // Extract binary names @@ -96,6 +91,6 @@ func listBinaries() { // Sort binaries alphabetically sort.Strings(uniqueBinaries) - // Print binaries - fmt.Println(strings.Join(uniqueBinaries, "\n")) + // Return the list of binaries + return uniqueBinaries, nil } diff --git a/main.go b/main.go index 45b2ccb..c539bae 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,4 @@ -// main.go // This is the main entrypoint, which calls all the different functions // - +// main.go // This is the main entrypoint, which calls all the different functions //> package main import ( @@ -41,8 +40,8 @@ func init() { } } -const RMetadataURL = "https://raw.githubusercontent.com/metis-os/hysp-pkgs/main/data/metadata.json" -const VERSION = "1.1" +const RMetadataURL = "https://raw.githubusercontent.com/Azathothas/Toolpacks/main/metadata.json" +const VERSION = "1.2" ///// YOU MAY CHANGE THESE TO POINT TO ANOTHER PLACE. @@ -55,24 +54,29 @@ const ( ) func printHelp() { - fmt.Println("Usage: bigdl [-vh] {list|install|remove|run|info|search|tldr} [args...]") + fmt.Println("Usage: bigdl [-vh] {list|install|remove|update|run|info|fast_info|search|tldr} [args...]") fmt.Println("\nOptions:") - fmt.Println(" -h, --help Show this help message") - fmt.Println(" -v, --version Show the version number") + fmt.Println(" -h, --help Show this help message") + fmt.Println(" -v, --version Show the version number") fmt.Println("\nCommands:") - fmt.Println(" list List all available binaries") - fmt.Println(" install Install a binary") - fmt.Println(" remove Remove a binary") - fmt.Println(" run Run a binary") - fmt.Println(" info Show information about a specific binary") - fmt.Println(" search Search for a binary - (not all binaries have metadata. Use list to see all binaries)") - fmt.Println(" tldr Show a brief description & usage examples for a given program/command") + fmt.Println(" list List all available binaries") + fmt.Println(" install, add Install a binary") + fmt.Println(" remove, del Remove a binary") + fmt.Println(" update Update binaries, by checking their SHA against the repo's SHA.") + fmt.Println(" run Run a binary") + fmt.Println(" info Show information about a specific binary") + fmt.Println(" fast_info Show information about a specific binary - Using a single metadata file") + fmt.Println(" search Search for a binary - (not all binaries have metadata. Use list to see all binaries)") + fmt.Println(" tldr Show a brief description & usage examples for a given program/command") fmt.Println("\nExamples:") fmt.Println(" bigdl install micro") fmt.Println(" bigdl remove bed") fmt.Println(" bigdl info jq") - fmt.Println(" bigdl search fzf") + fmt.Println(" bigdl search editor") fmt.Println(" bigdl tldr gum") + fmt.Println(" bigdl run --verbose neofetch") + fmt.Println(" bigdl run --silent micro") + fmt.Println(" bigdl run btop") fmt.Println("\nVersion:", VERSION) } @@ -91,7 +95,7 @@ func main() { // If no arguments are received, show the usage text if len(os.Args) < 2 { - fmt.Println("Usage: bigdl [-vh] {list|install|remove|run|info|search|tldr} [args...]") + fmt.Println("Usage: bigdl [-vh] {list|install|remove|update|run|info|fast_info|search|tldr} [args...]") os.Exit(1) } @@ -103,10 +107,17 @@ func main() { } findURLCommand(os.Args[2]) case "list": - listBinaries() + binaries, err := listBinaries() + if err != nil { + fmt.Println("Error listing binaries:", err) + os.Exit(1) + } + for _, binary := range binaries { + fmt.Println(binary) + } case "install", "add": if len(os.Args) < 3 { - fmt.Println("Usage: bigdl install [install_dir] [install_message]") + fmt.Printf("Usage: bigdl %s [install_dir] [install_message]\n", os.Args[1]) os.Exit(1) } binaryName := os.Args[2] @@ -117,17 +128,20 @@ func main() { if len(os.Args) > 4 { installMessage = os.Args[4] } - installCommand(binaryName, []string{installDir, installMessage}) + err := installCommand(binaryName, []string{installDir, installMessage}) + if err != nil { + fmt.Printf("%s\n", err.Error()) + os.Exit(1) + } case "remove", "del": if len(os.Args) != 3 { - fmt.Println("Usage: bigdl remove ") + fmt.Printf("Usage: bigdl %s \n", os.Args[1]) os.Exit(1) } - binaryToRemove := os.Args[2] - remove(binaryToRemove) + remove(os.Args[2]) case "run": if len(os.Args) < 3 { - fmt.Println("Usage: bigdl run [--verbose] [args...]") + fmt.Println("Usage: bigdl run [--verbose, --silent] [args...]") os.Exit(1) } RunFromCache(os.Args[2], os.Args[3:]) @@ -143,7 +157,41 @@ func main() { os.Exit(1) } binaryName := os.Args[2] - showBinaryInfo(binaryName) + binaryInfo, err := getBinaryInfo(binaryName) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Print the fields + if binaryInfo.Name != "" { + fmt.Printf("Name: %s\n", binaryInfo.Name) + } + if binaryInfo.Description != "" { + fmt.Printf("Description: %s\n", binaryInfo.Description) + } + if binaryInfo.Repo != "" { + fmt.Printf("Repo: %s\n", binaryInfo.Repo) + } + if binaryInfo.Size != "" { + fmt.Printf("Size: %s\n", binaryInfo.Size) + } + if binaryInfo.SHA256 != "" { + fmt.Printf("SHA256: %s\n", binaryInfo.SHA256) + } + if binaryInfo.B3SUM != "" { + fmt.Printf("B3SUM: %s\n", binaryInfo.B3SUM) + } + if binaryInfo.Source != "" { + fmt.Printf("Source: %s\n", binaryInfo.Source) + } + case "fast_info": + if len(os.Args) != 3 { + fmt.Println("Usage: bigdl fast_info ") + os.Exit(1) + } + binaryName := os.Args[2] + fast_showBinaryInfo(binaryName, validatedArch[1]) case "search": if len(os.Args) != 3 { fmt.Println("Usage: bigdl search ") @@ -151,6 +199,8 @@ func main() { } searchTerm := os.Args[2] fSearch(searchTerm, validatedArch[1]) + case "update": + update() default: fmt.Printf("bigdl: Unknown command: %s\n", os.Args[1]) os.Exit(1) diff --git a/remove.go b/remove.go index a883c91..15c9598 100644 --- a/remove.go +++ b/remove.go @@ -1,3 +1,4 @@ +// remove.go // This file implements the functionality of "remove" //> package main import ( diff --git a/run.go b/run.go index 99229c6..1af4c36 100644 --- a/run.go +++ b/run.go @@ -1,5 +1,4 @@ -// run.go // This file implements functions related to the Run options // - +// run.go // This file implements the "run" functionality //> package main import ( @@ -13,6 +12,7 @@ import ( ) var verboseMode bool +var silentMode bool // ReturnCachedFile retrieves the cached file location. // Returns an empty string and error code 1 if not found. @@ -44,16 +44,35 @@ func RunFromCache(binaryName string, args []string) { } } + // Check for silent mode flag + if binaryName == "--silent" { + // In this case, we should set binaryName to the next argument if available + if len(args) > 0 { + binaryName = args[0] + args = args[1:] // Remove the flag from the arguments + silentMode = true + } else { + fmt.Println("Error: Binary name not provided after --silent flag.") + os.Exit(1) + } + } + cachedFile := filepath.Join(TEMP_DIR, binaryName+".bin") if fileExists(cachedFile) && isExecutable(cachedFile) { - fmt.Printf("Running '%s' from cache...\n", binaryName) + if !silentMode { // Check if not in silent mode before printing + fmt.Printf("Running '%s' from cache...\n", binaryName) + } runBinary(cachedFile, args, verboseMode) cleanCache() } else { - fmt.Printf("Error: cached binary for '%s' not found. Fetching a new one...\n", binaryName) + if !silentMode { // Check if not in silent mode before printing + fmt.Printf("Error: cached binary for '%s' not found. Fetching a new one...\n", binaryName) + } err := fetchBinary(binaryName) if err != nil { - fmt.Printf("Error fetching binary for '%s': %v\n", binaryName, err) + if !silentMode { // Check if not in silent mode before printing + fmt.Printf("Error fetching binary for '%s': %v\n", binaryName, err) + } os.Exit(1) } cleanCache() @@ -163,7 +182,9 @@ func cleanCache() { for i := 0; i < BinariesToDelete; i++ { err := os.Remove(filepath.Join(TEMP_DIR, filesWithAtime[i].info.Name())) if err != nil { - fmt.Printf("Error removing file: %v\n", err) + if !silentMode { // Check if not in silent mode before printing + fmt.Printf("Error removing file: %v\n", err) + } } } } diff --git a/spin.go b/spin.go index fef5019..9614730 100644 --- a/spin.go +++ b/spin.go @@ -1,5 +1,4 @@ -// spin.go // Implements the spin function which fetch_binary uses to report status. - +// spin.go // Implements the spin function which fetch_binary uses to report status. //> package main import ( diff --git a/update.go b/update.go new file mode 100644 index 0000000..c93cb62 --- /dev/null +++ b/update.go @@ -0,0 +1,125 @@ +// update.go // This file holds the implementation for the "update" functionality //> +package main + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "os" + "path/filepath" +) + +// update checks for updates to the valid programs and installs any that have changed. +func update() error { + validPrograms, err := listBinaries() + if err != nil { + return fmt.Errorf("failed to list binaries: %w", err) + } + + installDir := os.Getenv("INSTALL_DIR") + if installDir == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get user home directory: %w", err) + } + installDir = filepath.Join(homeDir, ".local", "bin") + } + + info, err := os.Stat(installDir) + if err != nil || !info.IsDir() { + return fmt.Errorf("installation directory %s is not a directory", installDir) + } + + files, err := os.ReadDir(installDir) + if err != nil { + return fmt.Errorf("failed to read directory %s: %w", installDir, err) + } + + // Initialize counters + var skipped, updated, checked int + + for _, file := range files { + if file.IsDir() { + continue + } + binaryName := file.Name() + if contains(validPrograms, binaryName) { + checked++ // Increment the checked counter for every processed binary + + localSHA256, err := getLocalSHA256(filepath.Join(installDir, binaryName)) + if err != nil { + fmt.Printf("\033[2K\rWarning: Failed to get SHA256 for %s. Skipping.", binaryName) + skipped++ + continue + } + + binaryInfo, err := getBinaryInfo(binaryName) + if err != nil { + fmt.Printf("\033[2K\rWarning: Failed to get metadata for %s. Skipping.", binaryName) + skipped++ + continue + } + + // Skip if the SHA field is null + if binaryInfo.SHA256 == "" { + fmt.Printf("\033[2K\rSkipping %s because the SHA256 field is null.", binaryName) + skipped++ + continue + } + + if checkDifferences(localSHA256, binaryInfo.SHA256) == 1 { + fmt.Printf("\033[2K\rDetected a difference in %s. Updating...", binaryName) + installMessage := fmt.Sprintf("Updating %s to version %s", binaryName, binaryInfo.SHA256) + err := installCommand(binaryName, []string{installDir, installMessage}) + if err != nil { + fmt.Printf("\033[2K\rError: Failed to update %s: %v", binaryName, err) + continue + } + fmt.Printf("\033[2K\rSuccessfully updated %s.", binaryName) + updated++ + } else { + fmt.Printf("\033[2K\rNo updates available for %s.", binaryName) + } + } + } + // Print final counts + fmt.Printf("\033[2K\rSkipped: %d\tUpdated: %d\tChecked: %d\n", skipped, updated, checked) + + return nil +} + +func contains(slice []string, str string) bool { + for _, v := range slice { + if v == str { + return true + } + } + return false +} + +// getLocalSHA256 calculates the SHA256 checksum of the local file. +func getLocalSHA256(filePath string) (string, error) { + // Open the file for reading + file, err := os.Open(filePath) + if err != nil { + return "", fmt.Errorf("failed to open file: %v", err) + } + defer file.Close() + + // Calculate SHA256 checksum + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + return "", fmt.Errorf("failed to calculate SHA256: %v", err) + } + sha256Checksum := hex.EncodeToString(hasher.Sum(nil)) + + return sha256Checksum, nil +} + +func checkDifferences(localSHA256, remoteSHA256 string) int { + if localSHA256 != remoteSHA256 { + return 1 + } + return 0 +}