feat: Add SQLite database integration for aircraft history and callsign enhancement
- 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>
This commit is contained in:
parent
cd51d3ecc0
commit
37c4fa2b57
25 changed files with 4771 additions and 12 deletions
174
internal/database/path.go
Normal file
174
internal/database/path.go
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue