168 lines
3.3 KiB
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
|
|
}
|