2026-03-25 14:41:05 +03:00
|
|
|
package database
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-08-11 18:58:45 +03:00
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2026-03-25 14:41:05 +03:00
|
|
|
"net/http"
|
2026-08-11 18:58:45 +03:00
|
|
|
"os"
|
2026-03-25 14:41:05 +03:00
|
|
|
"path"
|
|
|
|
|
"path/filepath"
|
2026-08-11 18:58:45 +03:00
|
|
|
"slices"
|
2026-03-25 14:41:05 +03:00
|
|
|
"strings"
|
2026-08-11 18:58:45 +03:00
|
|
|
"sync"
|
|
|
|
|
|
2026-03-25 14:41:05 +03:00
|
|
|
"qulay/config"
|
2026-08-11 18:58:45 +03:00
|
|
|
"qulay/helpers"
|
2026-03-25 14:41:05 +03:00
|
|
|
|
2026-08-11 18:58:45 +03:00
|
|
|
"codeberg.org/UzbekLinux/uzbekdb-go"
|
2026-03-25 14:41:05 +03:00
|
|
|
"github.com/codeclysm/extract/v3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var _name string = filepath.Base(os.Args[0])
|
|
|
|
|
|
2026-08-11 18:58:45 +03:00
|
|
|
type Repo struct {
|
|
|
|
|
Name string
|
|
|
|
|
URL string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetRepos(destDir string) []Repo {
|
|
|
|
|
dir := filepath.Join(destDir, paths.ReposConfigDir)
|
|
|
|
|
|
|
|
|
|
entries, err := os.ReadDir(dir)
|
2026-03-25 14:41:05 +03:00
|
|
|
if err != nil {
|
2026-08-11 18:58:45 +03:00
|
|
|
return nil
|
2026-03-25 14:41:05 +03:00
|
|
|
}
|
|
|
|
|
|
2026-08-11 18:58:45 +03:00
|
|
|
var repos []Repo
|
|
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
|
if entry.IsDir() {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data, err := os.ReadFile(
|
|
|
|
|
filepath.Join(dir, entry.Name()),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-03-25 14:41:05 +03:00
|
|
|
|
2026-08-11 18:58:45 +03:00
|
|
|
m := uzbekdb.Loads(string(data), "map")
|
|
|
|
|
if m == nil {
|
2026-03-25 14:41:05 +03:00
|
|
|
continue
|
|
|
|
|
}
|
2026-08-11 18:58:45 +03:00
|
|
|
|
|
|
|
|
mp, ok := m.(map[interface{}][]interface{})
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repo := Repo{Name: entry.Name()}
|
|
|
|
|
|
|
|
|
|
if vals, ok := mp["url"]; ok && len(vals) > 0 {
|
|
|
|
|
repo.URL = fmt.Sprintf("%v", vals[0])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repos = append(repos, repo)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
slices.SortFunc(repos, func(a, b Repo) int {
|
|
|
|
|
return strings.Compare(a.Name, b.Name)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return repos
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func RepoURL(destDir, name string) string {
|
|
|
|
|
for _, repo := range GetRepos(destDir) {
|
|
|
|
|
if repo.Name == name {
|
|
|
|
|
return repo.URL
|
|
|
|
|
}
|
2026-03-25 14:41:05 +03:00
|
|
|
}
|
2026-08-11 18:58:45 +03:00
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DownloadRepos(destDir string) {
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
|
|
for _, repo := range GetRepos(destDir) {
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
|
|
|
|
|
go func(base, name string) {
|
2026-03-25 14:41:05 +03:00
|
|
|
defer wg.Done()
|
2026-08-11 18:58:45 +03:00
|
|
|
|
|
|
|
|
link := base
|
|
|
|
|
if !strings.HasSuffix(link, "/") {
|
|
|
|
|
link += "/"
|
|
|
|
|
}
|
|
|
|
|
link += name + ".tar.zst"
|
|
|
|
|
|
2026-03-25 14:41:05 +03:00
|
|
|
archiveName := path.Base(link)
|
|
|
|
|
destArchivePath := filepath.Join(destDir, "tmp", archiveName)
|
|
|
|
|
destPath := filepath.Join(destDir, paths.ReposDir)
|
2026-08-11 18:58:45 +03:00
|
|
|
fullDestPath := filepath.Join(destPath, name)
|
2026-03-25 14:41:05 +03:00
|
|
|
|
2026-08-11 18:58:45 +03:00
|
|
|
os.MkdirAll(filepath.Dir(destArchivePath), 0755)
|
|
|
|
|
defer os.Remove(destArchivePath)
|
2026-03-25 14:41:05 +03:00
|
|
|
|
2026-08-11 18:58:45 +03:00
|
|
|
client := helpers.HTTPClient()
|
2026-03-25 14:41:05 +03:00
|
|
|
|
|
|
|
|
resp, err := client.Get(link)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println("!! failed to download", link, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
2026-08-11 18:58:45 +03:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
|
fmt.Println("!! http error", resp.StatusCode, link)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-03-25 14:41:05 +03:00
|
|
|
|
|
|
|
|
f, err := os.Create(destArchivePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println("!! failed to create file", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
|
|
_, err = io.Copy(f, resp.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println("!! failed to copy", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println(":: downloaded", archiveName, "to", destArchivePath)
|
|
|
|
|
|
|
|
|
|
fmt.Println(":: removing old repo", fullDestPath)
|
2026-08-11 18:58:45 +03:00
|
|
|
err = os.RemoveAll(fullDestPath)
|
2026-03-25 14:41:05 +03:00
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println("w! failed to delete old repo", fullDestPath, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file, err := os.Open(destArchivePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println("!! failed to open archive file", destArchivePath, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-08-11 18:58:45 +03:00
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
err = extract.Zstd(context.TODO(), file, fullDestPath, nil)
|
2026-03-25 14:41:05 +03:00
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println("!! failed to extract", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-08-11 18:58:45 +03:00
|
|
|
}(repo.URL, repo.Name)
|
2026-03-25 14:41:05 +03:00
|
|
|
}
|
2026-08-11 18:58:45 +03:00
|
|
|
|
2026-03-25 14:41:05 +03:00
|
|
|
wg.Wait()
|
|
|
|
|
fmt.Println(":: repo updating ended")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ReadInstalled(destDir string) map[interface{}][]interface{} {
|
|
|
|
|
installedFullPath := filepath.Join(destDir, paths.InstalledFile)
|
|
|
|
|
file, err := os.OpenFile(installedFullPath, os.O_RDONLY, 0644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return map[interface{}][]interface{}{}
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
text, err := io.ReadAll(file)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return map[interface{}][]interface{}{}
|
|
|
|
|
}
|
|
|
|
|
data := uzbekdb.Loads(string(text), "map")
|
|
|
|
|
if data == nil {
|
2026-08-11 18:58:45 +03:00
|
|
|
return map[interface{}][]interface{}{}
|
2026-03-25 14:41:05 +03:00
|
|
|
}
|
|
|
|
|
return data.(map[interface{}][]interface{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WriteInstalled(data map[interface{}][]interface{}, destDir string) error {
|
|
|
|
|
installedFullPath := filepath.Join(destDir, paths.InstalledFile)
|
|
|
|
|
file, err := os.OpenFile(installedFullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
_, err = file.WriteString(uzbekdb.Dumps(data))
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func IsInstalled(name string, destDir string) bool {
|
|
|
|
|
data := ReadInstalled(destDir)
|
|
|
|
|
if !strings.Contains(name, "/") {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
_, ok := data[name]
|
|
|
|
|
return ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetPackageVersion(name string, destDir string) string {
|
|
|
|
|
data := ReadInstalled(destDir)
|
2026-08-11 18:58:45 +03:00
|
|
|
if !strings.Contains(name, "/") {
|
|
|
|
|
for k := range data {
|
|
|
|
|
if kStr, ok := k.(string); ok {
|
|
|
|
|
if strings.HasSuffix(kStr, "/"+name) {
|
|
|
|
|
name = kStr
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-25 14:41:05 +03:00
|
|
|
alo, _ := data[name]
|
|
|
|
|
if len(alo) > 0 {
|
2026-08-11 18:58:45 +03:00
|
|
|
return fmt.Sprintf("%v", alo[0])
|
2026-03-25 14:41:05 +03:00
|
|
|
}
|
2026-08-11 18:58:45 +03:00
|
|
|
return ""
|
2026-03-25 14:41:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func RegPackage(name string, version string, destDir string) error {
|
|
|
|
|
data := ReadInstalled(destDir)
|
2026-05-04 14:07:50 +03:00
|
|
|
val, installed := data[name]
|
2026-08-11 18:58:45 +03:00
|
|
|
|
2026-05-04 14:07:50 +03:00
|
|
|
if installed && len(val) > 0 {
|
|
|
|
|
currentVersion := fmt.Sprintf("%v", val[0])
|
|
|
|
|
if currentVersion != version {
|
2026-03-25 14:41:05 +03:00
|
|
|
data[name][0] = version
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
data[name] = []interface{}{version}
|
|
|
|
|
}
|
|
|
|
|
return WriteInstalled(data, destDir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func UnRegPackage(name string, destDir string) error {
|
|
|
|
|
data := ReadInstalled(destDir)
|
|
|
|
|
_, ok := data[name]
|
|
|
|
|
if !ok {
|
2026-08-11 18:58:45 +03:00
|
|
|
return nil
|
2026-03-25 14:41:05 +03:00
|
|
|
}
|
|
|
|
|
delete(data, name)
|
|
|
|
|
return WriteInstalled(data, destDir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func PurgeRepository(name string, destDir string) error {
|
|
|
|
|
dirPath := filepath.Join(destDir, paths.ReposDir, name)
|
2026-08-11 18:58:45 +03:00
|
|
|
if _, err := os.Stat(dirPath); err != nil {
|
|
|
|
|
return fmt.Errorf("repository '%s' not found", name)
|
|
|
|
|
}
|
2026-03-25 14:41:05 +03:00
|
|
|
err := os.RemoveAll(dirPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|