Add update_appindex.go
This commit is contained in:
parent
866d9856dc
commit
725f467d13
1 changed files with 362 additions and 0 deletions
362
update_appindex.go
Normal file
362
update_appindex.go
Normal file
|
@ -0,0 +1,362 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PackageAPPINDEX updates the local APPINDEX file by removing any old
|
||||
// entry matching (P, R, A, o, p) and appending a new entry.
|
||||
//
|
||||
// It includes:
|
||||
// - Compressed/uncompressed file measurement (size + checksums).
|
||||
// - A user-defined remotePath for the final download URL.
|
||||
// - Additional flags for icon, screenshots, tags, and notes.
|
||||
func PackageAPPINDEX(
|
||||
name string, // P: Package name, e.g., "spitfire-browser"
|
||||
release string, // R: Release type, e.g., "nightly"
|
||||
version string, // V: Version, e.g., "2025.02.07"
|
||||
arch string, // A: Architecture, e.g., "amd64"
|
||||
description string, // X: Short description, e.g., "Short summary"
|
||||
url string, // U: Project URL, e.g., "https://spitfirebrowser.xyz/"
|
||||
license string, // L: License, e.g., "AGPL-3.0"
|
||||
origin string, // o: Origin, e.g., "browser"
|
||||
maintainer string, // m: Maintainer, e.g., "Internet Addict"
|
||||
dependencies string, // D: Dependencies, e.g., "default-theme, browser"
|
||||
platform string, // p: Platform, e.g., "linux"
|
||||
remotePath string, // d: Remote file path, e.g., "browser/amd64/nightly/2025.02.07/browser-amd64-nightly-windows.tar.gz"
|
||||
icon string, // I: Icon URL, e.g., "https://weforge.xyz/Spitfire/Branding/raw/branch/main/active/browser/icon.svg"
|
||||
screenshots string, // S: Screenshots URL, e.g., "https://spitfirebrowser.xyz/static/images/screenshots/1.png"
|
||||
tags string, // T: Tags, e.g., "browser,experimental,testing"
|
||||
notes string, // r: Notes, e.g., "Automated build of Spitfire"
|
||||
compressedFile string, // Path to the compressed file, used for size & checksum
|
||||
uncompressedFile string, // Path to the uncompressed file, used for size & checksum
|
||||
) error {
|
||||
//----------------------------------------------------------------------
|
||||
// 1) Measure compressed file
|
||||
//----------------------------------------------------------------------
|
||||
compSizeBytes := measureFileSize(compressedFile)
|
||||
compSize := fmt.Sprintf("%d", compSizeBytes)
|
||||
compChecksum := calcFileChecksum(compressedFile)
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 2) Measure uncompressed file
|
||||
//----------------------------------------------------------------------
|
||||
uncompSizeBytes := measureFileSize(uncompressedFile)
|
||||
uncompSize := fmt.Sprintf("%d", uncompSizeBytes)
|
||||
uncompChecksum := calcFileChecksum(uncompressedFile)
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 3) Remove old entry from APPINDEX (based on P,R,A,o,p).
|
||||
//----------------------------------------------------------------------
|
||||
removeExistingEntry(name, release, arch, origin, platform)
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 4) Check for existence of APPINDEX
|
||||
//----------------------------------------------------------------------
|
||||
appIndexPath := "./APPINDEX"
|
||||
if _, err := os.Stat(appIndexPath); os.IsNotExist(err) {
|
||||
fmt.Println("APPINDEX does not exist. Creating a new one...")
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 5) Open (or create) the APPINDEX file for appending.
|
||||
//----------------------------------------------------------------------
|
||||
file, err := os.OpenFile(appIndexPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open APPINDEX file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 6) Build the final download URL from `remotePath`
|
||||
//----------------------------------------------------------------------
|
||||
downloadURL := fmt.Sprintf("https://downloads.sourceforge.net/project/spitfire-browser/%s", remotePath)
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 7) Current Unix timestamp for t:
|
||||
//----------------------------------------------------------------------
|
||||
timestamp := time.Now().Unix()
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 8) Format final entry
|
||||
//
|
||||
// We'll store:
|
||||
// C:%s => compressed checksum
|
||||
// S:%s => compressed size
|
||||
// I:%s => uncompressed size
|
||||
// c:%s => uncompressed checksum
|
||||
// X:%s => short text/description (custom field, formerly "T:")
|
||||
//----------------------------------------------------------------------
|
||||
entry := fmt.Sprintf(`
|
||||
C:%s
|
||||
P:%s
|
||||
R:%s
|
||||
V:%s
|
||||
A:%s
|
||||
S:%s
|
||||
I:%s
|
||||
X:%s
|
||||
U:%s
|
||||
L:%s
|
||||
o:%s
|
||||
m:%s
|
||||
t:%d
|
||||
D:%s
|
||||
p:%s
|
||||
q:
|
||||
d:%s
|
||||
I:%s
|
||||
S:%s
|
||||
T:%s
|
||||
r:%s
|
||||
c:%s
|
||||
`,
|
||||
compChecksum, // C: compressed checksum
|
||||
name, // P:
|
||||
release, // R:
|
||||
version, // V:
|
||||
arch, // A:
|
||||
compSize, // S: compressed size
|
||||
uncompSize, // I: uncompressed size
|
||||
description, // X: short text description
|
||||
url, // U:
|
||||
license, // L:
|
||||
origin, // o:
|
||||
maintainer, // m:
|
||||
timestamp, // t:
|
||||
dependencies, // D:
|
||||
platform, // p:
|
||||
downloadURL, // d:
|
||||
icon, // I: icon URL
|
||||
screenshots, // S: screenshots URL
|
||||
tags, // T: tags
|
||||
notes, // r: notes
|
||||
uncompChecksum, // c: uncompressed checksum
|
||||
)
|
||||
|
||||
// Trim leading newline for neatness
|
||||
entry = strings.TrimPrefix(entry, "\n")
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 9) Write the new entry to APPINDEX
|
||||
//----------------------------------------------------------------------
|
||||
if _, err := file.WriteString(entry + "\n"); err != nil {
|
||||
return fmt.Errorf("failed to write to APPINDEX: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("APPINDEX has been updated successfully.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcFileChecksum calculates SHA-1 of an entire file. Returns "" if file not found or error.
|
||||
func calcFileChecksum(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha1.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// measureFileSize returns file size in bytes. Returns 0 if file not found or error.
|
||||
func measureFileSize(path string) int64 {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return info.Size()
|
||||
}
|
||||
|
||||
// removeExistingEntry removes an existing entry from APPINDEX if it matches
|
||||
// P (name) = "P:"
|
||||
// R (release) = "R:"
|
||||
// A (arch) = "A:"
|
||||
// o (origin) = "o:"
|
||||
// p (platform) = "p:"
|
||||
func removeExistingEntry(name, release, arch, origin, platform string) {
|
||||
content, err := os.ReadFile("./APPINDEX")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// No file to remove from
|
||||
return
|
||||
}
|
||||
log.Fatalf("Failed to read APPINDEX: %v", err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(content), "\n")
|
||||
var newLines []string
|
||||
var currentEntry []string
|
||||
inEntry := false
|
||||
|
||||
// We'll track P:, R:, A:, o:, p: fields for each entry
|
||||
var pVal, rVal, aVal, oVal, plVal string
|
||||
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
|
||||
// Each entry *starts* with "C:..."
|
||||
if strings.HasPrefix(trimmed, "C:") {
|
||||
// We've hit the start of a new entry. Decide whether
|
||||
// we keep the old one we were collecting.
|
||||
if inEntry && len(currentEntry) > 0 {
|
||||
if !(pVal == name && rVal == release && aVal == arch && oVal == origin && plVal == platform) {
|
||||
newLines = append(newLines, currentEntry...)
|
||||
newLines = append(newLines, "") // optional blank line
|
||||
}
|
||||
}
|
||||
// Start a new entry
|
||||
currentEntry = []string{trimmed}
|
||||
inEntry = true
|
||||
pVal, rVal, aVal, oVal, plVal = "", "", "", "", ""
|
||||
continue
|
||||
}
|
||||
|
||||
if inEntry {
|
||||
currentEntry = append(currentEntry, trimmed)
|
||||
// Check if it's one of the fields we track
|
||||
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 if trimmed != "" {
|
||||
// Lines outside an entry
|
||||
newLines = append(newLines, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
// After the loop, handle any last entry we collected
|
||||
if inEntry && len(currentEntry) > 0 {
|
||||
if !(pVal == name && rVal == release && aVal == arch && oVal == origin && plVal == platform) {
|
||||
newLines = append(newLines, currentEntry...)
|
||||
}
|
||||
}
|
||||
|
||||
finalContent := strings.Join(newLines, "\n")
|
||||
if !strings.HasSuffix(finalContent, "\n") {
|
||||
finalContent += "\n"
|
||||
}
|
||||
|
||||
if err := os.WriteFile("./APPINDEX", []byte(finalContent), 0644); err != nil {
|
||||
log.Fatalf("Failed to update APPINDEX: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
//----------------------------------------------------------------------
|
||||
// Default flags for basic fields
|
||||
//----------------------------------------------------------------------
|
||||
defaultRelease := "nightly"
|
||||
defaultVersion := time.Now().Format("2006.01.02") // e.g. "2025.02.07"
|
||||
defaultArch := runtime.GOARCH // e.g. "amd64"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Primary fields: P, R, V, A, X (desc), U, L, o, m, D, p
|
||||
//----------------------------------------------------------------------
|
||||
name := flag.String("name", "spitfire-browser", "P: Package name")
|
||||
release := flag.String("release", defaultRelease, "R: Release (nightly, stable, etc.)")
|
||||
version := flag.String("version", defaultVersion, "V: Version (date or semantic)")
|
||||
arch := flag.String("arch", defaultArch, "A: Architecture (default=GOARCH)")
|
||||
description := flag.String("description", "Spitfire build", "X: Short text description")
|
||||
url := flag.String("url", "https://spitfirebrowser.xyz/", "U: Project URL")
|
||||
license := flag.String("license", "AGPL-3.0", "L: License")
|
||||
origin := flag.String("origin", "browser", "o: Origin name")
|
||||
maintainer := flag.String("maintainer", "Internet Addict", "m: Maintainer name")
|
||||
dependencies := flag.String("dependencies", "", "D: Dependencies (comma-separated)")
|
||||
platform := flag.String("platform", "windows", "p: Platform (linux, windows, etc.)")
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Additional flags for icon, screenshots, tags, notes
|
||||
//----------------------------------------------------------------------
|
||||
icon := flag.String("icon",
|
||||
"https://weforge.xyz/Spitfire/Branding/raw/branch/main/active/browser/icon.svg",
|
||||
"I: Icon URL")
|
||||
screenshots := flag.String("screenshots",
|
||||
"https://spitfirebrowser.xyz/static/images/screenshots/1.png",
|
||||
"S: Screenshot(s) URL(s)")
|
||||
tags := flag.String("tags",
|
||||
"browser,experimental,testing",
|
||||
"T: Comma-separated tags")
|
||||
notes := flag.String("notes",
|
||||
"Automated build of Spitfire",
|
||||
"r: Additional notes")
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// remotePath is the path under:
|
||||
// https://downloads.sourceforge.net/project/spitfire-browser/<remotePath>
|
||||
// Example: "browser/amd64/nightly/2025.02.07/browser-amd64-nightly-windows.tar.gz"
|
||||
//----------------------------------------------------------------------
|
||||
remotePath := flag.String("remotePath", "",
|
||||
"Path under https://downloads.sourceforge.net/project/spitfire-browser/...")
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Compressed & uncompressed artifact paths
|
||||
//----------------------------------------------------------------------
|
||||
compressedFile := flag.String("compressedFile",
|
||||
"browser-amd64-nightly-windows.tar.gz",
|
||||
"Local path to compressed artifact (for size/checksum)")
|
||||
|
||||
uncompressedFile := flag.String("uncompressedFile",
|
||||
"browser-amd64-nightly-windows",
|
||||
"Local path to uncompressed artifact (for size/checksum)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// If user didn't specify remotePath, we build a naive default:
|
||||
// "browser/<arch>/<release>/<version>/<origin>-<arch>-<release>-<platform>.tar.gz"
|
||||
//----------------------------------------------------------------------
|
||||
if *remotePath == "" {
|
||||
*remotePath = fmt.Sprintf(
|
||||
"browser/%s/%s/%s/%s-%s-%s-%s.tar.gz",
|
||||
*arch, *release, *version, *origin, *arch, *release, *platform,
|
||||
)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Execute the logic
|
||||
//----------------------------------------------------------------------
|
||||
err := PackageAPPINDEX(
|
||||
*name,
|
||||
*release,
|
||||
*version,
|
||||
*arch,
|
||||
*description,
|
||||
*url,
|
||||
*license,
|
||||
*origin,
|
||||
*maintainer,
|
||||
*dependencies,
|
||||
*platform,
|
||||
*remotePath,
|
||||
*icon,
|
||||
*screenshots,
|
||||
*tags,
|
||||
*notes,
|
||||
*compressedFile,
|
||||
*uncompressedFile,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("APPINDEX update failed: %v", err)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue