up
This commit is contained in:
@@ -556,31 +556,10 @@ func (a *App) startup(ctx context.Context) {
|
||||
}
|
||||
a.db = conn
|
||||
|
||||
// Choose where the QSO logbook lives. On a MySQL failure we fall back to the
|
||||
// local SQLite logbook so the operator can still log (and fix the config).
|
||||
logbookConn := conn
|
||||
if mb := readBootstrap(dataDir).MySQL; mb != nil && mb.Enabled {
|
||||
applog.Printf("startup: logbook backend = MySQL (%s:%d/%s)", mb.Host, mb.Port, mb.Database)
|
||||
mysqlConn, mErr := db.OpenMySQL(db.MySQLConfig{
|
||||
Host: mb.Host, Port: mb.Port, User: mb.User, Password: mb.Password, Database: mb.Database,
|
||||
})
|
||||
if mErr != nil {
|
||||
applog.Printf("startup: MySQL open failed (%v) — falling back to SQLite logbook", mErr)
|
||||
a.dbBackendErr = "MySQL: " + mErr.Error()
|
||||
} else {
|
||||
logbookConn = mysqlConn
|
||||
a.dbBackend = "mysql"
|
||||
}
|
||||
}
|
||||
if a.dbBackend == "" {
|
||||
a.dbBackend = "sqlite"
|
||||
}
|
||||
// db.Dialect describes the LOGBOOK backend — the only place SQL actually
|
||||
// varies (qso JSON extraction). Config repos always run on SQLite.
|
||||
db.SetDialect(a.dbBackend)
|
||||
applog.Printf("startup: logbook backend = %s", a.dbBackend)
|
||||
a.logDb = logbookConn
|
||||
a.qso = qso.NewRepo(logbookConn)
|
||||
// Wire the LOCAL config repos first — they're backed by the already-open
|
||||
// SQLite file, so the station/profiles/settings are ready instantly. Doing
|
||||
// this BEFORE the (possibly slow, remote) MySQL logbook connect means the UI
|
||||
// doesn't briefly think the station is unconfigured while MySQL is dialing.
|
||||
a.settings = settings.NewStore(conn)
|
||||
a.settings.SetSensitivePredicate(isSensitiveSetting) // encrypt passwords at rest when a passphrase is set
|
||||
a.profiles = profile.NewRepo(conn)
|
||||
@@ -609,6 +588,32 @@ func (a *App) startup(ctx context.Context) {
|
||||
a.lookup = lookup.NewManager(a.cache)
|
||||
a.reloadLookupProviders()
|
||||
|
||||
// Now choose where the QSO logbook lives. On a MySQL failure we fall back to
|
||||
// the local SQLite logbook so the operator can still log (and fix config).
|
||||
logbookConn := conn
|
||||
if mb := readBootstrap(dataDir).MySQL; mb != nil && mb.Enabled {
|
||||
applog.Printf("startup: logbook backend = MySQL (%s:%d/%s)", mb.Host, mb.Port, mb.Database)
|
||||
mysqlConn, mErr := db.OpenMySQL(db.MySQLConfig{
|
||||
Host: mb.Host, Port: mb.Port, User: mb.User, Password: mb.Password, Database: mb.Database,
|
||||
})
|
||||
if mErr != nil {
|
||||
applog.Printf("startup: MySQL open failed (%v) — falling back to SQLite logbook", mErr)
|
||||
a.dbBackendErr = "MySQL: " + mErr.Error()
|
||||
} else {
|
||||
logbookConn = mysqlConn
|
||||
a.dbBackend = "mysql"
|
||||
}
|
||||
}
|
||||
if a.dbBackend == "" {
|
||||
a.dbBackend = "sqlite"
|
||||
}
|
||||
// db.Dialect describes the LOGBOOK backend — the only place SQL actually
|
||||
// varies (qso JSON extraction). Config repos always run on SQLite.
|
||||
db.SetDialect(a.dbBackend)
|
||||
applog.Printf("startup: logbook backend = %s", a.dbBackend)
|
||||
a.logDb = logbookConn
|
||||
a.qso = qso.NewRepo(logbookConn)
|
||||
|
||||
// cty.dat for offline DXCC / country resolution. Cached on disk; first
|
||||
// run downloads it from country-files.com in the background so startup
|
||||
// stays fast even if the network is slow.
|
||||
@@ -877,12 +882,22 @@ func (a *App) runBackupForShutdown() error {
|
||||
if folder == "" {
|
||||
folder = s.DefaultFolder
|
||||
}
|
||||
if backup.HasBackupToday(folder) {
|
||||
mysql := a.dbBackend == "mysql"
|
||||
done := backup.HasBackupToday(folder)
|
||||
if mysql {
|
||||
done = backup.HasADIFBackupToday(folder)
|
||||
}
|
||||
if done {
|
||||
return nil
|
||||
}
|
||||
if _, err := backup.Run(a.ctx, a.db, a.dbPath, folder, s.Rotation, s.Zip); err != nil {
|
||||
return err
|
||||
}
|
||||
if mysql {
|
||||
if _, err := a.backupLogADIF(folder, s.Rotation, s.Zip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return a.settings.Set(a.ctx, keyBackupLast, time.Now().UTC().Format(time.RFC3339))
|
||||
}
|
||||
|
||||
@@ -1103,6 +1118,30 @@ func (a *App) GetDBBackendStatus() DBBackendStatus {
|
||||
}
|
||||
}
|
||||
|
||||
// DBConnectionInfo is a compact description of where the QSO logbook lives, for
|
||||
// the status bar: a MySQL server endpoint or the local SQLite file path.
|
||||
type DBConnectionInfo struct {
|
||||
Backend string `json:"backend"` // "sqlite" | "mysql"
|
||||
Label string `json:"label"` // "host:port/database" or the .db path
|
||||
}
|
||||
|
||||
// GetDBConnectionInfo reports the logbook connection for display in the status
|
||||
// bar. For MySQL it shows host:port/database (the shared logbook); for SQLite
|
||||
// it shows the local database file path.
|
||||
func (a *App) GetDBConnectionInfo() DBConnectionInfo {
|
||||
if a.dbBackend == "mysql" {
|
||||
if mb := readBootstrap(a.dataDir).MySQL; mb != nil {
|
||||
port := mb.Port
|
||||
if port == 0 {
|
||||
port = 3306
|
||||
}
|
||||
return DBConnectionInfo{Backend: "mysql", Label: fmt.Sprintf("%s:%d/%s", mb.Host, port, mb.Database)}
|
||||
}
|
||||
return DBConnectionInfo{Backend: "mysql", Label: "MySQL"}
|
||||
}
|
||||
return DBConnectionInfo{Backend: "sqlite", Label: a.dbPath}
|
||||
}
|
||||
|
||||
// GetMySQLSettings returns the stored shared-database config from the bootstrap
|
||||
// file (config.json), with defaults applied. Read before the DB is open, so it
|
||||
// must not depend on the settings table.
|
||||
@@ -5748,14 +5787,38 @@ func (a *App) RunBackupNow() (string, error) {
|
||||
if folder == "" {
|
||||
folder = s.DefaultFolder
|
||||
}
|
||||
// Always snapshot the local SQLite (config + any pre-MySQL local QSOs).
|
||||
path, err := backup.Run(a.ctx, a.db, a.dbPath, folder, s.Rotation, s.Zip)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
// On MySQL the live QSO log isn't in the local DB — export it to ADIF so the
|
||||
// contacts are actually protected. The ADIF path is the one we surface.
|
||||
if a.dbBackend == "mysql" {
|
||||
adiPath, aerr := a.backupLogADIF(folder, s.Rotation, s.Zip)
|
||||
if aerr != nil {
|
||||
return adiPath, aerr
|
||||
}
|
||||
path = adiPath
|
||||
}
|
||||
a.setSetting(keyBackupLast, time.Now().UTC().Format(time.RFC3339))
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// backupLogADIF writes a rotating ADIF export of the (MySQL) logbook into the
|
||||
// backup folder. The full set of ADIF + app fields is included so the backup is
|
||||
// a complete, re-importable copy of the log.
|
||||
func (a *App) backupLogADIF(folder string, rotation int, zip bool) (string, error) {
|
||||
if a.qso == nil {
|
||||
return "", fmt.Errorf("logbook not initialized")
|
||||
}
|
||||
return backup.RunADIF(folder, rotation, zip, func(p string) error {
|
||||
ex := &adif.Exporter{Repo: a.qso, AppName: "OpsLog", AppVersion: "0.1", IncludeAppFields: true}
|
||||
_, e := ex.ExportFile(a.ctx, p)
|
||||
return e
|
||||
})
|
||||
}
|
||||
|
||||
// maybeShutdownBackup runs a backup at shutdown if the user enabled it
|
||||
// and no backup for today already exists. Running at shutdown (not at
|
||||
// startup) means the snapshot includes the QSOs the user just logged
|
||||
@@ -5773,13 +5836,26 @@ func (a *App) maybeShutdownBackup() {
|
||||
if folder == "" {
|
||||
folder = s.DefaultFolder
|
||||
}
|
||||
if backup.HasBackupToday(folder) {
|
||||
mysql := a.dbBackend == "mysql"
|
||||
// In MySQL mode the ADIF log export is the backup that matters; gate the
|
||||
// "already done today" skip on whichever backup type applies.
|
||||
done := backup.HasBackupToday(folder)
|
||||
if mysql {
|
||||
done = backup.HasADIFBackupToday(folder)
|
||||
}
|
||||
if done {
|
||||
return
|
||||
}
|
||||
if _, err := backup.Run(a.ctx, a.db, a.dbPath, folder, s.Rotation, s.Zip); err != nil {
|
||||
fmt.Println("OpsLog: shutdown backup failed:", err)
|
||||
return
|
||||
}
|
||||
if mysql {
|
||||
if _, err := a.backupLogADIF(folder, s.Rotation, s.Zip); err != nil {
|
||||
fmt.Println("OpsLog: shutdown ADIF log backup failed:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
a.setSetting(keyBackupLast, time.Now().UTC().Format(time.RFC3339))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user