Merge pull request #6404 from carljohnsen/bugfix/rsync-fixes
Bugfix/Fixes to the argument parsing of the remote synchronization tool
This commit is contained in:
@@ -42,7 +42,7 @@ namespace Duplicati.UnitTest
|
||||
/// Tests that the remote synchronization tool doesn't do anything when the dry run option is used.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Category("Tools")]
|
||||
[Category("Tools/RemoteSynchronization")]
|
||||
public void TestDryRun()
|
||||
{
|
||||
var l1 = Path.Combine(TARGETFOLDER, "l1");
|
||||
@@ -67,7 +67,7 @@ namespace Duplicati.UnitTest
|
||||
/// Tests that the remote synchronization tool works with an empty source to an empty destination.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Category("Tools")]
|
||||
[Category("Tools/RemoteSynchronization")]
|
||||
public void TestEmptySourceAndDestination()
|
||||
{
|
||||
var l1 = Path.Combine(TARGETFOLDER, "empty_src");
|
||||
@@ -89,7 +89,7 @@ namespace Duplicati.UnitTest
|
||||
/// Test that remote synchronizing an empty source to a non-empty destination deletes the destination files.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Category("Tools")]
|
||||
[Category("Tools/RemoteSynchronization")]
|
||||
public void TestEmptySourceDeletesDestination()
|
||||
{
|
||||
var l1 = Path.Combine(TARGETFOLDER, "empty_src");
|
||||
@@ -113,7 +113,7 @@ namespace Duplicati.UnitTest
|
||||
/// Test that remote synchronizing an empty source to a non-empty destination renames the destination files when the `--retention` option is used.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Category("Tools")]
|
||||
[Category("Tools/RemoteSynchronization")]
|
||||
public void TestEmptySourceRenamesDestination()
|
||||
{
|
||||
var l1 = Path.Combine(TARGETFOLDER, "empty_src");
|
||||
@@ -147,6 +147,7 @@ namespace Duplicati.UnitTest
|
||||
/// Tests passing all arguments to the main method of the remote synchronization tool.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Category("Tools/RemoteSynchronization")]
|
||||
public void TestMainMethodParsesArgumentsCorrectly()
|
||||
{
|
||||
string[][] testCases =
|
||||
@@ -176,6 +177,18 @@ namespace Duplicati.UnitTest
|
||||
"--src-options", "somesrckey=somesrcvalue", "anothersrckey=anothersrcvalue",
|
||||
"--dst-options", "somedstkey=somedstvalue", "anotherdstkey=anotherdstvalue"
|
||||
],
|
||||
[
|
||||
"source", "destination", "--parse-arguments-only",
|
||||
"--global-options", "somekey=somevalue=with=extra=equals", "anotherkey=anothervalue",
|
||||
"--src-options", "somekey=somevalue=with=extra=equals", "anotherkey=anothervalue",
|
||||
"--dst-options", "somekey=somevalue=with=extra=equals", "anotherkey=anothervalue"
|
||||
],
|
||||
[
|
||||
"source", "destination", "--parse-arguments-only",
|
||||
"--global-options", "somekey=\"some value with spaces\"", "anotherkey=anothervalue",
|
||||
"--src-options", "somekey=\"some value with spaces\"", "anotherkey=anothervalue",
|
||||
"--dst-options", "somekey=\"some value with spaces\"", "anotherkey=anothervalue"
|
||||
]
|
||||
};
|
||||
|
||||
foreach (var args in testCases)
|
||||
@@ -192,7 +205,7 @@ namespace Duplicati.UnitTest
|
||||
/// Tests the original inded use of the remote synchronization tool on an empty destination.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Category("Tools")]
|
||||
[Category("Tools/RemoteSynchronization")]
|
||||
public void TestRemoteSynchronization()
|
||||
{
|
||||
var l1 = Path.Combine(TARGETFOLDER, "l1");
|
||||
@@ -203,7 +216,7 @@ namespace Duplicati.UnitTest
|
||||
var options = TestOptions;
|
||||
|
||||
var now = DateTime.Now;
|
||||
GenerateTestData(DATAFOLDER, 10, 3, 3, 1024 * 1024).Wait();
|
||||
GenerateTestData(DATAFOLDER, 5, 2, 2, 1024).Wait();
|
||||
Console.WriteLine($"Generated test data in {DATAFOLDER} in {DateTime.Now - now}");
|
||||
|
||||
// Create the directories if they do not exist
|
||||
@@ -247,7 +260,7 @@ namespace Duplicati.UnitTest
|
||||
using (var c = new Controller($"file://{l1}", options, null))
|
||||
{
|
||||
now = DateTime.Now;
|
||||
var results = c.Restore([Path.Combine(DATAFOLDER, "*")]);
|
||||
var results = c.Restore([]);
|
||||
Assert.AreEqual(0, results.Errors.Count());
|
||||
Assert.AreEqual(0, results.Warnings.Count());
|
||||
Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}");
|
||||
@@ -309,7 +322,7 @@ namespace Duplicati.UnitTest
|
||||
Assert.IsTrue(DirectoriesAndContentsAreEqual(DATAFOLDER, l2r), "Restored second level files is not equal to original files");
|
||||
|
||||
// Add some more files to the source
|
||||
GenerateTestData(Path.Combine(DATAFOLDER, "brand_new_files"), 5, 2, 2, 1024 * 1024).Wait();
|
||||
GenerateTestData(Path.Combine(DATAFOLDER, "brand_new_files"), 5, 2, 2, 1024).Wait();
|
||||
|
||||
// Backup the new files to l1
|
||||
using (var c = new Controller($"file://{l1}", options, null))
|
||||
@@ -413,7 +426,7 @@ namespace Duplicati.UnitTest
|
||||
/// Tests that the remote synchronization tool verifies the contents of the files.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Category("Tools")]
|
||||
[Category("Tools/RemoteSynchronization")]
|
||||
public void TestVerifies()
|
||||
{
|
||||
var l1 = Path.Combine(TARGETFOLDER, "l1");
|
||||
|
||||
@@ -143,9 +143,7 @@ destination will be verified before being overwritten (if they seemingly match).
|
||||
private static async Task<int> Run(Config config)
|
||||
{
|
||||
// Unpack and parse the multi token options
|
||||
Dictionary<string, string> global_options = config.GlobalOptions
|
||||
.Select(x => x.Split("="))
|
||||
.ToDictionary(x => x[0], x => x[1]);
|
||||
var global_options = ParseOptions(config.GlobalOptions);
|
||||
|
||||
// Parse the log level
|
||||
var log_level_parsed = Enum.TryParse<Duplicati.Library.Logging.LogMessageType>(config.LogLevel, true, out var log_level_enum);
|
||||
@@ -169,13 +167,8 @@ destination will be verified before being overwritten (if they seemingly match).
|
||||
// Start the logging scope
|
||||
using var _ = Duplicati.Library.Logging.Log.StartScope(multi_sink, log_level_enum);
|
||||
|
||||
Dictionary<string, string> src_opts = config.SrcOptions
|
||||
.Select(x => x.Split("="))
|
||||
.ToDictionary(x => x[0], x => x[1]);
|
||||
|
||||
Dictionary<string, string> dst_opts = config.DstOptions
|
||||
.Select(x => x.Split("="))
|
||||
.ToDictionary(x => x[0], x => x[1]);
|
||||
var src_opts = ParseOptions(config.SrcOptions);
|
||||
var dst_opts = ParseOptions(config.DstOptions);
|
||||
|
||||
// Merge the global options into the source and destination options. The global options will be overridden by the source and destination options.
|
||||
foreach (var x in global_options)
|
||||
@@ -529,6 +522,34 @@ destination will be verified before being overwritten (if they seemingly match).
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the options from a list of strings.
|
||||
/// Each option should be in the format "key=value". If the value contains spaces,
|
||||
/// it should be enclosed in quotes, e.g. "key=\"value with spaces\"".
|
||||
/// </summary>
|
||||
/// <param name="options">The list of string options to parse</param>
|
||||
/// <returns>A dictionary with the parsed options, where the key is the option name and the value is the option value.</returns>
|
||||
/// <exception cref="ArgumentException">If an option was not parsed correctly.</exception>
|
||||
private static Dictionary<string, string> ParseOptions(IEnumerable<string> options)
|
||||
{
|
||||
var result = options
|
||||
.Select(x => x.Split('='))
|
||||
.ToDictionary(x => x[0], x => string.Join("=", x.Skip(1)));
|
||||
|
||||
// Double check that the options are valid by reconstructing them from the dictionary
|
||||
foreach (var opt in result.Select(x => $"{x.Key}={x.Value}"))
|
||||
{
|
||||
if (!options.Contains(opt))
|
||||
{
|
||||
Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "rsync", null,
|
||||
"The source option '{0}' is not valid. Please check the syntax.", opt);
|
||||
throw new ArgumentException($"The source option '{opt}' has not been parsed correctly.");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the lists of files to copy, delete and verify.
|
||||
/// The files to copy are the files that are not in the destination, have a different size or have a more recent modification date.
|
||||
|
||||
Reference in New Issue
Block a user