mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-04-29 19:10:51 -05:00
fix(dump): stream files during restore to avoid memory pressure
Use a temporary file instead of io.ReadAll when restoring attachments from a dump. This prevents loading entire files into memory, which could cause OOM errors for large attachments during restore.
This commit is contained in:
@@ -174,21 +174,8 @@ func Restore(filename string, overrideConfig bool) error {
|
||||
return fmt.Errorf("could not parse file id %s: %w", i, err)
|
||||
}
|
||||
|
||||
f := &files.File{ID: id}
|
||||
|
||||
fc, err := file.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open file %s: %w", i, err)
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(fc)
|
||||
_ = fc.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read file %s: %w", i, err)
|
||||
}
|
||||
|
||||
if err := f.Save(bytes.NewReader(content)); err != nil {
|
||||
return fmt.Errorf("could not save file: %w", err)
|
||||
if err := restoreFile(id, file); err != nil {
|
||||
return fmt.Errorf("could not restore file %s: %w", i, err)
|
||||
}
|
||||
log.Infof("Restored file %s", i)
|
||||
}
|
||||
@@ -204,6 +191,38 @@ func Restore(filename string, overrideConfig bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreFile(id int64, zipFile *zip.File) error {
|
||||
f := &files.File{ID: id}
|
||||
|
||||
fc, err := zipFile.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open zip entry: %w", err)
|
||||
}
|
||||
defer fc.Close()
|
||||
|
||||
// Create a temporary file to make the content seekable without loading
|
||||
// it all into memory. zip.File.Open() returns io.ReadCloser which is not
|
||||
// seekable, but f.Save requires io.ReadSeeker.
|
||||
tmpFile, err := os.CreateTemp("", "vikunja-restore-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create temp file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = tmpFile.Close()
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(tmpFile, fc); err != nil {
|
||||
return fmt.Errorf("could not copy to temp file: %w", err)
|
||||
}
|
||||
|
||||
if _, err := tmpFile.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("could not seek temp file: %w", err)
|
||||
}
|
||||
|
||||
return f.Save(tmpFile)
|
||||
}
|
||||
|
||||
func convertFieldValue(fieldName string, value interface{}, isFloat bool) (interface{}, error) {
|
||||
// Check if this is a float field and the value is already a number
|
||||
if isFloat {
|
||||
|
||||
Reference in New Issue
Block a user