From 5d336c98330c94864aefa1780d456479c2aa0bd1 Mon Sep 17 00:00:00 2001 From: Eduardo Grajeda Date: Thu, 4 Jul 2024 19:58:31 +0200 Subject: [PATCH] feat: do not recover if the computer restarted I could have started my other computer in the meantime, and synced new things into remote. I don't want arkiv to override with "sync" what was sent by my other computer. --- arkiv-cli/arkiv.go | 49 +++++++++++++-- ...=> arkiv_bisyncOrSyncBasedOnState_test.go} | 61 +++++++++++++------ arkiv-cli/arkiv_test.go | 45 +++++++++++--- 3 files changed, 122 insertions(+), 33 deletions(-) rename arkiv-cli/{arkiv_bisyncOrSyncBasedOnStatus_test.go => arkiv_bisyncOrSyncBasedOnState_test.go} (78%) diff --git a/arkiv-cli/arkiv.go b/arkiv-cli/arkiv.go index 149ea46..cb2c6b6 100644 --- a/arkiv-cli/arkiv.go +++ b/arkiv-cli/arkiv.go @@ -75,7 +75,7 @@ func main() { os.Exit(0) } - lastRunStatus := bisyncOrSyncBasedOnCacheFile(rclone, configuration, getLastRunStateFilePath()) + lastRunStatus := bisyncOrSync(rclone, configuration, getLastRunStateFilePath(), getBisyncFailedAfterBootFilePath()) if lastRunStatus == LastRunBisyncSucceeded { os.Exit(0) } @@ -97,23 +97,25 @@ func resync(rclone rcloneRunner, configuration configuration) error { return nil } -func bisyncOrSyncBasedOnCacheFile(rclone rcloneRunner, configuration configuration, path string) string { - lastRun := readLastRunState(path) +func bisyncOrSync(rclone rcloneRunner, configuration configuration, lastRunStateFilePath string, bisyncFailedAfterBootFilePath string) string { + lastRun := readLastRunState(lastRunStateFilePath) + bisyncFailedAfterBoot := readBisyncFailedAfterBoot(bisyncFailedAfterBootFilePath) - newLastRunStatus := bisyncOrSyncBasedOnStatus(rclone, configuration, lastRun.Status) + newLastRunStatus := bisyncOrSyncBasedOnState(rclone, configuration, lastRun.Status, bisyncFailedAfterBoot) if !configuration.dryRun { newLastRun := lastRunState{ Datetime: time.Now().UTC(), Status: newLastRunStatus, } - writeLastRunState(path, newLastRun) + writeLastRunState(lastRunStateFilePath, newLastRun) + writeBisyncFailedAfterBoot(bisyncFailedAfterBootFilePath, newLastRunStatus != LastRunBisyncSucceeded) } return newLastRunStatus } -func bisyncOrSyncBasedOnStatus(rclone rcloneRunner, configuration configuration, lastRunStatus string) string { +func bisyncOrSyncBasedOnState(rclone rcloneRunner, configuration configuration, lastRunStatus string, bisyncFailedAfterBoot bool) string { log.Println("NOTICE: Arkiv - Last run status is:", lastRunStatus) switch lastRunStatus { @@ -140,6 +142,11 @@ func bisyncOrSyncBasedOnStatus(rclone rcloneRunner, configuration configuration, return lastRunStatus } + if !bisyncFailedAfterBoot { + log.Println("NOTICE: Arkiv - Skipping rclone sync because bisync failed before last boot") + return lastRunStatus + } + log.Println("NOTICE: Arkiv - Running rclone sync...") output, err := sync(rclone, configuration) @@ -209,3 +216,33 @@ func writeLastRunState(path string, lastRun lastRunState) { log.Fatal(err) } } + +func getBisyncFailedAfterBootFilePath() string { + return path.Join(os.TempDir(), "arkiv-failed-after-boot") +} + +func readBisyncFailedAfterBoot(path string) bool { + _, err := os.Stat(path) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return false + } + log.Fatal(err) + } + + return true +} + +func writeBisyncFailedAfterBoot(path string, failed bool) { + if failed { + err := os.WriteFile(path, nil, 0644) + if err != nil { + log.Fatal(err) + } + } else { + err := os.Remove(path) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + log.Fatal(err) + } + } +} diff --git a/arkiv-cli/arkiv_bisyncOrSyncBasedOnStatus_test.go b/arkiv-cli/arkiv_bisyncOrSyncBasedOnState_test.go similarity index 78% rename from arkiv-cli/arkiv_bisyncOrSyncBasedOnStatus_test.go rename to arkiv-cli/arkiv_bisyncOrSyncBasedOnState_test.go index 13e53c5..952863f 100644 --- a/arkiv-cli/arkiv_bisyncOrSyncBasedOnStatus_test.go +++ b/arkiv-cli/arkiv_bisyncOrSyncBasedOnState_test.go @@ -16,7 +16,7 @@ func Test_shouldRunBisync(t *testing.T) { } // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, LastRunBisyncSucceeded) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, LastRunBisyncSucceeded, false) // Then // TODO: Assert we print the output @@ -35,7 +35,7 @@ func Test_shouldRunBisync_AndFail_WhenCorruptedOnTransfer(t *testing.T) { } // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, LastRunBisyncSucceeded) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, LastRunBisyncSucceeded, false) // Then assert.Equal(t, LastRunBisyncFailedWithCorruptedOnTransfer, nextLastRunStatus) @@ -52,7 +52,7 @@ func Test_shouldRunBisync_AndFail_WhenUnknownError(t *testing.T) { } // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, LastRunBisyncSucceeded) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, LastRunBisyncSucceeded, false) // Then assert.Equal(t, LastRunBisyncFailedWithUnknownError, nextLastRunStatus) @@ -70,7 +70,7 @@ func Test_shouldRunBisync_WithDryRun(t *testing.T) { } // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, LastRunBisyncSucceeded) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, LastRunBisyncSucceeded, false) // Then assert.Equal(t, LastRunBisyncSucceeded, nextLastRunStatus) @@ -88,7 +88,7 @@ func Test_shouldRunBisync_AndFail_WithDryRun(t *testing.T) { } // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, LastRunBisyncSucceeded) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, LastRunBisyncSucceeded, false) // Then assert.Equal(t, LastRunBisyncFailedWithCorruptedOnTransfer, nextLastRunStatus) @@ -106,7 +106,7 @@ func Test_shouldRunBisync_AndDeleteFiles_OnlyWhenRequested(t *testing.T) { } // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, LastRunBisyncSucceeded) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, LastRunBisyncSucceeded, false) // Then assert.Equal(t, LastRunBisyncSucceeded, nextLastRunStatus) @@ -124,7 +124,7 @@ func Test_shouldRunBisync_AndLogToFile(t *testing.T) { } // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, LastRunBisyncSucceeded) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, LastRunBisyncSucceeded, false) // Then assert.Equal(t, LastRunBisyncSucceeded, nextLastRunStatus) @@ -141,9 +141,10 @@ func Test_shouldRunSync_OnlyWhenRecoverIsEnabled(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, LastRunBisyncSucceeded, nextLastRunStatus) @@ -161,9 +162,10 @@ func Test_shouldRunSync_AndFail_WhenCorruptedOnTransfer(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, LastRunBisyncFailedWithCorruptedOnTransfer, nextLastRunStatus) @@ -181,9 +183,10 @@ func Test_shouldRunSync_AndFail_WhenUnknownError(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, LastRunBisyncFailedWithUnknownError, nextLastRunStatus) @@ -202,9 +205,10 @@ func Test_shouldRunSync_WithDryRun(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, LastRunBisyncSucceeded, nextLastRunStatus) @@ -223,9 +227,10 @@ func Test_shouldRunSync_AndFail_WithDryRun(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, LastRunBisyncFailedWithCorruptedOnTransfer, nextLastRunStatus) @@ -244,9 +249,10 @@ func Test_shouldRunSync_AndDeleteFiles_OnlyWhenRequested(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, LastRunBisyncSucceeded, nextLastRunStatus) @@ -265,9 +271,10 @@ func Test_shouldRunSync_AndLogToFile(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, LastRunBisyncSucceeded, nextLastRunStatus) @@ -285,9 +292,10 @@ func Test_shouldNotRunSync_WhenRecoverIsDisabled(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, lastRunStatus, nextLastRunStatus) @@ -295,7 +303,25 @@ func Test_shouldNotRunSync_WhenRecoverIsDisabled(t *testing.T) { assert.Empty(t, runner.bisyncResyncArgs) } -// TODO: Should not sync if the computer restarted +func Test_shouldNotRunSync_WhenBisyncFailedBeforeLastBoot(t *testing.T) { + // Given + runner := newRcloneRunnerBuilder().Build() + configuration := configuration{ + path1: "/local", + path2: "remote:", + recover: true, + } + lastRunStatus := LastRunBisyncFailedWithCorruptedOnTransfer + bisyncFailedAfterBoot := false + + // When + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) + + // Then + assert.Equal(t, lastRunStatus, nextLastRunStatus) + assert.Empty(t, runner.syncArgs) + assert.Empty(t, runner.bisyncResyncArgs) +} func Test_shouldDoNothing_WhenUnknownError(t *testing.T) { // Given @@ -306,9 +332,10 @@ func Test_shouldDoNothing_WhenUnknownError(t *testing.T) { defaultArgs: []string{"--drive-skip-gdocs"}, } lastRunStatus := LastRunBisyncFailedWithUnknownError + bisyncFailedAfterBoot := true // When - nextLastRunStatus := bisyncOrSyncBasedOnStatus(&runner, configuration, lastRunStatus) + nextLastRunStatus := bisyncOrSyncBasedOnState(&runner, configuration, lastRunStatus, bisyncFailedAfterBoot) // Then assert.Equal(t, lastRunStatus, nextLastRunStatus) diff --git a/arkiv-cli/arkiv_test.go b/arkiv-cli/arkiv_test.go index afc1e42..12e8783 100644 --- a/arkiv-cli/arkiv_test.go +++ b/arkiv-cli/arkiv_test.go @@ -10,30 +10,32 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_bisyncOrSyncBasedOnCacheFile_shouldReadAndWriteLastRunState(t *testing.T) { +func Test_bisyncOrSync_shouldReadAndWriteLastRunState_AndBisyncFailedAfterBoot(t *testing.T) { // Given runner := newRcloneRunnerBuilder().WithCorruptedOnTransferErrorOn(RcloneBisync).Build() configuration := configuration{ path1: "/local", path2: "remote:", } - pathDirectory, _ := os.MkdirTemp(os.TempDir(), "arkiv-test") - path := path.Join(pathDirectory, "last-run.json") + lastRunStateFilePath := path.Join(t.TempDir(), "last-run.json") + bisyncFailedAfterBootFilePath := path.Join(t.TempDir(), "arkiv-failed-after-boot") // When - lastRunStatus := bisyncOrSyncBasedOnCacheFile(&runner, configuration, path) + lastRunStatus := bisyncOrSync(&runner, configuration, lastRunStateFilePath, bisyncFailedAfterBootFilePath) // Then assert.Equal(t, LastRunBisyncFailedWithCorruptedOnTransfer, lastRunStatus) var lastRunStateInFile lastRunState - lastRunStateInFileAsJson, _ := os.ReadFile(path) + lastRunStateInFileAsJson, _ := os.ReadFile(lastRunStateFilePath) json.Unmarshal(lastRunStateInFileAsJson, &lastRunStateInFile) assert.WithinDuration(t, time.Now().UTC(), lastRunStateInFile.Datetime, 1*time.Second) assert.Equal(t, LastRunBisyncFailedWithCorruptedOnTransfer, lastRunStateInFile.Status) + + assert.FileExists(t, bisyncFailedAfterBootFilePath) } -func Test_bisyncOrSyncBasedOnCacheFile_shouldNotWriteLastRunStatus_WhenDryRun(t *testing.T) { +func Test_bisyncOrSync_shouldNotWriteLastRunStatus_WhenDryRun(t *testing.T) { // Given runner := newRcloneRunnerBuilder().WithCorruptedOnTransferErrorOn(RcloneBisync).Build() configuration := configuration{ @@ -41,15 +43,38 @@ func Test_bisyncOrSyncBasedOnCacheFile_shouldNotWriteLastRunStatus_WhenDryRun(t path2: "remote:", dryRun: true, } - pathDirectory, _ := os.MkdirTemp(os.TempDir(), "arkiv-test") - path := path.Join(pathDirectory, "last-run.json") + lastRunStateFilePath := path.Join(t.TempDir(), "last-run.json") + bisyncFailedAfterBootFilePath := path.Join(t.TempDir(), "arkiv-failed-after-boot") // When - lastRunStatus := bisyncOrSyncBasedOnCacheFile(&runner, configuration, path) + lastRunStatus := bisyncOrSync(&runner, configuration, lastRunStateFilePath, bisyncFailedAfterBootFilePath) // Then assert.Equal(t, LastRunBisyncFailedWithCorruptedOnTransfer, lastRunStatus) - assert.NoFileExists(t, path) + assert.NoFileExists(t, lastRunStateFilePath) + assert.NoFileExists(t, bisyncFailedAfterBootFilePath) +} + +func Test_bisyncOrSync_shouldRunBisync_AndFail_AndRecover(t *testing.T) { + // Given + runner := newRcloneRunnerBuilder().WithCorruptedOnTransferErrorOn(RcloneBisync).Build() + configuration := configuration{ + path1: "/local", + path2: "remote:", + recover: true, + } + lastRunStateFilePath := path.Join(t.TempDir(), "last-run.json") + bisyncFailedAfterBootFilePath := path.Join(t.TempDir(), "arkiv-failed-after-boot") + + // When + lastRunStatus1 := bisyncOrSync(&runner, configuration, lastRunStateFilePath, bisyncFailedAfterBootFilePath) + lastRunStatus2 := bisyncOrSync(&runner, configuration, lastRunStateFilePath, bisyncFailedAfterBootFilePath) + + // Then + assert.Equal(t, LastRunBisyncFailedWithCorruptedOnTransfer, lastRunStatus1) + assert.Equal(t, LastRunBisyncSucceeded, lastRunStatus2) + + assert.NoFileExists(t, bisyncFailedAfterBootFilePath) } func Test_getLastRunStateFilePath_shouldUseUserCacheDir(t *testing.T) { -- 2.45.2