mirror of
https://github.com/dnote/dnote
synced 2026-03-15 06:55:49 +01:00
* Migrate edit_book action data and action * Fix test * Reduce change of books * Bump version * Add uuid
246 lines
5.4 KiB
Go
246 lines
5.4 KiB
Go
package migrate
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
"gopkg.in/yaml.v2"
|
|
|
|
"github.com/dnote-io/cli/infra"
|
|
"github.com/dnote-io/cli/utils"
|
|
)
|
|
|
|
var (
|
|
schemaFilename = "schema"
|
|
backupDirName = ".dnote-bak"
|
|
)
|
|
|
|
// migration IDs
|
|
const (
|
|
_ = iota
|
|
migrationV1
|
|
migrationV2
|
|
migrationV3
|
|
migrationV4
|
|
migrationV5
|
|
)
|
|
|
|
var migrationSequence = []int{
|
|
migrationV1,
|
|
migrationV2,
|
|
migrationV3,
|
|
migrationV4,
|
|
migrationV5,
|
|
}
|
|
|
|
type schema struct {
|
|
CurrentVersion int `yaml:"current_version"`
|
|
}
|
|
|
|
func makeSchema(complete bool) schema {
|
|
s := schema{}
|
|
|
|
var currentVersion int
|
|
if complete {
|
|
currentVersion = len(migrationSequence)
|
|
}
|
|
|
|
s.CurrentVersion = currentVersion
|
|
|
|
return s
|
|
}
|
|
|
|
// Migrate determines migrations to be run and performs them
|
|
func Migrate(ctx infra.DnoteCtx) error {
|
|
unrunMigrations, err := getUnrunMigrations(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to get unrun migrations")
|
|
}
|
|
|
|
for _, mid := range unrunMigrations {
|
|
if err := performMigration(ctx, mid); err != nil {
|
|
return errors.Wrapf(err, "Failed to run migration #%d", mid)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// performMigration backs up current .dnote data, performs migration, and
|
|
// restores or cleans backups depending on if there is an error
|
|
func performMigration(ctx infra.DnoteCtx, migrationID int) error {
|
|
if err := backupDnoteDir(ctx); err != nil {
|
|
return errors.Wrap(err, "Failed to back up dnote directory")
|
|
}
|
|
|
|
var migrationError error
|
|
|
|
switch migrationID {
|
|
case migrationV1:
|
|
migrationError = migrateToV1(ctx)
|
|
case migrationV2:
|
|
migrationError = migrateToV2(ctx)
|
|
case migrationV3:
|
|
migrationError = migrateToV3(ctx)
|
|
case migrationV4:
|
|
migrationError = migrateToV4(ctx)
|
|
case migrationV5:
|
|
migrationError = migrateToV5(ctx)
|
|
default:
|
|
return errors.Errorf("Unrecognized migration id %d", migrationID)
|
|
}
|
|
|
|
if migrationError != nil {
|
|
if err := restoreBackup(ctx); err != nil {
|
|
panic(errors.Wrap(err, "Failed to restore backup for a failed migration"))
|
|
}
|
|
|
|
return errors.Wrapf(migrationError, "Failed to perform migration #%d", migrationID)
|
|
}
|
|
|
|
if err := clearBackup(ctx); err != nil {
|
|
return errors.Wrap(err, "Failed to clear backup")
|
|
}
|
|
|
|
if err := updateSchemaVersion(ctx, migrationID); err != nil {
|
|
return errors.Wrap(err, "Failed to update schema version")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// backupDnoteDir backs up the dnote directory to a temporary backup directory
|
|
func backupDnoteDir(ctx infra.DnoteCtx) error {
|
|
srcPath := fmt.Sprintf("%s/.dnote", ctx.HomeDir)
|
|
tmpPath := fmt.Sprintf("%s/%s", ctx.HomeDir, backupDirName)
|
|
|
|
if err := utils.CopyDir(srcPath, tmpPath); err != nil {
|
|
return errors.Wrap(err, "Failed to copy the .dnote directory")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func restoreBackup(ctx infra.DnoteCtx) error {
|
|
var err error
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
log.Printf(`Failed to restore backup for a failed migration.
|
|
Don't worry. Your data is still intact in the backup directory.
|
|
Get help on https://github.com/dnote-io/cli/issues`)
|
|
}
|
|
}()
|
|
|
|
srcPath := fmt.Sprintf("%s/.dnote", ctx.HomeDir)
|
|
backupPath := fmt.Sprintf("%s/%s", ctx.HomeDir, backupDirName)
|
|
|
|
if err = os.RemoveAll(srcPath); err != nil {
|
|
return errors.Wrapf(err, "Failed to clear current dnote data at %s", backupPath)
|
|
}
|
|
|
|
if err = os.Rename(backupPath, srcPath); err != nil {
|
|
return errors.Wrap(err, `Failed to copy backup data to the original directory.`)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func clearBackup(ctx infra.DnoteCtx) error {
|
|
backupPath := fmt.Sprintf("%s/%s", ctx.HomeDir, backupDirName)
|
|
|
|
if err := os.RemoveAll(backupPath); err != nil {
|
|
return errors.Wrapf(err, "Failed to remove backup at %s", backupPath)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getSchemaPath returns the path to the file containing schema info
|
|
func getSchemaPath(ctx infra.DnoteCtx) string {
|
|
return fmt.Sprintf("%s/%s", ctx.DnoteDir, schemaFilename)
|
|
}
|
|
|
|
// InitSchemaFile creates a migration file
|
|
func InitSchemaFile(ctx infra.DnoteCtx, pristine bool) error {
|
|
path := getSchemaPath(ctx)
|
|
if utils.FileExists(path) {
|
|
return nil
|
|
}
|
|
|
|
s := makeSchema(pristine)
|
|
err := writeSchema(ctx, s)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to write schema")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func readSchema(ctx infra.DnoteCtx) (schema, error) {
|
|
var ret schema
|
|
|
|
path := getSchemaPath(ctx)
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return ret, errors.Wrap(err, "Failed to read schema file")
|
|
}
|
|
|
|
err = yaml.Unmarshal(b, &ret)
|
|
if err != nil {
|
|
return ret, errors.Wrap(err, "Failed to unmarshal the schema JSON")
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func writeSchema(ctx infra.DnoteCtx, s schema) error {
|
|
path := getSchemaPath(ctx)
|
|
d, err := yaml.Marshal(&s)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to marshal schema into yaml")
|
|
}
|
|
|
|
if err := ioutil.WriteFile(path, d, 0644); err != nil {
|
|
return errors.Wrap(err, "Failed to write schema file")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getUnrunMigrations(ctx infra.DnoteCtx) ([]int, error) {
|
|
var ret []int
|
|
|
|
schema, err := readSchema(ctx)
|
|
if err != nil {
|
|
return ret, errors.Wrap(err, "Failed to read schema")
|
|
}
|
|
|
|
if schema.CurrentVersion == len(migrationSequence) {
|
|
return ret, nil
|
|
}
|
|
|
|
nextVersion := schema.CurrentVersion
|
|
ret = migrationSequence[nextVersion:]
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func updateSchemaVersion(ctx infra.DnoteCtx, mID int) error {
|
|
s, err := readSchema(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to read schema")
|
|
}
|
|
|
|
s.CurrentVersion = mID
|
|
|
|
err = writeSchema(ctx, s)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to write schema")
|
|
}
|
|
|
|
return nil
|
|
}
|