From d1b6ab46d6974b177309df2ffdec218fc33a6f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=9D=A8=E5=B8=86?= <39647285+leno23@users.noreply.github.com> Date: Sun, 17 May 2026 15:53:16 +0800 Subject: [PATCH] fix: allow resume without download state file Treat download.state2 as optional for --resume so an existing backup directory without state can be validated and resumed from scratch. --- pkg/backup/download.go | 18 +++++++++++------- pkg/backup/download_test.go | 12 ++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/pkg/backup/download.go b/pkg/backup/download.go index c469e488..b091f2b4 100644 --- a/pkg/backup/download.go +++ b/pkg/backup/download.go @@ -40,6 +40,16 @@ var ( ErrBackupIsAlreadyExists = errors.New("backup is already exists") ) +func (b *Backuper) resumeExistingBackup(backupName string) bool { + _, checkDownloadErr := os.Stat(path.Join(b.DefaultDataPath, "backup", backupName, "download.state2")) + if errors.Is(checkDownloadErr, os.ErrNotExist) { + log.Warn().Msgf("%s already exists but no download.state2 found, will resume download from scratch", backupName) + } else { + log.Warn().Msgf("%s already exists will try to resume download", backupName) + } + return true +} + func (b *Backuper) Download(backupName string, tablePattern string, partitions []string, schemaOnly, rbacOnly, configsOnly, namedCollectionsOnly, resume bool, hardlinkExistsFiles bool, backupVersion string, commandId int) error { if pidCheckErr := pidlock.CheckAndCreatePidFile(backupName, "download"); pidCheckErr != nil { return errors.WithMessage(pidCheckErr, "CheckAndCreatePidFile") @@ -87,14 +97,8 @@ func (b *Backuper) Download(backupName string, tablePattern string, partitions [ } if !b.resume { return ErrBackupIsAlreadyExists - } else { - _, checkDownloadErr := os.Stat(path.Join(b.DefaultDataPath, "backup", backupName, "download.state2")) - if errors.Is(checkDownloadErr, os.ErrNotExist) { - return ErrBackupIsAlreadyExists - } - isResumeExists = true - log.Warn().Msgf("%s already exists will try to resume download", backupName) } + isResumeExists = b.resumeExistingBackup(backupName) } } startDownload := time.Now() diff --git a/pkg/backup/download_test.go b/pkg/backup/download_test.go index 27ea7434..ac7c5f7e 100644 --- a/pkg/backup/download_test.go +++ b/pkg/backup/download_test.go @@ -1,6 +1,8 @@ package backup import ( + "os" + "path" "regexp" "testing" "time" @@ -91,6 +93,16 @@ var remoteBackup = storage.Backup{ UploadDate: time.Now(), } +func TestResumeExistingBackupAllowsMissingStateFile(t *testing.T) { + backupName := "test_resume_crash" + defaultDataPath := t.TempDir() + assert.NoError(t, os.MkdirAll(path.Join(defaultDataPath, "backup", backupName), 0o750)) + + backuper := &Backuper{DefaultDataPath: defaultDataPath} + + assert.True(t, backuper.resumeExistingBackup(backupName)) +} + func TestReBalanceTablesMetadataIfDiskNotExists_Files_NoErrors(t *testing.T) { remoteBackup.DataFormat = "tar" baseTable := metadata.TableMetadata{