added SPM
This commit is contained in:
parent
aea202a94d
commit
646851f9f8
7 changed files with 723 additions and 16 deletions
108
spm/appindex.go
Normal file
108
spm/appindex.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package spm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const appIndexURL = "https://downloads.sourceforge.net/project/spitfire-browser/APPINDEX"
|
||||
|
||||
func DownloadAppIndex(dest string) error {
|
||||
UpdateProgress(0, "Downloading APPINDEX")
|
||||
resp, err := http.Get(appIndexURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
out, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
totalSize := resp.ContentLength
|
||||
var downloaded int64
|
||||
|
||||
// Track progress as bytes are downloaded
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := resp.Body.Read(buf)
|
||||
if n > 0 {
|
||||
downloaded += int64(n)
|
||||
percentage := int(float64(downloaded) / float64(totalSize) * 100)
|
||||
UpdateProgress(percentage, "Downloading APPINDEX")
|
||||
if _, err := out.Write(buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
UpdateProgress(100, "APPINDEX downloaded")
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppIndexEntry struct {
|
||||
Name string
|
||||
Version string
|
||||
Release string
|
||||
Arch string
|
||||
OS string
|
||||
DownloadURL string
|
||||
}
|
||||
|
||||
func ParseAppIndex(filePath string) ([]AppIndexEntry, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var entries []AppIndexEntry
|
||||
scanner := bufio.NewScanner(file)
|
||||
entry := AppIndexEntry{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "C:") {
|
||||
if entry.Name != "" {
|
||||
entries = append(entries, entry)
|
||||
entry = AppIndexEntry{}
|
||||
}
|
||||
}
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "P":
|
||||
entry.Name = parts[1]
|
||||
case "R":
|
||||
entry.Release = parts[1]
|
||||
case "V":
|
||||
entry.Version = parts[1]
|
||||
case "A":
|
||||
entry.Arch = parts[1]
|
||||
case "p":
|
||||
entry.OS = parts[1]
|
||||
case "d":
|
||||
entry.DownloadURL = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Name != "" {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries, scanner.Err()
|
||||
}
|
91
spm/download.go
Normal file
91
spm/download.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package spm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Package struct {
|
||||
Name string
|
||||
Arch string
|
||||
OS string
|
||||
DownloadURL string
|
||||
}
|
||||
|
||||
func DownloadPackage(pkg Package, destDir string) error {
|
||||
UpdateProgress(0, fmt.Sprintf("Downloading %s", pkg.Name))
|
||||
resp, err := http.Get(pkg.DownloadURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
filePath := filepath.Join(destDir, filepath.Base(pkg.DownloadURL))
|
||||
out, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
totalSize := resp.ContentLength
|
||||
var downloaded int64
|
||||
|
||||
// Track progress as bytes are downloaded
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := resp.Body.Read(buf)
|
||||
if n > 0 {
|
||||
downloaded += int64(n)
|
||||
percentage := int(float64(downloaded) / float64(totalSize) * 100)
|
||||
UpdateProgress(percentage, fmt.Sprintf("Downloading %s", pkg.Name))
|
||||
if _, err := out.Write(buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
UpdateProgress(100, fmt.Sprintf("%s downloaded", pkg.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindPackage(entries []AppIndexEntry, name, release, arch, os string) (*AppIndexEntry, error) {
|
||||
for _, entry := range entries {
|
||||
if entry.Name == name && entry.Release == release && entry.Arch == arch && entry.OS == os {
|
||||
return &entry, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("package not found")
|
||||
}
|
||||
|
||||
func DownloadPackageFromAppIndex(appIndexPath, packageName, release, destDir string) error {
|
||||
arch := runtime.GOARCH
|
||||
osName := runtime.GOOS
|
||||
|
||||
entries, err := ParseAppIndex(appIndexPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry, err := FindPackage(entries, packageName, release, arch, osName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return DownloadPackage(Package{
|
||||
Name: entry.Name,
|
||||
Arch: entry.Arch,
|
||||
OS: entry.OS,
|
||||
DownloadURL: entry.DownloadURL,
|
||||
}, destDir)
|
||||
}
|
161
spm/install.go
Normal file
161
spm/install.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
package spm
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func DecompressToTemp(filePath string) (string, error) {
|
||||
UpdateProgress(0, "Decompressing package")
|
||||
tempDir, err := os.MkdirTemp("", "spm_decompress")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
tarReader := tar.NewReader(gzr)
|
||||
var totalFiles, processedFiles int
|
||||
// Count total files
|
||||
for {
|
||||
_, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
totalFiles++
|
||||
}
|
||||
|
||||
file.Seek(0, io.SeekStart)
|
||||
gzr.Reset(file)
|
||||
tarReader = tar.NewReader(gzr)
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
targetPath := filepath.Join(tempDir, header.Name)
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
outFile, err := os.Create(targetPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
return "", err
|
||||
}
|
||||
outFile.Close()
|
||||
}
|
||||
|
||||
processedFiles++
|
||||
UpdateProgress(int(float64(processedFiles)/float64(totalFiles)*100), "Decompressing package")
|
||||
}
|
||||
|
||||
UpdateProgress(100, "Package decompressed")
|
||||
return tempDir, nil
|
||||
}
|
||||
|
||||
func MoveFilesToInstallDir(tempDir, installDir string) error {
|
||||
// Count total files to copy
|
||||
var totalFiles, copiedFiles int
|
||||
err := filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
totalFiles++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy files and track progress
|
||||
err = filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(tempDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetPath := filepath.Join(installDir, relPath)
|
||||
if info.IsDir() {
|
||||
// Create directories in the install directory
|
||||
if err := os.MkdirAll(targetPath, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Copy files to the install directory
|
||||
if err := copyFile(path, targetPath); err != nil {
|
||||
return err
|
||||
}
|
||||
copiedFiles++
|
||||
UpdateProgress(int(float64(copiedFiles)/float64(totalFiles)*100), "Copying files to install directory")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean up temporary directory
|
||||
UpdateProgress(100, "Cleaning up temporary files")
|
||||
return os.RemoveAll(tempDir)
|
||||
}
|
||||
|
||||
// copyFile copies the contents of the source file to the destination file.
|
||||
func copyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
// Create the destination file
|
||||
destinationFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destinationFile.Close()
|
||||
|
||||
// Copy the file content
|
||||
if _, err := io.Copy(destinationFile, sourceFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Preserve file permissions
|
||||
info, err := sourceFile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(dst, info.Mode())
|
||||
}
|
30
spm/progress.go
Normal file
30
spm/progress.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package spm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Progress struct {
|
||||
mu sync.Mutex
|
||||
percentage int
|
||||
task string
|
||||
}
|
||||
|
||||
var progress = Progress{}
|
||||
|
||||
// UpdateProgress sets the current percentage and task.
|
||||
func UpdateProgress(percentage int, task string) {
|
||||
progress.mu.Lock()
|
||||
defer progress.mu.Unlock()
|
||||
progress.percentage = percentage
|
||||
progress.task = task
|
||||
fmt.Printf("\r[%3d%%] %s", percentage, task) // Print progress to the terminal
|
||||
}
|
||||
|
||||
// GetProgress returns the current progress state.
|
||||
func GetProgress() (int, string) {
|
||||
progress.mu.Lock()
|
||||
defer progress.mu.Unlock()
|
||||
return progress.percentage, progress.task
|
||||
}
|
20
spm/utils.go
Normal file
20
spm/utils.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package spm
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetTempDownloadDir() string {
|
||||
dir, err := os.MkdirTemp("", "spm_downloads")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func SetDownloadFolder(customDir string) (string, error) {
|
||||
if err := os.MkdirAll(customDir, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return customDir, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue