diff --git a/GUIDE.md b/GUIDE.md index 275c167..d09c565 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -50,6 +50,7 @@ OPTIONS: -stats show statistics during and after backup -threads number of uploading threads -limit-rate the maximum upload rate (in kilobytes/sec) + -dry-run dry run for testing, don't backup anything. Use with -stats and -d -vss enable the Volume Shadow Copy service (Windows only) -storage backup to the specified storage instead of the default one ``` diff --git a/duplicacy/duplicacy_main.go b/duplicacy/duplicacy_main.go index 955fd74..c2ed886 100644 --- a/duplicacy/duplicacy_main.go +++ b/duplicacy/duplicacy_main.go @@ -623,12 +623,14 @@ func backupRepository(context *cli.Context) { enableVSS := context.Bool("vss") + dryRun := context.Bool("dry-run") uploadRateLimit := context.Int("limit-rate") storage.SetRateLimits(0, uploadRateLimit) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password) duplicacy.SavePassword(*preference, "password", password) backupManager.SetupSnapshotCache(preference.Name) + backupManager.SetDryRun(dryRun) backupManager.Backup(repository, quickMode, threads, context.String("t"), showStatistics, enableVSS) runScript(context, preference.Name, "post") @@ -1211,6 +1213,10 @@ func main() { Usage: "the maximum upload rate (in kilobytes/sec)", Argument: "", }, + cli.BoolFlag { + Name: "dry-run", + Usage: "Dry run for testing, don't backup anything. Use with -stats and -d", + }, cli.BoolFlag { Name: "vss", Usage: "enable the Volume Shadow Copy service (Windows only)", diff --git a/src/duplicacy_backupmanager.go b/src/duplicacy_backupmanager.go index 398a1ad..31b1c30 100644 --- a/src/duplicacy_backupmanager.go +++ b/src/duplicacy_backupmanager.go @@ -36,6 +36,10 @@ type BackupManager struct { } +func (manager *BackupManager) SetDryRun(dryRun bool) { + manager.config.dryRun = dryRun +} + // CreateBackupManager creates a backup manager using the specified 'storage'. 'snapshotID' is a unique id to // identify snapshots created for this repository. 'top' is the top directory of the repository. 'password' is the @@ -630,7 +634,9 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta } skippedFiles = append(skippedFiles, fileReader.SkippedFiles...) - manager.SnapshotManager.CleanSnapshotCache(localSnapshot, nil) + if !manager.config.dryRun { + manager.SnapshotManager.CleanSnapshotCache(localSnapshot, nil) + } LOG_INFO("BACKUP_END", "Backup for %s at revision %d completed", top, localSnapshot.Revision) RunAtError = func() {} @@ -1109,8 +1115,9 @@ func (manager *BackupManager) UploadSnapshot(chunkMaker *ChunkMaker, uploader *C } path := fmt.Sprintf("snapshots/%s/%d", manager.snapshotID, snapshot.Revision) - manager.SnapshotManager.UploadFile(path, path, description) - + if !manager.config.dryRun { + manager.SnapshotManager.UploadFile(path, path, description) + } return totalSnapshotChunkSize, numberOfNewSnapshotChunks, totalUploadedSnapshotChunkSize, totalUploadedSnapshotChunkBytes } diff --git a/src/duplicacy_chunkuploader.go b/src/duplicacy_chunkuploader.go index a9f2777..99cb900 100644 --- a/src/duplicacy_chunkuploader.go +++ b/src/duplicacy_chunkuploader.go @@ -134,13 +134,17 @@ func (uploader *ChunkUploader) Upload(threadIndex int, task ChunkUploadTask) boo return false } - err = uploader.storage.UploadFile(threadIndex, chunkPath, chunk.GetBytes()) - if err != nil { - LOG_ERROR("UPLOAD_CHUNK", "Failed to upload the chunk %s: %v", chunkID, err) - return false + if !uploader.config.dryRun { + err = uploader.storage.UploadFile(threadIndex, chunkPath, chunk.GetBytes()) + if err != nil { + LOG_ERROR("UPLOAD_CHUNK", "Failed to upload the chunk %s: %v", chunkID, err) + return false + } + LOG_DEBUG("CHUNK_UPLOAD", "Chunk %s has been uploaded", chunkID) + } else { + LOG_DEBUG("CHUNK_UPLOAD", "Uploading was skipped for chunk %s", chunkID) } - LOG_DEBUG("CHUNK_UPLOAD", "Chunk %s has been uploaded", chunkID) uploader.completionFunc(chunk, task.chunkIndex, false, chunkSize, chunk.GetLength()) atomic.AddInt32(&uploader.numberOfUploadingTasks, -1) return true diff --git a/src/duplicacy_config.go b/src/duplicacy_config.go index 8c3fb8e..e92f2f2 100644 --- a/src/duplicacy_config.go +++ b/src/duplicacy_config.go @@ -55,6 +55,7 @@ type Config struct { chunkPool chan *Chunk `json:"-"` numberOfChunks int32 + dryRun bool } // Create an alias to avoid recursive calls on Config.MarshalJSON diff --git a/src/duplicacy_snapshotmanager.go b/src/duplicacy_snapshotmanager.go index 4c984dc..8594334 100644 --- a/src/duplicacy_snapshotmanager.go +++ b/src/duplicacy_snapshotmanager.go @@ -594,19 +594,21 @@ func (manager *SnapshotManager) ListAllFiles(storage Storage, top string) (allFi } } - if top == "chunks/" { - // We're listing all chunks so this is the perfect place to detect if a directory contains too many - // chunks. Create sub-directories if necessary - if len(files) > 1024 && !storage.IsFastListing() { - for i := 0; i < 256; i++ { - subdir := dir + fmt.Sprintf("%02x\n", i) - manager.storage.CreateDirectory(0, subdir) + if !manager.config.dryRun { + if top == "chunks/" { + // We're listing all chunks so this is the perfect place to detect if a directory contains too many + // chunks. Create sub-directories if necessary + if len(files) > 1024 && !storage.IsFastListing() { + for i := 0; i < 256; i++ { + subdir := dir + fmt.Sprintf("%02x\n", i) + manager.storage.CreateDirectory(0, subdir) + } + } + } else { + // Remove chunk sub-directories that are empty + if len(files) == 0 && strings.HasPrefix(dir, "chunks/") && dir != "chunks/" { + storage.DeleteFile(0, dir) } - } - } else { - // Remove chunk sub-directories that are empty - if len(files) == 0 && strings.HasPrefix(dir, "chunks/") && dir != "chunks/" { - storage.DeleteFile(0, dir) } } }