diff --git a/Duplicati/Library/Main/Controller.cs b/Duplicati/Library/Main/Controller.cs
index dc5620119..0ca46706c 100644
--- a/Duplicati/Library/Main/Controller.cs
+++ b/Duplicati/Library/Main/Controller.cs
@@ -114,7 +114,7 @@ namespace Duplicati.Library.Main
}
///
- public async Task BackupAsync(string[] inputsources, IFilter filter = null)
+ public async Task BackupAsync(string[] inputsources, IFilter inputFilter = null)
{
UsageReporter.Reporter.Report("USE_BACKEND", new Library.Utility.Uri(m_backendUrl).Scheme);
UsageReporter.Reporter.Report("USE_COMPRESSION", m_options.CompressionModule);
@@ -130,9 +130,8 @@ namespace Duplicati.Library.Main
CheckAutoCompactInterval();
CheckAutoVacuumInterval();
- return await RunActionAsync(new BackupResults(), inputsources, filter, async (result, backendManager) =>
+ return await RunActionAsync(new BackupResults(), inputsources, inputFilter, async (result, backendManager, filter) =>
{
-
using (var h = new Operation.BackupHandler(m_options, result))
await h.RunAsync(ExpandInputSources(inputsources, filter), backendManager, filter)
.ConfigureAwait(false);
@@ -148,9 +147,9 @@ namespace Duplicati.Library.Main
}
///
- public async Task RestoreAsync(string[] paths, IFilter filter = null)
+ public async Task RestoreAsync(string[] paths, IFilter inputFilter = null)
{
- return await RunActionAsync(new RestoreResults(), paths, filter, async (result, backendManager) =>
+ return await RunActionAsync(new RestoreResults(), paths, inputFilter, async (result, backendManager, filter) =>
{
using var restoreDestination =
(m_options.Restorepath ?? "").StartsWith("@")
@@ -186,9 +185,9 @@ namespace Duplicati.Library.Main
}
///
- public async Task RestoreControlFilesAsync(IEnumerable files = null, IFilter filter = null)
+ public async Task RestoreControlFilesAsync(IEnumerable files = null, IFilter inputFilter = null)
{
- return await RunActionAsync(new RestoreControlFilesResults(), filter, (result, backendManager) =>
+ return await RunActionAsync(new RestoreControlFilesResults(), inputFilter, (result, backendManager, filter) =>
new Operation.RestoreControlFilesHandler(m_options, result)
.RunAsync(files, backendManager, filter)
).ConfigureAwait(false);
@@ -197,16 +196,16 @@ namespace Duplicati.Library.Main
///
public async Task DeleteAsync()
{
- return await RunActionAsync(new DeleteResults(), (result, backendManager) =>
+ return await RunActionAsync(new DeleteResults(), (result, backendManager, filter) =>
new Operation.DeleteHandler(m_options, result)
.RunAsync(backendManager)
).ConfigureAwait(false);
}
///
- public async Task RepairAsync(IFilter filter = null)
+ public async Task RepairAsync(IFilter inputFilter = null)
{
- return await RunActionAsync(new RepairResults(), filter, (result, backendManager) =>
+ return await RunActionAsync(new RepairResults(), inputFilter, (result, backendManager, filter) =>
new Operation.RepairHandler(m_options, result)
.RunAsync(backendManager, filter)
).ConfigureAwait(false);
@@ -214,25 +213,25 @@ namespace Duplicati.Library.Main
///
public async Task ListFilesetsAsync()
- => await RunActionAsync(new ListFilesetResults(), (result, backendManager) =>
+ => await RunActionAsync(new ListFilesetResults(), (result, backendManager, _) =>
Operation.ListFilesetsHandler.RunAsync(m_options, result, backendManager)
).ConfigureAwait(false);
///
public async Task ListFolderAsync(string[] folders, long offset, long limit, bool extendedData)
- => await RunActionAsync(new ListFolderResults(), (result, _) =>
+ => await RunActionAsync(new ListFolderResults(), (result, _, _) =>
Operation.ListFolderHandler.RunAsync(m_options, result, folders, offset, limit, extendedData)
).ConfigureAwait(false);
///
public async Task ListFileVersionsAsync(string[] files, long offset, long limit)
- => await RunActionAsync(new ListFileVersionsResults(), (result, backendManager) =>
+ => await RunActionAsync(new ListFileVersionsResults(), (result, backendManager, _) =>
Operation.ListFileVersionsHandler.RunAsync(m_options, result, files, offset, limit)
).ConfigureAwait(false);
///
- public async Task SearchEntriesAsync(string[] pathprefixes, IFilter filter, long offset, long limit, bool extendedData)
- => await RunActionAsync(new SearchFilesResults(), filter, (result, backendManager) =>
+ public async Task SearchEntriesAsync(string[] pathprefixes, IFilter inputFilter, long offset, long limit, bool extendedData)
+ => await RunActionAsync(new SearchFilesResults(), inputFilter, (result, backendManager, filter) =>
Operation.SearchEntriesHandler.RunAsync(m_options, result, pathprefixes, filter, offset, limit, extendedData)
).ConfigureAwait(false);
@@ -245,18 +244,18 @@ namespace Duplicati.Library.Main
=> ListAsync(filterstring == null ? null : new string[] { filterstring }, null);
///
- public async Task ListAsync(IEnumerable filterstrings, IFilter filter)
+ public async Task ListAsync(IEnumerable filterstrings, IFilter inputFilter)
{
- return await RunActionAsync(new ListResults(), filter, (result, backendManager) =>
+ return await RunActionAsync(new ListResults(), inputFilter, (result, backendManager, filter) =>
new Operation.ListFilesHandler(m_options, result)
.RunAsync(backendManager, filterstrings, filter)
).ConfigureAwait(false);
}
///
- public async Task ListControlFilesAsync(IEnumerable filterstrings, IFilter filter)
+ public async Task ListControlFilesAsync(IEnumerable filterstrings, IFilter inputFilter)
{
- return await RunActionAsync(new ListResults(), filter, (result, backendManager) =>
+ return await RunActionAsync(new ListResults(), inputFilter, (result, backendManager, filter) =>
new Operation.ListControlFilesHandler(m_options, result)
.RunAsync(backendManager, filterstrings, filter)
).ConfigureAwait(false);
@@ -265,7 +264,7 @@ namespace Duplicati.Library.Main
///
public async Task ListRemoteAsync()
{
- return await RunActionAsync(new ListRemoteResults(), async (result, backendManager) =>
+ return await RunActionAsync(new ListRemoteResults(), async (result, backendManager, _) =>
{
using var tf = File.Exists(m_options.Dbpath) ? null : new TempFile();
await using var db = await LocalDatabase.CreateLocalDatabaseAsync(((string)tf) ?? m_options.Dbpath, "list-remote", true, null, result.TaskControl.ProgressToken).ConfigureAwait(false);
@@ -276,7 +275,7 @@ namespace Duplicati.Library.Main
///
public async Task DeleteAllRemoteFilesAsync()
{
- return await RunActionAsync(new ListRemoteResults(), async (result, backendManager) =>
+ return await RunActionAsync(new ListRemoteResults(), async (result, backendManager, _) =>
{
result.OperationProgressUpdater.UpdatePhase(OperationPhase.Delete_Listing);
{
@@ -335,7 +334,7 @@ namespace Duplicati.Library.Main
{
CheckAutoVacuumInterval();
- return await RunActionAsync(new CompactResults(), (result, backendManager) =>
+ return await RunActionAsync(new CompactResults(), (result, backendManager, _) =>
new Operation.CompactHandler(m_options, result)
.RunAsync(backendManager)
).ConfigureAwait(false);
@@ -344,7 +343,7 @@ namespace Duplicati.Library.Main
///
public async Task SetLocksAsync()
{
- return await RunActionAsync(new SetLockResults(), (result, backendManager) =>
+ return await RunActionAsync(new SetLockResults(), (result, backendManager, _) =>
new Operation.SetLocksHandler(m_options, result)
.RunAsync(backendManager)
).ConfigureAwait(false);
@@ -353,18 +352,18 @@ namespace Duplicati.Library.Main
///
public async Task ReadLockInfoAsync()
{
- return await RunActionAsync(new ReadLockInfoResults(), (result, backendManager) =>
+ return await RunActionAsync(new ReadLockInfoResults(), (result, backendManager, _) =>
new Operation.ReadLockInfoHandler(m_options, result)
.RunAsync(backendManager)
).ConfigureAwait(false);
}
///
- public async Task UpdateDatabaseWithVersionsAsync(IFilter filter = null)
+ public async Task UpdateDatabaseWithVersionsAsync(IFilter inputFilter = null)
{
var filelistfilter = Operation.RestoreHandler.GetNumberedFilelistFilterDelegate(m_options.Time, m_options.Version, singleTimeMatch: true);
- return await RunActionAsync(new RecreateDatabaseResults(), filter, async (result, backendManager) =>
+ return await RunActionAsync(new RecreateDatabaseResults(), inputFilter, async (result, backendManager, filter) =>
{
using (var h = new Operation.RecreateDatabaseHandler(m_options, result))
await h.RunUpdateAsync(backendManager, filter, filelistfilter, null)
@@ -377,17 +376,17 @@ namespace Duplicati.Library.Main
{
var t = new string[] { targetpath };
- return await RunActionAsync(new CreateLogDatabaseResults(), t, async (result, backendManager) =>
+ return await RunActionAsync(new CreateLogDatabaseResults(), t, async (result, backendManager, _) =>
await new Operation.CreateBugReportHandler(t[0], m_options, result).RunAsync()
).ConfigureAwait(false);
}
///
- public async Task ListChangesAsync(string baseVersion, string targetVersion, IEnumerable filterstrings = null, IFilter filter = null, Action>> callback = null)
+ public async Task ListChangesAsync(string baseVersion, string targetVersion, IEnumerable filterstrings = null, IFilter inputFilter = null, Action>> callback = null)
{
var t = new string[] { baseVersion, targetVersion };
- return await RunActionAsync(new ListChangesResults(), t, filter, async (result, backendManager) =>
+ return await RunActionAsync(new ListChangesResults(), t, inputFilter, async (result, backendManager, filter) =>
await new Operation.ListChangesHandler(m_options, result)
.RunAsync(t[0], t[1], backendManager, filterstrings, filter, callback)
).ConfigureAwait(false);
@@ -396,7 +395,7 @@ namespace Duplicati.Library.Main
///
public async Task ListAffectedAsync(List args, Action callback = null)
{
- return await RunActionAsync(new ListAffectedResults(), (result, backendManager) =>
+ return await RunActionAsync(new ListAffectedResults(), (result, backendManager, _) =>
new Operation.ListAffected(m_options, result)
.RunAsync(args, callback)
).ConfigureAwait(false);
@@ -408,14 +407,14 @@ namespace Duplicati.Library.Main
if (!m_options.RawOptions.ContainsKey("full-remote-verification"))
m_options.RawOptions["full-remote-verification"] = "true";
- return await RunActionAsync(new TestResults(), (result, backendManager) =>
+ return await RunActionAsync(new TestResults(), (result, backendManager, _) =>
new Operation.TestHandler(m_options, result)
.RunAsync(samples, backendManager)
).ConfigureAwait(false);
}
///
- public async Task TestFilterAsync(string[] paths, IFilter filter = null)
+ public async Task TestFilterAsync(string[] paths, IFilter inputFilter = null)
{
m_options.RawOptions["dry-run"] = "true";
m_options.RawOptions["dbpath"] = "INVALID!";
@@ -424,7 +423,7 @@ namespace Duplicati.Library.Main
var filtertag = Logging.Log.LogTagFromType(typeof(Operation.Backup.FileEnumerationProcess));
using (Logging.Log.StartScope(m_messageSink.WriteMessage, x => x.FilterTag.Contains(filtertag)))
{
- return await RunActionAsync(new TestFilterResults(), paths, filter, (result, backendManager) =>
+ return await RunActionAsync(new TestFilterResults(), paths, inputFilter, (result, backendManager, filter) =>
new Operation.TestFilterHandler(m_options, result)
.RunAsync(ExpandInputSources(paths, filter), filter)
).ConfigureAwait(false);
@@ -434,33 +433,33 @@ namespace Duplicati.Library.Main
///
public async Task SystemInfoAsync()
{
- return await RunActionAsync(new SystemInfoResults(), (result, backendManager) =>
+ return await RunActionAsync(new SystemInfoResults(), (result, backendManager, _) =>
Operation.SystemInfoHandler.RunAsync(result)
).ConfigureAwait(false);
}
///
- public async Task PurgeFilesAsync(IFilter filter)
+ public async Task PurgeFilesAsync(IFilter inputFilter)
{
- return await RunActionAsync(new PurgeFilesResults(), (result, backendManager) =>
+ return await RunActionAsync(new PurgeFilesResults(), (result, backendManager, filter) =>
new Operation.PurgeFilesHandler(m_options, result)
.RunAsync(backendManager, filter)
).ConfigureAwait(false);
}
///
- public async Task ListBrokenFilesAsync(IFilter filter, Func callbackhandler = null)
+ public async Task ListBrokenFilesAsync(IFilter inputFilter, Func callbackhandler = null)
{
- return await RunActionAsync(new ListBrokenFilesResults(), (result, backendManager) =>
+ return await RunActionAsync(new ListBrokenFilesResults(), (result, backendManager, filter) =>
new Operation.ListBrokenFilesHandler(m_options, result)
.RunAsync(backendManager, filter, callbackhandler)
).ConfigureAwait(false);
}
///
- public async Task PurgeBrokenFilesAsync(IFilter filter)
+ public async Task PurgeBrokenFilesAsync(IFilter inputFilter)
{
- return await RunActionAsync(new PurgeBrokenFilesResults(), (result, backendManager) =>
+ return await RunActionAsync(new PurgeBrokenFilesResults(), (result, backendManager, filter) =>
new Operation.PurgeBrokenFilesHandler(m_options, result)
.RunAsync(backendManager, filter)
).ConfigureAwait(false);
@@ -489,7 +488,7 @@ namespace Duplicati.Library.Main
var filtertag = Logging.Log.LogTagFromType(DynamicLoader.GenericLoader.GetModule(sendMailModuleKey).GetType());
using (Logging.Log.StartScope(m_messageSink.WriteMessage, x => x.FilterTag.Contains(filtertag)))
{
- return await RunActionAsync(new SendMailResults(), (result, backendManager) =>
+ return await RunActionAsync(new SendMailResults(), (result, backendManager, _) =>
Task.Run(() =>
{
result.Lines = new string[0];
@@ -502,13 +501,13 @@ namespace Duplicati.Library.Main
///
public async Task VacuumAsync()
{
- return await RunActionAsync(new VacuumResults(), (result, backendManager) =>
+ return await RunActionAsync(new VacuumResults(), (result, backendManager, _) =>
new Operation.VacuumHandler(m_options, result)
.RunAsync()
).ConfigureAwait(false);
}
- private Task RunActionAsync(T result, Func method)
+ private Task RunActionAsync(T result, Func method)
where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider
{
var tmp = new string[0];
@@ -516,21 +515,21 @@ namespace Duplicati.Library.Main
return RunActionAsync(result, tmp, tempfilter, method);
}
- private Task RunActionAsync(T result, string[] paths, Func method)
+ private Task RunActionAsync(T result, string[] paths, Func method)
where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider
{
IFilter tempfilter = null;
return RunActionAsync(result, paths, tempfilter, method);
}
- private Task RunActionAsync(T result, IFilter filter, Func method)
+ private Task RunActionAsync(T result, IFilter filter, Func method)
where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider
{
var tmp = new string[0];
return RunActionAsync(result, tmp, filter, method);
}
- private async Task RunActionAsync(T result, string[] paths, IFilter filter, Func method)
+ private async Task RunActionAsync(T result, string[] paths, IFilter filter, Func method)
where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider
{
OnOperationStarted?.Invoke(result);
@@ -563,7 +562,7 @@ namespace Duplicati.Library.Main
{
backend.UpdateThrottleValues(m_options.MaxUploadPrSecond, m_options.MaxDownloadPrSecond);
m_currentBackendManager = backend;
- await method(result, backend).ConfigureAwait(false);
+ await method(result, backend, filter).ConfigureAwait(false);
}
finally
{
@@ -633,7 +632,7 @@ namespace Duplicati.Library.Main
}
}
- OperationComplete(result, null);
+ OperationComplete(result, null, filter);
Logging.Log.WriteInformationMessage(LOGTAG, "CompletedOperation", Strings.Controller.CompletedOperationMessage(m_options.MainAction));
@@ -661,7 +660,7 @@ namespace Duplicati.Library.Main
}
// Do not propagate the cancel exception
- OperationComplete(result, null);
+ OperationComplete(result, null, filter);
return result;
}
@@ -686,7 +685,7 @@ namespace Duplicati.Library.Main
}
// Report the result, and the failure
- OperationComplete(result, ex);
+ OperationComplete(result, ex, filter);
throw;
}
@@ -697,8 +696,45 @@ namespace Duplicati.Library.Main
}
}
- private void OperationComplete(IBasicResults result, Exception exception)
+ ///
+ /// Temporarily pushes the filter into the raw options so modules can read it.
+ /// Returns a disposable that removes the key when disposed.
+ ///
+ /// The options to push the filter into
+ /// The filter to push
+ /// A disposable that removes the key when disposed
+ internal static IDisposable PushFilterToOptions(Options options, IFilter filter)
+ => PushFilterToOptions(options, filter, out var _);
+ ///
+ /// Temporarily pushes the filter into the raw options so modules can read it.
+ /// Returns a disposable that removes the key when disposed.
+ ///
+ /// The options to push the filter into
+ /// The filter to push
+ /// A disposable that removes the key when disposed
+ internal static IDisposable PushFilterToOptions(Options options, IFilter filter, out string filterString)
{
+ filterString = filter != null && !filter.Empty
+ ? string.Join(Path.PathSeparator.ToString(), FilterExpression.Serialize(filter))
+ : null;
+
+ options.RawOptions["filter"] = filterString;
+ return new FilterOptionScope(options);
+ }
+
+ ///
+ /// Helper to remove the filter option on dispose
+ ///
+ private sealed class FilterOptionScope : IDisposable
+ {
+ private readonly Options m_opts;
+ public FilterOptionScope(Options opts) => m_opts = opts;
+ public void Dispose() => m_opts?.RawOptions.Remove("filter");
+ }
+
+ private void OperationComplete(IBasicResults result, Exception exception, IFilter filter)
+ {
+ using var _ = PushFilterToOptions(m_options, filter);
if (m_options?.LoadedModules != null)
{
foreach (var mx in m_options.LoadedModules)
@@ -756,45 +792,42 @@ namespace Duplicati.Library.Main
m_options.AddLoadedModule(DynamicLoader.GenericLoader.GetModule(m.Key));
}
- // Make the filter read-n-write able in the generic modules
- var pristinefilter = string.Join(Path.PathSeparator.ToString(), FilterExpression.Serialize(filter));
- m_options.RawOptions["filter"] = pristinefilter;
-
// Store the URL connection options separately, as these should only be visible to modules implementing IConnectionModule
var conopts = new Dictionary(m_options.RawOptions);
var qp = new Library.Utility.Uri(m_backendUrl).QueryParameters;
foreach (var k in qp.Keys)
conopts[(string)k] = qp[(string)k];
- foreach (var mx in m_options.LoadedModules)
+ // Make the filter read-n-write able in the generic modules
+ using (PushFilterToOptions(m_options, filter, out var pristinefilter))
{
- if (mx is IConnectionModule)
- mx.Configure(conopts);
- else
- mx.Configure(m_options.RawOptions);
-
- if (mx is IGenericSourceModule sourcemodule)
+ foreach (var mx in m_options.LoadedModules)
{
- if (sourcemodule.ContainFilesForBackup(paths))
- {
- var sourceoptions = sourcemodule.ParseSourcePaths(ref paths, ref pristinefilter, m_options.RawOptions);
+ if (mx is IConnectionModule)
+ mx.Configure(conopts);
+ else
+ mx.Configure(m_options.RawOptions);
- foreach (var sourceoption in sourceoptions)
- m_options.RawOptions[sourceoption.Key] = sourceoption.Value;
+ if (mx is IGenericSourceModule sourcemodule)
+ {
+ if (sourcemodule.ContainFilesForBackup(paths))
+ {
+ var sourceoptions = sourcemodule.ParseSourcePaths(ref paths, ref pristinefilter, m_options.RawOptions);
+
+ foreach (var sourceoption in sourceoptions)
+ m_options.RawOptions[sourceoption.Key] = sourceoption.Value;
+ }
}
+
+ if (mx is IGenericCallbackModule module)
+ module.OnStart(result.MainOperation.ToString(), ref m_backendUrl, ref paths);
}
- if (mx is IGenericCallbackModule module)
- module.OnStart(result.MainOperation.ToString(), ref m_backendUrl, ref paths);
+ // If the filters were changed by a module, read them back in
+ if (pristinefilter != m_options.RawOptions["filter"])
+ filter = FilterExpression.Deserialize(m_options.RawOptions["filter"].Split(new string[] { Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries));
}
- // If the filters were changed by a module, read them back in
- if (pristinefilter != m_options.RawOptions["filter"])
- {
- filter = FilterExpression.Deserialize(m_options.RawOptions["filter"].Split(new string[] { Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries));
- }
- m_options.RawOptions.Remove("filter"); // "--filter" is not a supported command line option
-
if (!string.IsNullOrEmpty(m_options.Logfile))
{
var path = Path.GetDirectoryName(Path.GetFullPath(m_options.Logfile));
diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs
index 5a3f2287e..bb4218d89 100644
--- a/Duplicati/Library/Main/Operation/BackupHandler.cs
+++ b/Duplicati/Library/Main/Operation/BackupHandler.cs
@@ -899,6 +899,8 @@ namespace Duplicati.Library.Main.Operation
// and sources are disposed, but before verification
if (await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false) && m_options.LoadedModules != null)
{
+ // Make filter available to modules
+ using var _ = Controller.PushFilterToOptions(m_options, filter);
foreach (var mx in m_options.LoadedModules)
if (mx is IBackupCallbackModule module)
try { module.OnFinishBackup(m_result, null); }
diff --git a/Duplicati/Library/Modules/Builtin/RunScript.cs b/Duplicati/Library/Modules/Builtin/RunScript.cs
index 92b1c03a7..063cee1ef 100644
--- a/Duplicati/Library/Modules/Builtin/RunScript.cs
+++ b/Duplicati/Library/Modules/Builtin/RunScript.cs
@@ -265,13 +265,11 @@ namespace Duplicati.Library.Modules.Builtin
else
level = ParsedResultType.Error;
- using (TempFile tmpfile = new TempFile())
- {
- using (var streamWriter = new StreamWriter(tmpfile))
- streamWriter.Write(resultFormatSerializer.Serialize(result, exception, m_logstorage, null));
+ using var tmpfile = new TempFile();
+ using (var streamWriter = new StreamWriter(tmpfile))
+ streamWriter.Write(resultFormatSerializer.Serialize(result, exception, m_logstorage, null));
- Execute(m_finishScript, "AFTER", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_enableArguments, m_options, tmpfile, level);
- }
+ Execute(m_finishScript, "AFTER", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_enableArguments, m_options, tmpfile, level);
}
///
diff --git a/Duplicati/UnitTest/RunScriptTests.cs b/Duplicati/UnitTest/RunScriptTests.cs
index 3e8b5150b..726ac217f 100644
--- a/Duplicati/UnitTest/RunScriptTests.cs
+++ b/Duplicati/UnitTest/RunScriptTests.cs
@@ -96,9 +96,6 @@ namespace Duplicati.UnitTest
options["blocksize"] = blocksize.ToString() + "b";
options["run-script-timeout"] = "5s";
- // We need a small delay as we run very small backups back-to-back
- var PAUSE_TIME = TimeSpan.FromSeconds(3);
-
BorderTests.WriteTestFilesToFolder(DATAFOLDER, blocksize, 0);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
@@ -110,7 +107,6 @@ namespace Duplicati.UnitTest
throw new Exception("Unexpected result from base backup");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(0);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -121,7 +117,6 @@ namespace Duplicati.UnitTest
throw new Exception("Backup did not examine any files for code 0?");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(1);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -132,7 +127,6 @@ namespace Duplicati.UnitTest
throw new Exception("Backup did examine files for code 1?");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(2);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -143,7 +137,6 @@ namespace Duplicati.UnitTest
throw new Exception("Backup did not examine any files for code 2?");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(3);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -154,7 +147,6 @@ namespace Duplicati.UnitTest
throw new Exception("Backup did examine files for code 3?");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(4);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -167,7 +159,6 @@ namespace Duplicati.UnitTest
foreach (int exitCode in new[] { 5, 6, 10, 99 })
{
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(exitCode);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -179,7 +170,6 @@ namespace Duplicati.UnitTest
}
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(2, "TEST WARNING MESSAGE");
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -192,7 +182,6 @@ namespace Duplicati.UnitTest
throw new Exception("Found no warning message in output for code 2");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(3, "TEST WARNING MESSAGE");
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -205,7 +194,6 @@ namespace Duplicati.UnitTest
throw new Exception("Found no warning message in output for code 3");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(4, "TEST ERROR MESSAGE");
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -218,7 +206,6 @@ namespace Duplicati.UnitTest
throw new Exception("Found no error message in output for code 4");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(5, "TEST ERROR MESSAGE");
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -231,7 +218,6 @@ namespace Duplicati.UnitTest
throw new Exception("Found no error message in output for code 5");
}
- System.Threading.Thread.Sleep(PAUSE_TIME);
options["run-script-before"] = CreateScript(0, sleeptime: 10);
using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null))
{
@@ -381,6 +367,109 @@ namespace Duplicati.UnitTest
Assert.AreEqual(3, customTargetEntries.Count());
}
+ [Test]
+ [Category("Border")]
+ public async Task CustomFilterAsync()
+ {
+ var expectedFilter = "+[^(.*b.*)$]";
+ var parsedResultFileAfter = Path.Combine(this.RESTOREFOLDER, "result_after.txt");
+ var parsedResultFilePostBackup = Path.Combine(this.RESTOREFOLDER, "result_post_backup.txt");
+
+ var customCommandsBefore = new List
+ {
+ $"echo --filter=\"{expectedFilter}\""
+ };
+
+ var customCommandsAfter = new List();
+ if (OperatingSystem.IsWindows())
+ {
+ customCommandsAfter.Add($"echo %DUPLICATI__filter%>\"{parsedResultFileAfter}\"");
+ }
+ else
+ {
+ customCommandsAfter.Add($"echo \"$DUPLICATI__filter\">\"{parsedResultFileAfter}\"");
+ }
+
+ var customCommandsPostBackup = new List();
+ if (OperatingSystem.IsWindows())
+ {
+ customCommandsPostBackup.Add($"echo %DUPLICATI__filter%>\"{parsedResultFilePostBackup}\"");
+ }
+ else
+ {
+ customCommandsPostBackup.Add($"echo \"$DUPLICATI__filter\">\"{parsedResultFilePostBackup}\"");
+ }
+
+ var options = new Dictionary(this.TestOptions);
+ options["run-script-before"] = CreateScript(0, null, null, 0, customCommandsBefore);
+ options["run-script-after"] = CreateScript(0, null, null, 0, customCommandsAfter);
+ options["run-script-post-backup"] = CreateScript(0, null, null, 0, customCommandsPostBackup);
+
+ using (var c = new Library.Main.Controller("file://" + this.TARGETFOLDER, options, null))
+ {
+ var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER });
+ Assert.AreEqual(0, backupResults.Errors.Count());
+ Assert.AreEqual(0, backupResults.Warnings.Count());
+ }
+
+ var linesAfter = File.ReadAllLines(parsedResultFileAfter);
+ Assert.AreEqual(1, linesAfter.Length);
+ Assert.AreEqual(expectedFilter, linesAfter[0]);
+
+ var linesPostBackup = File.ReadAllLines(parsedResultFilePostBackup);
+ Assert.AreEqual(1, linesPostBackup.Length);
+ Assert.AreEqual(expectedFilter, linesPostBackup[0]);
+ }
+
+ [Test]
+ [Category("Border")]
+ public async Task CommandLineFilterSeenByScriptsAsync()
+ {
+ var expectedFilter = "+*B*";
+ var parsedResultFileAfter = Path.Combine(this.RESTOREFOLDER, "result_after_cmdline.txt");
+ var parsedResultFilePostBackup = Path.Combine(this.RESTOREFOLDER, "result_post_backup_cmdline.txt");
+
+ var customCommandsAfter = new List();
+ if (OperatingSystem.IsWindows())
+ {
+ customCommandsAfter.Add($"echo %DUPLICATI__filter%>\"{parsedResultFileAfter}\"");
+ }
+ else
+ {
+ customCommandsAfter.Add($"echo \"$DUPLICATI__filter\">\"{parsedResultFileAfter}\"");
+ }
+
+ var customCommandsPostBackup = new List();
+ if (OperatingSystem.IsWindows())
+ {
+ customCommandsPostBackup.Add($"echo %DUPLICATI__filter%>\"{parsedResultFilePostBackup}\"");
+ }
+ else
+ {
+ customCommandsPostBackup.Add($"echo \"$DUPLICATI__filter\">\"{parsedResultFilePostBackup}\"");
+ }
+
+ var options = new Dictionary(this.TestOptions);
+ options["run-script-after"] = CreateScript(0, null, null, 0, customCommandsAfter);
+ options["run-script-post-backup"] = CreateScript(0, null, null, 0, customCommandsPostBackup);
+
+ using (var c = new Library.Main.Controller("file://" + this.TARGETFOLDER, options, null))
+ {
+ var filter = new Library.Utility.FilterExpression("*b*", true);
+ var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }, filter);
+ Assert.AreEqual(0, backupResults.Errors.Count());
+ Assert.AreEqual(0, backupResults.Warnings.Count());
+ }
+
+ var linesAfter = File.ReadAllLines(parsedResultFileAfter);
+ Assert.AreEqual(1, linesAfter.Length);
+ Assert.AreEqual(expectedFilter, linesAfter[0]);
+
+ var linesPostBackup = File.ReadAllLines(parsedResultFilePostBackup);
+ Assert.AreEqual(1, linesPostBackup.Length);
+ Assert.AreEqual(expectedFilter, linesPostBackup[0]);
+ }
+
private string CreateScript(int exitcode, string stderr = null, string stdout = null, int sleeptime = 0, List customCommands = null)
{
var id = Guid.NewGuid().ToString("N").Substring(0, 6);
@@ -403,13 +492,10 @@ namespace Duplicati.UnitTest
}
else
{
- var commands = new List();
- commands.Add("#!/bin/sh");
+ var commands = new List() { "#!/bin/sh" };
if (customCommands != null)
- {
commands.AddRange(customCommands);
- }
if (!string.IsNullOrWhiteSpace(stdout))
commands.Add($@"echo {stdout}");