mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-09-08 15:41:18 +02:00
wrapped backup zip copy operations in a mutex since the file hook can be triggered by different goroutine
This commit is contained in:
+56
-21
@@ -13,6 +13,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
@@ -150,22 +151,13 @@ func createZip(be *BackupEvent, tempZipPath string) error {
|
|||||||
be.App.onFilesystemNewWriter().Unbind(tempFilesHookId)
|
be.App.onFilesystemNewWriter().Unbind(tempFilesHookId)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
zf, err := os.Create(tempZipPath)
|
zipper, err := newZipWriter(tempZipPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// call defer even though z.f.Close will error if invoked multiple times
|
||||||
zw := zip.NewWriter(zf)
|
|
||||||
zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
|
|
||||||
return flate.NewWriter(out, flate.BestSpeed)
|
|
||||||
})
|
|
||||||
|
|
||||||
closeZip := func() error {
|
|
||||||
return errors.Join(zw.Close(), zf.Close())
|
|
||||||
}
|
|
||||||
// call defer even though zf.Close will error if invoked multiple times
|
|
||||||
// because otherwise the code become too brittle
|
// because otherwise the code become too brittle
|
||||||
defer closeZip()
|
defer zipper.close()
|
||||||
|
|
||||||
excluded := store.New[string, struct{}](nil)
|
excluded := store.New[string, struct{}](nil)
|
||||||
for _, name := range be.Exclude {
|
for _, name := range be.Exclude {
|
||||||
@@ -192,7 +184,7 @@ func createZip(be *BackupEvent, tempZipPath string) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// copy to zip before delete
|
// copy to zip before delete
|
||||||
err := copyFileToZip(zw, localPath, zipPath)
|
err := zipper.copyFileToZip(localPath, zipPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// it is ok to ignore directories
|
// it is ok to ignore directories
|
||||||
if !errors.Is(err, errIsDir) {
|
if !errors.Is(err, errIsDir) {
|
||||||
@@ -230,7 +222,7 @@ func createZip(be *BackupEvent, tempZipPath string) error {
|
|||||||
slog.Float64("execTime", float64(time.Since(dataStartTime))/float64(time.Millisecond)),
|
slog.Float64("execTime", float64(time.Since(dataStartTime))/float64(time.Millisecond)),
|
||||||
)
|
)
|
||||||
|
|
||||||
err = copyFileToZip(zw, tempDataDBPath, dataDBFilename)
|
err = zipper.copyFileToZip(tempDataDBPath, dataDBFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(tempDataDBPath)
|
_ = os.Remove(tempDataDBPath)
|
||||||
return err
|
return err
|
||||||
@@ -273,7 +265,7 @@ func createZip(be *BackupEvent, tempZipPath string) error {
|
|||||||
slog.Float64("execTime", float64(time.Since(auxStartTime))/float64(time.Millisecond)),
|
slog.Float64("execTime", float64(time.Since(auxStartTime))/float64(time.Millisecond)),
|
||||||
)
|
)
|
||||||
|
|
||||||
err = copyFileToZip(zw, tempAuxDBPath, auxDBFilename)
|
err = zipper.copyFileToZip(tempAuxDBPath, auxDBFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(tempAuxDBPath)
|
_ = os.Remove(tempAuxDBPath)
|
||||||
return err
|
return err
|
||||||
@@ -293,12 +285,12 @@ func createZip(be *BackupEvent, tempZipPath string) error {
|
|||||||
|
|
||||||
// copy the rest of the pb_data
|
// copy the rest of the pb_data
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
err = copyDirToZip(zw, os.DirFS(be.App.DataDir()), excluded)
|
err = zipper.copyDirToZip(os.DirFS(be.App.DataDir()), excluded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return closeZip()
|
return zipper.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize the provided file path to always end with forward slash
|
// normalize the provided file path to always end with forward slash
|
||||||
@@ -306,7 +298,44 @@ func normalizePathExclude(filePath string) string {
|
|||||||
return path.Clean(filePath) + "/"
|
return path.Clean(filePath) + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFileToZip(w *zip.Writer, localPath string, zipPath string) error {
|
type zipWriter struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
w *zip.Writer
|
||||||
|
f *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func newZipWriter(zipFilePath string) (*zipWriter, error) {
|
||||||
|
f, err := os.Create(zipFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := zip.NewWriter(f)
|
||||||
|
w.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
|
||||||
|
return flate.NewWriter(out, flate.BestSpeed)
|
||||||
|
})
|
||||||
|
|
||||||
|
return &zipWriter{
|
||||||
|
w: w,
|
||||||
|
f: f,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zipWriter) close() error {
|
||||||
|
var wErr, fErr error
|
||||||
|
|
||||||
|
if z.w != nil {
|
||||||
|
wErr = z.w.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if z.f != nil {
|
||||||
|
wErr = z.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(wErr, fErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zipWriter) copyFileToZip(localPath string, zipPath string) error {
|
||||||
info, err := os.Stat(localPath)
|
info, err := os.Stat(localPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -324,7 +353,10 @@ func copyFileToZip(w *zip.Writer, localPath string, zipPath string) error {
|
|||||||
h.Name = zipPath
|
h.Name = zipPath
|
||||||
h.Method = zip.Deflate
|
h.Method = zip.Deflate
|
||||||
|
|
||||||
fw, err := w.CreateHeader(h)
|
z.mu.Lock()
|
||||||
|
defer z.mu.Unlock()
|
||||||
|
|
||||||
|
fw, err := z.w.CreateHeader(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -340,7 +372,7 @@ func copyFileToZip(w *zip.Writer, localPath string, zipPath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyDirToZip(w *zip.Writer, fsys fs.FS, excludedPrefixes *store.Store[string, struct{}]) error {
|
func (z *zipWriter) copyDirToZip(fsys fs.FS, excludedPrefixes *store.Store[string, struct{}]) error {
|
||||||
return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error {
|
return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -377,7 +409,10 @@ func copyDirToZip(w *zip.Writer, fsys fs.FS, excludedPrefixes *store.Store[strin
|
|||||||
h.Name = name
|
h.Name = name
|
||||||
h.Method = zip.Deflate
|
h.Method = zip.Deflate
|
||||||
|
|
||||||
fw, err := w.CreateHeader(h)
|
z.mu.Lock()
|
||||||
|
defer z.mu.Unlock()
|
||||||
|
|
||||||
|
fw, err := z.w.CreateHeader(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user