Restore database file
This commit is contained in:
parent
29efc3df4c
commit
04e744b8e5
17
pkg/db/db.go
17
pkg/db/db.go
|
@ -168,3 +168,20 @@ func initSqliteEngine() (engine *xorm.Engine, err error) {
|
||||||
|
|
||||||
return xorm.NewEngine("sqlite3", path)
|
return xorm.NewEngine("sqlite3", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WipeEverything wipes all tables and their data. Use with caution...
|
||||||
|
func WipeEverything() error {
|
||||||
|
|
||||||
|
tables, err := x.DBMetas()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range tables {
|
||||||
|
if err := x.DropTables(t.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -40,3 +40,14 @@ func Dump() (data map[string][]byte, err error) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Restore(table string, contents []map[string]interface{}) (err error) {
|
||||||
|
|
||||||
|
for _, content := range contents {
|
||||||
|
if _, err := x.Table(table).Insert(content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -40,15 +40,8 @@ func LightInit() {
|
||||||
log.InitLogger()
|
log.InitLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FullInit initializes all kinds of things in the right order
|
// InitEngines intializes all db connections
|
||||||
func FullInit() {
|
func InitEngines() {
|
||||||
|
|
||||||
LightInit()
|
|
||||||
|
|
||||||
// Run the migrations
|
|
||||||
migration.Migrate(nil)
|
|
||||||
|
|
||||||
// Set Engine
|
|
||||||
err := models.SetEngine()
|
err := models.SetEngine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
|
@ -65,6 +58,18 @@ func FullInit() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullInit initializes all kinds of things in the right order
|
||||||
|
func FullInit() {
|
||||||
|
|
||||||
|
LightInit()
|
||||||
|
|
||||||
|
// Run the migrations
|
||||||
|
migration.Migrate(nil)
|
||||||
|
|
||||||
|
// Set Engine
|
||||||
|
InitEngines()
|
||||||
|
|
||||||
// Initialize the files handler
|
// Initialize the files handler
|
||||||
files.InitFileHandler()
|
files.InitFileHandler()
|
||||||
|
|
|
@ -19,10 +19,18 @@ package dump
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
"code.vikunja.io/api/pkg/files"
|
||||||
|
"code.vikunja.io/api/pkg/initialize"
|
||||||
"code.vikunja.io/api/pkg/log"
|
"code.vikunja.io/api/pkg/log"
|
||||||
|
"code.vikunja.io/api/pkg/migration"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
"src.techknowlogick.com/xormigrate"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,21 +53,93 @@ func Restore(filename string) error {
|
||||||
return fmt.Errorf("invalid confirmation message")
|
return fmt.Errorf("invalid confirmation message")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the config file
|
// Find the configFile, database and files files
|
||||||
var config *zip.File
|
var configFile *zip.File
|
||||||
|
dbfiles := make(map[string]*zip.File)
|
||||||
|
filesFiles := make(map[string]*zip.File)
|
||||||
for _, file := range r.File {
|
for _, file := range r.File {
|
||||||
if strings.HasPrefix(file.Name, "config") {
|
if strings.HasPrefix(file.Name, "config") {
|
||||||
config = file
|
configFile = file
|
||||||
break
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(file.Name, "database/") {
|
||||||
|
fname := strings.ReplaceAll(file.Name, "database/", "")
|
||||||
|
dbfiles[fname[:len(fname)-5]] = file
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(file.Name, "files/") {
|
||||||
|
filesFiles[strings.ReplaceAll(file.Name, "files/", "")] = file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config == nil {
|
if configFile == nil {
|
||||||
return fmt.Errorf("dump does not contain a config file")
|
return fmt.Errorf("dump does not contain a config file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeFile(config, config.Name); err != nil {
|
///////
|
||||||
|
// Restore the config file
|
||||||
|
if err := writeFile(configFile, configFile.Name); err != nil {
|
||||||
return fmt.Errorf("could not create config file: %s", err)
|
return fmt.Errorf("could not create config file: %s", err)
|
||||||
}
|
}
|
||||||
|
log.Info("Restored configFile file.")
|
||||||
|
|
||||||
|
// Init the configFile again since the restored configuration is most likely different from the one before
|
||||||
|
initialize.LightInit()
|
||||||
|
initialize.InitEngines()
|
||||||
|
files.InitFileHandler()
|
||||||
|
|
||||||
|
///////
|
||||||
|
// Restore the db
|
||||||
|
// Start by wiping everything
|
||||||
|
if err := db.WipeEverything(); err != nil {
|
||||||
|
return fmt.Errorf("could not wipe database: %s", err)
|
||||||
|
}
|
||||||
|
log.Info("Wiped database.")
|
||||||
|
|
||||||
|
// Because we don't explicitly saved the table definitions, we take the last ran db migration from the dump
|
||||||
|
// and execute everything until that point.
|
||||||
|
migrations := dbfiles["migration"]
|
||||||
|
rc, err := migrations.Open()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not open migrations: %s", err)
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if _, err := buf.ReadFrom(rc); err != nil {
|
||||||
|
return fmt.Errorf("could not read migrations: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ms := []*xormigrate.Migration{}
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &ms); err != nil {
|
||||||
|
return fmt.Errorf("could not read migrations: %s", err)
|
||||||
|
}
|
||||||
|
sort.Slice(ms, func(i, j int) bool {
|
||||||
|
return ms[i].ID > ms[j].ID
|
||||||
|
})
|
||||||
|
|
||||||
|
lastMigration := ms[len(ms)-1]
|
||||||
|
if err := migration.MigrateTo(lastMigration.ID, nil); err != nil {
|
||||||
|
return fmt.Errorf("could not create db structure: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore all db data
|
||||||
|
for table, d := range dbfiles {
|
||||||
|
content, err := unmarshalFileToJson(d)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read table %s: %s", table, err)
|
||||||
|
}
|
||||||
|
if err := db.Restore(table, content); err != nil {
|
||||||
|
return fmt.Errorf("could not restore table data for table %s: %s", table, err)
|
||||||
|
}
|
||||||
|
log.Infof("Restored table %s", table)
|
||||||
|
}
|
||||||
|
log.Infof("Restored %d tables", len(dbfiles))
|
||||||
|
|
||||||
|
// Run migrations again to migrate a potentially outdated dump
|
||||||
|
migration.Migrate(nil)
|
||||||
|
|
||||||
|
///////
|
||||||
|
// Restore Files
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -81,3 +161,22 @@ func writeFile(file *zip.File, path string) error {
|
||||||
_, err = io.Copy(outFile, rc)
|
_, err = io.Copy(outFile, rc)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unmarshalFileToJson(file *zip.File) (contents []map[string]interface{}, err error) {
|
||||||
|
rc, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if _, err := buf.ReadFrom(rc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = []map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &contents); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue