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 }