waveterm/wavesrv/pkg/sstore/migrate.go
Mike Sawka b136c915df
Restart command (#253)
* working on cmd restart logic

* button to restart command

* bind Cmd-R to restart selected command, and Cmd-Shift-R to restart last command.  Browser Refresh is now Option-R.  also fix 'clear' command to not delete running commands (like archive).  some small changes to keyboard utility code to always set 'alt' and 'meta' appropriately.  use 'cmd' and 'option' for crossplatform bindings

* focus restarted line

* update termopts, use current winsize to set termopts for new command

* add cmd.restartts to track restart time

* display restarted time in line w/ tooltip with original time

* add restartts to line:show
2024-01-26 16:25:21 -08:00

235 lines
5.7 KiB
Go

// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package sstore
import (
"errors"
"fmt"
"io"
"io/fs"
"log"
"os"
"strconv"
"time"
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
_ "github.com/golang-migrate/migrate/v4/source/file"
"github.com/golang-migrate/migrate/v4/source/iofs"
_ "github.com/mattn/go-sqlite3"
sh2db "github.com/wavetermdev/waveterm/wavesrv/db"
"github.com/golang-migrate/migrate/v4"
)
const MaxMigration = 31
const MigratePrimaryScreenVersion = 9
const CmdScreenSpecialMigration = 13
const CmdLineSpecialMigration = 20
const RISpecialMigration = 30
func MakeMigrate() (*migrate.Migrate, error) {
fsVar, err := iofs.New(sh2db.MigrationFS, "migrations")
if err != nil {
return nil, fmt.Errorf("opening iofs: %w", err)
}
// migrationPathUrl := fmt.Sprintf("file://%s", path.Join(wd, "db", "migrations"))
dbUrl := fmt.Sprintf("sqlite3://%s", GetDBName())
m, err := migrate.NewWithSourceInstance("iofs", fsVar, dbUrl)
// m, err := migrate.New(migrationPathUrl, dbUrl)
if err != nil {
return nil, fmt.Errorf("making migration db[%s]: %w", GetDBName(), err)
}
return m, nil
}
func copyFile(srcFile string, dstFile string, notFoundOk bool) error {
if srcFile == dstFile {
return fmt.Errorf("cannot copy %s to itself", srcFile)
}
srcFd, err := os.Open(srcFile)
if notFoundOk && err != nil && errors.Is(err, fs.ErrNotExist) {
return nil
}
if err != nil {
return fmt.Errorf("cannot open %s: %v", srcFile, err)
}
defer srcFd.Close()
dstFd, err := os.OpenFile(dstFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("cannot open destination file %s: %v", dstFile, err)
}
_, err = io.Copy(dstFd, srcFd)
if err != nil {
dstFd.Close()
return fmt.Errorf("error copying file: %v", err)
}
return dstFd.Close()
}
func MigrateUpStep(m *migrate.Migrate, newVersion uint) error {
startTime := time.Now()
err := m.Migrate(newVersion)
if err != nil {
return err
}
if newVersion == CmdScreenSpecialMigration {
mErr := RunMigration13()
if mErr != nil {
return fmt.Errorf("migrating to v%d: %w", newVersion, mErr)
}
}
if newVersion == CmdLineSpecialMigration {
mErr := RunMigration20()
if mErr != nil {
return fmt.Errorf("migrating to v%d: %w", newVersion, mErr)
}
}
if newVersion == RISpecialMigration {
mErr := RunMigration30()
if mErr != nil {
return fmt.Errorf("migrating to v%d: %w", newVersion, mErr)
}
}
log.Printf("[db] migration v%d, elapsed %v\n", newVersion, time.Since(startTime))
return nil
}
func MigrateUp(targetVersion uint) error {
m, err := MakeMigrate()
if err != nil {
return err
}
curVersion, dirty, err := MigrateVersion(m)
if dirty {
return fmt.Errorf("cannot migrate up, database is dirty")
}
if err != nil {
return fmt.Errorf("cannot get current migration version: %v", err)
}
if curVersion >= targetVersion {
return nil
}
log.Printf("[db] migrating from %d to %d\n", curVersion, targetVersion)
log.Printf("[db] backing up database %s to %s\n", DBFileName, DBFileNameBackup)
os.Remove(GetDBBackupName()) // don't report error
os.Remove(GetDBWALBackupName()) // don't report error
err = copyFile(GetDBName(), GetDBBackupName(), false)
if err != nil {
return fmt.Errorf("error creating database backup: %v", err)
}
err = copyFile(GetDBWALName(), GetDBWALBackupName(), true)
if err != nil {
return fmt.Errorf("error creating database(wal) backup: %v", err)
}
for newVersion := curVersion + 1; newVersion <= targetVersion; newVersion++ {
err = MigrateUpStep(m, newVersion)
if err != nil {
return fmt.Errorf("during migration v%d: %w", newVersion, err)
}
}
log.Printf("[db] migration done, new version = %d\n", targetVersion)
return nil
}
// returns curVersion, dirty, error
func MigrateVersion(m *migrate.Migrate) (uint, bool, error) {
if m == nil {
var err error
m, err = MakeMigrate()
if err != nil {
return 0, false, err
}
}
curVersion, dirty, err := m.Version()
if err == migrate.ErrNilVersion {
return 0, false, nil
}
return curVersion, dirty, err
}
func MigrateDown() error {
m, err := MakeMigrate()
if err != nil {
return err
}
err = m.Down()
if err != nil {
return err
}
return nil
}
func MigrateGoto(n uint) error {
curVersion, _, _ := MigrateVersion(nil)
if curVersion == n {
return nil
}
if curVersion < n {
return MigrateUp(n)
}
m, err := MakeMigrate()
if err != nil {
return err
}
err = m.Migrate(n)
if err != nil {
return err
}
return nil
}
func TryMigrateUp() error {
curVersion, _, _ := MigrateVersion(nil)
log.Printf("[db] db version = %d\n", curVersion)
if curVersion >= MaxMigration {
return nil
}
err := MigrateUp(MaxMigration)
if err != nil {
return err
}
return MigratePrintVersion()
}
func MigratePrintVersion() error {
version, dirty, err := MigrateVersion(nil)
if err != nil {
return fmt.Errorf("error getting db version: %v", err)
}
if dirty {
return fmt.Errorf("error db is dirty, version=%d", version)
}
log.Printf("[db] version=%d\n", version)
return nil
}
func MigrateCommandOpts(opts []string) error {
var err error
if opts[0] == "--migrate-up" {
fmt.Printf("migrate-up %v\n", GetDBName())
time.Sleep(3 * time.Second)
err = MigrateUp(MaxMigration)
} else if opts[0] == "--migrate-down" {
fmt.Printf("migrate-down %v\n", GetDBName())
time.Sleep(3 * time.Second)
err = MigrateDown()
} else if opts[0] == "--migrate-goto" {
n, err := strconv.Atoi(opts[1])
if err == nil {
fmt.Printf("migrate-goto %v => %d\n", GetDBName(), n)
time.Sleep(3 * time.Second)
err = MigrateGoto(uint(n))
}
} else {
err = fmt.Errorf("invalid migration command")
}
if err != nil && err.Error() == migrate.ErrNoChange.Error() {
err = nil
}
if err != nil {
return err
}
return MigratePrintVersion()
}