mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-08 23:49:34 +08:00
112
internal/appdata/root.go
Normal file
112
internal/appdata/root.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package appdata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const bootstrapFileName = "storage_root.json"
|
||||
|
||||
type bootstrapConfig struct {
|
||||
DataRoot string `json:"dataRoot"`
|
||||
}
|
||||
|
||||
func DefaultRoot() string {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil || strings.TrimSpace(homeDir) == "" {
|
||||
return "."
|
||||
}
|
||||
return filepath.Join(homeDir, ".gonavi")
|
||||
}
|
||||
|
||||
func BootstrapPath() string {
|
||||
return filepath.Join(DefaultRoot(), bootstrapFileName)
|
||||
}
|
||||
|
||||
func normalizeRoot(root string) (string, error) {
|
||||
trimmed := strings.TrimSpace(root)
|
||||
if trimmed == "" {
|
||||
trimmed = DefaultRoot()
|
||||
}
|
||||
abs, err := filepath.Abs(trimmed)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return abs, nil
|
||||
}
|
||||
|
||||
func ResolveRoot(root string) (string, error) {
|
||||
return normalizeRoot(root)
|
||||
}
|
||||
|
||||
func ResolveActiveRoot() (string, error) {
|
||||
defaultRoot, err := normalizeRoot(DefaultRoot())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := os.ReadFile(BootstrapPath())
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return defaultRoot, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
var cfg bootstrapConfig
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.TrimSpace(cfg.DataRoot) == "" {
|
||||
return defaultRoot, nil
|
||||
}
|
||||
return normalizeRoot(cfg.DataRoot)
|
||||
}
|
||||
|
||||
func MustResolveActiveRoot() string {
|
||||
root, err := ResolveActiveRoot()
|
||||
if err != nil {
|
||||
return DefaultRoot()
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func DriverRoot(activeRoot string) string {
|
||||
root := strings.TrimSpace(activeRoot)
|
||||
if root == "" {
|
||||
root = MustResolveActiveRoot()
|
||||
}
|
||||
return filepath.Join(root, "drivers")
|
||||
}
|
||||
|
||||
func SetActiveRoot(root string) (string, error) {
|
||||
targetRoot, err := normalizeRoot(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defaultRoot, err := normalizeRoot(DefaultRoot())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.MkdirAll(targetRoot, 0o755); err != nil {
|
||||
return "", fmt.Errorf("创建数据目录失败:%w", err)
|
||||
}
|
||||
if targetRoot == defaultRoot {
|
||||
if err := os.Remove(BootstrapPath()); err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
return defaultRoot, nil
|
||||
}
|
||||
if err := os.MkdirAll(defaultRoot, 0o755); err != nil {
|
||||
return "", fmt.Errorf("创建默认引导目录失败:%w", err)
|
||||
}
|
||||
payload, err := json.MarshalIndent(bootstrapConfig{DataRoot: targetRoot}, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.WriteFile(BootstrapPath(), payload, 0o644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return targetRoot, nil
|
||||
}
|
||||
69
internal/appdata/root_test.go
Normal file
69
internal/appdata/root_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package appdata
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResolveActiveRootDefaultsToLegacyGonaviDir(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
t.Setenv("HOME", homeDir)
|
||||
t.Setenv("USERPROFILE", homeDir)
|
||||
|
||||
root, err := ResolveActiveRoot()
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveActiveRoot returned error: %v", err)
|
||||
}
|
||||
expected := filepath.Join(homeDir, ".gonavi")
|
||||
if root != expected {
|
||||
t.Fatalf("expected default root %q, got %q", expected, root)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetActiveRootWritesBootstrapAndResolveUsesIt(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
t.Setenv("HOME", homeDir)
|
||||
t.Setenv("USERPROFILE", homeDir)
|
||||
|
||||
customRoot := filepath.Join(t.TempDir(), "gonavi-data")
|
||||
savedRoot, err := SetActiveRoot(customRoot)
|
||||
if err != nil {
|
||||
t.Fatalf("SetActiveRoot returned error: %v", err)
|
||||
}
|
||||
if savedRoot != customRoot {
|
||||
t.Fatalf("expected saved root %q, got %q", customRoot, savedRoot)
|
||||
}
|
||||
if _, err := os.Stat(BootstrapPath()); err != nil {
|
||||
t.Fatalf("expected bootstrap file to exist: %v", err)
|
||||
}
|
||||
resolvedRoot, err := ResolveActiveRoot()
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveActiveRoot returned error: %v", err)
|
||||
}
|
||||
if resolvedRoot != customRoot {
|
||||
t.Fatalf("expected custom root %q, got %q", customRoot, resolvedRoot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetActiveRootResetToDefaultRemovesBootstrap(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
t.Setenv("HOME", homeDir)
|
||||
t.Setenv("USERPROFILE", homeDir)
|
||||
|
||||
customRoot := filepath.Join(t.TempDir(), "gonavi-data")
|
||||
if _, err := SetActiveRoot(customRoot); err != nil {
|
||||
t.Fatalf("SetActiveRoot custom returned error: %v", err)
|
||||
}
|
||||
defaultRoot, err := SetActiveRoot("")
|
||||
if err != nil {
|
||||
t.Fatalf("SetActiveRoot default returned error: %v", err)
|
||||
}
|
||||
expectedDefault := filepath.Join(homeDir, ".gonavi")
|
||||
if defaultRoot != expectedDefault {
|
||||
t.Fatalf("expected default root %q, got %q", expectedDefault, defaultRoot)
|
||||
}
|
||||
if _, err := os.Stat(BootstrapPath()); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected bootstrap file to be removed, got err=%v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user