qbolt/export.go

168 lines
3.3 KiB
Go

package main
import (
"archive/zip"
"bytes"
"fmt"
"io"
"io/fs"
"os"
"path"
"strings"
)
func Bolt_ExportDatabaseToZip(dbpath, zippath string) error {
db, err := Bolt_Open(true, dbpath)
if err != nil {
return fmt.Errorf("Error opening database: %w", err)
}
defer db.Close()
fh, err := os.OpenFile(zippath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("Error opening output file: %w", err)
}
defer fh.Close()
zw := zip.NewWriter(fh)
// Filenames in zip files cannot contain `/` characters. Mangle it
safename := func(n string) string {
return strings.ReplaceAll(string(n), `/`, `__`)
}
var process func(currentPath []string) error
process = func(currentPath []string) error {
return Bolt_ListBuckets(db, currentPath, func(bucket string) error {
// Create entry for our own bucket
ourBucket := zip.FileHeader{
Name: path.Join(path.Join(Apply(currentPath, safename)...), safename(bucket)) + `/`, // Trailing slash = directory
}
ourBucket.SetMode(fs.ModeDir | 0755)
_, err := zw.CreateHeader(&ourBucket)
if err != nil {
return err
}
// Child pathspec
childPath := CopySliceAdd(currentPath, bucket)
// Create file entries for all non-bucket children
err = Bolt_ListItems(db, childPath, func(li ListItemInfo) error {
fileItem := zip.FileHeader{
Name: path.Join(path.Join(Apply(childPath, safename)...), safename(string(li.Name))),
}
fileItem.SetMode(0644)
fileW, err := zw.CreateHeader(&fileItem)
if err != nil {
return err
}
buff, err := Bolt_GetItem(db, childPath, []byte(li.Name))
if err != nil {
return err
}
_, err = io.CopyN(fileW, bytes.NewReader(buff), li.DataLen)
return err
})
if err != nil {
return err
}
// Recurse for all bucket-type children
process(childPath)
// Done
return nil
})
}
err = process([]string{})
if err != nil {
return err
}
err = zw.Flush()
if err != nil {
return err
}
err = zw.Close()
if err != nil {
return err
}
return fh.Close()
}
func Bolt_ImportZipToDatabase(dbpath, zippath string) error {
db, err := Bolt_Open(false, dbpath)
if err != nil {
return fmt.Errorf("Error opening target database: %w", err)
}
defer db.Close()
fh, err := os.OpenFile(zippath, os.O_RDONLY, 0400)
if err != nil {
return fmt.Errorf("Error opening input archive: %w", err)
}
defer fh.Close()
fstat, err := fh.Stat()
if err != nil {
return err
}
zr, err := zip.NewReader(fh, fstat.Size())
if err != nil {
return fmt.Errorf("Reading zip file format: %w", err)
}
for _, zf := range zr.File {
if strings.HasSuffix(zf.Name, `/`) || (zf.Mode()&fs.ModeDir) != 0 {
// Bucket
bucketPath := strings.Split(strings.TrimSuffix(zf.Name, `/`), `/`)
err = Bolt_CreateBucket(db, bucketPath[0:len(bucketPath)-1], bucketPath[len(bucketPath)-1])
if err != nil {
return fmt.Errorf("Creating bucket %q: %w", zf.Name, err)
}
} else {
// Object
objectPath := strings.Split(zf.Name, `/`)
rc, err := zf.Open()
if err != nil {
return err
}
content, err := io.ReadAll(rc)
if err != nil {
return err
}
err = Bolt_SetItem(db, objectPath[0:len(objectPath)-1], []byte(objectPath[len(objectPath)-1]), content)
if err != nil {
return err
}
err = rc.Close()
if err != nil {
return err
}
}
}
// Done
return nil
}