2024-10-02 08:59:56 +00:00
|
|
|
package localregistry
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
|
|
|
"log/slog"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
|
2024-10-04 17:23:51 +00:00
|
|
|
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
|
|
|
|
2024-10-02 08:59:56 +00:00
|
|
|
// sqlite driver
|
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
)
|
|
|
|
|
|
|
|
var db *sql.DB
|
|
|
|
var dbWriteMutex = sync.Mutex{}
|
|
|
|
|
|
|
|
func Init() error {
|
|
|
|
dbPath := filepath.Join(viper.GetString("cache-dir"), "registry.db")
|
|
|
|
|
|
|
|
err := os.MkdirAll(filepath.Dir(dbPath), 0o777)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create local registry directory: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
db, err = sql.Open("sqlite", dbPath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to open database: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set pragmas here because modernc.org/sqlite does not support them in the connection string
|
|
|
|
_, err = db.Exec(`
|
|
|
|
PRAGMA journal_mode = WAL;
|
|
|
|
PRAGMA foreign_keys = ON;
|
|
|
|
PRAGMA busy_timeout = 5000;
|
|
|
|
`)
|
|
|
|
if err != nil {
|
2024-10-04 17:23:51 +00:00
|
|
|
return fmt.Errorf("failed to setup connection pragmas: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = applyMigrations(db)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to apply migrations: %w", err)
|
2024-10-02 08:59:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-10-04 17:23:51 +00:00
|
|
|
func Add(modReference string, modVersions []ficsit.ModVersion) {
|
2024-10-02 08:59:56 +00:00
|
|
|
dbWriteMutex.Lock()
|
|
|
|
defer dbWriteMutex.Unlock()
|
|
|
|
|
|
|
|
tx, err := db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
slog.Error("failed to start local registry transaction", slog.Any("err", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// In case the transaction is not committed, revert and release
|
|
|
|
defer tx.Rollback() //nolint:errcheck
|
|
|
|
|
|
|
|
_, err = tx.Exec("DELETE FROM versions WHERE mod_reference = ?", modReference)
|
|
|
|
if err != nil {
|
|
|
|
slog.Error("failed to delete existing mod versions from local registry", slog.Any("err", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, modVersion := range modVersions {
|
|
|
|
l := slog.With(slog.String("mod", modReference), slog.String("version", modVersion.Version))
|
|
|
|
|
2024-10-04 17:23:51 +00:00
|
|
|
_, err = tx.Exec("INSERT INTO versions (id, mod_reference, version, game_version, required_on_remote) VALUES (?, ?, ?, ?, ?)", modVersion.ID, modReference, modVersion.Version, modVersion.GameVersion, modVersion.RequiredOnRemote)
|
2024-10-02 08:59:56 +00:00
|
|
|
if err != nil {
|
|
|
|
l.Error("failed to insert mod version into local registry", slog.Any("err", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, dependency := range modVersion.Dependencies {
|
|
|
|
_, err = tx.Exec("INSERT INTO dependencies (version_id, dependency, condition, optional) VALUES (?, ?, ?, ?)", modVersion.ID, dependency.ModID, dependency.Condition, dependency.Optional)
|
|
|
|
if err != nil {
|
|
|
|
l.Error("failed to insert dependency into local registry", slog.String("dependency", dependency.ModID), slog.Any("err", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, target := range modVersion.Targets {
|
|
|
|
_, err = tx.Exec("INSERT INTO targets (version_id, target_name, link, hash, size) VALUES (?, ?, ?, ?, ?)", modVersion.ID, target.TargetName, target.Link, target.Hash, target.Size)
|
|
|
|
if err != nil {
|
|
|
|
l.Error("failed to insert target into local registry", slog.Any("target", target.TargetName), slog.Any("err", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
slog.Error("failed to commit local registry transaction", slog.Any("err", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-04 17:23:51 +00:00
|
|
|
func GetModVersions(modReference string) ([]ficsit.ModVersion, error) {
|
|
|
|
versionRows, err := db.Query("SELECT id, version, game_version, required_on_remote FROM versions WHERE mod_reference = ?", modReference)
|
2024-10-02 08:59:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to fetch mod versions from local registry: %w", err)
|
|
|
|
}
|
|
|
|
defer versionRows.Close()
|
|
|
|
|
2024-10-04 17:23:51 +00:00
|
|
|
var versions []ficsit.ModVersion
|
2024-10-02 08:59:56 +00:00
|
|
|
for versionRows.Next() {
|
2024-10-04 17:23:51 +00:00
|
|
|
var version ficsit.ModVersion
|
|
|
|
err = versionRows.Scan(&version.ID, &version.Version, &version.GameVersion, &version.RequiredOnRemote)
|
2024-10-02 08:59:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to scan version row: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
dependencies, err := getVersionDependencies(version.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
version.Dependencies = dependencies
|
|
|
|
|
|
|
|
targets, err := getVersionTargets(version.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
version.Targets = targets
|
|
|
|
|
|
|
|
versions = append(versions, version)
|
|
|
|
}
|
|
|
|
|
|
|
|
return versions, nil
|
|
|
|
}
|
|
|
|
|
2024-10-04 17:23:51 +00:00
|
|
|
func getVersionDependencies(versionID string) ([]ficsit.Dependency, error) {
|
|
|
|
var dependencies []ficsit.Dependency
|
2024-10-02 08:59:56 +00:00
|
|
|
dependencyRows, err := db.Query("SELECT dependency, condition, optional FROM dependencies WHERE version_id = ?", versionID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to fetch dependencies from local registry: %w", err)
|
|
|
|
}
|
|
|
|
defer dependencyRows.Close()
|
|
|
|
|
|
|
|
for dependencyRows.Next() {
|
2024-10-04 17:23:51 +00:00
|
|
|
var dependency ficsit.Dependency
|
2024-10-02 08:59:56 +00:00
|
|
|
err = dependencyRows.Scan(&dependency.ModID, &dependency.Condition, &dependency.Optional)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to scan dependency row: %w", err)
|
|
|
|
}
|
|
|
|
dependencies = append(dependencies, dependency)
|
|
|
|
}
|
|
|
|
|
|
|
|
return dependencies, nil
|
|
|
|
}
|
|
|
|
|
2024-10-04 17:23:51 +00:00
|
|
|
func getVersionTargets(versionID string) ([]ficsit.Target, error) {
|
|
|
|
var targets []ficsit.Target
|
2024-10-02 08:59:56 +00:00
|
|
|
targetRows, err := db.Query("SELECT target_name, link, hash, size FROM targets WHERE version_id = ?", versionID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to fetch targets from local registry: %w", err)
|
|
|
|
}
|
|
|
|
defer targetRows.Close()
|
|
|
|
|
|
|
|
for targetRows.Next() {
|
2024-10-04 17:23:51 +00:00
|
|
|
var target ficsit.Target
|
2024-10-02 08:59:56 +00:00
|
|
|
err = targetRows.Scan(&target.TargetName, &target.Link, &target.Hash, &target.Size)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to scan target row: %w", err)
|
|
|
|
}
|
|
|
|
targets = append(targets, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
return targets, nil
|
|
|
|
}
|