package database import ( domain "RenewCMS/internal/domain/update" "bufio" "os" "time" ) const ( dumpFileExtension = ".sql" dumpPartialFileExtension = ".partial" ) type DumpBackup struct { open func() (DumpSource, error) directory string now func() time.Time dialect dumpDialect } var _ domain.DatabaseBackup = &DumpBackup{} func NewDumpBackup(open func() (DumpSource, error), directory string, now func() time.Time) *DumpBackup { return &DumpBackup{open: open, directory: directory, now: now, dialect: mysqlDumpDialect} } func (b *DumpBackup) Backup() (string, error) { target, err := newBackupTarget(b.directory, b.now(), dumpFileExtension) if err != nil { return "", err } if err := b.writeAtomically(target); err != nil { return "", err } if err := pruneBackups(b.directory, dumpFileExtension); err != nil { return "", err } return target, nil } func (b *DumpBackup) writeAtomically(target string) error { partial := target + dumpPartialFileExtension if err := b.writePartial(partial); err != nil { _ = os.Remove(partial) return err } if err := os.Rename(partial, target); err != nil { _ = os.Remove(partial) return err } return nil } func (b *DumpBackup) writePartial(partial string) error { source, err := b.open() if err != nil { return err } defer source.Close() file, err := os.OpenFile(partial, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) if err != nil { return err } buffered := bufio.NewWriter(file) if err := writeDump(buffered, source, b.dialect); err != nil { _ = file.Close() return err } if err := buffered.Flush(); err != nil { _ = file.Close() return err } if err := file.Sync(); err != nil { _ = file.Close() return err } return file.Close() }