- Implement comprehensive database package with versioned migrations - Add skyview-data CLI tool for managing aviation reference data - Integrate database with merger for real-time aircraft history persistence - Support OurAirports and OpenFlights data sources (runtime loading) - Add systemd timer for automated database updates - Fix transaction-based bulk loading for 2400% performance improvement - Add callsign enhancement system with airline/airport lookups - Update Debian packaging with database directory and permissions Database features: - Aircraft position history with configurable retention - External aviation data loading (airlines, airports) - Callsign parsing and enhancement - API client for external lookups (OpenSky, etc.) - Privacy mode for complete offline operation CLI commands: - skyview-data status: Show database statistics - skyview-data update: Load aviation reference data - skyview-data list: Show available data sources - skyview-data clear: Remove specific data sources 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
174 lines
No EOL
4.4 KiB
Go
174 lines
No EOL
4.4 KiB
Go
package database
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
// ResolveDatabasePath determines the appropriate database file location
|
|
// based on configuration, system type, and available permissions
|
|
func ResolveDatabasePath(configPath string) (string, error) {
|
|
// Use explicit configuration path if provided
|
|
if configPath != "" {
|
|
if err := ensureDirExists(filepath.Dir(configPath)); err != nil {
|
|
return "", fmt.Errorf("cannot create directory for configured path %s: %v", configPath, err)
|
|
}
|
|
return configPath, nil
|
|
}
|
|
|
|
// Try system location first (for services)
|
|
if systemPath, err := trySystemPath(); err == nil {
|
|
return systemPath, nil
|
|
}
|
|
|
|
// Try user data directory
|
|
if userPath, err := tryUserPath(); err == nil {
|
|
return userPath, nil
|
|
}
|
|
|
|
// Fallback to current directory
|
|
return tryCurrentDirPath()
|
|
}
|
|
|
|
// trySystemPath attempts to use system-wide database location
|
|
func trySystemPath() (string, error) {
|
|
var systemDir string
|
|
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
systemDir = "/var/lib/skyview"
|
|
case "darwin":
|
|
systemDir = "/usr/local/var/skyview"
|
|
case "windows":
|
|
systemDir = filepath.Join(os.Getenv("PROGRAMDATA"), "skyview")
|
|
default:
|
|
return "", fmt.Errorf("system path not supported on %s", runtime.GOOS)
|
|
}
|
|
|
|
// Check if directory exists and is writable
|
|
if err := ensureDirExists(systemDir); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
dbPath := filepath.Join(systemDir, "skyview.db")
|
|
|
|
// Test write permissions
|
|
if err := testWritePermissions(dbPath); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return dbPath, nil
|
|
}
|
|
|
|
// tryUserPath attempts to use user data directory
|
|
func tryUserPath() (string, error) {
|
|
var userDataDir string
|
|
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
|
|
userDataDir = xdgData
|
|
} else {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
userDataDir = filepath.Join(home, ".local", "share")
|
|
}
|
|
case "darwin":
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
userDataDir = filepath.Join(home, "Library", "Application Support")
|
|
case "windows":
|
|
userDataDir = os.Getenv("APPDATA")
|
|
if userDataDir == "" {
|
|
return "", fmt.Errorf("APPDATA environment variable not set")
|
|
}
|
|
default:
|
|
return "", fmt.Errorf("user path not supported on %s", runtime.GOOS)
|
|
}
|
|
|
|
skyviewDir := filepath.Join(userDataDir, "skyview")
|
|
|
|
if err := ensureDirExists(skyviewDir); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
dbPath := filepath.Join(skyviewDir, "skyview.db")
|
|
|
|
// Test write permissions
|
|
if err := testWritePermissions(dbPath); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return dbPath, nil
|
|
}
|
|
|
|
// tryCurrentDirPath uses current directory as fallback
|
|
func tryCurrentDirPath() (string, error) {
|
|
currentDir, err := os.Getwd()
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot get current directory: %v", err)
|
|
}
|
|
|
|
dbPath := filepath.Join(currentDir, "skyview.db")
|
|
|
|
// Test write permissions
|
|
if err := testWritePermissions(dbPath); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return dbPath, nil
|
|
}
|
|
|
|
// ensureDirExists creates directory if it doesn't exist
|
|
func ensureDirExists(dir string) error {
|
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("cannot create directory %s: %v", dir, err)
|
|
}
|
|
} else if err != nil {
|
|
return fmt.Errorf("cannot access directory %s: %v", dir, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// testWritePermissions verifies write access to the database path
|
|
func testWritePermissions(dbPath string) error {
|
|
dir := filepath.Dir(dbPath)
|
|
|
|
// Check directory write permissions
|
|
testFile := filepath.Join(dir, ".skyview_write_test")
|
|
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
|
|
return fmt.Errorf("no write permission to directory %s: %v", dir, err)
|
|
}
|
|
|
|
// Clean up test file
|
|
os.Remove(testFile)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetDatabaseDirectory returns the directory containing the database file
|
|
func GetDatabaseDirectory(dbPath string) string {
|
|
return filepath.Dir(dbPath)
|
|
}
|
|
|
|
// IsSystemPath returns true if the database path is in a system location
|
|
func IsSystemPath(dbPath string) bool {
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
return filepath.HasPrefix(dbPath, "/var/lib/skyview")
|
|
case "darwin":
|
|
return filepath.HasPrefix(dbPath, "/usr/local/var/skyview")
|
|
case "windows":
|
|
programData := os.Getenv("PROGRAMDATA")
|
|
return programData != "" && filepath.HasPrefix(dbPath, filepath.Join(programData, "skyview"))
|
|
}
|
|
return false
|
|
} |