mirror of
https://github.com/stashapp/stash.git
synced 2026-02-28 18:22:59 +01:00
185 lines
4.9 KiB
Go
185 lines
4.9 KiB
Go
package manager
|
|
|
|
import (
|
|
"archive/zip"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/stashapp/stash/internal/manager/config"
|
|
"github.com/stashapp/stash/pkg/fsutil"
|
|
"github.com/stashapp/stash/pkg/logger"
|
|
)
|
|
|
|
type databaseBackupZip struct {
|
|
*zip.Writer
|
|
}
|
|
|
|
func (z *databaseBackupZip) zipFileRename(fn, outDir, outFn string) error {
|
|
p := filepath.Join(outDir, outFn)
|
|
p = filepath.ToSlash(p)
|
|
|
|
f, err := z.Create(p)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating zip entry for %s: %v", fn, err)
|
|
}
|
|
|
|
i, err := os.Open(fn)
|
|
if err != nil {
|
|
return fmt.Errorf("error opening %s: %v", fn, err)
|
|
}
|
|
|
|
defer i.Close()
|
|
|
|
if _, err := io.Copy(f, i); err != nil {
|
|
return fmt.Errorf("error writing %s to zip: %v", fn, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (z *databaseBackupZip) zipFile(fn, outDir string) error {
|
|
return z.zipFileRename(fn, outDir, filepath.Base(fn))
|
|
}
|
|
|
|
func (s *Manager) BackupDatabase(download bool, includeBlobs bool) (string, string, error) {
|
|
var backupPath string
|
|
var backupName string
|
|
|
|
// if we include blobs, then the output is a zip file
|
|
// if not, using the same backup logic as before, which creates a sqlite file
|
|
if !includeBlobs || s.Config.GetBlobsStorage() != config.BlobStorageTypeFilesystem {
|
|
return s.backupDatabaseOnly(download)
|
|
}
|
|
|
|
// use tmp directory for the backup
|
|
backupDir := s.Paths.Generated.Tmp
|
|
if err := fsutil.EnsureDir(backupDir); err != nil {
|
|
return "", "", fmt.Errorf("could not create backup directory %v: %w", backupDir, err)
|
|
}
|
|
f, err := os.CreateTemp(backupDir, "backup*.sqlite")
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
backupPath = f.Name()
|
|
backupName = s.Database.DatabaseBackupPath("")
|
|
f.Close()
|
|
|
|
// delete the temp file so that the backup operation can create it
|
|
if err := os.Remove(backupPath); err != nil {
|
|
return "", "", fmt.Errorf("could not remove temporary backup file %v: %w", backupPath, err)
|
|
}
|
|
|
|
if err := s.Database.Backup(backupPath); err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
// create a zip file
|
|
zipFileDir := s.Paths.Generated.Downloads
|
|
if !download {
|
|
zipFileDir = s.Config.GetBackupDirectoryPathOrDefault()
|
|
if zipFileDir != "" {
|
|
if err := fsutil.EnsureDir(zipFileDir); err != nil {
|
|
return "", "", fmt.Errorf("could not create backup directory %v: %w", zipFileDir, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
zipFileName := backupName + ".zip"
|
|
zipFilePath := filepath.Join(zipFileDir, zipFileName)
|
|
|
|
logger.Debugf("Preparing zip file for database backup at %v", zipFilePath)
|
|
|
|
zf, err := os.Create(zipFilePath)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("could not create zip file %v: %w", zipFilePath, err)
|
|
}
|
|
defer zf.Close()
|
|
|
|
z := databaseBackupZip{
|
|
Writer: zip.NewWriter(zf),
|
|
}
|
|
|
|
defer z.Close()
|
|
|
|
// move the database file into the zip
|
|
dbFn := filepath.Base(s.Config.GetDatabasePath())
|
|
if err := z.zipFileRename(backupPath, "", dbFn); err != nil {
|
|
return "", "", fmt.Errorf("could not add database backup to zip file: %w", err)
|
|
}
|
|
|
|
if err := os.Remove(backupPath); err != nil {
|
|
return "", "", fmt.Errorf("could not remove temporary backup file %v: %w", backupPath, err)
|
|
}
|
|
|
|
// walk the blobs directory and add files to the zip
|
|
blobsDir := s.Config.GetBlobsPath()
|
|
err = filepath.WalkDir(blobsDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
// calculate out dir by removing the blobsDir prefix from the path
|
|
outDir := filepath.Join("blobs", strings.TrimPrefix(filepath.Dir(path), blobsDir))
|
|
if err := z.zipFile(path, outDir); err != nil {
|
|
return fmt.Errorf("could not add blob %v to zip file: %w", path, err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("error walking blobs directory: %w", err)
|
|
}
|
|
|
|
return zipFilePath, zipFileName, nil
|
|
}
|
|
|
|
func (s *Manager) backupDatabaseOnly(download bool) (string, string, error) {
|
|
var backupPath string
|
|
var backupName string
|
|
|
|
if download {
|
|
backupDir := s.Paths.Generated.Downloads
|
|
if err := fsutil.EnsureDir(backupDir); err != nil {
|
|
return "", "", fmt.Errorf("could not create backup directory %v: %w", backupDir, err)
|
|
}
|
|
f, err := os.CreateTemp(backupDir, "backup*.sqlite")
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
backupPath = f.Name()
|
|
backupName = s.Database.DatabaseBackupPath("")
|
|
f.Close()
|
|
|
|
// delete the temp file so that the backup operation can create it
|
|
if err := os.Remove(backupPath); err != nil {
|
|
return "", "", fmt.Errorf("could not remove temporary backup file %v: %w", backupPath, err)
|
|
}
|
|
} else {
|
|
backupDir := s.Config.GetBackupDirectoryPathOrDefault()
|
|
if backupDir != "" {
|
|
if err := fsutil.EnsureDir(backupDir); err != nil {
|
|
return "", "", fmt.Errorf("could not create backup directory %v: %w", backupDir, err)
|
|
}
|
|
}
|
|
backupPath = s.Database.DatabaseBackupPath(backupDir)
|
|
backupName = filepath.Base(backupPath)
|
|
}
|
|
|
|
err := s.Database.Backup(backupPath)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
return backupPath, backupName, nil
|
|
}
|