mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
* Add failed state and error to Job * Move migration code * Add websocket monitor * Make migrate a job managed task
153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
package task
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/stashapp/stash/pkg/job"
|
|
"github.com/stashapp/stash/pkg/logger"
|
|
"github.com/stashapp/stash/pkg/sqlite"
|
|
)
|
|
|
|
type migrateJobConfig interface {
|
|
GetBackupDirectoryPath() string
|
|
GetBackupDirectoryPathOrDefault() string
|
|
}
|
|
|
|
type MigrateJob struct {
|
|
BackupPath string
|
|
Config migrateJobConfig
|
|
Database *sqlite.Database
|
|
}
|
|
|
|
func (s *MigrateJob) Execute(ctx context.Context, progress *job.Progress) error {
|
|
required, err := s.required()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if required == 0 {
|
|
logger.Infof("database is already at the latest schema version")
|
|
return nil
|
|
}
|
|
|
|
// set the number of tasks = required steps + optimise
|
|
progress.SetTotal(int(required + 1))
|
|
|
|
database := s.Database
|
|
|
|
// always backup so that we can roll back to the previous version if
|
|
// migration fails
|
|
backupPath := s.BackupPath
|
|
if backupPath == "" {
|
|
backupPath = database.DatabaseBackupPath(s.Config.GetBackupDirectoryPath())
|
|
} else {
|
|
// check if backup path is a filename or path
|
|
// filename goes into backup directory, path is kept as is
|
|
filename := filepath.Base(backupPath)
|
|
if backupPath == filename {
|
|
backupPath = filepath.Join(s.Config.GetBackupDirectoryPathOrDefault(), filename)
|
|
}
|
|
}
|
|
|
|
// perform database backup
|
|
if err := database.Backup(backupPath); err != nil {
|
|
return fmt.Errorf("error backing up database: %s", err)
|
|
}
|
|
|
|
if err := s.runMigrations(ctx, progress); err != nil {
|
|
errStr := fmt.Sprintf("error performing migration: %s", err)
|
|
|
|
// roll back to the backed up version
|
|
restoreErr := database.RestoreFromBackup(backupPath)
|
|
if restoreErr != nil {
|
|
errStr = fmt.Sprintf("ERROR: unable to restore database from backup after migration failure: %s\n%s", restoreErr.Error(), errStr)
|
|
} else {
|
|
errStr = "An error occurred migrating the database to the latest schema version. The backup database file was automatically renamed to restore the database.\n" + errStr
|
|
}
|
|
|
|
return errors.New(errStr)
|
|
}
|
|
|
|
// if no backup path was provided, then delete the created backup
|
|
if s.BackupPath == "" {
|
|
if err := os.Remove(backupPath); err != nil {
|
|
logger.Warnf("error removing unwanted database backup (%s): %s", backupPath, err.Error())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *MigrateJob) required() (uint, error) {
|
|
database := s.Database
|
|
|
|
m, err := sqlite.NewMigrator(database)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
defer m.Close()
|
|
|
|
currentSchemaVersion := m.CurrentSchemaVersion()
|
|
targetSchemaVersion := m.RequiredSchemaVersion()
|
|
|
|
if targetSchemaVersion < currentSchemaVersion {
|
|
// shouldn't happen
|
|
return 0, nil
|
|
}
|
|
|
|
return targetSchemaVersion - currentSchemaVersion, nil
|
|
}
|
|
|
|
func (s *MigrateJob) runMigrations(ctx context.Context, progress *job.Progress) error {
|
|
database := s.Database
|
|
|
|
m, err := sqlite.NewMigrator(database)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer m.Close()
|
|
|
|
for {
|
|
currentSchemaVersion := m.CurrentSchemaVersion()
|
|
targetSchemaVersion := m.RequiredSchemaVersion()
|
|
|
|
if currentSchemaVersion >= targetSchemaVersion {
|
|
break
|
|
}
|
|
|
|
var err error
|
|
progress.ExecuteTask(fmt.Sprintf("Migrating database to schema version %d", currentSchemaVersion+1), func() {
|
|
err = m.RunMigration(ctx, currentSchemaVersion+1)
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("error running migration for schema %d: %s", currentSchemaVersion+1, err)
|
|
}
|
|
|
|
progress.Increment()
|
|
}
|
|
|
|
// reinitialise the database
|
|
if err := database.ReInitialise(); err != nil {
|
|
return fmt.Errorf("error reinitialising database: %s", err)
|
|
}
|
|
|
|
// optimise the database
|
|
progress.ExecuteTask("Optimising database", func() {
|
|
err = database.Optimise(ctx)
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("error optimising database: %s", err)
|
|
}
|
|
|
|
progress.Increment()
|
|
|
|
return nil
|
|
}
|