Added reported statistics

This commit is contained in:
Kenneth Skovhede
2026-06-26 11:45:56 +02:00
parent b661c00037
commit f529ff9402
5 changed files with 158 additions and 4 deletions
@@ -579,8 +579,64 @@ namespace Duplicati.Library.Interface
{
}
/// <summary>
/// Results of a sync operation.
/// </summary>
public interface ISyncResults : IBasicResults
{
/// <summary>
/// The number of folders created on the remote destination.
/// </summary>
long FoldersCreated { get; }
/// <summary>
/// The number of folders deleted from the remote destination.
/// </summary>
long FoldersDeleted { get; }
/// <summary>
/// The number of files uploaded to the remote destination (new files plus
/// updated files whose content changed).
/// </summary>
long FilesUploaded { get; }
/// <summary>
/// The number of files that were examined but left unchanged because the
/// local and remote content already matched (size/mtime, or hash when
/// <c>--sync-verify-hash</c> is set).
/// </summary>
long UnchangedFiles { get; }
/// <summary>
/// The number of files deleted from the remote destination (unknown remote
/// files removed when <c>--sync-then-delete</c> is set).
/// </summary>
long FilesDeleted { get; }
/// <summary>
/// The total number of local source files encountered during enumeration
/// across all folders, regardless of whether they were uploaded, unchanged,
/// or skipped.
/// </summary>
long SourceFiles { get; }
/// <summary>
/// The total size in bytes of all local source files encountered during
/// enumeration, regardless of whether they were uploaded or left unchanged.
/// </summary>
long SizeOfSourceFiles { get; }
/// <summary>
/// The total size in bytes of the files uploaded to the remote destination
/// (new files plus updated files whose content changed).
/// </summary>
long SizeOfUploadedFiles { get; }
/// <summary>
/// The total size in bytes of the files deleted from the remote destination
/// (unknown remote files removed when <c>--sync-then-delete</c> is set).
/// </summary>
long SizeOfDeletedFiles { get; }
}
public interface ISetLockResults : IBasicResults
+20 -1
View File
@@ -266,6 +266,16 @@ public class VacuumResultsDto : BasicResultsDto
[Serializable]
public class SyncResultsDto : BasicResultsDto
{
public long FoldersCreated { get; set; }
public long FoldersDeleted { get; set; }
public long FilesUploaded { get; set; }
public long UnchangedFiles { get; set; }
public long FilesDeleted { get; set; }
public long SourceFiles { get; set; }
public long SizeOfSourceFiles { get; set; }
public long SizeOfUploadedFiles { get; set; }
public long SizeOfDeletedFiles { get; set; }
public static SyncResultsDto FromResults(ISyncResults results)
{
if (results == null) return null;
@@ -278,7 +288,16 @@ public class SyncResultsDto : BasicResultsDto
Warnings = results.Warnings?.ToList() ?? new List<string>(),
Messages = results.Messages?.ToList() ?? new List<string>(),
ParsedResult = results.ParsedResult,
Interrupted = results.Interrupted
Interrupted = results.Interrupted,
FoldersCreated = results.FoldersCreated,
FoldersDeleted = results.FoldersDeleted,
FilesUploaded = results.FilesUploaded,
UnchangedFiles = results.UnchangedFiles,
FilesDeleted = results.FilesDeleted,
SourceFiles = results.SourceFiles,
SizeOfSourceFiles = results.SizeOfSourceFiles,
SizeOfUploadedFiles = results.SizeOfUploadedFiles,
SizeOfDeletedFiles = results.SizeOfDeletedFiles
};
}
}
@@ -162,6 +162,18 @@ public class VacuumResultsWrapper : BasicResultsWrapper, IVacuumResults
public class SyncResultsWrapper : BasicResultsWrapper, ISyncResults
{
public SyncResultsWrapper(SyncResultsDto dto) : base(dto) { }
protected new SyncResultsDto _dto => (SyncResultsDto)base._dto;
public long FoldersCreated => _dto.FoldersCreated;
public long FoldersDeleted => _dto.FoldersDeleted;
public long FilesUploaded => _dto.FilesUploaded;
public long UnchangedFiles => _dto.UnchangedFiles;
public long FilesDeleted => _dto.FilesDeleted;
public long SourceFiles => _dto.SourceFiles;
public long SizeOfSourceFiles => _dto.SizeOfSourceFiles;
public long SizeOfUploadedFiles => _dto.SizeOfUploadedFiles;
public long SizeOfDeletedFiles => _dto.SizeOfDeletedFiles;
}
/// <summary>
@@ -168,6 +168,24 @@ internal class SyncHandler
}
Logging.Log.WriteInformationMessage(LOGTAG, "SyncPlan", "Plan: Upload {0}, Update {1}, Delete {2}", planSummary.Upload, planSummary.Update, planSummary.Delete);
// Flush the running counters into the results so callers (CLI, IPC, web UI)
// see folders created, files uploaded/unchanged/deleted, and the total
// files/size encountered. Upload+Update is the combined "files uploaded"
// count; Delete maps to files deleted from the remote.
m_results.FoldersCreated = planSummary.FoldersCreated;
// The handler deletes unknown remote files only, never remote folders, so
// the folders-deleted count is always zero today; the field is exposed for
// completeness and future folder-deletion support.
m_results.FoldersDeleted = 0;
m_results.FilesUploaded = planSummary.Upload + planSummary.Update;
m_results.UnchangedFiles = planSummary.UnchangedFiles;
m_results.FilesDeleted = planSummary.Delete;
m_results.SourceFiles = planSummary.SourceFiles;
m_results.SizeOfSourceFiles = planSummary.SizeOfSourceFiles;
m_results.SizeOfUploadedFiles = planSummary.SizeOfUploadedFiles;
m_results.SizeOfDeletedFiles = planSummary.SizeOfDeletedFiles;
Logging.Log.WriteInformationMessage(LOGTAG, "SyncFinished", "Sync completed successfully.");
}
@@ -241,6 +259,12 @@ internal class SyncHandler
}
else
{
// Count every local source file encountered (regardless of whether it
// ends up uploaded, unchanged, or de-duplicated) so SourceFiles and
// SizeOfSourceFiles reflect the full local inventory scanned this run.
planSummary.SourceFiles++;
planSummary.SizeOfSourceFiles += Math.Max(child.Size, 0);
// INSERT OR IGNORE semantics: the first entry seen for a path wins, so a
// later duplicate in the same folder is ignored (paths are unique within
// a single folder in practice).
@@ -403,6 +427,7 @@ internal class SyncHandler
Logging.Log.WriteDryrunMessage(LOGTAG, "DryRun", "Would create folder: {0}", subRelPath);
else
await backendManager.EnsureFolderAsync(subRelPath, ct).ConfigureAwait(false);
planSummary.FoldersCreated++;
}
folderQueue.Enqueue(new FolderWorkItem(subEntry, subRelPath));
@@ -416,8 +441,14 @@ internal class SyncHandler
// either already has files (so it exists) or is brand new, in which case the
// sub-folder ensure above created it when it was enqueued as a child. The
// backend root (folderRelPath == "") is assumed to exist for all modes.
if (stateMode == SyncRemoteState.BlindlyUpload && !string.IsNullOrEmpty(folderRelPath) && !isDryRun)
await backendManager.EnsureFolderAsync(folderRelPath, ct).ConfigureAwait(false);
// BlindlyUpload always treats the current folder as new (we have no listing
// to prove it exists), so it counts as a created folder when not a dry run.
if (stateMode == SyncRemoteState.BlindlyUpload && !string.IsNullOrEmpty(folderRelPath))
{
if (!isDryRun)
await backendManager.EnsureFolderAsync(folderRelPath, ct).ConfigureAwait(false);
planSummary.FoldersCreated++;
}
// 5. Upload all new or changed files in this folder. The caller guarantees the
// current folder exists, so UploadOrUpdateAsync does not call EnsureFolderAsync.
@@ -459,7 +490,10 @@ internal class SyncHandler
}
if (!needsUpload)
{
planSummary.UnchangedFiles++;
continue;
}
if (hashRecheck)
{
@@ -470,7 +504,10 @@ internal class SyncHandler
{
var hash = Convert.ToBase64String(hasher.ComputeHash(s));
if (hash == remoteHash)
{
planSummary.UnchangedFiles++;
continue; // Content unchanged; no upload needed.
}
}
}
catch (Exception ex)
@@ -530,6 +567,7 @@ internal class SyncHandler
{
Logging.Log.WriteDryrunMessage(LOGTAG, "DryRun", "Would delete: {0}", relPath);
planSummary.Delete++;
planSummary.SizeOfDeletedFiles += Math.Max(rc.Size, 0);
continue;
}
@@ -542,6 +580,7 @@ internal class SyncHandler
await backendManager.DeleteAsync(relPath, 0, true, ct).ConfigureAwait(false);
deletedPaths.Add(relPath);
planSummary.Delete++;
planSummary.SizeOfDeletedFiles += Math.Max(rc.Size, 0);
}
// Commit the inventory removals and intent clearances for all files deleted
@@ -613,6 +652,7 @@ internal class SyncHandler
planSummary.Upload++;
else
planSummary.Update++;
planSummary.SizeOfUploadedFiles += Math.Max(lf.Size, 0);
return;
}
@@ -655,6 +695,7 @@ internal class SyncHandler
planSummary.Upload++;
else
planSummary.Update++;
planSummary.SizeOfUploadedFiles += Math.Max(lf.Size, 0);
}
/// <summary>
@@ -759,12 +800,19 @@ internal class SyncHandler
/// <summary>
/// Running counters for the plan summary log, bumped per operation as it is
/// planned/performed so the final log line reflects the whole run.
/// planned/performed so the final log line reflects the whole run. The same
/// counters are flushed into <see cref="SyncResults"/> at the end of the run.
/// </summary>
private sealed class PlanSummary
{
public long Upload;
public long Update;
public long Delete;
public long FoldersCreated;
public long UnchangedFiles;
public long SourceFiles;
public long SizeOfSourceFiles;
public long SizeOfUploadedFiles;
public long SizeOfDeletedFiles;
}
}
+19
View File
@@ -1058,5 +1058,24 @@ namespace Duplicati.Library.Main
public SyncResults(BasicResults p) : base(p) { }
public override OperationMode MainOperation { get { return OperationMode.Sync; } }
/// <inheritdoc/>
public long FoldersCreated { get; internal set; }
/// <inheritdoc/>
public long FoldersDeleted { get; internal set; }
/// <inheritdoc/>
public long FilesUploaded { get; internal set; }
/// <inheritdoc/>
public long UnchangedFiles { get; internal set; }
/// <inheritdoc/>
public long FilesDeleted { get; internal set; }
/// <inheritdoc/>
public long SourceFiles { get; internal set; }
/// <inheritdoc/>
public long SizeOfSourceFiles { get; internal set; }
/// <inheritdoc/>
public long SizeOfUploadedFiles { get; internal set; }
/// <inheritdoc/>
public long SizeOfDeletedFiles { get; internal set; }
}
}