package spitfire import ( "crypto/sha1" "fmt" "io" "log" "os" "strings" "time" ) // Package the APPINDEX update process func PackageAPPINDEX( name, // e.g. "spitfire-browser" release, // e.g. "nightly" version, // e.g. "2024.12.21" arch, // e.g. "amd64" size, // e.g. "838205457" (compressed size) installedSize, // e.g. "3595495684" (uncompressed size) description, // e.g. "Spitfire build" url, // e.g. "https://spitfirebrowser.xyz/" license, // e.g. "AGPL-3.0" origin, // e.g. "browser" maintainer, // e.g. "Internet Addict" dependencies, // usually blank ..." platform, // e.g. "linux" remoteDir string, // e.g. "nightly/linux/amd64" ) error { // Construct a filename that matches what you compress & upload // Adjust this naming convention to match your compress step exactly! // Example: "spitfire-browser-nightly-amd64-linux.tar.gz" fileName := fmt.Sprintf("%s-%s-%s-%s.tar.gz", origin, arch, release, platform) // If you have a real file on disk, you might call `calcChecksum(fileName)`. // For demo, we call `calcChecksum` with a mock package name. checksum := calcChecksum(fileName) // Remove existing entry based on P, R, A, o, and p: removeExistingEntry(name, release, arch, origin, platform) // Use current Unix timestamp timestamp := time.Now().Unix() // Open or create the APPINDEX file for appending file, err := os.OpenFile("./APPINDEX", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatalf("Failed to open APPINDEX file: %v", err) } defer file.Close() // Build the SourceForge-based download URL: // https://downloads.sourceforge.net/project/spitfire-browser// downloadURL := fmt.Sprintf("https://downloads.sourceforge.net/project/spitfire-browser/%s/%s", remoteDir, fileName) // Format the entry. entry := fmt.Sprintf(` C:%s P:%s R:%s V:%s A:%s S:%s I:%s T:%s U:%s L:%s o:%s m:%s t:%d D:%s p:%s q: d:%s I:https://weforge.xyz/Spitfire/Branding/raw/branch/main/active/browser/icon.svg S:https://spitfirebrowser.xyz/static/images/screenshots/1.png T:browser,experimental,testing r: Automated Nightly build of Spitfire c:%s `, checksum, name, release, version, arch, size, installedSize, description, url, license, origin, maintainer, timestamp, dependencies, platform, downloadURL, checksum) // Trim leading newline to keep it clean entry = strings.TrimPrefix(entry, "\n") if _, err := file.WriteString(entry + "\n"); err != nil { log.Fatalf("Failed to write to APPINDEX file: %v", err) } fmt.Println("APPINDEX has been updated successfully.") return nil } // calcChecksum calculates a checksum (SHA-1) for a given input string func calcChecksum(input string) string { h := sha1.New() _, _ = io.WriteString(h, input) return fmt.Sprintf("%x", h.Sum(nil)) } // removeExistingEntry removes an existing entry from APPINDEX if it matches P, R, A, o, and p fields. func removeExistingEntry(name, release, arch, origin, platform string) { // Read file contents content, err := os.ReadFile("./APPINDEX") if err != nil { if os.IsNotExist(err) { return // If file does not exist, no need to remove anything } log.Fatalf("Failed to read APPINDEX: %v", err) } lines := strings.Split(string(content), "\n") var newLines []string var currentEntry []string inEntry := false // true when we're reading lines for a single entry // We'll store the P, R, A, o, p values within the current entry var pVal, rVal, aVal, oVal, plVal string for _, line := range lines { trimmed := strings.TrimSpace(line) // Detect the start of an entry (a line that starts with "C:") if strings.HasPrefix(trimmed, "C:") { // If we were in an entry previously, check if it should be removed or kept if inEntry && len(currentEntry) > 0 { // Decide whether to keep the previous entry if !(pVal == name && rVal == release && aVal == arch && oVal == origin && plVal == platform) { newLines = append(newLines, currentEntry...) newLines = append(newLines, "") // Blank line to separate entries } } // Start a new entry currentEntry = []string{trimmed} inEntry = true // Reset these values for the new entry pVal, rVal, aVal, oVal, plVal = "", "", "", "", "" continue } if inEntry { // Collect lines for this entry currentEntry = append(currentEntry, trimmed) // Extract fields for matching later switch { case strings.HasPrefix(trimmed, "P:"): pVal = strings.TrimPrefix(trimmed, "P:") case strings.HasPrefix(trimmed, "R:"): rVal = strings.TrimPrefix(trimmed, "R:") case strings.HasPrefix(trimmed, "A:"): aVal = strings.TrimPrefix(trimmed, "A:") case strings.HasPrefix(trimmed, "o:"): oVal = strings.TrimPrefix(trimmed, "o:") case strings.HasPrefix(trimmed, "p:"): plVal = strings.TrimPrefix(trimmed, "p:") } } else { // Lines outside of entries just get appended directly // (e.g. if there's extraneous text before the first "C:" or after the last) if trimmed != "" { newLines = append(newLines, trimmed) } } } // Handle the last entry if we ended inEntry if inEntry && len(currentEntry) > 0 { // Decide whether to keep the final entry if !(pVal == name && rVal == release && aVal == arch && oVal == origin && plVal == platform) { newLines = append(newLines, currentEntry...) } } // Join everything back and ensure at least one trailing newline finalContent := strings.Join(newLines, "\n") if !strings.HasSuffix(finalContent, "\n") { finalContent += "\n" } err = os.WriteFile("./APPINDEX", []byte(finalContent), 0644) if err != nil { log.Fatalf("Failed to update APPINDEX: %v", err) } } // CleanAppIndex cleans up any orphaned "C:" entries and collapses excessive newlines func CleanAppIndex() error { // Read file contents content, err := os.ReadFile("./APPINDEX") if err != nil { return fmt.Errorf("failed to read APPINDEX: %v", err) } // Split the file content into lines lines := strings.Split(string(content), "\n") var newLines []string var currentEntry []string inEntry := false for _, line := range lines { line = strings.TrimSpace(line) // Start of an entry when we encounter a checksum if strings.HasPrefix(line, "C:") { // If we already have a valid entry, add it to newLines if inEntry && len(currentEntry) > 1 { newLines = append(newLines, currentEntry...) } currentEntry = []string{line} inEntry = true } else if inEntry && line == "" { // End of an entry if len(currentEntry) > 1 { newLines = append(newLines, currentEntry...) newLines = append(newLines, "") // Add a blank line to separate entries } currentEntry = nil inEntry = false } else if inEntry { // Continue adding lines to the current entry currentEntry = append(currentEntry, line) } else if line != "" { // Add non-entry lines (for extra safety) newLines = append(newLines, line) } } // In case the last entry was valid if inEntry && len(currentEntry) > 1 { newLines = append(newLines, currentEntry...) } // Collapse consecutive blank lines cleanedContent := strings.Join(newLines, "\n") cleanedContent = strings.ReplaceAll(cleanedContent, "\n\n\n", "\n\n") // Write the cleaned content back to the file err = os.WriteFile("./APPINDEX", []byte(cleanedContent), 0644) if err != nil { return fmt.Errorf("failed to write cleaned APPINDEX: %v", err) } fmt.Println("APPINDEX cleaned successfully.") return nil }