diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 648c6fb..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.idea -duplicacy_main - diff --git a/src/duplicacy_backupmanager.go b/src/duplicacy_backupmanager.go index 2d82197..0179330 100644 --- a/src/duplicacy_backupmanager.go +++ b/src/duplicacy_backupmanager.go @@ -918,7 +918,9 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu if deleteMode && len(patterns) == 0 { - for _, file := range extraFiles { + // Reverse the order to make sure directories are empty before being deleted + for i := range extraFiles { + file := extraFiles[len(extraFiles) - 1 - i] fullPath := joinPath(top, file) os.Remove(fullPath) LOG_INFO("RESTORE_DELETE", "Deleted %s", file) @@ -932,8 +934,6 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu } } - RemoveEmptyDirectories(top) - if showStatistics { for _, file := range downloadedFiles { LOG_INFO("DOWNLOAD_DONE", "Downloaded %s (%d)", file.Path, file.Size) diff --git a/src/duplicacy_backupmanager_test.go b/src/duplicacy_backupmanager_test.go index 1f83e0a..b29699c 100644 --- a/src/duplicacy_backupmanager_test.go +++ b/src/duplicacy_backupmanager_test.go @@ -104,6 +104,27 @@ func modifyFile(path string, portion float32) { } } +func checkExistence(t *testing.T, path string, exists bool, isDir bool) { + stat, err := os.Stat(path) + if exists { + if err != nil { + t.Errorf("%s does not exist: %v", path, err) + } else if isDir { + if !stat.Mode().IsDir() { + t.Errorf("%s is not a directory", path) + } + } else { + if stat.Mode().IsDir() { + t.Errorf("%s is not a file", path) + } + } + } else { + if err == nil || !os.IsNotExist(err) { + t.Errorf("%s may exist: %v", path, err) + } + } +} + func truncateFile(path string) { file, err := os.OpenFile(path, os.O_WRONLY, 0644) if err != nil { @@ -261,13 +282,25 @@ func TestBackupManager(t *testing.T) { } } + // Truncate file2 and add a few empty directories truncateFile(testDir + "/repository1/file2") + os.Mkdir(testDir + "/repository1/dir2", 0700) + os.Mkdir(testDir + "/repository1/dir2/dir3", 0700) + os.Mkdir(testDir + "/repository1/dir4", 0700) SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy") backupManager.Backup(testDir + "/repository1", /*quickMode=*/false, threads, "third", false, false) time.Sleep(time.Duration(delay) * time.Second) + + // Create some directories and files under repository2 that will be deleted during restore + os.Mkdir(testDir + "/repository2/dir5", 0700) + os.Mkdir(testDir + "/repository2/dir5/dir6", 0700) + os.Mkdir(testDir + "/repository2/dir7", 0700) + createRandomFile(testDir + "/repository2/file4", 100) + createRandomFile(testDir + "/repository2/dir5/file5", 100) + SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy") backupManager.Restore(testDir + "/repository2", 3, /*inPlace=*/true, /*quickMode=*/false, threads, /*overwrite=*/true, - /*deleteMode=*/false, /*showStatistics=*/false, /*patterns=*/nil) + /*deleteMode=*/true, /*showStatistics=*/false, /*patterns=*/nil) for _, f := range []string{ "file1", "file2", "dir1/file3" } { hash1 := getFileHash(testDir + "/repository1/" + f) @@ -277,6 +310,18 @@ func TestBackupManager(t *testing.T) { } } + // These files/dirs should not exist because deleteMode == true + checkExistence(t, testDir + "/repository2/dir5", false, false); + checkExistence(t, testDir + "/repository2/dir5/dir6", false, false); + checkExistence(t, testDir + "/repository2/dir7", false, false); + checkExistence(t, testDir + "/repository2/file4", false, false); + checkExistence(t, testDir + "/repository2/dir5/file5", false, false); + + // These empty dirs should exist + checkExistence(t, testDir + "/repository2/dir2", true, true); + checkExistence(t, testDir + "/repository2/dir2/dir3", true, true); + checkExistence(t, testDir + "/repository2/dir4", true, true); + // Remove file2 and dir1/file3 and restore them from revision 3 os.Remove(testDir + "/repository1/file2") os.Remove(testDir + "/repository1/dir1/file3") diff --git a/src/duplicacy_utils.go b/src/duplicacy_utils.go index 6e9addc..3770c66 100644 --- a/src/duplicacy_utils.go +++ b/src/duplicacy_utils.go @@ -9,7 +9,6 @@ import ( "os" "bufio" "io" - "io/ioutil" "time" "path" "path/filepath" @@ -190,54 +189,6 @@ func SavePassword(preference Preference, passwordType string, password string) { keyringSet(passwordID, password) } -// RemoveEmptyDirectories remove all empty subdirectoreies under top. -func RemoveEmptyDirectories(top string) { - - stack := make([]string, 0, 256) - - stack = append(stack, top) - - for len(stack) > 0 { - - dir := stack[len(stack) - 1] - stack = stack[:len(stack) - 1] - - files, err := ioutil.ReadDir(dir) - - if err != nil { - continue - } - - for _, file := range files { - if file.IsDir() && file.Name()[0] != '.' { - stack = append(stack, path.Join(dir, file.Name())) - } - } - - if len(files) == 0 { - if os.Remove(dir) != nil { - continue - } - - dir = path.Dir(dir) - for (len(dir) > len(top)) { - files, err := ioutil.ReadDir(dir) - if err != nil { - break - } - - if len(files) == 0 { - if os.Remove(dir) != nil { - break; - } - } - dir = path.Dir(dir) - } - } - } -} - - // The following code was modified from the online article 'Matching Wildcards: An Algorithm', by Kirk J. Krauss, // Dr. Dobb's, August 26, 2008. However, the version in the article doesn't handle cases like matching 'abcccd' // against '*ccd', and the version here fixed that issue.