commit a0521da54d1614eae426e12f5b76da600376ef66 Author: kenneth@hexad.dk Date: Tue Feb 12 21:43:14 2013 +0000 Initial version of the new block-based storage format. Still needs a work over in terms of error handling, progress reporting, warning output, refactoring etc. Also misses the volume reuse/reclaim algorithms. But it passes the unit tests now, with and without a local database. git-svn-id: https://duplicati.googlecode.com/svn/sandboxes/Kenneth/ForestHash@1526 59da171f-624f-0410-aa54-27559c288bec diff --git a/BuildTools/LocalizationTool/LocalizationTool.sln b/BuildTools/LocalizationTool/LocalizationTool.sln new file mode 100644 index 000000000..80c06b81c --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalizationTool", "LocalizationTool\LocalizationTool.csproj", "{F093C3D3-28A8-405E-96C0-97AE2C296CCC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F093C3D3-28A8-405E-96C0-97AE2C296CCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F093C3D3-28A8-405E-96C0-97AE2C296CCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F093C3D3-28A8-405E-96C0-97AE2C296CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F093C3D3-28A8-405E-96C0-97AE2C296CCC}.Release|Any CPU.Build.0 = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BuildTools/LocalizationTool/LocalizationTool/CSVReader.cs b/BuildTools/LocalizationTool/LocalizationTool/CSVReader.cs new file mode 100644 index 000000000..20dc55d89 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/CSVReader.cs @@ -0,0 +1,122 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LocalizationTool +{ + public class CSVReader : IDisposable + { + private System.IO.StreamReader m_reader; + + public CSVReader(string filename) + { + m_reader = new System.IO.StreamReader(filename, System.Text.Encoding.UTF8, true); + } + + public List AdvanceLine() + { + bool inQuote = false; + StringBuilder sb = new StringBuilder(); + do + { + string line = m_reader.ReadLine(); + if (line == null) + { + if (sb.Length == 0) + return null; + else + throw new Exception("Unexpected EOF, buffer was: " + sb.ToString()); + } + + sb.AppendLine(line); + + foreach (char c in line) + if (c == '"') + inQuote = !inQuote; + } while (inQuote); + + string txt = sb.ToString(); + + if (txt.EndsWith("\r\n")) + txt = txt.Substring(0, txt.Length - 2); + else if (txt.EndsWith("\r\n")) + txt = txt.Substring(0, txt.Length - 2); + else if (txt.EndsWith("\n")) + txt = txt.Substring(0, txt.Length - 1); + else if (txt.EndsWith("\r")) + txt = txt.Substring(0, txt.Length - 1); + + + inQuote = false; + int ix = 0; + List result = new List(); + for(int i = 0; i < txt.Length; i++) + if (inQuote) + { + if (txt[i] == '"') + inQuote = false; + } + else + { + if (txt[i] == '"') + inQuote = true; + else if (txt[i] == ',') + { + result.Add(CleanString(txt.Substring(ix, i - ix))); + ix = i + 1; + } + } + + if (inQuote) + throw new Exception("Failed to parse line: " + txt); + + if (ix != txt.Length) + result.Add(CleanString(txt.Substring(ix))); + + return result; + } + + private string CleanString(string v) + { + //v = v.Trim(); + + if (v.StartsWith("\"") && v.EndsWith("\"")) + return v.Substring(1, v.Length - 2).Replace("\"\"", "\""); + else + return v; + } + + #region IDisposable Members + + public void Dispose() + { + if (m_reader != null) + { + m_reader.Dispose(); + m_reader = null; + } + } + + #endregion + } +} diff --git a/BuildTools/LocalizationTool/LocalizationTool/LinqHelpers.cs b/BuildTools/LocalizationTool/LocalizationTool/LinqHelpers.cs new file mode 100644 index 000000000..20e282c08 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/LinqHelpers.cs @@ -0,0 +1,239 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LocalizationTool +{ + public static class LinqHelpers + { + // + // Summary: + // Creates a System.Collections.Generic.Dictionary from an System.Collections.Generic.IEnumerable + // according to a specified key selector function. + // + // Parameters: + // source: + // An System.Collections.Generic.IEnumerable to create a System.Collections.Generic.Dictionary + // from. + // + // keySelector: + // A function to extract a key from each element. + // + // Type parameters: + // TSource: + // The type of the elements of source. + // + // TKey: + // The type of the key returned by keySelector. + // + // Returns: + // A System.Collections.Generic.Dictionary that contains keys and + // values. + // + // Exceptions: + // System.ArgumentNullException: + // source or keySelector is null. -or- keySelector produces a key that is null. + public static Dictionary ToSafeDictionary(this IEnumerable source, Func keySelector, string locationName) + { + if (source == null) + throw new ArgumentNullException("source"); + if (keySelector == null) + throw new ArgumentNullException("keySelector"); + + Dictionary res = new Dictionary(); + foreach (TSource el in source) + { + TKey k = keySelector(el); + if (res.ContainsKey(k)) + Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName); + else + res.Add(k, el); + } + + return res; + } + // + // Summary: + // Creates a System.Collections.Generic.Dictionary from an System.Collections.Generic.IEnumerable + // according to specified key selector and element selector functions. + // + // Parameters: + // source: + // An System.Collections.Generic.IEnumerable to create a System.Collections.Generic.Dictionary + // from. + // + // keySelector: + // A function to extract a key from each element. + // + // elementSelector: + // A transform function to produce a result element value from each element. + // + // Type parameters: + // TSource: + // The type of the elements of source. + // + // TKey: + // The type of the key returned by keySelector. + // + // TElement: + // The type of the value returned by elementSelector. + // + // Returns: + // A System.Collections.Generic.Dictionary that contains values + // of type TElement selected from the input sequence. + // + // Exceptions: + // System.ArgumentNullException: + // source or keySelector or elementSelector is null. -or- keySelector produces + // a key that is null. + public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, Func elementSelector, string locationName) + { + if (source == null) + throw new ArgumentNullException("source"); + if (keySelector == null) + throw new ArgumentNullException("keySelector"); + if (elementSelector == null) + throw new ArgumentNullException("elementSelector"); + + Dictionary res = new Dictionary(); + foreach (TSource el in source) + { + TKey k = keySelector(el); + if (res.ContainsKey(k)) + Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName); + else + res.Add(k, elementSelector(el)); + } + + return res; + } + // + // Summary: + // Creates a System.Collections.Generic.Dictionary from an System.Collections.Generic.IEnumerable + // according to a specified key selector function and key comparer. + // + // Parameters: + // source: + // An System.Collections.Generic.IEnumerable to create a System.Collections.Generic.Dictionary + // from. + // + // keySelector: + // A function to extract a key from each element. + // + // comparer: + // An System.Collections.Generic.IEqualityComparer to compare keys. + // + // Type parameters: + // TSource: + // The type of the elements of source. + // + // TKey: + // The type of the keys returned by keySelector. + // + // Returns: + // A System.Collections.Generic.Dictionary that contains keys and + // values. + // + // Exceptions: + // System.ArgumentNullException: + // source or keySelector is null. -or- keySelector produces a key that is null. + public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, IEqualityComparer comparer, string locationName) + { + if (source == null) + throw new ArgumentNullException("source"); + if (keySelector == null) + throw new ArgumentNullException("keySelector"); + + Dictionary res = new Dictionary(comparer); + foreach (TSource el in source) + { + TKey k = keySelector(el); + if (res.ContainsKey(k)) + Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName); + else + res.Add(k, el); + } + + return res; + } + // + // Summary: + // Creates a System.Collections.Generic.Dictionary from an System.Collections.Generic.IEnumerable + // according to a specified key selector function, a comparer, and an element + // selector function. + // + // Parameters: + // source: + // An System.Collections.Generic.IEnumerable to create a System.Collections.Generic.Dictionary + // from. + // + // keySelector: + // A function to extract a key from each element. + // + // elementSelector: + // A transform function to produce a result element value from each element. + // + // comparer: + // An System.Collections.Generic.IEqualityComparer to compare keys. + // + // Type parameters: + // TSource: + // The type of the elements of source. + // + // TKey: + // The type of the key returned by keySelector. + // + // TElement: + // The type of the value returned by elementSelector. + // + // Returns: + // A System.Collections.Generic.Dictionary that contains values + // of type TElement selected from the input sequence. + // + // Exceptions: + // System.ArgumentNullException: + // source or keySelector or elementSelector is null. -or- keySelector produces + // a key that is null. + public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer, string locationName) + { + if (source == null) + throw new ArgumentNullException("source"); + if (keySelector == null) + throw new ArgumentNullException("keySelector"); + if (elementSelector == null) + throw new ArgumentNullException("elementSelector"); + + Dictionary res = new Dictionary(comparer); + foreach (TSource el in source) + { + TKey k = keySelector(el); + if (res.ContainsKey(k)) + Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName); + else + res.Add(k, elementSelector(el)); + } + + return res; + } + } +} diff --git a/BuildTools/LocalizationTool/LocalizationTool/LocalizationTool.csproj b/BuildTools/LocalizationTool/LocalizationTool/LocalizationTool.csproj new file mode 100644 index 000000000..7cced1e30 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/LocalizationTool.csproj @@ -0,0 +1,155 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {F093C3D3-28A8-405E-96C0-97AE2C296CCC} + Exe + Properties + LocalizationTool + LocalizationTool + + + 3.5 + v4.0 + + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + true + full + false + ..\..\..\Duplicati\Localization\ + DEBUG;TRACE + prompt + 4 + export + + + pdbonly + true + ..\..\..\Duplicati\Localization\ + TRACE + prompt + 4 + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + UpdateGUI.cs + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + Form + + + UpdateGUI.cs + + + + + + + + + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65} + Duplicati.Library.Utility + + + + + False + .NET Framework Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + false + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + + 3.5 + + + + + + 3.5 + + + + + + + + + \ No newline at end of file diff --git a/BuildTools/LocalizationTool/LocalizationTool/Program.cs b/BuildTools/LocalizationTool/LocalizationTool/Program.cs new file mode 100644 index 000000000..c156227ba --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/Program.cs @@ -0,0 +1,999 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Windows.Forms; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using System.IO; +using System.Net; + +namespace LocalizationTool +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main(string[] args) + { + if (args.Length == 0) + { + PrintUsage(); + return; + } + + //All relative paths are to the app dir + System.IO.Directory.SetCurrentDirectory(Application.StartupPath); + + string loc = args.Length >= 2 ? args[1] : null; + + switch (args[0].ToLower()) + { + case "clean": + case "cleanup": + Clean(loc); + break; + case "build": + Compile(loc); + break; + case "update": + Update(loc); + break; + case "create": + if (args.Length != 2) + { + Console.WriteLine("missing locale identifier"); + PrintUsage(); + return; + } + Create(loc); + break; + case "report": + Report(loc); + break; + case "export": + Export(loc); + break; + case "exportdiff": + if (args.Length != 3) + { + Console.WriteLine("missing locale identifier or input CSV file"); + PrintUsage(); + return; + } + + ExportDiff(loc, args[2]); + break; + case "import": + if (args.Length != 3) + { + Console.WriteLine("missing locale identifier or input CSV file"); + PrintUsage(); + return; + } + Import(loc, args[2]); + break; + case "guiupdate": + Application.EnableVisualStyles(); + Application.DoEvents(); + + Report(loc); + new UpdateGUI(System.IO.Path.Combine(Application.StartupPath, "report." + loc + ".xml")).ShowDialog(); + break; + case "webpull": + WebPull(loc, false); + break; + case "webupdate": + WebPull(loc, true); + break; + default: + PrintUsage(); + return; + } + } + + + private static void PrintUsage() + { + Console.WriteLine("Usage: "); + + Console.WriteLine("LocalizationTool.exe CLEAN [locale identifier]"); + Console.WriteLine("LocalizationTool.exe BUILD [locale identifier]"); + Console.WriteLine("LocalizationTool.exe UPDATE [locale identifier]"); + Console.WriteLine("LocalizationTool.exe REPORT [locale identifier]"); + Console.WriteLine("LocalizationTool.exe CREATE "); + Console.WriteLine("LocalizationTool.exe GUIUPDATE "); + Console.WriteLine("LocalizationTool.exe EXPORT [locale identifier]"); + Console.WriteLine("LocalizationTool.exe IMPORT "); + Console.WriteLine("LocalizationTool.exe EXPORTDIFF "); + Console.WriteLine("LocalizationTool.exe WEBPULL [locale identifier]"); + Console.WriteLine("LocalizationTool.exe WEBUPDATE [locale identifier]"); + } + + private struct CSVEntry + { + public string Filename; + public string Fieldkey; + public string Origvalue; + public string Value; + public string Status; + + public string[] extraFields; + + public CSVEntry(List fields) + { + Filename = fields[0]; + Fieldkey = fields[1]; + Status = fields[2]; + Origvalue = fields[3]; + Value = fields[4]; + + if (fields.Count > 5) + { + extraFields = new string[fields.Count - 5]; + fields.CopyTo(5, extraFields, 0, extraFields.Length); + } + else + extraFields = null; + } + } + + private static bool IsValidCulture(string c) + { + try + { + System.Globalization.CultureInfo.GetCultureInfo(c); + return true; + } + catch + { + return false; + } + } + + /// + /// Downloads updated CSV files from the web and generates diff files, and optionally updates the local CSV files + /// + /// The culture to use + /// True if the elements should be updated as well + private static void WebPull(string loc, bool update) + { + var locs = + from n in Directory.GetFiles(Application.StartupPath, "report.*.url") + where IsValidCulture(Path.GetFileNameWithoutExtension(n).Substring("report.".Length)) + select Path.GetFileNameWithoutExtension(n).Substring("report.".Length); + + if (!string.IsNullOrEmpty(loc)) + { + locs = locs.Where(c => string.Equals(c, loc, StringComparison.CurrentCultureIgnoreCase)); + if (locs.Count() == 0) + { + Console.WriteLine("No URL file found for culture {0}", loc); + return; + } + } + + if (locs.Count() == 0) + { + Console.WriteLine("No culture.url files found"); + return; + } + + foreach (var culture in locs) + { + var url = string.Format("report.{0}.url", culture); + var file = string.Format("report.{0}.new.csv", culture); + + if (File.Exists(file)) + File.Delete(file); + Console.Write("Downloading updated CSV for {0} ... ", culture); + try { new WebClient().DownloadFile(File.ReadAllText(url).Trim(), file); } + catch (Exception ex) + { + Console.WriteLine("Failed!"); + Console.WriteLine(" -> {0}", ex.Message); + continue; + } + Console.WriteLine("Done!"); + + if (update) + { + Console.Write("Updating local files for {0} .", culture); + ExportDiff(culture, file); + Console.Write("."); + Import(culture, file); + Console.Write("."); + Export(culture); + Console.WriteLine(" Done!"); + } + + } + + } + + /// + /// Imports a CSV file into a dictionary format, + /// Outer key is filename, inner key is fieldname, inner value is translated text + /// + /// The CSV file to read from + /// The current culture string + /// Dictionary where outer key is filename, inner key is fieldname, inner value is translated text + private static Dictionary> ImportCSV(string file, string loc, Func filter) + { + Dictionary> values = new Dictionary>(); + + using (CSVReader r = new CSVReader(file)) + { + List fields = null; + while ((fields = r.AdvanceLine()) != null) + if (fields.Count >= 5) + { + CSVEntry e = new CSVEntry(fields); + e.Filename = System.IO.Path.Combine(System.IO.Path.Combine(Application.StartupPath, loc), e.Filename); + + if (filter != null && filter(e)) + continue; + + if (!values.ContainsKey(e.Filename)) + values.Add(e.Filename, new Dictionary()); + + Dictionary l = values[e.Filename]; + l[e.Fieldkey] = e; + } + } + + return values; + } + + private static void Import(string loc, string p) + { + //Outer key is filename, inner key is fieldname, inner value is translated text + Dictionary> values = ImportCSV(p, loc, e => e.Value.Trim().Length == 0 || e.Value == e.Origvalue); + + if (Duplicati.Library.Utility.Utility.IsClientLinux) + { + Dictionary> remap = new Dictionary>(); + foreach(string key in values.Keys) + remap[key.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString ())] = values[key]; + + values = remap; + } + + string folder = System.IO.Path.Combine(Application.StartupPath, loc); + if (!System.IO.Directory.Exists(folder)) + Create(loc); + else + Update(loc); + + XNamespace xmlns = ""; + foreach (ResXFileInfo inf in GetResXList(loc)) + { + if (System.IO.File.Exists(inf.TargetFile) && values.ContainsKey(inf.TargetFile)) + { + XDocument targetDoc = XDocument.Load(inf.TargetFile); + XNode insertTarget = targetDoc.Element("root").LastNode; + var targetVals = targetDoc.Element("root").Elements("data").ToSafeDictionary(c => c.Attribute("name").Value, inf.TargetFile); + var sourceVals = values[inf.TargetFile]; + values.Remove(inf.TargetFile); + + bool updated = false; + foreach (var item in sourceVals) + if (targetVals.ContainsKey(item.Key)) + { + if (targetVals[item.Key].Element("value").Value.Replace("\r\n", "\n") != item.Value.Value.Replace("\r\n", "\n")) + { + updated = true; + targetVals[item.Key].Element("value").Value = item.Value.Value; + } + } + else + { + updated = true; + insertTarget.AddAfterSelf(new XElement("data", + new XAttribute("name", item.Key), + new XAttribute(xmlns + "space", "preserve"), + new XElement("value", item.Value.Value) + )); + } + + if (updated) + targetDoc.Save(inf.TargetFile); + } + } + + if (values.Count != 0) + Console.WriteLine("The following files were translated but did not exist: " + string.Join(Environment.NewLine, values.Keys.ToArray())); + } + + + public static IEnumerable GetLocaleFolders(string locid) + { + if (string.IsNullOrEmpty(locid)) + return System.IO.Directory.GetDirectories(Application.StartupPath).Where(c => + { + try + { + System.Globalization.CultureInfo.GetCultureInfo(System.IO.Path.GetFileName(c)); + return true; + } + catch + { + return false; + } + }).Select(c => System.IO.Path.GetFileName(c)); + else + return new string[] { locid }; + } + + private static void Export(string cultures) + { + Update(cultures); + Report(cultures); + + foreach (string culture in GetLocaleFolders(cultures)) + { + XDocument doc = XDocument.Load(System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".xml")); + string outfile = System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".csv"); + + Dictionary> elems = new Dictionary>(); + foreach (var file in doc.Element("root").Elements("file")) + { + string filename = file.Attribute("filename").Value.Substring(Duplicati.Library.Utility.Utility.AppendDirSeparator(Application.StartupPath).Length); + filename = filename.Substring(culture.Length + 1); + + Dictionary e = new Dictionary(); + elems[filename] = e; + + foreach (var item in file.Element("updated").Elements("item")) + { + CSVEntry c = new CSVEntry(); + c.Filename = filename; + c.Fieldkey = item.Attribute("name").Value; + c.Status = item.Parent.Name.LocalName; + c.Origvalue = item.Element("original").Value; + c.Value = item.Element("translated").Value; + e.Add(c.Fieldkey, c); + + } + + foreach (var item in file.Element("missing").Elements("item")) + { + CSVEntry c = new CSVEntry(); + c.Filename = filename; + c.Fieldkey = item.Attribute("name").Value; + c.Status = item.Parent.Name.LocalName; + c.Origvalue = item.Value; + c.Value = ""; + e.Add(c.Fieldkey, c); + } + + foreach (var item in file.Element("not-updated").Elements("item")) + { + CSVEntry c = new CSVEntry(); + c.Filename = filename; + c.Fieldkey = item.Attribute("name").Value; + c.Status = item.Parent.Name.LocalName; + c.Origvalue = item.Value; + c.Value = ""; + e.Add(c.Fieldkey, c); + } + + foreach (var item in file.Element("unused").Elements("item")) + { + CSVEntry c = new CSVEntry(); + c.Filename = filename; + c.Fieldkey = item.Attribute("name").Value; + c.Status = item.Parent.Name.LocalName; + c.Origvalue = ""; + c.Value = item.Value; + e.Add(c.Fieldkey, c); + } + + } + + WriteCSVFile(outfile, culture, elems, null); + } + } + + private static void ExportDiff(string culture, string inputfile) + { + inputfile = System.IO.Path.GetFullPath(inputfile); + + string currentFile = System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".csv"); + string diffFile = System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".diff.csv"); + + if (currentFile.Equals(inputfile, Duplicati.Library.Utility.Utility.ClientFilenameStringComparision)) + throw new Exception("Input file will be overwritten, please use another file"); + + Export(culture); + + + //Outer key is filename, inner key is fieldname, inner value is translated text + Dictionary> inputValues = ImportCSV(inputfile, culture, null); + Dictionary> currentValues = ImportCSV(currentFile, culture, null); + + Dictionary> added = new Dictionary>(); + Dictionary> removed = new Dictionary>(); + + foreach (var f in currentValues) + { + Dictionary other; + inputValues.TryGetValue(f.Key, out other); + + if (other == null) + { + added.Add(f.Key, f.Value); + } + else + { + Dictionary a = new Dictionary(); + + foreach (KeyValuePair s in f.Value) + { + if (other.ContainsKey(s.Key)) + other.Remove(s.Key); + else + a.Add(s.Key, s.Value); + } + + if (a.Count > 0) + added.Add(f.Key, a); + + if (other.Count > 0) + removed.Add(f.Key, other); + } + } + + Dictionary> overrides = new Dictionary>(); + Dictionary> diffElems = new Dictionary>(); + + foreach (KeyValuePair> f in added) + foreach (KeyValuePair s in f.Value) + if (s.Value.Origvalue == s.Value.Value || s.Value.Value.Trim().Length == 0) + { + if (!overrides.ContainsKey(f.Key)) + overrides.Add(f.Key, new Dictionary()); + if (!diffElems.ContainsKey(f.Key)) + diffElems.Add(f.Key, new Dictionary()); + overrides[f.Key].Add(s.Key, "not-updated"); + diffElems[f.Key].Add(s.Key, s.Value); + } + + foreach (KeyValuePair> f in removed) + foreach (KeyValuePair s in f.Value) + { + if (!overrides.ContainsKey(f.Key)) + overrides.Add(f.Key, new Dictionary()); + if (!diffElems.ContainsKey(f.Key)) + diffElems.Add(f.Key, new Dictionary()); + overrides[f.Key].Add(s.Key, "unused"); + diffElems[f.Key].Add(s.Key, s.Value); + } + + WriteCSVFile(diffFile, culture, diffElems, overrides); + + //Re-read the file + inputValues = ImportCSV(inputfile, culture, null); + + //Add the new entries + foreach (KeyValuePair> f in added) + { + if (!inputValues.ContainsKey(f.Key)) + inputValues.Add(f.Key, new Dictionary()); + + Dictionary o = inputValues[f.Key]; + + foreach (KeyValuePair s in f.Value) + o.Add(s.Key, s.Value); + + } + + foreach (KeyValuePair> f in currentValues) + { + Dictionary o = inputValues[f.Key]; + foreach (KeyValuePair s in f.Value) + { + CSVEntry c = o[s.Key]; + if (s.Value.Status == "unused") + c.Status = "unused"; + else if (c.Origvalue == c.Value) + c.Status = "not-updated"; + else if (string.IsNullOrEmpty(c.Value) || c.Value.Trim().Length == 0) + c.Status = "missing"; + else + c.Status = "updated"; + + o[s.Key] = c; + } + } + + //Update the removed entries + foreach (KeyValuePair> f in removed) + { + Dictionary o = inputValues[f.Key]; + foreach (KeyValuePair s in f.Value) + { + CSVEntry c = o[s.Key]; + c.Status = "unused"; + o[s.Key] = c; + } + } + + + + //Write the output file + diffFile = System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".updated.csv"); + WriteCSVFile(diffFile, culture, inputValues, null); + } + + private static void WriteCSVFile(string filename, string culture, Dictionary> entries, Dictionary> status_overrides) + { + if (status_overrides == null) + status_overrides = new Dictionary>(); + + List>> sortlist = new List>>(); + foreach (KeyValuePair> f in entries) + foreach (KeyValuePair s in f.Value) + sortlist.Add(new KeyValuePair>(f.Key, s)); + + var sortedlist = sortlist.OrderBy(x => x.Key + System.IO.Path.PathSeparator + x.Value.Key); + + using (System.IO.StreamWriter sw = new System.IO.StreamWriter(filename, false, new System.Text.UTF8Encoding(false))) + { + string filenameprefix = System.IO.Path.Combine(Application.StartupPath, culture); + int pfl = filenameprefix.Length + 1; + + foreach (KeyValuePair> f in sortedlist) + { + string status = f.Value.Value.Status; + if (status_overrides.ContainsKey(f.Key) && status_overrides[f.Key].ContainsKey(f.Value.Key)) + status = status_overrides[f.Key][f.Value.Key]; + + string path = f.Key; + if (f.Key.StartsWith(filenameprefix, Duplicati.Library.Utility.Utility.ClientFilenameStringComparision)) + path = f.Key.Substring(Math.Min(pfl, f.Key.Length)); + + if (Duplicati.Library.Utility.Utility.IsClientLinux) + path = path.Replace(System.IO.Path.DirectorySeparatorChar.ToString(), "\\"); + + WriteCSVLine(sw, path, f.Value.Key, status, f.Value.Value.Origvalue, f.Value.Value.Value, f.Value.Value.extraFields); + } + } + } + + private static string CSV_SEPARATOR = ","; + + private static void WriteCSVLine(System.IO.StreamWriter sw, string filename, string key, string status, string originalText, string translatedText, string[] extraFields) + { + sw.Write(EscapeCSVString(filename)); + sw.Write(CSV_SEPARATOR); + sw.Write(EscapeCSVString(key)); + sw.Write(CSV_SEPARATOR); + sw.Write(EscapeCSVString(status)); + sw.Write(CSV_SEPARATOR); + sw.Write(EscapeCSVString(originalText)); + sw.Write(CSV_SEPARATOR); + sw.Write(EscapeCSVString(translatedText)); + + if (extraFields != null && extraFields.Length > 0) + foreach (string s in extraFields) + { + sw.Write(CSV_SEPARATOR); + sw.Write(EscapeCSVString(s)); + } + + sw.Write("\r\n"); + } + + private static string EscapeCSVString(string value) + { + //.Replace("\\", "\\\\").Replace("\r\n", "\\n").Replace("\r", "\\n").Replace("\n", "\\n").Replace("\t", "\\t") + return "\"" + value.Replace("\"", "\"\"") + "\""; + } + + private static void Report(string cultures) + { + foreach (string culture in GetLocaleFolders(cultures)) + { + Dictionary ignores = new Dictionary(); + string ignorefile = System.IO.Path.Combine(Application.StartupPath, "ignore." + culture + ".xml"); + if (System.IO.File.Exists(ignorefile)) + ignores = XDocument.Load(ignorefile).Element("root").Elements("ignore").Select(c => c.Value).ToSafeDictionary(c => c.ToLower().Trim(), ignorefile); + + //Make sure we ignore empty strings + ignores[""] = ""; + + //Ignore common leftover values + for (int i = 1; i < 20; i++) + { + string _i = i.ToString(); + ignores["textbox" + _i] = ""; + ignores["combobox" + _i] = ""; + ignores["checkbox" + _i] = ""; + ignores["label" + _i] = ""; + ignores["radiobutton" + _i] = ""; + ignores["button" + _i] = ""; + ignores["toolstrip" + _i] = ""; + ignores["toolstripbutton" + _i] = ""; + ignores["statusstrip" + _i] = ""; + } + + + XDocument report = new XDocument( + new XElement("root") + ); + XElement reportRoot = report.Element("root"); + + IEnumerable files = GetResXList(culture); + var missingFiles = files.Where(c => !System.IO.File.Exists(c.TargetFile)); + var existingFiles = files.Where(c => System.IO.File.Exists(c.TargetFile)); + + var extraFiles = from x in Duplicati.Library.Utility.Utility.EnumerateFiles(System.IO.Path.Combine(Application.StartupPath, culture)) + where + x.EndsWith("." + culture + ".resx") + && + !files.Select(c => c.TargetFile).Contains(x) + select x; + + reportRoot.Add( + new XElement("files", + new XElement("missing", + from x in missingFiles + select new XElement("file", x.TargetFile) + ), + new XElement("unused", + from x in extraFiles + select new XElement("file", x) + ) + ) + ); + + foreach(ResXFileInfo inf in existingFiles) + { + IEnumerable sourceElements = XDocument.Load(inf.SourceFile).Element("root").Elements("data"); + IEnumerable targetElements = XDocument.Load(inf.TargetFile).Element("root").Elements("data"); + + if (inf.IsForm) + { + //Look only for strings + Func filter = + c => + !c.Attribute("name").Value.StartsWith(">>") + && + c.Attribute("mimetype") == null + && + ( + c.Attribute("type") == null + || + c.Attribute("type").Value == "System.String, mscorlib" + || + c.Attribute("type").Value == "System.String" + ); + + sourceElements = sourceElements.Where(filter); + targetElements = targetElements.Where(filter); + } + + //Filter the source and target before proceeding + sourceElements = sourceElements.Where(c => !ignores.ContainsKey(c.Element("value").Value.Trim().ToLower()) && !c.Element("value").Value.Trim().ToLower().StartsWith("..\\resources\\") && !c.Element("value").Value.Trim().ToLower().StartsWith("../resources/")); + targetElements = targetElements.Where(c => !ignores.ContainsKey(c.Element("value").Value.Trim().ToLower()) && !c.Element("value").Value.Trim().ToLower().StartsWith("..\\resources\\") && !c.Element("value").Value.Trim().ToLower().StartsWith("../resources/")); + + var sourceVals = sourceElements.ToSafeDictionary(c => c.Attribute("name").Value, inf.SourceFile); + var targetVals = targetElements.ToSafeDictionary(c => c.Attribute("name").Value, inf.TargetFile); + + var missing = sourceVals.Where(c => !targetVals.ContainsKey(c.Key)).ToList(); + var unused = targetVals.Where(c => !sourceVals.ContainsKey(c.Key)).ToList(); + var notUpdated = sourceVals.Where(c => + targetVals.ContainsKey(c.Key) + && + targetVals[c.Key].Element("value").Value == c.Value.Element("value").Value).ToList(); + + var updated = sourceVals.Where(c => + !missing.Contains(c) + && + !unused.Contains(c) + && + !notUpdated.Contains(c) + && + targetVals.ContainsKey(c.Key) + ); + + var yy = sourceVals.Values.Where(x => x.Value.Contains("System.Draw")).Concat(targetVals.Values.Where(x => x.Value.Contains("System.Draw"))); + if (yy.Count() != 0) + { + } + + reportRoot.Add( + new XElement("file", + new XAttribute("filename", inf.TargetFile), + new XElement("missing", + from x in missing + select new XElement("item", + new XAttribute("name", x.Key), + x.Value.Element("value").Value + ) + ), + new XElement("unused", + from x in unused + select new XElement("item", + new XAttribute("name", x.Key), + x.Value.Element("value").Value + ) + ), + new XElement("not-updated", + from x in notUpdated + select new XElement("item", + new XAttribute("name", x.Key), + x.Value.Element("value").Value + ) + ), + new XElement("updated", + from x in updated + select new XElement("item", + new XAttribute("name", x.Key), + new XElement("original", + x.Value.Element("value").Value + ), + new XElement("translated", + targetVals[x.Key].Element("value").Value + ) + ) + ) + ) + ); + } + + report.Save(System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".xml")); + } + } + + private static void Create(string culture) + { + System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.GetCultureInfo(culture); //Validate + string folder = System.IO.Path.Combine(Application.StartupPath, culture); + if (!System.IO.Directory.Exists(folder)) + System.IO.Directory.CreateDirectory(folder); + Update(culture); + } + + public class ResXFileInfo + { + public string SourceFile { get; set; } + public string TargetFile { get; set; } + public string NetrualTargetFile { get; set; } + public bool IsForm { get; set; } + } + + public static IEnumerable GetResXList(string culture) + { + List res = new List(); + foreach (XElement conf in XDocument.Load(System.IO.Path.Combine(Application.StartupPath, "configuration.xml")).Element("root").Elements("configuration")) + { + string sourcefolder = conf.Element("sourcefolder").Value; + if (Duplicati.Library.Utility.Utility.IsClientLinux) + sourcefolder = sourcefolder.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString()); + + string outputfolder = System.IO.Path.GetFullPath(Duplicati.Library.Utility.Utility.AppendDirSeparator(System.IO.Path.Combine(Application.StartupPath, culture))); + sourcefolder = Duplicati.Library.Utility.Utility.AppendDirSeparator(System.IO.Path.GetFullPath(sourcefolder)); + + foreach (XElement fn in conf.Elements("assembly")) + { + string subfolder = fn.Attribute("folder").Value; + if (Duplicati.Library.Utility.Utility.IsClientLinux) + subfolder = subfolder.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString()); + + foreach (string s in Duplicati.Library.Utility.Utility.EnumerateFiles(System.IO.Path.Combine(sourcefolder, subfolder))) + { + if (s.ToLower().StartsWith(Application.StartupPath.ToLower())) + continue; + + if (s.EndsWith(".resx")) + { + string targetNameNeutral = System.IO.Path.Combine(outputfolder, s.Substring(sourcefolder.Length)); + + if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(targetNameNeutral))) + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(targetNameNeutral)); + + string targetFilename = targetNameNeutral.Substring(0, targetNameNeutral.Length - "resx".Length) + culture + ".resx"; + if (res.Where(x => x.TargetFile == targetFilename).FirstOrDefault() == null) + res.Add(new ResXFileInfo() + { + SourceFile = s, + IsForm = System.IO.File.Exists(System.IO.Path.ChangeExtension(s, ".cs")), + NetrualTargetFile = targetNameNeutral, + TargetFile = targetFilename + }); + } + } + } + } + + return res; + } + + private static void Update(string cultures) + { + foreach (string culture in GetLocaleFolders(cultures)) + { + foreach (ResXFileInfo inf in GetResXList(culture)) + { + if (inf.IsForm) + System.IO.File.Copy(inf.SourceFile, inf.NetrualTargetFile, true); //Copy the updated resx + + if (System.IO.File.Exists(inf.TargetFile)) + { + //Merge, forms are auto-merged, in that they depend on the neutral .resx file + if (!inf.IsForm) + { + XDocument targetDoc = XDocument.Load(inf.TargetFile); + XNode insertTarget = targetDoc.Element("root").LastNode; + + var sourceVals = XDocument.Load(inf.SourceFile).Element("root").Elements("data").ToSafeDictionary(c => c.Attribute("name").Value, inf.SourceFile); + var targetVals = targetDoc.Element("root").Elements("data").ToSafeDictionary(c => c.Attribute("name").Value, inf.TargetFile); + + bool updated = false; + foreach (var item in sourceVals) + if (!targetVals.ContainsKey(item.Key)) + { + updated = true; + insertTarget.AddAfterSelf(new XElement(item.Value)); + } + + if (updated) + targetDoc.Save(inf.TargetFile); + } + } + else + { + if (inf.IsForm) + using (System.IO.StreamWriter sw = new System.IO.StreamWriter(inf.TargetFile)) + sw.Write(Properties.Resources.Empty_resx); + else + { + if (Duplicati.Library.Utility.Utility.IsClientLinux) + { + var doc = XDocument.Load(inf.SourceFile); + var entries = from x in doc.Element("root").Elements("data") + where + x.Attribute("type") != null + && + x.Attribute("type").Value == "System.Resources.ResXFileRef, System.Windows.Forms" + select x; + + foreach(var e in entries) + { + string name = e.Element("value").Value; + int ixpos = name.IndexOf(";"); + string path = name.Substring(0, ixpos); + path = path.Replace ("\\", System.IO.Path.DirectorySeparatorChar.ToString()); + e.Element("value").Value = path + name.Substring(ixpos); + } + + doc.Save(inf.TargetFile); + + } else { + System.IO.File.Copy(inf.SourceFile, inf.TargetFile); + } + } + } + + + foreach (var item in from y in + (from x in XDocument.Load(inf.SourceFile).Element("root").Elements("data") + where + x.Attribute("type") != null + && + x.Attribute("type").Value == "System.Resources.ResXFileRef, System.Windows.Forms" + select x) + let relname = y.Element("value").Value.Substring(0, y.Element("value").Value.IndexOf(";")) + let sourceRes = System.IO.Path.GetFullPath(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(inf.SourceFile), relname)).Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString()) + let targetRes = System.IO.Path.GetFullPath(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(inf.TargetFile), relname)).Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString()) + + select new { sourceRes, targetRes } + ) + { + if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(item.targetRes))) + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(item.targetRes)); + + if (!System.IO.File.Exists(item.targetRes)) + { + if (Duplicati.Library.Utility.Utility.IsClientLinux && !System.IO.File.Exists(item.sourceRes)) + { + string basefolder = System.IO.Path.GetDirectoryName(item.sourceRes); + string sourcefile = System.IO.Directory.GetFiles (basefolder).FirstOrDefault (x => item.sourceRes.Equals(x, StringComparison.InvariantCultureIgnoreCase)); + if (sourcefile == null) + Console.WriteLine ("Source file not found: " + item.sourceRes); + else + System.IO.File.Copy (sourcefile, item.targetRes); + + } else { + System.IO.File.Copy(item.sourceRes, item.targetRes); + } + } + } + + } + + string batchfilename = System.IO.Path.Combine(System.IO.Path.Combine(Application.StartupPath, culture), "gui update.bat"); + System.IO.File.WriteAllText(batchfilename, Properties.Resources.Batchjob_bat, System.Text.Encoding.Default); + } + } + + + + private static void Clean(string cultureReq) + { + string root = Duplicati.Library.Utility.Utility.AppendDirSeparator(Application.StartupPath); + if (!string.IsNullOrEmpty(cultureReq)) + root = System.IO.Path.Combine(root, cultureReq); + + foreach (string s in Duplicati.Library.Utility.Utility.EnumerateFiles(Application.StartupPath)) + if (System.IO.Path.GetDirectoryName(s) != Application.StartupPath && (System.IO.Path.GetExtension(s) == ".resources" || System.IO.Path.GetExtension(s) == ".dll")) + System.IO.File.Delete(s); + else if (System.IO.Path.GetExtension(s) == ".resx") + { + //Remove the extra files required for resgen.exe + string culture = s.Substring(root.Length); + culture = culture.Substring(0, culture.IndexOf(System.IO.Path.DirectorySeparatorChar)); + if (!s.ToLower().EndsWith("." + culture.ToLower() + ".resx") && System.IO.File.Exists(s.Substring(0, s.Length - "resx".Length) + culture + ".resx")) + System.IO.File.Delete(s); + } + + } + + private static void Compile(string cultureReq) + { + XDocument doc = XDocument.Load(System.IO.Path.Combine(Application.StartupPath, "configuration.xml")); + + foreach (XElement conf in doc.Element("root").Elements("configuration")) + { + string keyfile = conf.Element("keyfile") == null ? null : conf.Element("keyfile").Value; + string versionassembly = conf.Element("versionassembly").Value; + string outputfolder = conf.Element("outputfolder").Value; + string productname = conf.Element("productname").Value; + string fxVer = conf.Element("frameworkVersion") == null ? "4.0" : conf.Element("frameworkVersion").Value; + + Console.WriteLine("Compiling for framework: " + fxVer); + + foreach (XElement n in conf.Elements("assembly")) + { + string assemblyName = n.Attribute("name").Value; + string folder = n.Attribute("folder").Value; + string @namespace = n.Attribute("namespace") == null ? assemblyName : n.Attribute("namespace").Value; + + foreach (string culture in GetLocaleFolders(cultureReq)) + { + List excludes = (from x in n.Elements("exclude") + let fullpath = System.IO.Path.Combine(System.IO.Path.Combine(Application.StartupPath, culture), x.Value) + select fullpath).ToList(); + + string outfolder = System.IO.Path.GetFullPath(System.IO.Path.Combine(outputfolder, culture)); + if (!System.IO.Directory.Exists(outfolder)) + System.IO.Directory.CreateDirectory(outfolder); + + ResXCompiler.CompileResxFiles(System.IO.Path.Combine(System.IO.Path.Combine(Application.StartupPath, culture), folder), excludes, @namespace, System.IO.Path.Combine(outfolder, assemblyName + ".resources.dll"), System.IO.Path.GetFullPath(versionassembly), keyfile, culture, productname, fxVer); + } + } + } + } + } +} \ No newline at end of file diff --git a/BuildTools/LocalizationTool/LocalizationTool/Properties/AssemblyInfo.cs b/BuildTools/LocalizationTool/LocalizationTool/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..21e7ce27d --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/Properties/AssemblyInfo.cs @@ -0,0 +1,52 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LocalizationTool")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HexaD")] +[assembly: AssemblyProduct("LocalizationTool")] +[assembly: AssemblyCopyright("Copyright © HexaD 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6f59216b-26e5-48cc-907b-16a97c7625df")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BuildTools/LocalizationTool/LocalizationTool/Properties/Resources.Designer.cs b/BuildTools/LocalizationTool/LocalizationTool/Properties/Resources.Designer.cs new file mode 100644 index 000000000..c9038c95b --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/Properties/Resources.Designer.cs @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.544 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace LocalizationTool.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LocalizationTool.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to @echo off + /// + ///echo Processing updates, please wait ... + /// + ///set FOLDERNAME= + ///for /f "tokens=*" %%a in ("%CD%") do set FOLDERNAME=%%~na + /// + ///..\LocalizationTool.exe update %FOLDERNAME% + ///..\LocalizationTool.exe guiupdate %FOLDERNAME%. + /// + internal static string Batchjob_bat { + get { + return ResourceManager.GetString("Batchjob.bat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<root> + /// <!-- + /// Microsoft ResX Schema + /// + /// Version 2.0 + /// + /// The primary goals of this format is to allow a simple XML format + /// that is mostly human readable. The generation and parsing of the + /// various data types are done through the TypeConverter classes + /// associated with the data types. + /// + /// Example: + /// + /// ... ado.net/XML headers & schema ... + /// <resheader name="resmimetype">text/microsoft-resx</resheader> + /// <resheader n [rest of string was truncated]";. + /// + internal static string Empty_resx { + get { + return ResourceManager.GetString("Empty_resx", resourceCulture); + } + } + } +} diff --git a/BuildTools/LocalizationTool/LocalizationTool/Properties/Resources.resx b/BuildTools/LocalizationTool/LocalizationTool/Properties/Resources.resx new file mode 100644 index 000000000..3fb3e1583 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\Batchjob.bat.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + + ..\Resources\Empty_resx.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/BuildTools/LocalizationTool/LocalizationTool/Properties/Settings.Designer.cs b/BuildTools/LocalizationTool/LocalizationTool/Properties/Settings.Designer.cs new file mode 100644 index 000000000..5870bf2f4 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.544 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace LocalizationTool.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/BuildTools/LocalizationTool/LocalizationTool/Properties/Settings.settings b/BuildTools/LocalizationTool/LocalizationTool/Properties/Settings.settings new file mode 100644 index 000000000..2bd17f050 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/Properties/Settings.settings @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/BuildTools/LocalizationTool/LocalizationTool/ResXCompiler.cs b/BuildTools/LocalizationTool/LocalizationTool/ResXCompiler.cs new file mode 100644 index 000000000..b37ae3ad7 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/ResXCompiler.cs @@ -0,0 +1,230 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using System.Configuration; + +namespace LocalizationTool +{ + public static class ResXCompiler + { + private static string ExecuteAndRead(string command, string arguments) + { + var psi = new System.Diagnostics.ProcessStartInfo(command, arguments); + psi.RedirectStandardOutput = true; + psi.UseShellExecute = false; + + var pi = System.Diagnostics.Process.Start(psi); + pi.WaitForExit(5000); + if (pi.HasExited) + return pi.StandardOutput.ReadToEnd().Trim(); + + return null; + } + + public static void CompileResxFiles(string folder, List excludeFolders, string @namespace, string assemblyname, string versionAssembly, string keyfile, string culture, string productname, string fxVer) + { + string resgenexe; + string alexe; + if (Duplicati.Library.Utility.Utility.IsClientLinux) + { + resgenexe = ExecuteAndRead("which", "resgen"); + alexe = ExecuteAndRead("which", "al"); + } + else //Windows + { + //Order of these paths is also the search order + string[] known_sdk_paths = null; + if (fxVer == "2.0") + { + known_sdk_paths = new string[] + { + Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v2.0.50727\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft.NET\\SDK\\v2.0\\bin\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft.NET\\SDK\\v2.0\\bin\\"), + }; + } + else if (fxVer == "3.0") + { + known_sdk_paths = new string[] + { + Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v3.0\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v6.0A\\bin\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v6.0A\\bin\\"), + + }; + } + else if (fxVer == "3.5") + { + known_sdk_paths = new string[] + { + Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v3.5\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v7.0A\\bin\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v7.0A\\bin\\"), + }; + } + else + { + known_sdk_paths = new string[] + { + Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v4.0\\"), + Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v4.0.30319\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v8.0A\\bin\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v8.0A\\bin\\NETFX 4.0 Tools\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v7.1\\Bin\\NETFX 4.0 Tools\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v8.0A\\bin\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v8.0A\\bin\\NETFX 4.0 Tools\\"), + Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v7.1\\Bin\\NETFX 4.0 Tools\\"), + }; + } + resgenexe = "resgen.exe"; + alexe = "al.exe"; + + foreach(var p in known_sdk_paths) + if (System.IO.File.Exists(System.IO.Path.Combine(p, resgenexe))) + { + resgenexe = System.IO.Path.Combine(p, resgenexe); + break; + } + + foreach (var p in known_sdk_paths) + if (System.IO.File.Exists(System.IO.Path.Combine(p, alexe))) + { + alexe = System.IO.Path.Combine(p, alexe); + break; + } + + } + if (!System.IO.File.Exists(resgenexe)) + { + Console.WriteLine("Unable to locate file: {0}", resgenexe); + Console.WriteLine("This can be fixed by installing a microsoft platform SDK, or visual studio (express is fine)"); + return; + } + + if (!System.IO.File.Exists(alexe)) + { + Console.WriteLine("Unable to locate file: {0}", alexe); + Console.WriteLine("This can be fixed by installing the .Net framework version 2.0"); + return; + } + + if (!string.IsNullOrEmpty(keyfile) && Duplicati.Library.Utility.Utility.IsClientLinux) + keyfile = keyfile.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString()); + + + List resources = new List(); + + folder = Duplicati.Library.Utility.Utility.AppendDirSeparator(folder); + + foreach (string s in Duplicati.Library.Utility.Utility.EnumerateFiles(folder)) + { + if (s.ToLower().EndsWith("." + culture.ToLower() + ".resx")) + { + if (excludeFolders.Any(xf => s.ToLower().StartsWith(Duplicati.Library.Utility.Utility.AppendDirSeparator(xf).ToLower()))) + continue; + + string resname = System.IO.Path.ChangeExtension(s, ".resources"); + + if (!System.IO.File.Exists(resname) || System.IO.File.GetLastWriteTime(resname) < System.IO.File.GetLastWriteTime(s)) + { + Console.WriteLine("Compiling: " + s); + System.Diagnostics.ProcessStartInfo pi = new System.Diagnostics.ProcessStartInfo(resgenexe, "\"" + s + "\""); + pi.CreateNoWindow = true; + pi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; + pi.RedirectStandardOutput = true; + pi.RedirectStandardError = true; + pi.UseShellExecute = false; + pi.WorkingDirectory = System.IO.Path.GetDirectoryName(s); + + System.Diagnostics.Process pr = System.Diagnostics.Process.Start(pi); + pr.WaitForExit(); + + if (pr.ExitCode != 0) + { + Console.WriteLine("Error"); + Console.WriteLine(pr.StandardOutput.ReadToEnd()); + Console.WriteLine(pr.StandardError.ReadToEnd()); + throw new Exception("Resgen failure: " + s); + } + } + else + Console.WriteLine("Not modified: " + s); + + resources.Add(resname); + } + } + + if (resources.Count == 0) + return; + + if (!System.IO.File.Exists(versionAssembly) && Duplicati.Library.Utility.Utility.IsClientLinux) + versionAssembly = versionAssembly.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString()); + + if (!System.IO.File.Exists(versionAssembly)) + { + Console.WriteLine("Unable to locate file: {0}", versionAssembly); + Console.WriteLine("This can be fixed by compiling the application or modifying the file configuration.xml"); + return; + } + + using (Duplicati.Library.Utility.TempFile tf = new Duplicati.Library.Utility.TempFile()) + { + using (System.IO.StreamWriter sw = new System.IO.StreamWriter(tf)) + { + System.Reflection.Assembly asm = System.Reflection.Assembly.ReflectionOnlyLoadFrom(versionAssembly); + + sw.WriteLine("/t:lib"); + sw.WriteLine("/out:\"" + assemblyname + "\""); + sw.WriteLine("/product:\"" + productname + "\""); + sw.WriteLine("/title:\"" + productname + "\""); + sw.WriteLine("/version:" + asm.GetName().Version.ToString()); + if (!string.IsNullOrEmpty(keyfile)) + sw.WriteLine("/keyfile:\"" + keyfile + "\""); + sw.WriteLine("/culture:" + culture); + + foreach (string s in resources) + { + string resname = s.Substring(folder.Length); + resname = resname.Replace("\\", "."); + resname = resname.Replace(" ", "_"); + resname = @namespace + "." + resname; + + sw.WriteLine("/embed:\"" + s + "\"," + resname); + } + } + + Console.WriteLine("Linking ..."); + System.Diagnostics.ProcessStartInfo pi = new System.Diagnostics.ProcessStartInfo(alexe, "@\"" + tf + "\""); + pi.CreateNoWindow = true; + pi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; + + System.Diagnostics.Process pr = System.Diagnostics.Process.Start(pi); + pr.WaitForExit(); + + if (pr.ExitCode != 0) + throw new Exception("Linker failure"); + } + + } + } +} diff --git a/BuildTools/LocalizationTool/LocalizationTool/Resources/Batchjob.bat.txt b/BuildTools/LocalizationTool/LocalizationTool/Resources/Batchjob.bat.txt new file mode 100644 index 000000000..3c41bda52 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/Resources/Batchjob.bat.txt @@ -0,0 +1,9 @@ +@echo off + +echo Processing updates, please wait ... + +set FOLDERNAME= +for /f "tokens=*" %%a in ("%CD%") do set FOLDERNAME=%%~na + +..\LocalizationTool.exe update %FOLDERNAME% +..\LocalizationTool.exe guiupdate %FOLDERNAME% \ No newline at end of file diff --git a/BuildTools/LocalizationTool/LocalizationTool/Resources/Empty_resx.txt b/BuildTools/LocalizationTool/LocalizationTool/Resources/Empty_resx.txt new file mode 100644 index 000000000..ff31a6db5 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/Resources/Empty_resx.txt @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.Designer.cs b/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.Designer.cs new file mode 100644 index 000000000..876605a2b --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.Designer.cs @@ -0,0 +1,311 @@ +namespace LocalizationTool +{ + partial class UpdateGUI + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.panel1 = new System.Windows.Forms.Panel(); + this.panel2 = new System.Windows.Forms.Panel(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.treeView1 = new System.Windows.Forms.TreeView(); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.panel3 = new System.Windows.Forms.Panel(); + this.label7 = new System.Windows.Forms.Label(); + this.panel4 = new System.Windows.Forms.Panel(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.label8 = new System.Windows.Forms.Label(); + this.panel1.SuspendLayout(); + this.panel2.SuspendLayout(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.panel3.SuspendLayout(); + this.panel4.SuspendLayout(); + this.SuspendLayout(); + // + // panel1 + // + this.panel1.Controls.Add(this.button3); + this.panel1.Controls.Add(this.button2); + this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel1.Location = new System.Drawing.Point(0, 264); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(459, 45); + this.panel1.TabIndex = 0; + // + // panel2 + // + this.panel2.Controls.Add(this.label6); + this.panel2.Controls.Add(this.label5); + this.panel2.Controls.Add(this.label4); + this.panel2.Controls.Add(this.label3); + this.panel2.Dock = System.Windows.Forms.DockStyle.Top; + this.panel2.Location = new System.Drawing.Point(0, 0); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(459, 32); + this.panel2.TabIndex = 1; + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 32); + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.treeView1); + this.splitContainer1.Panel1.Controls.Add(this.panel4); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.panel3); + this.splitContainer1.Panel2.Controls.Add(this.label7); + this.splitContainer1.Size = new System.Drawing.Size(459, 232); + this.splitContainer1.SplitterDistance = 227; + this.splitContainer1.TabIndex = 2; + // + // treeView1 + // + this.treeView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.treeView1.HideSelection = false; + this.treeView1.Location = new System.Drawing.Point(0, 0); + this.treeView1.Name = "treeView1"; + this.treeView1.Size = new System.Drawing.Size(227, 200); + this.treeView1.TabIndex = 0; + this.treeView1.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView1_AfterSelect); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(16, 8); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(25, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Key"; + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox1.Location = new System.Drawing.Point(56, 8); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + this.textBox1.Size = new System.Drawing.Size(42, 20); + this.textBox1.TabIndex = 1; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(16, 40); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(34, 13); + this.label2.TabIndex = 2; + this.label2.Text = "Value"; + // + // textBox2 + // + this.textBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox2.Location = new System.Drawing.Point(16, 56); + this.textBox2.Multiline = true; + this.textBox2.Name = "textBox2"; + this.textBox2.Size = new System.Drawing.Size(82, 136); + this.textBox2.TabIndex = 3; + this.textBox2.TextChanged += new System.EventHandler(this.textBox2_TextChanged); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(8, 8); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(83, 13); + this.label3.TabIndex = 0; + this.label3.Text = "Items remaining:"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(96, 8); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(37, 13); + this.label4.TabIndex = 1; + this.label4.Text = "12345"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(152, 8); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(36, 13); + this.label5.TabIndex = 2; + this.label5.Text = "Folder"; + // + // label6 + // + this.label6.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label6.AutoEllipsis = true; + this.label6.Location = new System.Drawing.Point(200, 8); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(256, 16); + this.label6.TabIndex = 3; + this.label6.Text = "label6"; + // + // panel3 + // + this.panel3.Controls.Add(this.textBox2); + this.panel3.Controls.Add(this.label2); + this.panel3.Controls.Add(this.label1); + this.panel3.Controls.Add(this.textBox1); + this.panel3.Location = new System.Drawing.Point(8, 0); + this.panel3.Name = "panel3"; + this.panel3.Size = new System.Drawing.Size(104, 208); + this.panel3.TabIndex = 4; + this.panel3.Visible = false; + // + // label7 + // + this.label7.Anchor = System.Windows.Forms.AnchorStyles.None; + this.label7.Location = new System.Drawing.Point(67, 92); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(100, 16); + this.label7.TabIndex = 5; + this.label7.Text = "Click on an entry"; + this.label7.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // panel4 + // + this.panel4.Controls.Add(this.label8); + this.panel4.Controls.Add(this.button1); + this.panel4.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel4.Location = new System.Drawing.Point(0, 200); + this.panel4.Name = "panel4"; + this.panel4.Size = new System.Drawing.Size(227, 32); + this.panel4.TabIndex = 6; + // + // button1 + // + this.button1.Location = new System.Drawing.Point(5, 8); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 6; + this.button1.Text = "&Next"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // button2 + // + this.button2.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.button2.Location = new System.Drawing.Point(144, 8); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(75, 23); + this.button2.TabIndex = 0; + this.button2.Text = "&OK"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button3 + // + this.button3.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.button3.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button3.Location = new System.Drawing.Point(240, 8); + this.button3.Name = "button3"; + this.button3.Size = new System.Drawing.Size(75, 23); + this.button3.TabIndex = 1; + this.button3.Text = "&Cancel"; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(96, 16); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(134, 13); + this.label8.TabIndex = 2; + this.label8.Text = "Press ALT+N for next entry"; + // + // UpdateGUI + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button3; + this.ClientSize = new System.Drawing.Size(459, 309); + this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.panel2); + this.Controls.Add(this.panel1); + this.Name = "UpdateGUI"; + this.Text = "Localization updater"; + this.Load += new System.EventHandler(this.UpdateGUI_Load); + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.UpdateGUI_FormClosing); + this.panel1.ResumeLayout(false); + this.panel2.ResumeLayout(false); + this.panel2.PerformLayout(); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + this.splitContainer1.ResumeLayout(false); + this.panel3.ResumeLayout(false); + this.panel3.PerformLayout(); + this.panel4.ResumeLayout(false); + this.panel4.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.TreeView treeView1; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Panel panel3; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Panel panel4; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Label label8; + } +} \ No newline at end of file diff --git a/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.cs b/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.cs new file mode 100644 index 000000000..f7d142d45 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.cs @@ -0,0 +1,233 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using System.Xml.Linq; + +namespace LocalizationTool +{ + public partial class UpdateGUI : Form + { + private string m_reportfile; + private int m_totalcount; + private Dictionary m_updates = new Dictionary(); + + public UpdateGUI(string reportfile) + { + InitializeComponent(); + m_reportfile = reportfile; + } + + private void UpdateGUI_Load(object sender, EventArgs e) + { + panel3.Dock = DockStyle.Fill; + string culturename = System.IO.Path.GetExtension(System.IO.Path.GetFileNameWithoutExtension(m_reportfile)).Substring(1); + this.Text = "Localizing culture " + culturename; + + var files = XDocument.Load(m_reportfile).Element("root").Elements("file").Where(n => + (n.Element("missing") != null && n.Element("missing").Elements().Count() > 0) + || + (n.Element("not-updated") != null && n.Element("not-updated").Elements().Count() > 0) + ); + + string rootfolder = Duplicati.Library.Utility.Utility.AppendDirSeparator(System.IO.Path.Combine(Application.StartupPath, culturename)); + + treeView1.BeginUpdate(); + treeView1.Nodes.Clear(); + foreach (XElement x in files) + { + TreeNodeCollection cl = treeView1.Nodes; + string relpath = x.Attribute("filename").Value.Substring(rootfolder.Length); + foreach (string s in relpath.Split(System.IO.Path.DirectorySeparatorChar)) + { + bool found = false; + foreach (TreeNode n in cl) + if (n.Text == s) + { + cl = n.Nodes; + found = true; + break; + } + + if (!found) + { + TreeNode nn = new TreeNode(s); + nn.ForeColor = Color.DarkBlue; + cl.Add(nn); + cl = nn.Nodes; + } + } + + foreach (XElement el in x.Element("missing").Elements("item").Union(x.Element("not-updated").Elements("item"))) + { + //These show up occasionally, but mut not be translated + if ( + el.Attribute("name").Value.EndsWith(".Name") + || + el.Attribute("name").Value.EndsWith(".MappingName") + ) + continue; + + string val = el.Value; + if (val.Length > 100) + val = val.Substring(0, 97) + "..."; + TreeNode tn = new TreeNode(el.Attribute("name").Value + ": " + val); + tn.ForeColor = Color.DarkRed; + tn.Tag = el; + cl.Add(tn); + m_totalcount++; + } + } + treeView1.EndUpdate(); + treeView1.ExpandAll(); + if (treeView1.Nodes.Count > 0) + { + TreeNode top = treeView1.Nodes[0]; + while (top.Nodes.Count > 0) + top = top.Nodes[0]; + treeView1.SelectedNode = top; + } + label4.Text = m_totalcount.ToString(); + label6.Text = System.IO.Path.Combine(Application.StartupPath, culturename); + this.WindowState = FormWindowState.Maximized; + } + + private void treeView1_AfterSelect(object sender, TreeViewEventArgs e) + { + if (treeView1.SelectedNode == null || treeView1.SelectedNode.Tag as XElement == null) + panel3.Visible = false; + else + { + panel3.Visible = true; + XElement el = treeView1.SelectedNode.Tag as XElement; + panel3.Tag = null; + textBox1.Text = el.Attribute("name").Value; + textBox2.Text = el.Value.Replace(Environment.NewLine, "\n").Replace("\n", Environment.NewLine); + try + { + textBox2.Focus(); + textBox2.SelectAll(); + } + catch { } + panel3.Tag = el; + } + + } + + private void textBox2_TextChanged(object sender, EventArgs e) + { + if (panel3.Tag as XElement == null) + return; + + XElement el = panel3.Tag as XElement; + el.Value = textBox2.Text; + string dictkey = el.Parent.Parent.Attribute("filename").Value + "@" + el.Attribute("name").Value; + m_updates[dictkey] = el; + label4.Text = (m_totalcount - m_updates.Count).ToString(); + + if (treeView1.SelectedNode != null && treeView1.SelectedNode.Tag == el) + treeView1.SelectedNode.ForeColor = Color.DarkGreen; + } + + private void button3_Click(object sender, EventArgs e) + { + if (m_updates.Count > 0) + if (MessageBox.Show(this, "Close and loose all changes?", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button3) != DialogResult.Yes) + return; + + this.DialogResult = DialogResult.Cancel; + this.Close(); + } + + private void button1_Click(object sender, EventArgs e) + { + TreeNode t = treeView1.SelectedNode; + if (t == null) + return; + + List visited = new List(); + + while (t != null) + { + if (t.Nodes.Count > 0 && !visited.Contains(t)) + t = t.Nodes[0]; + else if (t.NextNode != null) + t = t.NextNode; + else + { + if (t.Parent != null && !visited.Contains(t.Parent)) + visited.Add(t.Parent); + t = t.Parent; + } + + if (t != null && t.ForeColor == Color.DarkRed) + { + treeView1.SelectedNode = t; + return; + } + } + + MessageBox.Show(this, "No more items", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + private void button2_Click(object sender, EventArgs e) + { + foreach (XElement el in m_updates.Values) + { + XDocument file = XDocument.Load(el.Parent.Parent.Attribute("filename").Value); + var insertEl = file.Element("root").Elements("data").Where(c => c.Attribute("name").Value == el.Attribute("name").Value).FirstOrDefault(); + if (insertEl != null) + insertEl.Element("value").Value = el.Value; + else + file.Element("root").Elements().Last().AddAfterSelf( + new XElement("data", + new XAttribute("name", el.Attribute("name").Value), + new XAttribute(XNamespace.Xml + "space", "preserve"), + new XElement("value", el.Value) + ) + ); + + file.Save(el.Parent.Parent.Attribute("filename").Value); + } + + m_updates.Clear(); + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void UpdateGUI_FormClosing(object sender, FormClosingEventArgs e) + { + if (e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.FormOwnerClosing) + if (m_updates.Count > 0) + if (MessageBox.Show(this, "Close and loose all changes?", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button3) != DialogResult.Yes) + { + e.Cancel = true; + return; + } + + } + } +} diff --git a/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.resx b/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.resx new file mode 100644 index 000000000..ff31a6db5 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/UpdateGUI.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BuildTools/LocalizationTool/LocalizationTool/app.config b/BuildTools/LocalizationTool/LocalizationTool/app.config new file mode 100644 index 000000000..ad9e257c4 --- /dev/null +++ b/BuildTools/LocalizationTool/LocalizationTool/app.config @@ -0,0 +1,5 @@ + + + + + diff --git a/BuildTools/UpdateVersionNumber/DisplayItem.cs b/BuildTools/UpdateVersionNumber/DisplayItem.cs new file mode 100644 index 000000000..a2541b350 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/DisplayItem.cs @@ -0,0 +1,54 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Text; + +namespace UpdateVersionNumber +{ + public class DisplayItem + { + public DisplayItem(string file, Version version) + { + m_file = file; + m_version = version; + } + + private string m_file; + + public string File + { + get { return m_file; } + set { m_file = value; } + } + private Version m_version; + + public Version Version + { + get { return m_version; } + set { m_version = value; } + } + + public override string ToString() + { + return m_file + " (" + m_version.ToString() + ")"; + } + } +} diff --git a/BuildTools/UpdateVersionNumber/Form1.Designer.cs b/BuildTools/UpdateVersionNumber/Form1.Designer.cs new file mode 100644 index 000000000..02bf1b15d --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Form1.Designer.cs @@ -0,0 +1,225 @@ +namespace UpdateVersionNumber +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.checkedListBox1 = new System.Windows.Forms.CheckedListBox(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.folderBrowserDialog1 = new System.Windows.Forms.FolderBrowserDialog(); + this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); + this.panel1 = new System.Windows.Forms.Panel(); + this.panel2 = new System.Windows.Forms.Panel(); + this.progressBar1 = new System.Windows.Forms.ProgressBar(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.button3 = new System.Windows.Forms.Button(); + this.panel1.SuspendLayout(); + this.panel2.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(16, 32); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(66, 13); + this.label1.TabIndex = 0; + this.label1.Text = "New version"; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(96, 32); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(100, 20); + this.textBox1.TabIndex = 1; + // + // checkedListBox1 + // + this.checkedListBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.checkedListBox1.CheckOnClick = true; + this.checkedListBox1.FormattingEnabled = true; + this.checkedListBox1.IntegralHeight = false; + this.checkedListBox1.Location = new System.Drawing.Point(16, 64); + this.checkedListBox1.Name = "checkedListBox1"; + this.checkedListBox1.SelectionMode = System.Windows.Forms.SelectionMode.None; + this.checkedListBox1.Size = new System.Drawing.Size(212, 98); + this.checkedListBox1.Sorted = true; + this.checkedListBox1.TabIndex = 2; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(16, 8); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(36, 13); + this.label2.TabIndex = 3; + this.label2.Text = "Folder"; + // + // textBox2 + // + this.textBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox2.Location = new System.Drawing.Point(96, 8); + this.textBox2.Name = "textBox2"; + this.textBox2.ReadOnly = true; + this.textBox2.Size = new System.Drawing.Size(92, 20); + this.textBox2.TabIndex = 4; + this.textBox2.TextChanged += new System.EventHandler(this.textBox2_TextChanged); + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.button1.Location = new System.Drawing.Point(196, 8); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(24, 20); + this.button1.TabIndex = 5; + this.button1.Text = "..."; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // button2 + // + this.button2.Anchor = System.Windows.Forms.AnchorStyles.Bottom; + this.button2.Location = new System.Drawing.Point(86, 176); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(75, 23); + this.button2.TabIndex = 6; + this.button2.Text = "Update"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // folderBrowserDialog1 + // + this.folderBrowserDialog1.Description = "Select project folder"; + this.folderBrowserDialog1.ShowNewFolderButton = false; + // + // backgroundWorker1 + // + this.backgroundWorker1.WorkerSupportsCancellation = true; + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork); + this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); + // + // panel1 + // + this.panel1.Controls.Add(this.button2); + this.panel1.Controls.Add(this.checkedListBox1); + this.panel1.Controls.Add(this.button1); + this.panel1.Controls.Add(this.label1); + this.panel1.Controls.Add(this.textBox2); + this.panel1.Controls.Add(this.textBox1); + this.panel1.Controls.Add(this.label2); + this.panel1.Location = new System.Drawing.Point(16, 24); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(240, 216); + this.panel1.TabIndex = 7; + // + // panel2 + // + this.panel2.Controls.Add(this.button3); + this.panel2.Controls.Add(this.groupBox1); + this.panel2.Location = new System.Drawing.Point(272, 24); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(256, 216); + this.panel2.TabIndex = 8; + // + // progressBar1 + // + this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.progressBar1.Location = new System.Drawing.Point(16, 32); + this.progressBar1.Name = "progressBar1"; + this.progressBar1.Size = new System.Drawing.Size(200, 23); + this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee; + this.progressBar1.TabIndex = 0; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.progressBar1); + this.groupBox1.Location = new System.Drawing.Point(16, 72); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(224, 72); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Loading, please wait"; + // + // button3 + // + this.button3.Anchor = System.Windows.Forms.AnchorStyles.Bottom; + this.button3.Location = new System.Drawing.Point(91, 176); + this.button3.Name = "button3"; + this.button3.Size = new System.Drawing.Size(75, 23); + this.button3.TabIndex = 7; + this.button3.Text = "Stop"; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(564, 272); + this.Controls.Add(this.panel2); + this.Controls.Add(this.panel1); + this.Name = "Form1"; + this.Text = "Form1"; + this.Load += new System.EventHandler(this.Form1_Load); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.panel2.ResumeLayout(false); + this.groupBox1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.CheckedListBox checkedListBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.FolderBrowserDialog folderBrowserDialog1; + private System.ComponentModel.BackgroundWorker backgroundWorker1; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.ProgressBar progressBar1; + private System.Windows.Forms.Button button3; + } +} + diff --git a/BuildTools/UpdateVersionNumber/Form1.cs b/BuildTools/UpdateVersionNumber/Form1.cs new file mode 100644 index 000000000..67f43f103 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Form1.cs @@ -0,0 +1,262 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace UpdateVersionNumber +{ + public partial class Form1 : Form + { + private readonly System.Text.RegularExpressions.Regex RXP = new System.Text.RegularExpressions.Regex(@"\[assembly\: AssemblyVersion\(\""(?\d+\.\d+\.\d+\.\d+)\""\)\]"); + private readonly System.Text.RegularExpressions.Regex RXP2 = new System.Text.RegularExpressions.Regex(@"\[assembly\: AssemblyFileVersion\(\""(?\d+\.\d+\.\d+\.\d+)\""\)\]"); + private readonly System.Text.RegularExpressions.Regex RXP3 = new System.Text.RegularExpressions.Regex(@"\[assembly\: AssemblyFileVersionAttribute\(\""(?\d+\.\d+\.\d+\.\d+)\""\)\]"); + + private readonly System.Text.RegularExpressions.Regex RXP4 = new System.Text.RegularExpressions.Regex(@"\<\?define ProductVersion\=\""(?\d+\.\d+\.\d+\.\d+)\"" \?\>"); + + private readonly System.Text.RegularExpressions.Regex RXP5 = new System.Text.RegularExpressions.Regex(@"newVersion\=\""(?\d+\.\d+\.\d+\.\d+)\"""); + + public Form1() + { + InitializeComponent(); + panel1.Dock = DockStyle.Fill; + panel2.Dock = DockStyle.Fill; + } + + private void textBox2_TextChanged(object sender, EventArgs e) + { + panel1.Visible = false; + panel2.Visible = true; + + backgroundWorker1.RunWorkerAsync(textBox2.Text); + } + + private void Form1_Load(object sender, EventArgs e) + { + textBox2.Text = Application.StartupPath; + } + + private void button1_Click(object sender, EventArgs e) + { + if (folderBrowserDialog1.ShowDialog(this) == DialogResult.OK) + if (textBox2.Text == folderBrowserDialog1.SelectedPath) + textBox2_TextChanged(sender, e); + else + textBox2.Text = folderBrowserDialog1.SelectedPath; + } + + private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) + { + List results = new List(); + e.Result = results; + Queue folders = new Queue(); + string startfolder = e.Argument as string; + if (string.IsNullOrEmpty(startfolder)) + return; + + folders.Enqueue(startfolder); + + while (folders.Count > 0) + { + string folder = folders.Dequeue(); + + foreach (string f in System.IO.Directory.GetDirectories(folder)) + folders.Enqueue(f); + + if (backgroundWorker1.CancellationPending) + { + e.Cancel = true; + return; + } + + string file = System.IO.Path.Combine(folder, "AssemblyInfo.cs"); + if (System.IO.File.Exists(file)) + { + string content = System.IO.File.ReadAllText(file); + System.Text.RegularExpressions.Match m = RXP.Match(content); + if (!m.Success) + m = RXP2.Match(content); + if (!m.Success) + m = RXP3.Match(content); + + if (m.Success) + { + string v = m.Groups["version"].Value; + results.Add(new DisplayItem(file, new Version(v))); + } + + } + + file = System.IO.Path.Combine(folder, "UpgradeData.wxi"); + if (System.IO.File.Exists(file)) + { + string content = System.IO.File.ReadAllText(file); + System.Text.RegularExpressions.Match m = RXP4.Match(content); + + if (m.Success) + { + string v = m.Groups["version"].Value; + results.Add(new DisplayItem(file, new Version(v))); + } + } + + file = System.IO.Path.Combine(folder, "AssemblyRedirects.xml"); + if (System.IO.File.Exists(file)) + { + string content = System.IO.File.ReadAllText(file); + System.Text.RegularExpressions.Match m = RXP5.Match(content); + + if (m.Success) + { + string v = m.Groups["version"].Value; + results.Add(new DisplayItem(file, new Version(v))); + } + } + + } + } + + private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + panel1.Visible =true; + panel2.Visible = false; + + if (e.Error != null) + MessageBox.Show(this, "An error occured: " + e.Error.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); + + if (e.Cancelled || e.Error != null) + { + checkedListBox1.Items.Clear(); + button2.Enabled = false; + return; + } + + Dictionary overrideDefault = new Dictionary(System.Environment.OSVersion.Platform == PlatformID.MacOSX || System.Environment.OSVersion.Platform == PlatformID.Unix ? StringComparer.InvariantCulture : StringComparer.InvariantCultureIgnoreCase ); + string configfile = null; + bool updateDefault = true; + try + { + configfile = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "UpdateVersionNumber.config"); + if (System.IO.File.Exists(configfile)) + { + System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); + doc.Load(configfile); + + System.Xml.XmlNode root = doc["root"]; + if (root.Attributes["update_as_default"] != null) + updateDefault = bool.Parse(root.Attributes["update_as_default"].Value); + + foreach (System.Xml.XmlNode file in root.SelectNodes("file")) + { + string filename = file.InnerText; + if (!System.IO.Path.IsPathRooted(filename)) + filename = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), filename); + + overrideDefault[filename] = bool.Parse(file.Attributes["update"].Value); + } + } + } + catch(Exception ex) + { + MessageBox.Show(this, string.Format("An error occured while reading config file \"{0}\": {1}", configfile, ex.Message), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + button2.Enabled = true; + List files = (List)e.Result; + checkedListBox1.Items.Clear(); + Version vmax = new Version(); + foreach (DisplayItem d in files) + { + checkedListBox1.Items.Add(d, overrideDefault.ContainsKey(d.File) ? overrideDefault[d.File] : updateDefault); + vmax = d.Version > vmax ? d.Version : vmax; + } + + textBox1.Text = vmax.ToString(); + + } + + private void button3_Click(object sender, EventArgs e) + { + if (!backgroundWorker1.CancellationPending && backgroundWorker1.IsBusy) + backgroundWorker1.CancelAsync(); + } + + private void button2_Click(object sender, EventArgs e) + { + if (checkedListBox1.CheckedItems.Count == 0) + { + MessageBox.Show(this, "No files selected", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + Version v; + + try + { + v = new Version(textBox1.Text); + } + catch (Exception ex) + { + MessageBox.Show(this, "Invalid version number: " + ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + string replacement = "[assembly: AssemblyVersion(\"" + v.ToString() + "\")]"; + string replacement2 = "[assembly: AssemblyFileVersion(\"" + v.ToString() + "\")]"; + string replacement3 = "[assembly: AssemblyFileVersionAttribute(\"" + v.ToString() + "\")]"; + string replacement4 = ""; + string replacement5 = "newVersion=\"" + v.ToString() + "\""; + bool errors = false; + + foreach (DisplayItem d in checkedListBox1.CheckedItems) + { + try + { + string content = System.IO.File.ReadAllText(d.File); + if (d.File.ToLower().EndsWith(".wxi")) + content = RXP4.Replace(content, replacement4); + else if (System.IO.Path.GetFileName(d.File).Equals("AssemblyRedirects.xml", StringComparison.InvariantCultureIgnoreCase)) + { + content = RXP5.Replace(content, replacement5); + } + else + { + content = RXP.Replace(content, replacement); + content = RXP2.Replace(content, replacement2); + content = RXP3.Replace(content, replacement3); + } + System.IO.File.WriteAllText(d.File, content); + } + catch (Exception ex) + { + errors = true; + MessageBox.Show(this, "Failed to update file: " + d.File + "\r\nError: " + ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + if (!errors) + this.Close(); + } + } +} diff --git a/BuildTools/UpdateVersionNumber/Form1.resx b/BuildTools/UpdateVersionNumber/Form1.resx new file mode 100644 index 000000000..583cfc846 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Form1.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 175, 17 + + \ No newline at end of file diff --git a/BuildTools/UpdateVersionNumber/Program.cs b/BuildTools/UpdateVersionNumber/Program.cs new file mode 100644 index 000000000..776ac62a5 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Program.cs @@ -0,0 +1,39 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace UpdateVersionNumber +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/BuildTools/UpdateVersionNumber/Properties/AssemblyInfo.cs b/BuildTools/UpdateVersionNumber/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..01e267f31 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UpdateVersionNumber")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HexaD")] +[assembly: AssemblyProduct("UpdateVersionNumber")] +[assembly: AssemblyCopyright("Copyright © HexaD 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9118bb59-ad2d-4660-81ad-cc23f12ce450")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BuildTools/UpdateVersionNumber/Properties/Resources.Designer.cs b/BuildTools/UpdateVersionNumber/Properties/Resources.Designer.cs new file mode 100644 index 000000000..4134defd4 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.237 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UpdateVersionNumber.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UpdateVersionNumber.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/BuildTools/UpdateVersionNumber/Properties/Resources.resx b/BuildTools/UpdateVersionNumber/Properties/Resources.resx new file mode 100644 index 000000000..ffecec851 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BuildTools/UpdateVersionNumber/Properties/Settings.Designer.cs b/BuildTools/UpdateVersionNumber/Properties/Settings.Designer.cs new file mode 100644 index 000000000..18c66d951 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.237 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UpdateVersionNumber.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/BuildTools/UpdateVersionNumber/Properties/Settings.settings b/BuildTools/UpdateVersionNumber/Properties/Settings.settings new file mode 100644 index 000000000..abf36c5d3 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/BuildTools/UpdateVersionNumber/UpdateVersionNumber.csproj b/BuildTools/UpdateVersionNumber/UpdateVersionNumber.csproj new file mode 100644 index 000000000..33b5e2664 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/UpdateVersionNumber.csproj @@ -0,0 +1,86 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {2CD0151C-C091-4F98-B7B7-144DF02EF2E7} + WinExe + Properties + UpdateVersionNumber + UpdateVersionNumber + v2.0 + 512 + + + 3.5 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + \ No newline at end of file diff --git a/BuildTools/UpdateVersionNumber/UpdateVersionNumber.sln b/BuildTools/UpdateVersionNumber/UpdateVersionNumber.sln new file mode 100644 index 000000000..6c30c19c6 --- /dev/null +++ b/BuildTools/UpdateVersionNumber/UpdateVersionNumber.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateVersionNumber", "UpdateVersionNumber.csproj", "{2CD0151C-C091-4F98-B7B7-144DF02EF2E7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2CD0151C-C091-4F98-B7B7-144DF02EF2E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CD0151C-C091-4F98-B7B7-144DF02EF2E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CD0151C-C091-4F98-B7B7-144DF02EF2E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CD0151C-C091-4F98-B7B7-144DF02EF2E7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BuildTools/WixIncludeMake/AssemblyInfo.cs b/BuildTools/WixIncludeMake/AssemblyInfo.cs new file mode 100644 index 000000000..fd422f2fc --- /dev/null +++ b/BuildTools/WixIncludeMake/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("WixIncludeMake")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("kenneth")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/BuildTools/WixIncludeMake/LinqHelpers.cs b/BuildTools/WixIncludeMake/LinqHelpers.cs new file mode 100644 index 000000000..1e4486781 --- /dev/null +++ b/BuildTools/WixIncludeMake/LinqHelpers.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace WixIncludeMake +{ + public static class LinqHelpers + { + public static Dictionary ToSafeDictionary(this IEnumerable source, Func keySelector, Func valueSelector, string locationName) + { + if (source == null) + throw new ArgumentNullException("source"); + if (keySelector == null) + throw new ArgumentNullException("keySelector"); + if (valueSelector == null) + throw new ArgumentNullException("valueSelector"); + + Dictionary res = new Dictionary(); + foreach (TSource el in source) + { + TKey k = keySelector(el); + TValue v = valueSelector(el); + + if (res.ContainsKey(k)) + Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName); + else + res.Add(k, v); + } + + return res; + } + } +} + diff --git a/BuildTools/WixIncludeMake/Program.cs b/BuildTools/WixIncludeMake/Program.cs new file mode 100644 index 000000000..b123a02c0 --- /dev/null +++ b/BuildTools/WixIncludeMake/Program.cs @@ -0,0 +1,380 @@ +using System; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using System.Collections.Generic; +using System.Reflection; + +namespace WixIncludeMake +{ + public class Program + { + private const string DB_FILE = "generated-keys.xml"; + private const string USER_FILE = "fixed-keys.xml"; + private static readonly string DIR_SEP = System.IO.Path.DirectorySeparatorChar.ToString(); + private static readonly string basedir = System.IO.Path.GetDirectoryName (System.Reflection.Assembly.GetExecutingAssembly().Location); + + public static readonly XNamespace XWIX = "http://schemas.microsoft.com/wix/2006/wi"; + + private interface IFolder + { + Dictionary Folders { get; } + Dictionary Files { get; } + } + + private class FolderInfo : IFolder + { + public Dictionary Folders { get; private set; } + public Dictionary Files { get; private set; } + public string Name { get; set; } + private string m_id; + public string ID { get { return m_id.Replace(" ", "").Replace("-", "").ToUpperInvariant(); } set { m_id = value; } } + public Guid GUID { get; set; } + + private FolderInfo() + { + Folders = new Dictionary(); + Files = new Dictionary(); + } + + public FolderInfo(string name, string id, Guid guid) + : this() + { + this.Name = name; + this.ID = id; + this.GUID = guid; + } + + public IEnumerable FileNodes + { + get + { + return + from x in Files + orderby x.Key + select + new XElement(XWIX + "File", + new XAttribute("Id", "file_" + x.Value.ID), + new XAttribute("Checksum", "yes"), + new XAttribute("Source", x.Value.SourceFile) + ); + } + } + + public XElement FolderNode + { + get + { + return + new XElement(XWIX + "Directory", + new XAttribute("Id", "dir_" + this.ID), + new XAttribute("Name", this.Name), + new XElement(XWIX + "Component", + new XAttribute("Id", "comp_" + this.ID), + new XAttribute("DiskId", 1), + new XAttribute("KeyPath", "yes"), + new XAttribute("Guid", this.GUID.ToString()), + new XAttribute("Win64", "$(var.Win64)"), + FileNodes + ), + from x in Folders + orderby x.Key + select x.Value.FolderNode + ); + + } + } + + public IEnumerable Subfolders + { + get + { + List res = new List(); + res.Add (this); + foreach(var f in this.Folders.Values) + res.AddRange(f.Subfolders); + return res; + } + } + } + + private class FileInfo + { + public string Name { get; set; } + private string m_id; + public string ID { get { return m_id.Replace(" ", "").Replace("-", "").ToUpperInvariant(); } set { m_id = value; } } + public string SourceFile { get; set; } + + public FileInfo(string name, string id, string sourcefile) + { + this.Name = name; + this.ID = id; + this.SourceFile = sourcefile; + } + } + + private class RootFolderInfo : FolderInfo + { + public Dictionary GeneratedKeys { get; private set; } + public Dictionary UserKeys { get; private set; } + public string ComponentID { get; set; } + private readonly Options m_opt; + + public RootFolderInfo(Options opt, IEnumerable paths = null) + : base("", "", Guid.NewGuid()) + { + GeneratedKeys = ReadKeyDatabase(opt.dbfilename); + UserKeys = ReadKeyDatabase(opt.userfilename); + ComponentID = opt.componentid; + m_opt = opt; + + string id; + if (!GeneratedKeys.TryGetValue(DIR_SEP, out id)) + id = Guid.NewGuid().ToString(); + + if (UserKeys.ContainsKey(DIR_SEP)) + id = UserKeys[DIR_SEP]; + + this.ID = id; + + Add (paths); + } + + public void Add(IEnumerable paths) + { + if (paths == null) + return; + + foreach(var p in paths) + Add (p); + } + + public void Add(string path) + { + if (string.IsNullOrEmpty(path)) + return; + + bool isDir = false; + if (path.EndsWith(DIR_SEP)) + { + isDir = true; + path = path.Substring(0, path.Length - 1); + } + + List folders = new List(); + string filename = null; + + while(!string.IsNullOrEmpty(path)) + { + string sourcepath = System.IO.Path.GetDirectoryName(path); + string elname = System.IO.Path.GetFileName(path); + folders.Add(elname); + + path = sourcepath; + if (path.EndsWith(DIR_SEP)) + path = path.Substring(0, path.Length - 1); + + } + + if (!isDir) + { + filename = folders[0]; + folders.RemoveAt (0); + } + + IFolder cur = this; + folders.Reverse(); + + StringBuilder sbp = new StringBuilder(); + + foreach(var f in folders) + { + sbp.Append(f); + sbp.Append(DIR_SEP); + + FolderInfo next; + if (!cur.Folders.TryGetValue(f, out next)) + { + //We have a new folder + string id; + if (!GeneratedKeys.TryGetValue(sbp.ToString(), out id)) + { + id = Guid.NewGuid().ToString(); + GeneratedKeys.Add(sbp.ToString(), id); + } + + Guid g; + try { g = new Guid(id); } + catch + { + g = Guid.NewGuid(); + GeneratedKeys[sbp.ToString()] = g.ToString(); + Console.Error.WriteLine ("Unable to parse {0} into a GUID, I told you not to edit the file!!!{1}A new GUID will be used: {2}", id, Environment.NewLine, g); + } + + string userkey; + if (!UserKeys.TryGetValue(sbp.ToString(), out userkey)) + userkey = g.ToString(); + + next = new FolderInfo(f, userkey, g); + cur.Folders.Add(f, next); + } + + cur = next; + } + + if (!string.IsNullOrEmpty(filename)) + { + sbp.Append(filename); + + string id; + if (!GeneratedKeys.TryGetValue(sbp.ToString(), out id)) + GeneratedKeys.Add(sbp.ToString(), id = Guid.NewGuid().ToString()); + if (UserKeys.ContainsKey(sbp.ToString())) + id = UserKeys[sbp.ToString()]; + + string fullpath = System.IO.Path.Combine(m_opt.fileprefix, sbp.ToString()).Replace(DIR_SEP, "\\"); + + cur.Files.Add(filename, new FileInfo(filename, id, fullpath)); + } + } + + public IEnumerable Node + { + get + { + List res = new List(); + res.Add(new XElement(XWIX + "Component", + new XAttribute("Id", "comp_" + this.ID), + new XAttribute("DiskId", 1), + new XAttribute("KeyPath", "yes"), + new XAttribute("Guid", this.GUID.ToString()), + new XAttribute("Win64", "$(var.Win64)"), + FileNodes + )); + + res.AddRange( + from x in Folders + orderby x.Key + select x.Value.FolderNode + ); + + return res; + } + } + + public XElement GroupNode + { + get + { + return new XElement(XWIX + "ComponentGroup", + new XAttribute("Id", this.ComponentID), + from x in this.Subfolders + where x != this + select new XElement(XWIX + "ComponentRef", + new XAttribute("Id", "comp_" + x.ID) + ) + ); + } + } + } + + public static string GuidToString(Guid g) + { + StringBuilder sb = new StringBuilder(); + foreach(var b in g.ToByteArray()) + sb.Append(string.Format("{0:x2}", b)); + return sb.ToString(); + } + + private class Options + { + public string dbfilename = System.IO.Path.Combine(basedir, DB_FILE); + public string userfilename = System.IO.Path.Combine(basedir, USER_FILE); + public string sourcefolder = System.IO.Path.GetFullPath(Environment.CurrentDirectory); + public string outputfile = System.IO.Path.GetFullPath("binfiles.wxs"); + public string ignorefilter = null; + public string dirref = "INSTALLLOCATION"; + public string componentid = "FILES_COMPONENT"; + public string fileprefix = ""; + + public void Fixup() + { + sourcefolder = System.IO.Path.GetFullPath(sourcefolder); + outputfile = System.IO.Path.GetFullPath(outputfile); + sourcefolder = Duplicati.Library.Utility.Utility.AppendDirSeparator(sourcefolder); + } + } + + public static void Main (string[] _args) + { + List args = new List(_args); + Dictionary options = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args); + Options opt = new Options(); + + + foreach(FieldInfo fi in opt.GetType().GetFields()) + if (options.ContainsKey(fi.Name)) + fi.SetValue(opt, options[fi.Name]); + + opt.Fixup(); + + Duplicati.Library.Utility.FilenameFilter filter = null; + if (!string.IsNullOrEmpty(opt.ignorefilter)) + filter = new Duplicati.Library.Utility.FilenameFilter( + new List( + new Duplicati.Library.Utility.IFilenameFilter[] { + new Duplicati.Library.Utility.RegularExpressionFilter(false, opt.ignorefilter) + } + ) + ); + + + var paths = Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(opt.sourcefolder, filter).ConvertAll(x => x.Substring(opt.sourcefolder.Length)); + + //A bit backwards, but we have flattend the file list, and now we re-construct the tree, + // but we do not care much about performance here + RootFolderInfo rootfolder = new RootFolderInfo(opt, paths); + + new XDocument( + new XElement(XWIX + "Wix", + new XElement(XWIX + "Fragment", + rootfolder.GroupNode, + new XElement(XWIX + "DirectoryRef", + new XAttribute("Id", opt.dirref), + rootfolder.Node + ) + ) + ) + ).Save(opt.outputfile); + + WriteKeyDatabase(rootfolder.GeneratedKeys, opt.dbfilename, true); + + Console.WriteLine("Generated wxs: {0}", opt.outputfile); + + } + + private static Dictionary ReadKeyDatabase(string filename) + { + if (!System.IO.File.Exists(filename)) + return new Dictionary(); + + return XDocument.Load(filename).Element("root").Elements("entry").ToSafeDictionary( + c => c.Attribute("name").Value.Replace("\\", DIR_SEP), c => c.Attribute("key").Value, filename); + } + + private static void WriteKeyDatabase(Dictionary keys, string filename, bool isKeyDb) + { + new XDocument( + new XComment(isKeyDb ? "This file is autogenerated, do not edit! EVER!" : "format is "), + new XElement("root", + from x in keys + select new XElement("entry", + new XAttribute("name", x.Key.Replace(DIR_SEP, "\\")), + new XAttribute("key", x.Value) + ) + ) + ).Save(filename); + } + } +} diff --git a/BuildTools/WixIncludeMake/WixIncludeMake.csproj b/BuildTools/WixIncludeMake/WixIncludeMake.csproj new file mode 100644 index 000000000..20e2058cb --- /dev/null +++ b/BuildTools/WixIncludeMake/WixIncludeMake.csproj @@ -0,0 +1,47 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {34426731-7BAD-4B28-BD80-FD117EC082E5} + Exe + WixIncludeMake + WixIncludeMake + WixIncludeMake.Program + + + full + false + ..\..\Installer + 4 + false + true + --sourcefolder=../Duplicati/GUI/bin/Release --fileprefix=bin/Release/ --ignorefilter=(.*/\.svn/)|(.*\.config) + + + none + false + ..\..\Installer + 4 + + + + + + + + + + + + + + + + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65} + Duplicati.Library.Utility + + + \ No newline at end of file diff --git a/BuildTools/WixIncludeMake/WixIncludeMake.sln b/BuildTools/WixIncludeMake/WixIncludeMake.sln new file mode 100644 index 000000000..a27a9fb3a --- /dev/null +++ b/BuildTools/WixIncludeMake/WixIncludeMake.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixIncludeMake", "WixIncludeMake.csproj", "{34426731-7BAD-4B28-BD80-FD117EC082E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {34426731-7BAD-4B28-BD80-FD117EC082E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34426731-7BAD-4B28-BD80-FD117EC082E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34426731-7BAD-4B28-BD80-FD117EC082E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34426731-7BAD-4B28-BD80-FD117EC082E5}.Release|Any CPU.Build.0 = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.ActiveCfg = Debug|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.Build.0 = Debug|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.ActiveCfg = Release|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = WixIncludeMake.csproj + EndGlobalSection +EndGlobal diff --git a/BuildTools/WixProjBuilder/Program.cs b/BuildTools/WixProjBuilder/Program.cs new file mode 100644 index 000000000..9ee5e411c --- /dev/null +++ b/BuildTools/WixProjBuilder/Program.cs @@ -0,0 +1,256 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Text; + +namespace WixProjBuilder +{ + class Program + { + static void Main(string[] _args) + { + List args = new List(_args); + Dictionary options = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args); + + if (args.Count != 1) + { + Console.WriteLine("Usage: "); + Console.WriteLine(" WixProjBuilder.exe [option=value]"); + return; + } + + if (!System.IO.File.Exists(args[0])) + { + Console.WriteLine(string.Format("File not found: {0}", args[0])); + return; + } + + string wixpath; + if (options.ContainsKey("wixpath")) + wixpath = options["wixpath"]; + else + wixpath = System.Environment.GetEnvironmentVariable("WIXPATH"); + + if (string.IsNullOrEmpty(wixpath)) + { + string[] known_wix_names = new string[] + { + "Windows Installer XML v3", + "Windows Installer XML v3.1", + "Windows Installer XML v3.2", + "Windows Installer XML v3.3", + "Windows Installer XML v3.4", + "Windows Installer XML v3.5", + "Windows Installer XML v3.6", + "Windows Installer XML v3.7" + }; + + foreach(var p in known_wix_names) + { + foreach(var p2 in new string[] {"%ProgramFiles(x86)%", "%programfiles%"}) + { + wixpath = System.IO.Path.Combine(System.IO.Path.Combine(Environment.ExpandEnvironmentVariables(p2), p), "bin"); + if (System.IO.Directory.Exists(wixpath)) + { + Console.WriteLine(string.Format("*** wixpath not specified, using: {0}", wixpath)); + break; + } + } + + if (System.IO.Directory.Exists(wixpath)) + break; + } + + } + + + if (!System.IO.Directory.Exists(wixpath)) + { + Console.WriteLine(string.Format("WiX not found, please install Windows Installer XML 3 or greater")); + Console.WriteLine(string.Format(" supply path to WiX bin folder with option --wixpath=...path...")); + return; + } + + args[0] = System.IO.Path.GetFullPath(args[0]); + + Console.WriteLine(string.Format("Parsing wixproj file: {0}", args[0])); + + System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); + doc.Load(args[0]); + + string config = "Release"; + if (options.ContainsKey("configuration")) + config = options["configuration"]; + + string projdir = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(args[0])); + + List includes = new List(); + List refs = new List(); + List content = new List(); + + System.Xml.XmlNamespaceManager nm = new System.Xml.XmlNamespaceManager(doc.NameTable); + nm.AddNamespace("ms", "http://schemas.microsoft.com/developer/msbuild/2003"); + + foreach (System.Xml.XmlNode n in doc.SelectNodes("ms:Project/ms:ItemGroup/ms:Compile", nm)) + includes.Add(n.Attributes["Include"].Value); + + foreach (System.Xml.XmlNode n in doc.SelectNodes("ms:Project/ms:ItemGroup/ms:Content", nm)) + content.Add(n.Attributes["Include"].Value); + + foreach (System.Xml.XmlNode n in doc.SelectNodes("ms:Project/ms:ItemGroup/ms:WixExtension", nm)) + refs.Add(n.Attributes["Include"].Value); + + + string objdir = System.IO.Path.Combine(System.IO.Path.Combine(projdir, "obj"), config); + string packagename = "output"; + string outdir = System.IO.Path.Combine("bin", config); + string outtype = "Package"; + + //TODO: Support multiconfiguration system correctly + foreach (System.Xml.XmlNode n in doc.SelectNodes("ms:Project/ms:PropertyGroup/ms:Configuration", nm)) + if (true) //if (string.Compare(n.InnerText, config, true) == 0) + { + System.Xml.XmlNode p = n.ParentNode; + if (p["OutputName"] != null) + packagename = p["OutputName"].InnerText; + if (p["OutputType"] != null) + outtype = p["OutputType"].InnerText; + if (p["OutputPath"] != null) + outdir = p["OutputPath"].InnerText.Replace("$(Configuration)", config); + if (p["IntermediateOutputPath"] != null) + objdir = p["IntermediateOutputPath"].InnerText.Replace("$(Configuration)", config); + } + + if (!objdir.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) + objdir += System.IO.Path.DirectorySeparatorChar; + + string msiname = System.IO.Path.Combine(outdir, packagename + ".msi"); + + Console.WriteLine(" Compiling ... "); + if (includes.Count == 0) + { + Console.WriteLine("No files found to compile in project file"); + return; + } + + string compile_args = "\"" + string.Join("\" \"", includes.ToArray()) + "\""; + compile_args += " -out \"" + objdir.Replace("\\", "\\\\") + "\""; + + if (options.ContainsKey("platform") && options["platform"] == "x64") + compile_args += " -dPlatform=x64 -dWin64=yes"; + else + compile_args += " -dPlatform=x86 -dWin64=no"; + + int res = Execute( + System.IO.Path.Combine(wixpath, "candle.exe"), + projdir, + compile_args); + + if (res != 0) + { + Console.WriteLine("Compilation failed, aborting"); + return; + } + + Console.WriteLine(" Linking ..."); + + for (int i = 0; i < includes.Count; i++) + includes[i] = System.IO.Path.Combine(objdir, System.IO.Path.GetFileNameWithoutExtension(includes[i]) + ".wixobj"); + + for (int i = 0; i < refs.Count; i++) + if (!System.IO.Path.IsPathRooted(refs[i])) + { + refs[i] = FindDll(refs[i] + ".dll", new string[] { projdir, wixpath }); + if (!System.IO.Path.IsPathRooted(refs[i])) + refs[i] = FindDll(refs[i]); + } + + string link_args = "\"" + string.Join("\" \"", includes.ToArray()) + "\""; + + if (refs.Count > 0) + link_args += " -ext \"" + string.Join("\" -ext \"", refs.ToArray()) + "\""; + + link_args += " -out \"" + msiname + "\""; + + res = Execute( + System.IO.Path.Combine(wixpath, "light.exe"), + projdir, + link_args); + + if (res != 0) + { + Console.WriteLine("Link failed, aborting"); + return; + } + + if (!System.IO.Path.IsPathRooted(msiname)) + msiname = System.IO.Path.GetFullPath(System.IO.Path.Combine(projdir, msiname)); + + Console.WriteLine(string.Format("Done: {0}", msiname)); + } + + private static int Execute(string app, string workdir, string args) + { + System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(app, args); + psi.CreateNoWindow = true; + psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; + psi.WorkingDirectory = workdir; + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + psi.UseShellExecute = false; + + System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi); + p.WaitForExit(5 * 60 * 1000); //Wait up to five minutes + if (!p.HasExited) + { + try { p.Kill(); } + catch { } + + Console.WriteLine("Stdout: " + p.StandardOutput.ReadToEnd()); + Console.WriteLine("Stderr: " + p.StandardError.ReadToEnd()); + + throw new Exception(string.Format("Application {0} hung", app)); + } + + Console.WriteLine(); + Console.WriteLine(p.StandardOutput.ReadToEnd()); + Console.WriteLine(p.StandardError.ReadToEnd()); + Console.WriteLine(); + + + return p.ExitCode; + } + + private static string FindDll(string filename) + { + return FindDll(filename, System.Environment.GetEnvironmentVariable("path").Split(System.IO.Path.PathSeparator)); + } + + private static string FindDll(string filename, IEnumerable paths) + { + foreach (string p in paths) + if (System.IO.File.Exists(System.IO.Path.Combine(p, filename))) + return System.IO.Path.Combine(p, filename); + + return filename; + } + } +} diff --git a/BuildTools/WixProjBuilder/Properties/AssemblyInfo.cs b/BuildTools/WixProjBuilder/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..5c6a23cc1 --- /dev/null +++ b/BuildTools/WixProjBuilder/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WixProjBuilder")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HexaD")] +[assembly: AssemblyProduct("WixProjBuilder")] +[assembly: AssemblyCopyright("Copyright © HexaD 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("aec90a98-c777-46f0-ba4f-1f81dc9fda11")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BuildTools/WixProjBuilder/WixProjBuilder.csproj b/BuildTools/WixProjBuilder/WixProjBuilder.csproj new file mode 100644 index 000000000..2b32bc59c --- /dev/null +++ b/BuildTools/WixProjBuilder/WixProjBuilder.csproj @@ -0,0 +1,70 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {794CD060-AB3B-4885-B23B-6B26272C84E0} + Exe + Properties + WixProjBuilder + WixProjBuilder + v4.0 + 512 + + + 3.5 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + 3.5 + + + + + + + + + + + + + + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65} + Duplicati.Library.Utility + + + + + \ No newline at end of file diff --git a/BuildTools/WixProjBuilder/WixProjBuilder.sln b/BuildTools/WixProjBuilder/WixProjBuilder.sln new file mode 100644 index 000000000..49f93b496 --- /dev/null +++ b/BuildTools/WixProjBuilder/WixProjBuilder.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixProjBuilder", "WixProjBuilder.csproj", "{794CD060-AB3B-4885-B23B-6B26272C84E0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {794CD060-AB3B-4885-B23B-6B26272C84E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {794CD060-AB3B-4885-B23B-6B26272C84E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {794CD060-AB3B-4885-B23B-6B26272C84E0}.Debug|x86.ActiveCfg = Debug|Any CPU + {794CD060-AB3B-4885-B23B-6B26272C84E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {794CD060-AB3B-4885-B23B-6B26272C84E0}.Release|Any CPU.Build.0 = Release|Any CPU + {794CD060-AB3B-4885-B23B-6B26272C84E0}.Release|x86.ActiveCfg = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.ActiveCfg = Debug|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.Build.0 = Debug|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.ActiveCfg = Release|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BuildTools/WixProjBuilder/app.config b/BuildTools/WixProjBuilder/app.config new file mode 100644 index 000000000..89dc7d426 --- /dev/null +++ b/BuildTools/WixProjBuilder/app.config @@ -0,0 +1,3 @@ + + + diff --git a/BuildTools/WixProjBuilder/license.txt b/BuildTools/WixProjBuilder/license.txt new file mode 100644 index 000000000..b7187c597 --- /dev/null +++ b/BuildTools/WixProjBuilder/license.txt @@ -0,0 +1,491 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + + Copyright 2008, http://www.hexad.dk, opensource@hexad.dk diff --git a/Duplicati Scheduler.sln b/Duplicati Scheduler.sln new file mode 100644 index 000000000..a5efe2b4d --- /dev/null +++ b/Duplicati Scheduler.sln @@ -0,0 +1,234 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Main", "Duplicati\Library\Main\Duplicati.Library.Main.csproj", "{10D2D1B7-C664-41D8-9B3A-00040C3D421B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.File", "Duplicati\Library\Backend\File\Duplicati.Library.Backend.File.csproj", "{FC9B7611-836F-4127-8B44-A7C31F506807}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.FTP", "Duplicati\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj", "{F61679A9-E5DE-468A-B5A4-05F92D0143D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Compression", "Duplicati\Library\Compression\Duplicati.Library.Compression.csproj", "{19ECCE09-B5EB-406C-8C57-BAC66997D469}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Encryption", "Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj", "{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.SharpRSync", "Duplicati\Library\SharpRSync\Duplicati.Library.SharpRSync.csproj", "{47FE73E1-0832-44F7-96B9-2580273206C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Logging", "Duplicati\Library\Logging\Duplicati.Library.Logging.csproj", "{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Interface", "Duplicati\Library\Interface\Duplicati.Library.Interface.csproj", "{C5899F45-B0FF-483C-9D38-24A9FCAAB237}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.DynamicLoader", "Duplicati\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj", "{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Snapshots", "Duplicati\Library\Snapshots\Duplicati.Library.Snapshots.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.License", "Duplicati\License\Duplicati.License.csproj", "{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Winforms.Controls", "Duplicati\Winforms\Controls\Duplicati.Winforms.Controls.csproj", "{7886BA79-A38A-4288-8B8A-9F29D0E15AC0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Modules.Builtin", "Duplicati\Library\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj", "{52826615-7964-47FE-B4B3-1B2DBDF605B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Scheduler", "Duplicati\Scheduler\Duplicati.Scheduler.csproj", "{D1D503E8-5CF7-413B-81FB-403D65B43AAB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Scheduler.Data", "Duplicati\Scheduler\Data\Duplicati.Scheduler.Data.csproj", "{C8F0DD19-E242-4E9C-A707-2FFADE8D7970}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Scheduler.Monitor.SQL", "Duplicati\Scheduler\Monitor.SQL\Duplicati.Scheduler.Monitor.SQL.csproj", "{ECA674A3-CC59-4C75-90E3-5AACA591EC5D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Scheduler.RunBackup", "Duplicati\Scheduler\RunBackup\Duplicati.Scheduler.RunBackup.csproj", "{520FA2BD-1C59-40D9-BCB5-EDA11327B7F0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Scheduler.Utility", "Duplicati\Scheduler\Utility\Duplicati.Scheduler.Utility.csproj", "{0E3DF716-5657-4B51-9BF2-3791880930E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectListView2010", "thirdparty\ObjectListView\ObjectListView\ObjectListView2010.csproj", "{18FEDA0C-D147-4286-B39A-01204808106A}" +EndProject +Global + GlobalSection(SubversionScc) = preSolution + Svn-Managed = True + Manager = AnkhSVN - Subversion Support for Visual Studio + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.ActiveCfg = Debug|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.Build.0 = Debug|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.ActiveCfg = Release|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.Build.0 = Release|x86 + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|x86.ActiveCfg = Debug|x86 + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|x86.Build.0 = Debug|x86 + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|Any CPU.Build.0 = Release|Any CPU + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|x86.ActiveCfg = Release|x86 + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|x86.Build.0 = Release|x86 + {FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|x86.ActiveCfg = Debug|x86 + {FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|x86.Build.0 = Debug|x86 + {FC9B7611-836F-4127-8B44-A7C31F506807}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC9B7611-836F-4127-8B44-A7C31F506807}.Release|Any CPU.Build.0 = Release|Any CPU + {FC9B7611-836F-4127-8B44-A7C31F506807}.Release|x86.ActiveCfg = Release|x86 + {FC9B7611-836F-4127-8B44-A7C31F506807}.Release|x86.Build.0 = Release|x86 + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|x86.ActiveCfg = Debug|x86 + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|x86.Build.0 = Debug|x86 + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|Any CPU.Build.0 = Release|Any CPU + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|x86.ActiveCfg = Release|x86 + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|x86.Build.0 = Release|x86 + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|x86.ActiveCfg = Debug|x86 + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|x86.Build.0 = Debug|x86 + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.Build.0 = Release|Any CPU + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|x86.ActiveCfg = Release|x86 + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|x86.Build.0 = Release|x86 + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|x86.ActiveCfg = Debug|x86 + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|x86.Build.0 = Debug|x86 + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.Build.0 = Release|Any CPU + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|x86.ActiveCfg = Release|x86 + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|x86.Build.0 = Release|x86 + {47FE73E1-0832-44F7-96B9-2580273206C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Debug|x86.ActiveCfg = Debug|x86 + {47FE73E1-0832-44F7-96B9-2580273206C3}.Debug|x86.Build.0 = Debug|x86 + {47FE73E1-0832-44F7-96B9-2580273206C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Release|Any CPU.Build.0 = Release|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Release|x86.ActiveCfg = Release|x86 + {47FE73E1-0832-44F7-96B9-2580273206C3}.Release|x86.Build.0 = Release|x86 + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|x86.ActiveCfg = Debug|x86 + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|x86.Build.0 = Debug|x86 + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.Build.0 = Release|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|x86.ActiveCfg = Release|x86 + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|x86.Build.0 = Release|x86 + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|x86.ActiveCfg = Debug|x86 + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|x86.Build.0 = Debug|x86 + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.Build.0 = Release|Any CPU + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|x86.ActiveCfg = Release|x86 + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|x86.Build.0 = Release|x86 + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|x86.ActiveCfg = Debug|x86 + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|x86.Build.0 = Debug|x86 + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|Any CPU.Build.0 = Release|Any CPU + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|x86.ActiveCfg = Release|x86 + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|x86.Build.0 = Release|x86 + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|x86.ActiveCfg = Debug|x86 + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|x86.Build.0 = Debug|x86 + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|Any CPU.Build.0 = Release|Any CPU + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|x86.ActiveCfg = Release|x86 + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|x86.Build.0 = Release|x86 + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|x86.ActiveCfg = Debug|x86 + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|x86.Build.0 = Debug|x86 + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.Build.0 = Release|Any CPU + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|x86.ActiveCfg = Release|x86 + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|x86.Build.0 = Release|x86 + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Debug|x86.ActiveCfg = Debug|x86 + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Debug|x86.Build.0 = Debug|x86 + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Release|Any CPU.Build.0 = Release|Any CPU + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Release|x86.ActiveCfg = Release|x86 + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Release|x86.Build.0 = Release|x86 + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|x86.ActiveCfg = Debug|x86 + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|x86.Build.0 = Debug|x86 + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|Any CPU.Build.0 = Release|Any CPU + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|x86.ActiveCfg = Release|x86 + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|x86.Build.0 = Release|x86 + {D1D503E8-5CF7-413B-81FB-403D65B43AAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1D503E8-5CF7-413B-81FB-403D65B43AAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1D503E8-5CF7-413B-81FB-403D65B43AAB}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1D503E8-5CF7-413B-81FB-403D65B43AAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1D503E8-5CF7-413B-81FB-403D65B43AAB}.Release|Any CPU.Build.0 = Release|Any CPU + {D1D503E8-5CF7-413B-81FB-403D65B43AAB}.Release|x86.ActiveCfg = Release|Any CPU + {C8F0DD19-E242-4E9C-A707-2FFADE8D7970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8F0DD19-E242-4E9C-A707-2FFADE8D7970}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8F0DD19-E242-4E9C-A707-2FFADE8D7970}.Debug|x86.ActiveCfg = Debug|Any CPU + {C8F0DD19-E242-4E9C-A707-2FFADE8D7970}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8F0DD19-E242-4E9C-A707-2FFADE8D7970}.Release|Any CPU.Build.0 = Release|Any CPU + {C8F0DD19-E242-4E9C-A707-2FFADE8D7970}.Release|x86.ActiveCfg = Release|Any CPU + {ECA674A3-CC59-4C75-90E3-5AACA591EC5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECA674A3-CC59-4C75-90E3-5AACA591EC5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECA674A3-CC59-4C75-90E3-5AACA591EC5D}.Debug|x86.ActiveCfg = Debug|Any CPU + {ECA674A3-CC59-4C75-90E3-5AACA591EC5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECA674A3-CC59-4C75-90E3-5AACA591EC5D}.Release|Any CPU.Build.0 = Release|Any CPU + {ECA674A3-CC59-4C75-90E3-5AACA591EC5D}.Release|x86.ActiveCfg = Release|Any CPU + {520FA2BD-1C59-40D9-BCB5-EDA11327B7F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {520FA2BD-1C59-40D9-BCB5-EDA11327B7F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {520FA2BD-1C59-40D9-BCB5-EDA11327B7F0}.Debug|x86.ActiveCfg = Debug|Any CPU + {520FA2BD-1C59-40D9-BCB5-EDA11327B7F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {520FA2BD-1C59-40D9-BCB5-EDA11327B7F0}.Release|Any CPU.Build.0 = Release|Any CPU + {520FA2BD-1C59-40D9-BCB5-EDA11327B7F0}.Release|x86.ActiveCfg = Release|Any CPU + {0E3DF716-5657-4B51-9BF2-3791880930E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E3DF716-5657-4B51-9BF2-3791880930E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E3DF716-5657-4B51-9BF2-3791880930E1}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E3DF716-5657-4B51-9BF2-3791880930E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E3DF716-5657-4B51-9BF2-3791880930E1}.Release|Any CPU.Build.0 = Release|Any CPU + {0E3DF716-5657-4B51-9BF2-3791880930E1}.Release|x86.ActiveCfg = Release|Any CPU + {18FEDA0C-D147-4286-B39A-01204808106A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18FEDA0C-D147-4286-B39A-01204808106A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18FEDA0C-D147-4286-B39A-01204808106A}.Debug|x86.ActiveCfg = Debug|Any CPU + {18FEDA0C-D147-4286-B39A-01204808106A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18FEDA0C-D147-4286-B39A-01204808106A}.Release|Any CPU.Build.0 = Release|Any CPU + {18FEDA0C-D147-4286-B39A-01204808106A}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Duplicati\GUI\Duplicati.GUI.TrayIcon\Duplicati.GUI.TrayIcon.csproj + Policies = $0 + $0.StandardHeader = $1 + $1.Text = @ Copyright (C) 2011, Kenneth Skovhede\r\n http://www.hexad.dk, opensource@hexad.dk\n \n This library is free software; you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as\n published by the Free Software Foundation; either version 2.1 of the\n License, or (at your option) any later version.\n\n This library is distributed in the hope that it will be useful, but\n WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public\n License along with this library; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + $1.IncludeInNewFiles = True + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = None + $2.ResourceNamePolicy = FileFormatDefault + $0.TextStylePolicy = $3 + $3.FileWidth = 120 + $3.EolMarker = Windows + $3.inheritsSet = VisualStudio + $3.inheritsScope = text/plain + $3.scope = text/x-csharp + $0.CSharpFormattingPolicy = $4 + $4.BeforeMethodCallParentheses = False + $4.BeforeConstructorDeclarationParentheses = False + $4.BeforeIndexerDeclarationBracket = False + $4.BeforeDelegateDeclarationParentheses = False + $4.NewParentheses = False + $4.SpacesBeforeBrackets = False + $4.inheritsSet = Mono + $4.inheritsScope = text/x-csharp + $4.scope = text/x-csharp + EndGlobalSection +EndGlobal diff --git a/Duplicati.sln b/Duplicati.sln new file mode 100644 index 000000000..bb77fe70b --- /dev/null +++ b/Duplicati.sln @@ -0,0 +1,360 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati", "Duplicati\GUI\Duplicati.csproj", "{1470F75F-D364-4010-91D0-FD4DDA1C5159}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Main", "Duplicati\Library\Main\Duplicati.Library.Main.csproj", "{10D2D1B7-C664-41D8-9B3A-00040C3D421B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.File", "Duplicati\Library\Backend\File\Duplicati.Library.Backend.File.csproj", "{FC9B7611-836F-4127-8B44-A7C31F506807}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.FTP", "Duplicati\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj", "{F61679A9-E5DE-468A-B5A4-05F92D0143D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Compression", "Duplicati\Library\Compression\Duplicati.Library.Compression.csproj", "{19ECCE09-B5EB-406C-8C57-BAC66997D469}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Encryption", "Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj", "{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.SharpRSync", "Duplicati\Library\SharpRSync\Duplicati.Library.SharpRSync.csproj", "{47FE73E1-0832-44F7-96B9-2580273206C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Logging", "Duplicati\Library\Logging\Duplicati.Library.Logging.csproj", "{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.S3", "Duplicati\Library\Backend\S3\Duplicati.Library.Backend.S3.csproj", "{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine", "Duplicati\CommandLine\Duplicati.CommandLine.csproj", "{81765A64-3661-4E3E-B850-2F6F87A51F74}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.Decrypter", "Duplicati\CommandLine\Decrypter\Duplicati.CommandLine.Decrypter.csproj", "{E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.WEBDAV", "Duplicati\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj", "{BAE27510-8B5D-44B2-B33E-372A98908041}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.CloudFiles", "Duplicati\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj", "{1BFAE226-8364-4086-825C-BB83F6F3EE4C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Datamodel", "Duplicati\GUI\Datamodel\Datamodel.csproj", "{B60D0993-B0C5-457A-90D9-DB6EEB33CE47}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WizardForm", "WizardForm\WizardForm.csproj", "{F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.BackendTester", "Duplicati\CommandLine\BackendTester\Duplicati.CommandLine.BackendTester.csproj", "{E7280DCA-7776-4A73-B9B5-41FD77FC8799}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Interface", "Duplicati\Library\Interface\Duplicati.Library.Interface.csproj", "{C5899F45-B0FF-483C-9D38-24A9FCAAB237}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.DynamicLoader", "Duplicati\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj", "{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Snapshots", "Duplicati\Library\Snapshots\Duplicati.Library.Snapshots.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.License", "Duplicati\License\Duplicati.License.csproj", "{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Winforms.Controls", "Duplicati\Winforms\Controls\Duplicati.Winforms.Controls.csproj", "{7886BA79-A38A-4288-8B8A-9F29D0E15AC0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Modules.Builtin", "Duplicati\Library\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj", "{52826615-7964-47FE-B4B3-1B2DBDF605B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.TahoeLAFS", "Duplicati\Library\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj", "{C0270709-2A40-43B5-8CF1-69581B9FA2A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.SkyDrive", "Duplicati\Library\Backend\SkyDrive\Duplicati.Library.Backend.SkyDrive.csproj", "{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.GoogleDocs", "Duplicati\Library\Backend\GoogleDocs\Duplicati.Library.Backend.GoogleDocs.csproj", "{42C91180-11EB-4590-82EA-80FE9D4DEBEC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.SSHv2", "Duplicati\Library\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj", "{FF2BF37C-E502-4C98-BEA0-701671DDFA08}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Server", "Duplicati\Server\Duplicati.Server.csproj", "{19E661D2-C5DA-4F35-B3EE-7586E5734B5F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.GUI.TrayIcon", "Duplicati\GUI\Duplicati.GUI.TrayIcon\Duplicati.GUI.TrayIcon.csproj", "{17566860-3D98-4604-AA5B-47661F75609F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Server.Serialization", "Duplicati\Server\Duplicati.Server.Serialization\Duplicati.Server.Serialization.csproj", "{33FD1D24-C28F-4C71-933F-98F1586EA76C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.GUI.MacTrayIcon", "Duplicati\GUI\Duplicati.GUI.MacTrayIcon\Duplicati.GUI.MacTrayIcon.csproj", "{9E9675C7-1198-44CD-85E0-FD582E9EEAFF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + Debug|X86 = Debug|X86 + Release|X86 = Release|X86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|x86.ActiveCfg = Debug|x86 + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|x86.Build.0 = Debug|x86 + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|Any CPU.Build.0 = Release|Any CPU + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|x86.ActiveCfg = Release|x86 + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|x86.Build.0 = Release|x86 + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|x86.ActiveCfg = Debug|x86 + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|x86.Build.0 = Debug|x86 + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|Any CPU.Build.0 = Release|Any CPU + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|x86.ActiveCfg = Release|x86 + {10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|x86.Build.0 = Release|x86 + {1470F75F-D364-4010-91D0-FD4DDA1C5159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1470F75F-D364-4010-91D0-FD4DDA1C5159}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1470F75F-D364-4010-91D0-FD4DDA1C5159}.Debug|x86.ActiveCfg = Debug|x86 + {1470F75F-D364-4010-91D0-FD4DDA1C5159}.Debug|x86.Build.0 = Debug|x86 + {1470F75F-D364-4010-91D0-FD4DDA1C5159}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1470F75F-D364-4010-91D0-FD4DDA1C5159}.Release|Any CPU.Build.0 = Release|Any CPU + {1470F75F-D364-4010-91D0-FD4DDA1C5159}.Release|x86.ActiveCfg = Release|x86 + {1470F75F-D364-4010-91D0-FD4DDA1C5159}.Release|x86.Build.0 = Release|x86 + {17566860-3D98-4604-AA5B-47661F75609F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17566860-3D98-4604-AA5B-47661F75609F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17566860-3D98-4604-AA5B-47661F75609F}.Debug|x86.ActiveCfg = Debug|x86 + {17566860-3D98-4604-AA5B-47661F75609F}.Debug|x86.Build.0 = Debug|x86 + {17566860-3D98-4604-AA5B-47661F75609F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17566860-3D98-4604-AA5B-47661F75609F}.Release|Any CPU.Build.0 = Release|Any CPU + {17566860-3D98-4604-AA5B-47661F75609F}.Release|x86.ActiveCfg = Release|x86 + {17566860-3D98-4604-AA5B-47661F75609F}.Release|x86.Build.0 = Release|x86 + {19E661D2-C5DA-4F35-B3EE-7586E5734B5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19E661D2-C5DA-4F35-B3EE-7586E5734B5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19E661D2-C5DA-4F35-B3EE-7586E5734B5F}.Debug|x86.ActiveCfg = Debug|x86 + {19E661D2-C5DA-4F35-B3EE-7586E5734B5F}.Debug|x86.Build.0 = Debug|x86 + {19E661D2-C5DA-4F35-B3EE-7586E5734B5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19E661D2-C5DA-4F35-B3EE-7586E5734B5F}.Release|Any CPU.Build.0 = Release|Any CPU + {19E661D2-C5DA-4F35-B3EE-7586E5734B5F}.Release|x86.ActiveCfg = Release|x86 + {19E661D2-C5DA-4F35-B3EE-7586E5734B5F}.Release|x86.Build.0 = Release|x86 + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|x86.ActiveCfg = Debug|x86 + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|x86.Build.0 = Debug|x86 + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.Build.0 = Release|Any CPU + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|x86.ActiveCfg = Release|x86 + {19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|x86.Build.0 = Release|x86 + {1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Debug|x86.ActiveCfg = Debug|x86 + {1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Debug|x86.Build.0 = Debug|x86 + {1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Release|Any CPU.Build.0 = Release|Any CPU + {1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Release|x86.ActiveCfg = Release|x86 + {1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Release|x86.Build.0 = Release|x86 + {33FD1D24-C28F-4C71-933F-98F1586EA76C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33FD1D24-C28F-4C71-933F-98F1586EA76C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33FD1D24-C28F-4C71-933F-98F1586EA76C}.Debug|x86.ActiveCfg = Debug|x86 + {33FD1D24-C28F-4C71-933F-98F1586EA76C}.Debug|x86.Build.0 = Debug|x86 + {33FD1D24-C28F-4C71-933F-98F1586EA76C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33FD1D24-C28F-4C71-933F-98F1586EA76C}.Release|Any CPU.Build.0 = Release|Any CPU + {33FD1D24-C28F-4C71-933F-98F1586EA76C}.Release|x86.ActiveCfg = Release|x86 + {33FD1D24-C28F-4C71-933F-98F1586EA76C}.Release|x86.Build.0 = Release|x86 + {42C91180-11EB-4590-82EA-80FE9D4DEBEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42C91180-11EB-4590-82EA-80FE9D4DEBEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42C91180-11EB-4590-82EA-80FE9D4DEBEC}.Debug|x86.ActiveCfg = Debug|Any CPU + {42C91180-11EB-4590-82EA-80FE9D4DEBEC}.Debug|x86.Build.0 = Debug|Any CPU + {42C91180-11EB-4590-82EA-80FE9D4DEBEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42C91180-11EB-4590-82EA-80FE9D4DEBEC}.Release|Any CPU.Build.0 = Release|Any CPU + {42C91180-11EB-4590-82EA-80FE9D4DEBEC}.Release|x86.ActiveCfg = Release|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Debug|x86.ActiveCfg = Debug|x86 + {47FE73E1-0832-44F7-96B9-2580273206C3}.Debug|x86.Build.0 = Debug|x86 + {47FE73E1-0832-44F7-96B9-2580273206C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Release|Any CPU.Build.0 = Release|Any CPU + {47FE73E1-0832-44F7-96B9-2580273206C3}.Release|x86.ActiveCfg = Release|x86 + {47FE73E1-0832-44F7-96B9-2580273206C3}.Release|x86.Build.0 = Release|x86 + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|x86.ActiveCfg = Debug|x86 + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|x86.Build.0 = Debug|x86 + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.Build.0 = Release|Any CPU + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|x86.ActiveCfg = Release|x86 + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|x86.Build.0 = Release|x86 + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|x86.ActiveCfg = Debug|x86 + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|x86.Build.0 = Debug|x86 + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|Any CPU.Build.0 = Release|Any CPU + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|x86.ActiveCfg = Release|x86 + {52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|x86.Build.0 = Release|x86 + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Debug|x86.ActiveCfg = Debug|x86 + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Debug|x86.Build.0 = Debug|x86 + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Release|Any CPU.Build.0 = Release|Any CPU + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Release|x86.ActiveCfg = Release|x86 + {7886BA79-A38A-4288-8B8A-9F29D0E15AC0}.Release|x86.Build.0 = Release|x86 + {81765A64-3661-4E3E-B850-2F6F87A51F74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81765A64-3661-4E3E-B850-2F6F87A51F74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81765A64-3661-4E3E-B850-2F6F87A51F74}.Debug|x86.ActiveCfg = Debug|x86 + {81765A64-3661-4E3E-B850-2F6F87A51F74}.Debug|x86.Build.0 = Debug|x86 + {81765A64-3661-4E3E-B850-2F6F87A51F74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81765A64-3661-4E3E-B850-2F6F87A51F74}.Release|Any CPU.Build.0 = Release|Any CPU + {81765A64-3661-4E3E-B850-2F6F87A51F74}.Release|x86.ActiveCfg = Release|x86 + {81765A64-3661-4E3E-B850-2F6F87A51F74}.Release|x86.Build.0 = Release|x86 + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|x86.ActiveCfg = Debug|x86 + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|x86.Build.0 = Debug|x86 + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.Build.0 = Release|Any CPU + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|x86.ActiveCfg = Release|x86 + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|x86.Build.0 = Release|x86 + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Debug|X86.ActiveCfg = Debug|X86 + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Debug|x86.ActiveCfg = Debug|x86 + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Debug|x86.Build.0 = Debug|x86 + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Debug|X86.Build.0 = Debug|X86 + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Release|Any CPU.Build.0 = Release|Any CPU + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Release|X86.ActiveCfg = Release|X86 + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Release|x86.ActiveCfg = Release|x86 + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Release|X86.Build.0 = Release|X86 + {9E9675C7-1198-44CD-85E0-FD582E9EEAFF}.Release|x86.Build.0 = Release|x86 + {B60D0993-B0C5-457A-90D9-DB6EEB33CE47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B60D0993-B0C5-457A-90D9-DB6EEB33CE47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B60D0993-B0C5-457A-90D9-DB6EEB33CE47}.Debug|x86.ActiveCfg = Debug|x86 + {B60D0993-B0C5-457A-90D9-DB6EEB33CE47}.Debug|x86.Build.0 = Debug|x86 + {B60D0993-B0C5-457A-90D9-DB6EEB33CE47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B60D0993-B0C5-457A-90D9-DB6EEB33CE47}.Release|Any CPU.Build.0 = Release|Any CPU + {B60D0993-B0C5-457A-90D9-DB6EEB33CE47}.Release|x86.ActiveCfg = Release|x86 + {B60D0993-B0C5-457A-90D9-DB6EEB33CE47}.Release|x86.Build.0 = Release|x86 + {BAE27510-8B5D-44B2-B33E-372A98908041}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAE27510-8B5D-44B2-B33E-372A98908041}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAE27510-8B5D-44B2-B33E-372A98908041}.Debug|x86.ActiveCfg = Debug|x86 + {BAE27510-8B5D-44B2-B33E-372A98908041}.Debug|x86.Build.0 = Debug|x86 + {BAE27510-8B5D-44B2-B33E-372A98908041}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAE27510-8B5D-44B2-B33E-372A98908041}.Release|Any CPU.Build.0 = Release|Any CPU + {BAE27510-8B5D-44B2-B33E-372A98908041}.Release|x86.ActiveCfg = Release|x86 + {BAE27510-8B5D-44B2-B33E-372A98908041}.Release|x86.Build.0 = Release|x86 + {C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Debug|x86.ActiveCfg = Debug|x86 + {C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Debug|x86.Build.0 = Debug|x86 + {C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Release|Any CPU.Build.0 = Release|Any CPU + {C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Release|x86.ActiveCfg = Release|x86 + {C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Release|x86.Build.0 = Release|x86 + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Debug|x86.ActiveCfg = Debug|x86 + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Debug|x86.Build.0 = Debug|x86 + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Release|Any CPU.Build.0 = Release|Any CPU + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Release|x86.ActiveCfg = Release|x86 + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Release|x86.Build.0 = Release|x86 + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|x86.ActiveCfg = Debug|x86 + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|x86.Build.0 = Debug|x86 + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.Build.0 = Release|Any CPU + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|x86.ActiveCfg = Release|x86 + {C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|x86.Build.0 = Release|x86 + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Debug|x86.ActiveCfg = Debug|Any CPU + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Debug|x86.Build.0 = Debug|Any CPU + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Release|Any CPU.Build.0 = Release|Any CPU + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Release|x86.ActiveCfg = Release|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|x86.ActiveCfg = Debug|x86 + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|x86.Build.0 = Debug|x86 + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.Build.0 = Release|Any CPU + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|x86.ActiveCfg = Release|x86 + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|x86.Build.0 = Release|x86 + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|x86.ActiveCfg = Debug|x86 + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|x86.Build.0 = Debug|x86 + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|Any CPU.Build.0 = Release|Any CPU + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|x86.ActiveCfg = Release|x86 + {D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|x86.Build.0 = Release|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.ActiveCfg = Debug|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.Build.0 = Debug|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.ActiveCfg = Release|x86 + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.Build.0 = Release|x86 + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}.Debug|x86.ActiveCfg = Debug|x86 + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}.Debug|x86.Build.0 = Debug|x86 + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}.Release|Any CPU.Build.0 = Release|Any CPU + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}.Release|x86.ActiveCfg = Release|x86 + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F}.Release|x86.Build.0 = Release|x86 + {E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Debug|x86.ActiveCfg = Debug|x86 + {E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Debug|x86.Build.0 = Debug|x86 + {E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Release|Any CPU.Build.0 = Release|Any CPU + {E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Release|x86.ActiveCfg = Release|x86 + {E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Release|x86.Build.0 = Release|x86 + {F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}.Debug|x86.ActiveCfg = Debug|x86 + {F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}.Debug|x86.Build.0 = Debug|x86 + {F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}.Release|Any CPU.Build.0 = Release|Any CPU + {F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}.Release|x86.ActiveCfg = Release|x86 + {F2854848-BF78-4A6D-9A2A-2AE2AFB70DF1}.Release|x86.Build.0 = Release|x86 + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|x86.ActiveCfg = Debug|x86 + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|x86.Build.0 = Debug|x86 + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|Any CPU.Build.0 = Release|Any CPU + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|x86.ActiveCfg = Release|x86 + {F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|x86.Build.0 = Release|x86 + {FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|x86.ActiveCfg = Debug|x86 + {FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|x86.Build.0 = Debug|x86 + {FC9B7611-836F-4127-8B44-A7C31F506807}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC9B7611-836F-4127-8B44-A7C31F506807}.Release|Any CPU.Build.0 = Release|Any CPU + {FC9B7611-836F-4127-8B44-A7C31F506807}.Release|x86.ActiveCfg = Release|x86 + {FC9B7611-836F-4127-8B44-A7C31F506807}.Release|x86.Build.0 = Release|x86 + {FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Debug|x86.ActiveCfg = Debug|x86 + {FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Debug|x86.Build.0 = Debug|x86 + {FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Release|Any CPU.Build.0 = Release|Any CPU + {FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Release|x86.ActiveCfg = Release|x86 + {FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Duplicati\CommandLine\Duplicati.CommandLine.csproj + Policies = $0 + $0.StandardHeader = $1 + $1.Text = @ Copyright (C) 2011, Kenneth Skovhede\r\n http://www.hexad.dk, opensource@hexad.dk\n \n This library is free software; you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as\n published by the Free Software Foundation; either version 2.1 of the\n License, or (at your option) any later version.\n\n This library is distributed in the hope that it will be useful, but\n WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public\n License along with this library; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + $1.IncludeInNewFiles = True + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = None + $2.ResourceNamePolicy = FileFormatDefault + $0.TextStylePolicy = $3 + $3.FileWidth = 120 + $3.EolMarker = Windows + $3.inheritsSet = VisualStudio + $3.inheritsScope = text/plain + $3.scope = text/x-csharp + $0.CSharpFormattingPolicy = $4 + $4.BeforeMethodCallParentheses = False + $4.BeforeConstructorDeclarationParentheses = False + $4.BeforeIndexerDeclarationBracket = False + $4.BeforeDelegateDeclarationParentheses = False + $4.NewParentheses = False + $4.SpacesBeforeBrackets = False + $4.inheritsSet = Mono + $4.inheritsScope = text/x-csharp + $4.scope = text/x-csharp + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(SubversionScc) = preSolution + Svn-Managed = True + Manager = AnkhSVN - Subversion Support for Visual Studio + EndGlobalSection +EndGlobal diff --git a/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj b/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj new file mode 100644 index 000000000..83ff94da6 --- /dev/null +++ b/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj @@ -0,0 +1,137 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {E7280DCA-7776-4A73-B9B5-41FD77FC8799} + Exe + Properties + Duplicati.CommandLine.BackendTester + Duplicati.CommandLine.BackendTester + 512 + + + 3.5 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + Duplicati.snk + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + Duplicati.snk + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + 4 + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + 4 + + + + + + + + + + + + + + + + + + + + {1BFAE226-8364-4086-825C-BB83F6F3EE4C} + Duplicati.Library.Backend.CloudFiles + + + {FC9B7611-836F-4127-8B44-A7C31F506807} + Duplicati.Library.Backend.File + + + {F61679A9-E5DE-468A-B5A4-05F92D0143D2} + Duplicati.Library.Backend.FTP + + + {42C91180-11EB-4590-82EA-80FE9D4DEBEC} + Duplicati.Library.Backend.GoogleDocs + + + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2} + Duplicati.Library.Backend.S3 + + + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3} + Duplicati.Library.Backend.SkyDrive + + + {FF2BF37C-E502-4C98-BEA0-701671DDFA08} + Duplicati.Library.Backend.SSHv2 + + + {C0270709-2A40-43B5-8CF1-69581B9FA2A1} + Duplicati.Library.Backend.TahoeLAFS + + + {BAE27510-8B5D-44B2-B33E-372A98908041} + Duplicati.Library.Backend.WEBDAV + + + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65} + Duplicati.Library.Utility + + + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92} + Duplicati.Library.DynamicLoader + + + {C5899F45-B0FF-483C-9D38-24A9FCAAB237} + Duplicati.Library.Interface + + + {81765A64-3661-4E3E-B850-2F6F87A51F74} + Duplicati.CommandLine + + + + + diff --git a/Duplicati/CommandLine/BackendTester/Duplicati.snk b/Duplicati/CommandLine/BackendTester/Duplicati.snk new file mode 100644 index 000000000..e0c1e2dd8 Binary files /dev/null and b/Duplicati/CommandLine/BackendTester/Duplicati.snk differ diff --git a/Duplicati/CommandLine/BackendTester/NonSeekableStream.cs b/Duplicati/CommandLine/BackendTester/NonSeekableStream.cs new file mode 100644 index 000000000..5f97ee5da --- /dev/null +++ b/Duplicati/CommandLine/BackendTester/NonSeekableStream.cs @@ -0,0 +1,55 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Text; + +namespace Duplicati.CommandLine.BackendTester +{ + /// + /// This class prevents the base stream from being seekable to + /// ensure that all operations work correctly on streams that + /// are not seekable. + /// + public class NonSeekableStream : Library.Utility.OverrideableStream + { + public NonSeekableStream(System.IO.Stream stream) + : base(stream) + { } + + public override bool CanSeek + { + get + { + return false; + } + } + + public override long Seek(long offset, System.IO.SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + } +} diff --git a/Duplicati/CommandLine/BackendTester/Program.cs b/Duplicati/CommandLine/BackendTester/Program.cs new file mode 100644 index 000000000..cd9b4a848 --- /dev/null +++ b/Duplicati/CommandLine/BackendTester/Program.cs @@ -0,0 +1,434 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Text; +using Duplicati.Library.Interface; + +namespace Duplicati.CommandLine.BackendTester +{ + class Program + { + class TempFile + { + public readonly string remotefilename; + public readonly string localfilename; + public readonly byte[] hash; + public readonly long length; + public bool found = false; + + public TempFile(string remotefilename, string localfilename, byte[] hash, long length) + { + this.remotefilename = remotefilename; + this.localfilename = localfilename; + this.hash = hash; + this.length = length; + } + } + + private const string ValidFilenameChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; + private const string ExtendedChars = "-_',=)(&%$#@! +"; + + static void Main(string[] _args) + { + try + { + List args = new List(_args); + Dictionary options = Library.Utility.CommandLineParser.ExtractOptions(args); + + if (!options.ContainsKey("ftp_password") && !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FTP_PASSWORD"))) + options["ftp_password"] = System.Environment.GetEnvironmentVariable("FTP_PASSWORD"); + + if (!options.ContainsKey("ftp_username") && !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FTP_USERNAME"))) + options["ftp_username"] = System.Environment.GetEnvironmentVariable("FTP_USERNAME"); + + if (options.ContainsKey("tempdir") && !string.IsNullOrEmpty(options["tempdir"])) + Library.Utility.TempFolder.SystemTempPath = options["tempdir"]; + + + if (args.Count != 1 || args[0].ToLower() == "help" || args[0] == "?") + { + Console.WriteLine("Usage: ://:@"); + Console.WriteLine("Example: ftp://user:pass@server/folder"); + Console.WriteLine(); + Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)); + + Console.WriteLine(); + List lines = new List(); + foreach (Library.Interface.ICommandLineArgument arg in SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg); + + foreach (string s in lines) + Console.WriteLine(s); + + return; + } + + int reruns = 5; + if (options.ContainsKey("reruns")) + reruns = int.Parse(options["reruns"]); + + for (int i = 0; i < reruns; i++) + { + Console.WriteLine("Starting run no {0}", i); + if (!Run(args, options, i == 0)) + return; + } + Console.WriteLine("Unittest complete!"); + } + catch (Exception ex) + { + Console.WriteLine("Unittest failed: " + ex.Message); + } + } + + static bool Run(List args, Dictionary options, bool first) + { + string allowedChars = ValidFilenameChars; + if (options.ContainsKey("extended-chars")) + allowedChars += options["extended-chars"]; + else + allowedChars += ExtendedChars; + + bool autoCreateFolders = Library.Utility.Utility.ParseBoolOption(options, "auto-create-folder"); + + Library.Interface.IBackend backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options); + if (backend == null) + { + Console.WriteLine("Unsupported backend"); + Console.WriteLine(); + Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)); + return false; + } + + string disabledModulesValue; + string enabledModulesValue; + options.TryGetValue("enable-module", out enabledModulesValue); + options.TryGetValue("disable-module", out disabledModulesValue); + string[] enabledModules = enabledModulesValue == null ? new string[0] : enabledModulesValue.Trim().ToLower().Split(','); + string[] disabledModules = disabledModulesValue == null ? new string[0] : disabledModulesValue.Trim().ToLower().Split(','); + + List loadedModules = new List(); + foreach (Library.Interface.IGenericModule m in Library.DynamicLoader.GenericLoader.Modules) + if (Array.IndexOf(disabledModules, m.Key.ToLower()) < 0 && (m.LoadAsDefault || Array.IndexOf(enabledModules, m.Key.ToLower()) >= 0)) + { + m.Configure(options); + loadedModules.Add(m); + } + + try + { + List curlist = null; + try + { + curlist = backend.List(); + } + catch (FolderMissingException fex) + { + if (autoCreateFolders) + { + try + { + if (backend is IBackend_v2) + ((IBackend_v2)backend).CreateFolder(); + + curlist = backend.List(); + } + catch (Exception ex) + { + Console.WriteLine("Autocreate folder failed with message: " + ex.Message); + } + } + + if (curlist == null) + throw fex; + } + + foreach (Library.Interface.IFileEntry fe in curlist) + if (!fe.IsFolder) + { + if (Library.Utility.Utility.ParseBoolOption(options, "auto-clean") && first) + if (Library.Utility.Utility.ParseBoolOption(options, "force")) + { + Console.WriteLine("Auto clean, removing file: {0}", fe.Name); + backend.Delete(fe.Name); + continue; + } + else + Console.WriteLine("Specify the --force flag to actually delete files"); + + Console.WriteLine("*** Remote folder is not empty, aborting"); + return false; + } + + + int number_of_files = 10; + int min_file_size = 1024; + int max_file_size = 1024 * 1024 * 50; + int min_filename_size = 5; + int max_filename_size = 80; + bool disableStreaming = Library.Utility.Utility.ParseBoolOption(options, "disable-streaming-transfers"); + bool skipOverwriteTest = Library.Utility.Utility.ParseBoolOption(options, "skip-overwrite-test"); + + if (options.ContainsKey("number-of-files")) + number_of_files = int.Parse(options["number-of-files"]); + if (options.ContainsKey("min-file-size")) + min_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["min-file-size"], "mb"); + if (options.ContainsKey("max-file-size")) + max_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["max-file-size"]); + + if (options.ContainsKey("min-filename-length")) + min_filename_size = int.Parse(options["min-filename-length"]); + if (options.ContainsKey("max-filename-length")) + max_filename_size = int.Parse(options["max-filename-length"]); + + Random rnd = new Random(); + System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); + + //Create random files + using (Library.Utility.TempFolder tf = new Duplicati.Library.Utility.TempFolder()) + { + List files = new List(); + for (int i = 0; i < number_of_files; i++) + { + + StringBuilder filename = new StringBuilder(); + int filenamelen = rnd.Next(min_filename_size, max_filename_size); + for (int j = 0; j < filenamelen; j++) + filename.Append(allowedChars[rnd.Next(0, allowedChars.Length)]); + + string localfilename = CreateRandomFile(tf, i, min_file_size, max_file_size, rnd); + + //Calculate local hash and length + using (System.IO.FileStream fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + files.Add(new TempFile(filename.ToString(), localfilename, sha.ComputeHash(fs), fs.Length)); + } + + byte[] dummyFileHash = null; + if (!skipOverwriteTest) + { + Console.WriteLine("Uploading wrong files ..."); + using (Library.Utility.TempFile dummy = new Library.Utility.TempFile(CreateRandomFile(tf, files.Count, 1024, 2048, rnd))) + { + using (System.IO.FileStream fs = new System.IO.FileStream(dummy, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + dummyFileHash = sha.ComputeHash(fs); + + //Upload a dummy file for entry 0 and the last one, they will be replaced by the real files afterwards + //We upload entry 0 twice just to try to freak any internal cache list + Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming); + Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming); + Uploadfile(dummy, files.Count - 1, files[files.Count - 1].remotefilename, backend, disableStreaming); + } + + } + + Console.WriteLine("Uploading files ..."); + + for (int i = 0; i < files.Count; i++) + Uploadfile(files[i].localfilename, i, files[i].remotefilename, backend, disableStreaming); + + Console.WriteLine("Verifying file list ..."); + + curlist = backend.List(); + foreach (Library.Interface.IFileEntry fe in curlist) + if (!fe.IsFolder) + { + bool found = false; + foreach (TempFile tx in files) + if (tx.remotefilename == fe.Name) + { + if (tx.found) + Console.WriteLine("*** File with name {0} was found more than once", tx.remotefilename); + found = true; + tx.found = true; + + if (fe.Size > 0 && tx.length != fe.Size) + Console.WriteLine("*** File with name {0} has size {1} but the size was reported as {2}", tx.remotefilename, tx.length, fe.Size); + + break; + } + + if (!found) + Console.WriteLine("*** File with name {0} was found on server but not uploaded!", fe.Name); + } + + foreach (TempFile tx in files) + if (!tx.found) + Console.WriteLine("*** File with name {0} was uploaded but not found afterwards", tx.remotefilename); + + Console.WriteLine("Downloading files"); + + for (int i = 0; i < files.Count; i++) + { + using (Duplicati.Library.Utility.TempFile cf = new Duplicati.Library.Utility.TempFile()) + { + Exception e = null; + Console.Write("Downloading file {0} ... ", i); + + try + { + if (backend is Library.Interface.IStreamingBackend && !disableStreaming) + { + using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) + using (NonSeekableStream nss = new NonSeekableStream(fs)) + (backend as Library.Interface.IStreamingBackend).Get(files[i].remotefilename, nss); + } + else + backend.Get(files[i].remotefilename, cf); + + e = null; + } + catch (Exception ex) + { + e = ex; + } + + if (e != null) + Console.WriteLine("failed\n*** Error: {0}", e.ToString()); + else + Console.WriteLine("done"); + + Console.Write("Checking hash ... "); + + using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + if (Convert.ToBase64String(sha.ComputeHash(fs)) != Convert.ToBase64String(files[i].hash)) + { + if (dummyFileHash != null && Convert.ToBase64String(sha.ComputeHash(fs)) == Convert.ToBase64String(dummyFileHash)) + Console.WriteLine("failed\n*** Downloaded file was the dummy file"); + else + Console.WriteLine("failed\n*** Downloaded file was corrupt"); + } + else + Console.WriteLine("done"); + } + } + + Console.WriteLine("Deleting files..."); + + foreach (TempFile tx in files) + try { backend.Delete(tx.remotefilename); } + catch (Exception ex) + { + Console.WriteLine("*** Failed to delete file {0}, message: {1}", tx.remotefilename, ex.ToString()); + } + + curlist = backend.List(); + foreach (Library.Interface.IFileEntry fe in curlist) + if (!fe.IsFolder) + { + Console.WriteLine("*** Remote folder contains {0} after cleanup", fe.Name); + } + + } + } + finally + { + foreach (Library.Interface.IGenericModule m in loadedModules) + if (m is IDisposable) + ((IDisposable)m).Dispose(); + } + + return true; + } + + private static void Uploadfile(string localfilename, int i, string remotefilename, IBackend backend, bool disableStreaming) + { + Console.Write("Uploading file {0}, {1} ... ", i, Duplicati.Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(localfilename).Length)); + Exception e = null; + + try + { + if (backend is Library.Interface.IStreamingBackend && !disableStreaming) + { + using (System.IO.FileStream fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read)) + using (NonSeekableStream nss = new NonSeekableStream(fs)) + (backend as Library.Interface.IStreamingBackend).Put(remotefilename, nss); + } + else + backend.Put(remotefilename, localfilename); + + e = null; + } + catch (Exception ex) + { + e = ex; + } + + if (e != null) + { + Console.WriteLine("Failed to upload file {0}, error message: {1}, remote name: {2}", i, e.ToString(), remotefilename); + while (e.InnerException != null) + { + e = e.InnerException; + Console.WriteLine(string.Format(" Inner exception: {0}", e.ToString())); + } + } + else + { + Console.WriteLine(" done!"); + } + } + + private static string CreateRandomFile(Library.Utility.TempFolder tf, int i, int min_file_size, int max_file_size, Random rnd) + { + Console.Write("Generating file {0}", i); + string filename = System.IO.Path.Combine(tf, i.ToString()); + using (System.IO.FileStream fs = new System.IO.FileStream(filename, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write)) + { + //Random size + byte[] buf = new byte[1024]; + int size = rnd.Next(min_file_size, max_file_size); + + Console.WriteLine(" ({0})", Duplicati.Library.Utility.Utility.FormatSizeString(size)); + + while (size > 0) + { + rnd.NextBytes(buf); + fs.Write(buf, 0, Math.Min(buf.Length, size)); + size -= buf.Length; + } + } + + return filename; + } + + public static IList SupportedCommands + { + get + { + return new List(new ICommandLineArgument[] { + new CommandLineArgument("reruns", CommandLineArgument.ArgumentType.Integer, "The number of test runs to perform", "A number that describes how many times the test is performed", "5"), + new CommandLineArgument("tempdir", CommandLineArgument.ArgumentType.Path, "The path used to store temporary files", "The backend tester will use the system default temp path. You can set this option to choose another path."), + new CommandLineArgument("extended-chars", CommandLineArgument.ArgumentType.String, "A list of allowed extended filename chars", "A list of characters besides {a-z, A-Z, 0-9} to use when generating filenames", ExtendedChars), + new CommandLineArgument("number-of-files", CommandLineArgument.ArgumentType.Integer, "The number of files to test with", "An integer describing how many files to upload during a test run", "10"), + new CommandLineArgument("min-file-size", CommandLineArgument.ArgumentType.Size, "The minimum allowed file size", "File sizes are chosen at random, this valus is the lower bound", "1kb"), + new CommandLineArgument("max-file-size", CommandLineArgument.ArgumentType.Size, "The maximum allowed file size", "File sizes are chosen at random, this valus is the upper bound", "50mb"), + new CommandLineArgument("min-filename-length", CommandLineArgument.ArgumentType.Integer, "The minimum allowed filename length", "File name lengths are chosen at random, this valus is the lower bound", "5"), + new CommandLineArgument("max-filename-length", CommandLineArgument.ArgumentType.Integer, "The minimum allowed filename length", "File name lengths are chosen at random, this valus is the upper bound", "80"), + new CommandLineArgument("auto-create-folder", CommandLineArgument.ArgumentType.Boolean, "Allows automatic folder creation", "A value that indicates if missing folders are created automatically", "false"), + new CommandLineArgument("skip-overwrite-test", CommandLineArgument.ArgumentType.Boolean, "Bypasses the overwrite test", "A value that indicates if dummy files should be uploaded prior to uploading the real files", "false"), + new CommandLineArgument("auto-clean", CommandLineArgument.ArgumentType.Boolean, "Removes any files found in target folder", "A value that indicates if all files in the target folder should be deleted before starting the first test", "false"), + new CommandLineArgument("force", CommandLineArgument.ArgumentType.Boolean, "Activates file deletion", "A value that indicates if existing files should really be deleted when using auto-clean", "false") + }); + + } + } + } +} diff --git a/Duplicati/CommandLine/BackendTester/Properties/AssemblyInfo.cs b/Duplicati/CommandLine/BackendTester/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..91d57f9ae --- /dev/null +++ b/Duplicati/CommandLine/BackendTester/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CommandLine.BackendTester")] +[assembly: AssemblyDescription("A backend debugging tool for Duplicati")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HexaD")] +[assembly: AssemblyProduct("CommandLine.BackendTester")] +[assembly: AssemblyCopyright("Copyright © HexaD 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("53df9eb0-a6e8-49a2-838d-b2a2c4a8d41f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.1.276")] +[assembly: AssemblyFileVersion("1.0.1.276")] diff --git a/Duplicati/CommandLine/BackendTester/app.config b/Duplicati/CommandLine/BackendTester/app.config new file mode 100644 index 000000000..50176fa16 --- /dev/null +++ b/Duplicati/CommandLine/BackendTester/app.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Duplicati/CommandLine/Checkout DSMCBE Data.bat b/Duplicati/CommandLine/Checkout DSMCBE Data.bat new file mode 100644 index 000000000..c00adac08 --- /dev/null +++ b/Duplicati/CommandLine/Checkout DSMCBE Data.bat @@ -0,0 +1,16 @@ +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 50 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r50 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 100 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r100 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 150 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r150 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 200 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r200 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 250 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 251 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 252 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 253 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 254 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 255 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 256 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 257 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 258 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 259 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 260 http://dsmcbe.googlecode.com/svn/trunk/ bin\Debug\DSMCBE\r250 +pause \ No newline at end of file diff --git a/Duplicati/CommandLine/Checkout MapGuide Data.bat b/Duplicati/CommandLine/Checkout MapGuide Data.bat new file mode 100644 index 000000000..83b878b88 --- /dev/null +++ b/Duplicati/CommandLine/Checkout MapGuide Data.bat @@ -0,0 +1,7 @@ +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 100 http://svn.osgeo.org/mapguide/trunk/MgDev bin\Debug\MapGuide\r100 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 1000 http://svn.osgeo.org/mapguide/trunk/MgDev bin\Debug\MapGuide\r1000 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 2000 http://svn.osgeo.org/mapguide/trunk/MgDev bin\Debug\MapGuide\r2000 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 3000 http://svn.osgeo.org/mapguide/trunk/MgDev bin\Debug\MapGuide\r3000 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 3100 http://svn.osgeo.org/mapguide/trunk/MgDev bin\Debug\MapGuide\r3100 +"%ProgramFiles%\CollabNet Subversion\svn" checkout -r 3200 http://svn.osgeo.org/mapguide/trunk/MgDev bin\Debug\MapGuide\r3200 +pause \ No newline at end of file diff --git a/Duplicati/CommandLine/Decrypter/Duplicati.CommandLine.Decrypter.csproj b/Duplicati/CommandLine/Decrypter/Duplicati.CommandLine.Decrypter.csproj new file mode 100644 index 000000000..571bb4d74 --- /dev/null +++ b/Duplicati/CommandLine/Decrypter/Duplicati.CommandLine.Decrypter.csproj @@ -0,0 +1,103 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {E2AF781D-D1E1-4512-BF2C-D6DA8D0F797F} + Exe + Properties + Duplicati.CommandLine.Decrypter + Duplicati.CommandLine.Decrypter + TrayUnknown.ico + + + 3.5 + + + true + Duplicati.snk + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + 4 + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + 4 + + + + + + + + + + + True + True + Program.resx + + + + + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87} + Duplicati.Library.Encryption + + + {C5899F45-B0FF-483C-9D38-24A9FCAAB237} + Duplicati.Library.Interface + + + + + + + + + + + Designer + ResXFileCodeGenerator + Program.Designer.cs + + + + + \ No newline at end of file diff --git a/Duplicati/CommandLine/Decrypter/Duplicati.snk b/Duplicati/CommandLine/Decrypter/Duplicati.snk new file mode 100644 index 000000000..e0c1e2dd8 Binary files /dev/null and b/Duplicati/CommandLine/Decrypter/Duplicati.snk differ diff --git a/Duplicati/CommandLine/Decrypter/Program.cs b/Duplicati/CommandLine/Decrypter/Program.cs new file mode 100644 index 000000000..f58653d58 --- /dev/null +++ b/Duplicati/CommandLine/Decrypter/Program.cs @@ -0,0 +1,41 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Text; +using Duplicati.Library.Encryption; + +namespace Duplicati.CommandLine.Decrypter +{ + class Program + { + static void Main(string[] args) + { + if (args.Length != 3) + { + Console.WriteLine(Strings.Program.ProgramUsage); + return; + } + + AESEncryption c = new AESEncryption(args[0], new Dictionary()); + c.Decrypt(args[1], args[2]); + } + } +} diff --git a/Duplicati/CommandLine/Decrypter/Properties/AssemblyInfo.cs b/Duplicati/CommandLine/Decrypter/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..f8d0ad5ab --- /dev/null +++ b/Duplicati/CommandLine/Decrypter/Properties/AssemblyInfo.cs @@ -0,0 +1,52 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Duplicati.CommandLine.Decrypter")] +[assembly: AssemblyDescription("A commandline decryption tool for Duplicati")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HexaD")] +[assembly: AssemblyProduct("Duplicati.CommandLine.Decrypter")] +[assembly: AssemblyCopyright("LGPL, Copyright © HexaD 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("31de2d8c-9bab-4357-99c6-6a99058928a2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.1.276")] +[assembly: AssemblyFileVersion("1.0.1.276")] diff --git a/Duplicati/CommandLine/Decrypter/Strings/Program.Designer.cs b/Duplicati/CommandLine/Decrypter/Strings/Program.Designer.cs new file mode 100644 index 000000000..231b27591 --- /dev/null +++ b/Duplicati/CommandLine/Decrypter/Strings/Program.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.237 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Duplicati.CommandLine.Decrypter.Strings { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Program { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Program() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Duplicati.CommandLine.Decrypter.Strings.Program", typeof(Program).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Usage: Duplicati.CommandLine.Decrypter.exe <passphrase> <inputfile> <outputfile>. + /// + internal static string ProgramUsage { + get { + return ResourceManager.GetString("ProgramUsage", resourceCulture); + } + } + } +} diff --git a/Duplicati/CommandLine/Decrypter/Strings/Program.resx b/Duplicati/CommandLine/Decrypter/Strings/Program.resx new file mode 100644 index 000000000..edea066fb --- /dev/null +++ b/Duplicati/CommandLine/Decrypter/Strings/Program.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Usage: Duplicati.CommandLine.Decrypter.exe <passphrase> <inputfile> <outputfile> + + \ No newline at end of file diff --git a/Duplicati/CommandLine/Decrypter/TrayUnknown.ico b/Duplicati/CommandLine/Decrypter/TrayUnknown.ico new file mode 100644 index 000000000..24bb4a579 Binary files /dev/null and b/Duplicati/CommandLine/Decrypter/TrayUnknown.ico differ diff --git a/Duplicati/CommandLine/Decrypter/app.config b/Duplicati/CommandLine/Decrypter/app.config new file mode 100644 index 000000000..89dc7d426 --- /dev/null +++ b/Duplicati/CommandLine/Decrypter/app.config @@ -0,0 +1,3 @@ + + + diff --git a/Duplicati/CommandLine/Duplicati.CommandLine.csproj b/Duplicati/CommandLine/Duplicati.CommandLine.csproj new file mode 100644 index 000000000..0d2529941 --- /dev/null +++ b/Duplicati/CommandLine/Duplicati.CommandLine.csproj @@ -0,0 +1,211 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {81765A64-3661-4E3E-B850-2F6F87A51F74} + Exe + Properties + Duplicati.CommandLine + Duplicati.CommandLine + true + Duplicati.snk + TrayWarning.ico + + + 3.5 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + 4 + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + 4 + + + + + + + + + + + + True + True + Program.resx + + + + + + {1BFAE226-8364-4086-825C-BB83F6F3EE4C} + Duplicati.Library.Backend.CloudFiles + + + {FC9B7611-836F-4127-8B44-A7C31F506807} + Duplicati.Library.Backend.File + + + {F61679A9-E5DE-468A-B5A4-05F92D0143D2} + Duplicati.Library.Backend.FTP + + + {42C91180-11EB-4590-82EA-80FE9D4DEBEC} + Duplicati.Library.Backend.GoogleDocs + + + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2} + Duplicati.Library.Backend.S3 + + + {CCD76347-7DC7-4B42-B7E1-E500E624CAC3} + Duplicati.Library.Backend.SkyDrive + + + {FF2BF37C-E502-4C98-BEA0-701671DDFA08} + Duplicati.Library.Backend.SSHv2 + + + {C0270709-2A40-43B5-8CF1-69581B9FA2A1} + Duplicati.Library.Backend.TahoeLAFS + + + {BAE27510-8B5D-44B2-B33E-372A98908041} + Duplicati.Library.Backend.WEBDAV + + + {19ECCE09-B5EB-406C-8C57-BAC66997D469} + Duplicati.Library.Compression + + + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65} + Duplicati.Library.Utility + + + {0CA86ECF-5BEC-4909-B4F6-110A03B30B92} + Duplicati.Library.DynamicLoader + + + {94484FDB-2EFA-4CF0-9BE6-A561157B4F87} + Duplicati.Library.Encryption + + + {C5899F45-B0FF-483C-9D38-24A9FCAAB237} + Duplicati.Library.Interface + + + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798} + Duplicati.Library.Logging + + + {10D2D1B7-C664-41D8-9B3A-00040C3D421B} + Duplicati.Library.Main + + + {52826615-7964-47FE-B4B3-1B2DBDF605B9} + Duplicati.Library.Modules.Builtin + + + {47FE73E1-0832-44F7-96B9-2580273206C3} + Duplicati.Library.SharpRSync + + + {4D012CB1-4B92-47F4-89B7-BF80A73A2E99} + Duplicati.License + + + + + + + + + win-tools\gpg2.exe + PreserveNewest + + + win-tools\gpg\gpg.exe + PreserveNewest + + + win-tools\iconv.dll + PreserveNewest + + + win-tools\libadns-1.dll + PreserveNewest + + + win-tools\libassuan-0.dll + PreserveNewest + + + win-tools\libgcrypt-11.dll + PreserveNewest + + + win-tools\libgpg-error-0.dll + PreserveNewest + + + win-tools\zlib1.dll + PreserveNewest + + + win-tools\pscp.exe + PreserveNewest + + + + + + + Designer + ResXFileCodeGenerator + Program.Designer.cs + + + + + \ No newline at end of file diff --git a/Duplicati/CommandLine/Duplicati.snk b/Duplicati/CommandLine/Duplicati.snk new file mode 100644 index 000000000..e0c1e2dd8 Binary files /dev/null and b/Duplicati/CommandLine/Duplicati.snk differ diff --git a/Duplicati/CommandLine/Help.cs b/Duplicati/CommandLine/Help.cs new file mode 100644 index 000000000..0eb63a4a2 --- /dev/null +++ b/Duplicati/CommandLine/Help.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Duplicati.CommandLine +{ + public static class Help + { + private static readonly Dictionary _document; + private const string RESOURCE_NAME = "Duplicati.CommandLine.help.txt"; + private static readonly System.Text.RegularExpressions.Regex NAMEDOPTION_REGEX = new System.Text.RegularExpressions.Regex("\\%OPTION\\:(?[^\\%]+)\\%"); + + static Help() + { + _document = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + using (System.IO.StreamReader sr = new System.IO.StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(RESOURCE_NAME))) + { + List keywords = new List(); + StringBuilder sb = new StringBuilder(); + foreach(var line in sr.ReadToEnd().Split(new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None)) + { + if (line.Trim().StartsWith("#")) + continue; + + if (line.Trim().StartsWith(">")) + { + if (sb.Length > 0) + { + string s = sb.ToString(); + foreach(var k in keywords) + _document[k] = s; + + keywords.Clear(); + sb.Clear(); + } + + string[] elems = line.Split(new string[] {" ", "\t"}, StringSplitOptions.RemoveEmptyEntries); + if (elems.Length >= 2 && string.Equals(elems[elems.Length - 2], "help", StringComparison.InvariantCultureIgnoreCase)) + keywords.Add(elems[elems.Length - 1]); + else if (elems.Length == 3 && string.Equals(elems[elems.Length - 1], "help", StringComparison.InvariantCultureIgnoreCase)) + keywords.Add("help"); + } + else + { + sb.AppendLine(line); + } + } + + if (sb.Length > 0) + { + string s = sb.ToString(); + foreach(var k in keywords) + _document[k] = s; + } + } + } + + public static void PrintUsage(string topic, IDictionary options) + { + try + { + //Force translation off + System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; + System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture; + } + catch { } + + if (string.IsNullOrWhiteSpace(topic)) + topic = "help"; + + if (string.Equals("help", topic, StringComparison.InvariantCultureIgnoreCase) && options.Count == 1) + topic = new List(options.Keys)[0]; + + + if (_document.ContainsKey(topic)) + { + string tp = _document[topic]; + Library.Main.Options opts = new Library.Main.Options(new Dictionary()); + + tp = tp.Replace("%VERSION%", License.VersionNumbers.Version); + tp = tp.Replace("%BACKENDS%", string.Join(", ", Library.DynamicLoader.BackendLoader.Keys)); + tp = tp.Replace("%MONO%", Library.Utility.Utility.IsMono ? "mono " : ""); + tp = tp.Replace("%APP_PATH%", System.IO.Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location)); + tp = tp.Replace("%EXAMPLE_SOURCE_PATH%", Library.Utility.Utility.IsClientLinux ? "/source" : "D:\\source"); + tp = tp.Replace("%EXAMPLE_SOURCE_FILE%", Library.Utility.Utility.IsClientLinux ? "/source/myfile.txt" : "D:\\source\\file.txt"); + tp = tp.Replace("%EXAMPLE_RESTORE_PATH%", Library.Utility.Utility.IsClientLinux ? "/restore" : "D:\\restore"); + tp = tp.Replace("%ENCRYPTIONMODULES%", string.Join(", ", Library.DynamicLoader.EncryptionLoader.Keys)); + tp = tp.Replace("%COMPRESSIONMODULES%", string.Join(", ", Library.DynamicLoader.CompressionLoader.Keys)); + tp = tp.Replace("%DEFAULTENCRYPTIONMODULE%", opts.EncryptionModule); + tp = tp.Replace("%DEFAULTCOMPRESSIONMODULE%", opts.CompressionModule); + tp = tp.Replace("%GENERICMODULES%", string.Join(", ", Library.DynamicLoader.GenericLoader.Keys)); + + if (tp.Contains("%MAINOPTIONS%")) + { + List lines = new List(); + SortedList sorted = new SortedList(); + foreach (Library.Interface.ICommandLineArgument arg in opts.SupportedCommands) + sorted.Add(arg.Name, arg); + + foreach (Library.Interface.ICommandLineArgument arg in Program.SupportedCommands) + + sorted.Add(arg.Name, arg); + + + foreach (Library.Interface.ICommandLineArgument arg in sorted.Values) + lines.Add(PrintArgSimple(arg, arg.Name)); + + tp = tp.Replace("%MAINOPTIONS%", string.Join(Environment.NewLine, lines.ToArray())); + } + + if (tp.Contains("%ALLOPTIONS%")) + { + List lines = new List(); + foreach (Library.Interface.ICommandLineArgument arg in opts.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + + + + foreach (Library.Interface.ICommandLineArgument arg in Program.SupportedCommands) + + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + + lines.Add(""); + lines.Add(""); + lines.Add(Strings.Program.SupportedBackendsHeader); + foreach (Duplicati.Library.Interface.IBackend back in Library.DynamicLoader.BackendLoader.Backends) + PrintBackend(back, lines); + + lines.Add(""); + lines.Add(""); + lines.Add(Strings.Program.SupportedEncryptionModulesHeader); + foreach (Duplicati.Library.Interface.IEncryption mod in Library.DynamicLoader.EncryptionLoader.Modules) + PrintEncryptionModule(mod, lines); + + lines.Add(""); + lines.Add(""); + lines.Add(Strings.Program.SupportedCompressionModulesHeader); + foreach (Duplicati.Library.Interface.ICompression mod in Library.DynamicLoader.CompressionLoader.Modules) + PrintCompressionModule(mod, lines); + + lines.Add(""); + + lines.Add(""); + lines.Add(""); + lines.Add(Strings.Program.GenericModulesHeader); + foreach (Duplicati.Library.Interface.IGenericModule mod in Library.DynamicLoader.GenericLoader.Modules) + PrintGenericModule(mod, lines); + + lines.Add(""); + + tp = tp.Replace("%ALLOPTIONS%", string.Join(Environment.NewLine, lines.ToArray())); + } + + if (tp.Contains("%MODULEOPTIONS%")) + { + //Figure out which module we are in + IList args = null; + bool found = false; + foreach (Duplicati.Library.Interface.IBackend backend in Library.DynamicLoader.BackendLoader.Backends) + if (string.Equals(backend.ProtocolKey, topic, StringComparison.InvariantCultureIgnoreCase)) + { + args = backend.SupportedCommands; + found = true; + break; + } + + if (args == null) + foreach (Duplicati.Library.Interface.IEncryption module in Library.DynamicLoader.EncryptionLoader.Modules) + if (string.Equals(module.FilenameExtension, topic, StringComparison.InvariantCultureIgnoreCase)) + { + args = module.SupportedCommands; + found = true; + break; + } + + if (args == null) + foreach (Duplicati.Library.Interface.ICompression module in Library.DynamicLoader.CompressionLoader.Modules) + if (string.Equals(module.FilenameExtension, topic, StringComparison.InvariantCultureIgnoreCase)) + { + args = module.SupportedCommands; + found = true; + break; + } + + if (args == null) + foreach (Duplicati.Library.Interface.IGenericModule module in Library.DynamicLoader.GenericLoader.Modules) + if (string.Equals(module.Key, topic, StringComparison.InvariantCultureIgnoreCase)) + { + args = module.SupportedCommands; + found = true; + break; + } + + //If the module is not found, we do not display the description + if (found) + tp = tp.Replace("%MODULEOPTIONS%", PrintArgsSimple(args)); + else + { + Console.WriteLine("Topic not found: {0}", topic); + Console.WriteLine(); + //Prevent recursive lookups + if (topic != "help") + PrintUsage("help", new Dictionary()); + return; + } + } + + if (NAMEDOPTION_REGEX.IsMatch(tp)) + tp = NAMEDOPTION_REGEX.Replace(tp, new Matcher().MathEvaluator); + + PrintFormatted(tp.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)); + } + else + { + List lines = new List(); + + foreach (Duplicati.Library.Interface.IBackend backend in Library.DynamicLoader.BackendLoader.Backends) + if (string.Equals(backend.ProtocolKey, topic, StringComparison.InvariantCultureIgnoreCase)) + { + PrintBackend(backend, lines); + break; + } + + if (lines.Count == 0) + foreach (Duplicati.Library.Interface.IEncryption mod in Library.DynamicLoader.EncryptionLoader.Modules) + if (string.Equals(mod.FilenameExtension, topic, StringComparison.InvariantCultureIgnoreCase)) + { + PrintEncryptionModule(mod, lines); + break; + } + + if (lines.Count == 0) + foreach (Duplicati.Library.Interface.ICompression mod in Library.DynamicLoader.CompressionLoader.Modules) + if (string.Equals(mod.FilenameExtension, topic, StringComparison.InvariantCultureIgnoreCase)) + { + PrintCompressionModule(mod, lines); + break; + } + + if (lines.Count == 0) + foreach (Duplicati.Library.Interface.IGenericModule mod in Library.DynamicLoader.GenericLoader.Modules) + if (string.Equals(mod.Key, topic, StringComparison.InvariantCultureIgnoreCase)) + { + PrintGenericModule(mod, lines); + break; + } + + + if (lines.Count == 0) + PrintArgumentIfFound(new Matcher().Values, topic, lines); + + if (lines.Count != 0) + { + PrintFormatted(lines); + } + else + { + Console.WriteLine("Topic not found: {0}", topic); + Console.WriteLine(); + PrintUsage("help", new Dictionary()); + } + } + } + + private static void PrintArgumentIfFound(IEnumerable args, string topic, List lines) + { + if (args == null) + return; + + foreach (Duplicati.Library.Interface.ICommandLineArgument arg in args) + { + if (string.Equals(arg.Name, topic, StringComparison.InvariantCultureIgnoreCase)) + { + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + return; + } + + if (arg.Aliases != null) + foreach (string k in arg.Aliases) + { + if (string.Equals(k, topic, StringComparison.InvariantCultureIgnoreCase)) + { + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + return; + } + } + } + } + + private static void PrintBackend(Duplicati.Library.Interface.IBackend back, List lines) + { + lines.Add(back.DisplayName + " (" + back.ProtocolKey + "):"); + lines.Add(" " + back.Description); + if (back.SupportedCommands != null && back.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in back.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + } + lines.Add(""); + } + + private static void PrintEncryptionModule(Duplicati.Library.Interface.IEncryption mod, List lines) + { + lines.Add(mod.DisplayName + " (." + mod.FilenameExtension + "):"); + lines.Add(" " + mod.Description); + if (mod.SupportedCommands != null && mod.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in mod.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + } + lines.Add(""); + } + + private static void PrintCompressionModule(Duplicati.Library.Interface.ICompression mod, List lines) + { + lines.Add(mod.DisplayName + " (." + mod.FilenameExtension + "):"); + lines.Add(" " + mod.Description); + if (mod.SupportedCommands != null && mod.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in mod.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + } + lines.Add(""); + } + + private static void PrintGenericModule(Duplicati.Library.Interface.IGenericModule mod, List lines) + { + lines.Add(mod.DisplayName + " (" + mod.Key + "):"); + lines.Add(" " + mod.Description); + lines.Add(" " + (mod.LoadAsDefault ? Strings.Program.ModuleIsLoadedAutomatically : Strings.Program.ModuleIsNotLoadedAutomatically)); + if (mod.SupportedCommands != null && mod.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in mod.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + } + lines.Add(""); + } + + private static string PrintArguments(IEnumerable args) + { + if (args == null) + return ""; + + List lines = new List(); + foreach (Library.Interface.ICommandLineArgument arg in args) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + + return string.Join(Environment.NewLine, lines.ToArray()); + + } + + private static void PrintFormatted(IEnumerable lines) + { + foreach (string s in lines) + { + if (string.IsNullOrEmpty(s) || s.Trim().Length == 0) + { + Console.WriteLine(); + continue; + } + + string c = s; + + string leadingSpaces = ""; + while (c.Length > 0 && c.StartsWith(" ")) + { + leadingSpaces += " "; + c = c.Remove(0, 1); + } + + bool extraIndent = c.StartsWith("--"); + + while (c.Length > 0) + { + int len = Math.Min(Console.WindowWidth - 2, leadingSpaces.Length + c.Length); + len -= leadingSpaces.Length; + if (len < c.Length) + { + int ix = c.LastIndexOf(" ", len); + if (ix > 0) + len = ix; + } + + Console.WriteLine(leadingSpaces + c.Substring(0, len).Trim()); + c = c.Remove(0, len); + if (extraIndent) + { + extraIndent = false; + leadingSpaces += " "; + } + } + } + } + + private class Matcher + { + Dictionary args = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public Matcher() + { + Library.Main.Options opts = new Library.Main.Options(new Dictionary()); + List> foundArgs = new List>(); + foundArgs.Add(new Library.Main.Options(new Dictionary()).SupportedCommands); + foundArgs.Add(Program.SupportedCommands); + + foreach (Duplicati.Library.Interface.IBackend backend in Library.DynamicLoader.BackendLoader.Backends) + if (backend.SupportedCommands != null) + foundArgs.Add(backend.SupportedCommands); + foreach (Duplicati.Library.Interface.IEncryption mod in Library.DynamicLoader.EncryptionLoader.Modules) + if (mod.SupportedCommands != null) + foundArgs.Add(mod.SupportedCommands); + foreach (Duplicati.Library.Interface.ICompression mod in Library.DynamicLoader.CompressionLoader.Modules) + if (mod.SupportedCommands != null) + foundArgs.Add(mod.SupportedCommands); + foreach (Duplicati.Library.Interface.IGenericModule mod in Library.DynamicLoader.GenericLoader.Modules) + if (mod.SupportedCommands != null) + foundArgs.Add(mod.SupportedCommands); + + foreach (IEnumerable arglst in foundArgs) + if (arglst != null) + { + foreach (Library.Interface.ICommandLineArgument arg in arglst) + { + if (!args.ContainsKey(arg.Name)) + args[arg.Name] = arg; + + if (arg.Aliases != null) + foreach (string a in arg.Aliases) + if (!args.ContainsKey(a)) + args[a] = arg; + } + } + } + + public IEnumerable Values { get { return args.Values; } } + + public string MathEvaluator(System.Text.RegularExpressions.Match m) + { + if (!m.Success || !m.Groups["name"].Success) + return ""; + + Duplicati.Library.Interface.ICommandLineArgument arg; + if (!args.TryGetValue(m.Groups["name"].Value, out arg)) + return ""; + else + return PrintArgSimple(arg, m.Groups["name"].Value.ToLowerInvariant()); + } + } + + private static string PrintArgsSimple(IEnumerable args) + { + if (args == null) + return ""; + + List lines = new List(); + foreach(Duplicati.Library.Interface.ICommandLineArgument arg in args) + lines.Add(PrintArgSimple(arg, arg.Name)); + + return string.Join(Environment.NewLine, lines.ToArray()); + } + + + private static string PrintArgSimple(Duplicati.Library.Interface.ICommandLineArgument arg, string name) + { + if (string.IsNullOrEmpty(arg.DefaultValue)) + return string.Format(" --{0}{1} {2}", name, Environment.NewLine, arg.LongDescription); + else + return string.Format(" --{0} = {1}{2} {3}", name, arg.DefaultValue, Environment.NewLine, arg.LongDescription); + } + } +} diff --git a/Duplicati/CommandLine/Program.cs b/Duplicati/CommandLine/Program.cs new file mode 100644 index 000000000..150dfe54a --- /dev/null +++ b/Duplicati/CommandLine/Program.cs @@ -0,0 +1,768 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System; +using System.Collections.Generic; +using System.Text; + +namespace Duplicati.CommandLine +{ + class Program + { + private static readonly string[] COMMANDS_AS_ARGUMENTS = new string[] { "delete-all-but-n-full", "delete-all-but-n", "delete-older-than" }; + + static int Main(string[] args) + { + bool verboseErrors = false; + try + { + List cargs = new List(args); + string filter = Duplicati.Library.Utility.FilenameFilter.EncodeAsFilter(Duplicati.Library.Utility.FilenameFilter.ParseCommandLine(cargs, true)); + Dictionary options = Library.Utility.CommandLineParser.ExtractOptions(cargs); + + verboseErrors = Library.Utility.Utility.ParseBoolOption(options, "debug-output"); + + //If we are on Windows, append the bundled "win-tools" programs to the search path + //We add it last, to allow the user to override with other versions + if (!Library.Utility.Utility.IsClientLinux) + { + string wintools = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "win-tools"); + Environment.SetEnvironmentVariable("PATH", + Environment.GetEnvironmentVariable("PATH") + + System.IO.Path.PathSeparator.ToString() + + wintools + + System.IO.Path.PathSeparator.ToString() + + System.IO.Path.Combine(wintools, "gpg") //GPG needs to be in a subfolder for wrapping reasons + ); + } + +#if DEBUG + if (cargs.Count > 1 && cargs[0].ToLower() == "unittest") + { + //The unit test is only enabled in DEBUG builds + //it works by getting a list of folders, and treats them as + //if they have the same data, but on different times + + //The first folder is used to make a full backup, + //and each subsequent folder is used to make an incremental backup + + //After all backups are made, the files are restored and verified against + //the original folders. + + //The best way to test it, is to use SVN checkouts at different + //revisions, as this is how a regular folder would evolve + + cargs.RemoveAt(0); + UnitTest.RunTest(cargs.ToArray(), options); + return 0; + } +#endif + + if (cargs.Count > 0) + { + //Because options are of the format --name=value, it seems natural to write "delete-all-but-n-full=5", + // so we allow that format as well + foreach (string s in COMMANDS_AS_ARGUMENTS) + if (cargs[0].Trim().ToLower().StartsWith(s + "=")) + { + cargs.Insert(1, cargs[0].Substring(s.Length + 1)); + cargs[0] = s; + } + } + + //AFTER converting options to commands, we check for internal switches + foreach (string internaloption in Library.Main.Options.InternalOptions) + if (options.ContainsKey(internaloption)) + { + Console.WriteLine(Strings.Program.InternalOptionUsedError, internaloption); + return 200; + } + + if ((options.ContainsKey("parameters-file") && !string.IsNullOrEmpty("parameters-file")) || (options.ContainsKey("parameter-file") && !string.IsNullOrEmpty("parameter-file"))) + { + string filename; + if (options.ContainsKey("parameters-file") && !string.IsNullOrEmpty("parameters-file")) + { + filename = options["parameters-file"]; + options.Remove("parameters-file"); + } + else + { + filename = options["parameter-file"]; + options.Remove("parameter-file"); + } + + if (!ReadOptionsFromFile(filename, ref filter, cargs, options)) + return 100; + } + + //After checking for internal options, we set the filter option + if (!string.IsNullOrEmpty(filter)) + options["filter"] = filter; + + if (cargs.Count == 1) + { + switch (cargs[0].Trim().ToLower()) + { + case "purge-signature-cache": + Library.Main.Interface.PurgeSignatureCache(options); + return 0; + } + } + + if (cargs.Count < 2 || cargs[0].Trim().Equals("help", StringComparison.InvariantCultureIgnoreCase)) + { + if (cargs.Count < 2) + Help.PrintUsage("help", options); + else + Help.PrintUsage(cargs[1], options); + + + return 0; + } + + + string source = cargs[0]; + string target = cargs[1]; + + if (source.Trim().ToLower() == "restore" && cargs.Count == 3) + { + source = target; + target = cargs[2]; + options["restore"] = null; + cargs.RemoveAt(0); + } + else if (source.Trim().ToLower() == "backup" && cargs.Count == 3) + { + source = target; + target = cargs[2]; + cargs.RemoveAt(0); + } + + if (!options.ContainsKey("passphrase")) + if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("PASSPHRASE"))) + options["passphrase"] = System.Environment.GetEnvironmentVariable("PASSPHRASE"); + + if (!options.ContainsKey("ftp-password")) + if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FTP_PASSWORD"))) + options["ftp-password"] = System.Environment.GetEnvironmentVariable("FTP_PASSWORD"); + + if (!options.ContainsKey("ftp-username")) + if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("FTP_USERNAME"))) + options["ftp-username"] = System.Environment.GetEnvironmentVariable("FTP_USERNAME"); + + if (source.Trim().ToLower() == "list") + Console.WriteLine(string.Join(Environment.NewLine, Duplicati.Library.Main.Interface.List(target, options))); + else if (source.Trim().ToLower() == "list-current-files") + { + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + Console.WriteLine(string.Join(Environment.NewLine, new List(Duplicati.Library.Main.Interface.ListCurrentFiles(target, options)).ToArray())); + } + else if (source.Trim().ToLower() == "list-source-folders") + { + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + Console.WriteLine(string.Join(Environment.NewLine, Duplicati.Library.Main.Interface.ListSourceFolders(target, options) ?? new string[0])); + } + else if (source.Trim().ToLower() == "list-actual-signature-files") + { + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + List> files = Duplicati.Library.Main.Interface.ListActualSignatureFiles(cargs[0], options); + + Console.WriteLine("* " + Strings.Program.DeletedFoldersHeader + ":"); + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.DeletedFolder) + Console.WriteLine(x.Value); + + Console.WriteLine(); + Console.WriteLine("* " + Strings.Program.AddedFoldersHeader + ":"); + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.AddedFolder) + Console.WriteLine(x.Value); + + Console.WriteLine(); + Console.WriteLine("* " + Strings.Program.DeletedFilesHeader + ":"); + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.DeletedFile) + Console.WriteLine(x.Value); + + bool hasCombinedSignatures = false; + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.AddedOrUpdatedFile) + { + hasCombinedSignatures = true; + break; + } + + if (hasCombinedSignatures) + { + Console.WriteLine(); + Console.WriteLine("* " + Strings.Program.NewOrModifiedFilesHeader + ":"); + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.AddedOrUpdatedFile) + Console.WriteLine(x.Value); + } + else + { + Console.WriteLine(); + Console.WriteLine("* " + Strings.Program.NewFilesHeader + ":"); + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.AddedFile) + Console.WriteLine(x.Value); + + Console.WriteLine(); + Console.WriteLine("* " + Strings.Program.ModifiedFilesHeader + ":"); + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.UpdatedFile) + Console.WriteLine(x.Value); + } + + Console.WriteLine(); + Console.WriteLine("* " + Strings.Program.ControlFilesHeader + ":"); + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.ControlFile) + Console.WriteLine(x.Value); + + Console.WriteLine(); + Console.WriteLine("* " + Strings.Program.SymlinksHeader + ":"); + foreach (KeyValuePair x in files) + if (x.Key == Duplicati.Library.Main.RSync.RSyncDir.PatchFileType.Symlink) + Console.WriteLine(x.Value); + } + else if (source.Trim().ToLower() == "collection-status") + { + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + List entries = Duplicati.Library.Main.Interface.ParseFileList(cargs[0], options); + + Console.WriteLine(Strings.Program.CollectionStatusHeader.Replace("\\t", "\t"), entries.Count); + + foreach (Duplicati.Library.Main.ManifestEntry m in entries) + { + Console.WriteLine(); + + long size = Math.Max(m.Fileentry.Size ,0); + foreach (KeyValuePair x in m.Volumes) + size += Math.Max(x.Key.Fileentry.Size, 0) + Math.Max(x.Value.Fileentry.Size, 0); + + Console.WriteLine(Strings.Program.CollectionStatusLineFull.Replace("\\t", "\t"), m.Time.ToString(), m.Volumes.Count, Library.Utility.Utility.FormatSizeString(size)); + + foreach (Duplicati.Library.Main.ManifestEntry mi in m.Incrementals) + { + size = Math.Max(mi.Fileentry.Size, 0); + foreach (KeyValuePair x in mi.Volumes) + size += Math.Max(x.Key.Fileentry.Size, 0) + Math.Max(x.Value.Fileentry.Size, 0); + + Console.WriteLine(Strings.Program.CollectionStatusLineInc.Replace("\\t", "\t"), mi.Time.ToString(), mi.Volumes.Count, Library.Utility.Utility.FormatSizeString(size)); + } + } + } + else if (source.Trim().ToLower() == "delete-all-but-n-full" || source.Trim().ToLower() == "delete-all-but-n") + { + int n = 0; + if (!int.TryParse(target, out n) || n < 0) + { + Console.WriteLine(string.Format(Strings.Program.IntegerParseError, target)); + return 200; + } + + options["delete-all-but-n-full"] = n.ToString(); + + cargs.RemoveAt(0); + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + if (source.Trim().ToLower() == "delete-all-but-n") + Console.WriteLine(Duplicati.Library.Main.Interface.DeleteAllButN(cargs[0], options)); + else + Console.WriteLine(Duplicati.Library.Main.Interface.DeleteAllButNFull(cargs[0], options)); + } + else if (source.Trim().ToLower() == "delete-older-than") + { + try + { + Duplicati.Library.Utility.Timeparser.ParseTimeSpan(target); + } + catch (Exception ex) + { + Console.WriteLine(string.Format(Strings.Program.TimeParseError, target, ex.Message)); + return 200; + } + + options["delete-older-than"] = target; + + cargs.RemoveAt(0); + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + Console.WriteLine(Duplicati.Library.Main.Interface.DeleteOlderThan(cargs[0], options)); + } + else if (source.Trim().ToLower() == "cleanup") + { + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + Console.WriteLine(Duplicati.Library.Main.Interface.Cleanup(cargs[0], options)); + } + else if (source.Trim().ToLower() == "create-folder") + { + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + Duplicati.Library.Main.Interface.CreateFolder(cargs[0], options); + Console.WriteLine(string.Format(Strings.Program.FolderCreatedMessage, cargs[0])); + } + else if (source.Trim().ToLower() == "find-last-version") + { + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + List> results = Duplicati.Library.Main.Interface.FindLastFileVersion(cargs[0], options); + Console.WriteLine(Strings.Program.FindLastVersionHeader.Replace("\\t", "\t")); + foreach(KeyValuePair k in results) + Console.WriteLine(string.Format(Strings.Program.FindLastVersionEntry.Replace("\\t", "\t"), k.Value.Ticks == 0 ? Strings.Program.FileEntryNotFound : k.Value.ToLocalTime().ToString("yyyyMMdd hhmmss"), k.Key)); + } + else if (source.Trim().ToLower() == "verify") + { + cargs.RemoveAt(0); + + if (cargs.Count != 1) + { + PrintWrongNumberOfArguments(cargs, 1); + return 200; + } + + List> results = Duplicati.Library.Main.Interface.VerifyBackup(cargs[0], options); + + int manifests = 0; + int signatures = 0; + int contentfiles = 0; + int errors = 0; + + foreach (KeyValuePair x in results) + { + if (x.Key is Duplicati.Library.Main.ManifestEntry) + manifests++; + else if (x.Key is Duplicati.Library.Main.SignatureEntry) + signatures++; + else if (x.Key is Duplicati.Library.Main.ContentEntry) + contentfiles++; + + if (x.Value != null) + errors++; + } + + Console.WriteLine(string.Format(Strings.Program.VerificationCompleted, manifests, signatures, contentfiles, errors)); + if (errors > 0) + { + Console.WriteLine(); + Console.WriteLine(Strings.Program.VerificationErrorHeader); + Console.WriteLine(); + + foreach (KeyValuePair x in results) + if (x.Value != null) + Console.WriteLine(string.Format("{0}: {1}", x.Key.Filename, x.Value.Message)); + } + } + else if (source.IndexOf("://") > 0 || options.ContainsKey("restore")) + { + if (cargs.Count != 2) + { + PrintWrongNumberOfArguments(cargs, 2); + return 200; + } + + Console.WriteLine(Duplicati.Library.Main.Interface.Restore(source, target.Split(System.IO.Path.PathSeparator), options)); + } + else + { + if (cargs.Count != 2) + { + PrintWrongNumberOfArguments(cargs, 2); + return 200; + } + + string result = Duplicati.Library.Main.Interface.Backup(source.Split(System.IO.Path.PathSeparator), target, options); + Console.WriteLine(result); + + Dictionary tmp = ParseDuplicatiOutput(result); + + //Interrupted = 50 + if (tmp.ContainsKey("PartialBackup")) + return 50; + + //Completed with warnings = 2 + if (tmp.ContainsKey("NumberOfWarnings")) + return 2; + + //Success, but no upload = 1 + if (tmp.ContainsKey("BytesUploaded")) + { + long s; + if (long.TryParse(tmp["BytesUploaded"], out s) && s == 0) + return 1; + } + } + + //Normal operation = 0 + return 0; + } + catch (Exception ex) + { + while (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) + ex = ex.InnerException; + + if (!(ex is Library.Interface.CancelException)) + { + if (!string.IsNullOrEmpty(ex.Message)) + { + Console.Error.WriteLine(); + Console.Error.WriteLine(verboseErrors ? ex.ToString() : ex.Message); + } + } + else + { + Console.Error.WriteLine(Strings.Program.UnhandledException, ex.ToString()); + + while (ex.InnerException != null) + { + ex = ex.InnerException; + Console.Error.WriteLine(); + Console.Error.WriteLine(Strings.Program.UnhandledInnerException, ex.ToString()); + } + } + + //Error = 100 + return 100; + } + } + + public static Dictionary ParseDuplicatiOutput(string output) + { + System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex("(?[^\\:]+)\\:(?[^\\n]*)", System.Text.RegularExpressions.RegexOptions.Singleline); + Dictionary res = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + foreach (System.Text.RegularExpressions.Match m in re.Matches(output)) + res[m.Groups["key"].Value.Trim()] = m.Groups["value"].Value.Trim(); + + return res; + } + + private static void PrintWrongNumberOfArguments(List args, int expected) + { + Console.WriteLine(Strings.Program.WrongNumberOfCommandsError_v2, args.Count, expected, args.Count == 0 ? "" : "\"" + string.Join("\", \"", args.ToArray()) + "\""); + } + + public static IList SupportedCommands + { + get + { + return new List(new Library.Interface.ICommandLineArgument[] { + new Library.Interface.CommandLineArgument("parameters-file", Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong, "", new string[] {"parameter-file"}) + }); + } + } + + private static bool ReadOptionsFromFile(string filename, ref string filter, IList cargs, Dictionary options) + { + try + { + List fargs = new List(Library.Utility.Utility.ReadFileWithDefaultEncoding(filename).Replace("\r", "").Split(new String[] { "\n" }, StringSplitOptions.RemoveEmptyEntries)); + + string newfilter = Duplicati.Library.Utility.FilenameFilter.EncodeAsFilter(Duplicati.Library.Utility.FilenameFilter.ParseCommandLine(fargs, true)); + + // If the user specifies parameters-file, all filters must be in the file. + // Allowing to specify some filters on the command line could result in wrong filter ordering + if (!string.IsNullOrEmpty(filter) && !string.IsNullOrEmpty(newfilter)) + throw new Exception(Strings.Program.FiltersCannotBeUsedWithFileError); + + filter = newfilter; + + Dictionary opt = Library.Utility.CommandLineParser.ExtractOptions(fargs); + String newsource = null, newtarget = null; + foreach (KeyValuePair keyvalue in opt) + { + switch (keyvalue.Key.ToLower()) + { + // This replaces any previous value, file parameters take precedence; + case "source": + newsource = keyvalue.Value; + break; + case "target": + newtarget = keyvalue.Value; + break; + default: + options[keyvalue.Key] = keyvalue.Value; + break; + } + } + + // When the action is to backup, allow the source and/or the target + // to be specified in the parameters file as --source= and --target=. + // Note: this block is faily complex due to the way parameters are handled by the rest of the + // procedure. It could likely be much simpler and versatile (allow --source and --target on + // restore or other actions) with some refactoring of the parameters decision tree + if (!String.IsNullOrEmpty(newsource) || !String.IsNullOrEmpty(newtarget)) + { + int offset = cargs.Count > 0 && (cargs[0] == "backup" || cargs[0] == "restore") ? 1 : 0; + bool isrestore = cargs.Count > 0 && cargs[0] == "restore" + || cargs.Count >= 2 && cargs[0].Contains("://"); + + if (cargs.Count == 0 + offset) + { + // if either is empty loading will fail later, so we don't really care. + if (!String.IsNullOrEmpty(newsource)) cargs.Add(newsource); + if (!String.IsNullOrEmpty(newtarget)) cargs.Add(newtarget); + } + else + { + bool isurl = cargs[offset].Contains("://"); + bool isdir = !isurl && cargs[offset].IndexOfAny(new char[] { '/', '\\', ':' }) >= 0; + if (offset > 0 || isdir || isurl) + { + if (cargs.Count == 1 + offset) + { + if (isrestore ^ isurl) + { + if (!String.IsNullOrEmpty(newtarget)) cargs[offset] = newtarget; + if (!String.IsNullOrEmpty(newsource)) cargs.Insert(offset, newsource); + } + else + { + if (!String.IsNullOrEmpty(newtarget)) cargs[offset] = newsource; + if (!String.IsNullOrEmpty(newsource)) cargs.Add(newtarget); + } + } + else + { + if (!String.IsNullOrEmpty(newsource)) cargs[offset] = newsource; + if (!String.IsNullOrEmpty(newtarget)) cargs[offset + 1] = newtarget; + } + } + } + } + + return true; + } + catch (Exception e) + { + Console.WriteLine(Strings.Program.FailedToParseParametersFileError, filename, e.Message); + return false; + } + } + + private static void PrintOldUsage(bool extended) + { + bool isLinux = Library.Utility.Utility.IsClientLinux; + + List lines = new List(); + lines.AddRange( + string.Format( + Strings.Program.ProgramUsageHeader.Replace("\r", ""), + License.VersionNumbers.Version + ).Split('\n') + ); + + lines.AddRange(("\n " + Strings.Program.ProgramUsageBackup.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageRestore.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageCleanup.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageListFiles.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageListSets.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageListContentFiles.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageListSourceFolders.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageListSignatureFiles.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageFindLastVersion.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageVerify.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsagePurgeCache.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageDeleteOld.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n " + Strings.Program.ProgramUsageCreateFolders.Replace("\r", "")).Split('\n')); + + lines.AddRange(("\n " + Strings.Program.ProgramUsageBackend.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n" + Strings.Program.ProgramUsageOptionTypes.Replace("\r", "")).Split('\n')); + lines.AddRange(("\n" + Strings.Program.ProgramUsageTimes.Replace("\r", "")).Split('\n')); + lines.AddRange( + string.Format( + "\n" + Strings.Program.ProgramUsageFilters.Replace("\r", ""), + isLinux ? Strings.Program.UsageExampleLinux : Strings.Program.UsageExampleWindows + ).Split('\n') + ); + + lines.Add(""); + + if (extended) + { + + lines.Add(Strings.Program.DuplicatiOptionsHeader); + Library.Main.Options opt = new Library.Main.Options(new Dictionary()); + foreach (Library.Interface.ICommandLineArgument arg in opt.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg); + + lines.Add(""); + lines.Add(""); + lines.Add(Strings.Program.SupportedBackendsHeader); + foreach (Duplicati.Library.Interface.IBackend back in Library.DynamicLoader.BackendLoader.Backends) + { + lines.Add(back.DisplayName + " (" + back.ProtocolKey + "):"); + lines.Add(" " + back.Description); + if (back.SupportedCommands != null && back.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in back.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg); + } + lines.Add(""); + } + + lines.Add(""); + lines.Add(""); + lines.Add(Strings.Program.SupportedEncryptionModulesHeader); + foreach (Duplicati.Library.Interface.IEncryption mod in Library.DynamicLoader.EncryptionLoader.Modules) + { + lines.Add(mod.DisplayName + " (." + mod.FilenameExtension + "):"); + lines.Add(" " + mod.Description); + if (mod.SupportedCommands != null && mod.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in mod.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg); + } + lines.Add(""); + } + + lines.Add(""); + lines.Add(""); + lines.Add(Strings.Program.SupportedCompressionModulesHeader); + foreach (Duplicati.Library.Interface.ICompression mod in Library.DynamicLoader.CompressionLoader.Modules) + { + lines.Add(mod.DisplayName + " (." + mod.FilenameExtension + "):"); + lines.Add(" " + mod.Description); + if (mod.SupportedCommands != null && mod.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in mod.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg); + } + lines.Add(""); + } + lines.Add(""); + + lines.Add(""); + lines.Add(""); + lines.Add(Strings.Program.GenericModulesHeader); + foreach (Duplicati.Library.Interface.IGenericModule mod in Library.DynamicLoader.GenericLoader.Modules) + { + lines.Add(mod.DisplayName + " (." + mod.Key + "):"); + lines.Add(" " + mod.Description); + lines.Add(" " + (mod.LoadAsDefault ? Strings.Program.ModuleIsLoadedAutomatically : Strings.Program.ModuleIsNotLoadedAutomatically)); + if (mod.SupportedCommands != null && mod.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in mod.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg); + } + lines.Add(""); + } + lines.Add(""); + } + + foreach (string s in lines) + { + if (string.IsNullOrEmpty(s)) + { + Console.WriteLine(); + continue; + } + + string c = s; + + string leadingSpaces = ""; + while (c.Length > 0 && c.StartsWith(" ")) + { + leadingSpaces += " "; + c = c.Remove(0, 1); + } + + while (c.Length > 0) + { + int len = Math.Min(Console.WindowWidth - 2, leadingSpaces.Length + c.Length); + len -= leadingSpaces.Length; + if (len < c.Length) + { + int ix = c.LastIndexOf(" ", len); + if (ix > 0) + len = ix; + } + + Console.WriteLine(leadingSpaces + c.Substring(0, len).Trim()); + c = c.Remove(0, len); + } + } + } + } +} diff --git a/Duplicati/CommandLine/Properties/AssemblyInfo.cs b/Duplicati/CommandLine/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b1b690e24 --- /dev/null +++ b/Duplicati/CommandLine/Properties/AssemblyInfo.cs @@ -0,0 +1,52 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Duplicati.CommandLine")] +[assembly: AssemblyDescription("A commandline version of Duplicati")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HexaD")] +[assembly: AssemblyProduct("Duplicati.CommandLine")] +[assembly: AssemblyCopyright("LGPL, Copyright © HexaD 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bfc1d214-8825-417d-8e2c-be4bb0d6524d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.1.276")] +[assembly: AssemblyFileVersion("1.0.1.276")] diff --git a/Duplicati/CommandLine/Run Unittest on DSMCBE data.bat b/Duplicati/CommandLine/Run Unittest on DSMCBE data.bat new file mode 100644 index 000000000..2c574ee24 --- /dev/null +++ b/Duplicati/CommandLine/Run Unittest on DSMCBE data.bat @@ -0,0 +1,3 @@ +cd bin\Debug +Duplicati.CommandLine.exe UNITTEST DSMCBE\r50 DSMCBE\r100 DSMCBE\r150 DSMCBE\r200 DSMCBE\r250 DSMCBE\r251 DSMCBE\r252 DSMCBE\r253 DSMCBE\r254 DSMCBE\r255 DSMCBE\r256 DSMCBE\r257 DSMCBE\r258 DSMCBE\r259 DSMCBE\r260 +pause \ No newline at end of file diff --git a/Duplicati/CommandLine/Run Unittest on MapGuide data.bat b/Duplicati/CommandLine/Run Unittest on MapGuide data.bat new file mode 100644 index 000000000..efeebfa1f --- /dev/null +++ b/Duplicati/CommandLine/Run Unittest on MapGuide data.bat @@ -0,0 +1,3 @@ +cd bin\Debug +Duplicati.CommandLine.exe UNITTEST MapGuide\r100 MapGuide\r1000 MapGuide\r2000 MapGuide\r3000 MapGuide\r3100 MapGuide\r3200 +pause \ No newline at end of file diff --git a/Duplicati/CommandLine/Strings/Program.Designer.cs b/Duplicati/CommandLine/Strings/Program.Designer.cs new file mode 100644 index 000000000..a5d1d6c80 --- /dev/null +++ b/Duplicati/CommandLine/Strings/Program.Designer.cs @@ -0,0 +1,607 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.296 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Duplicati.CommandLine.Strings { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Program { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Program() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Duplicati.CommandLine.Strings.Program", typeof(Program).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Added folders. + /// + internal static string AddedFoldersHeader { + get { + return ResourceManager.GetString("AddedFoldersHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found {0} backup chains on backend + /// + ///Type\tTime\t\t\tVolumes\tSize. + /// + internal static string CollectionStatusHeader { + get { + return ResourceManager.GetString("CollectionStatusHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Full\t{0}\t{1}\t{2}. + /// + internal static string CollectionStatusLineFull { + get { + return ResourceManager.GetString("CollectionStatusLineFull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inc\t{0}\t{1}\t{2}. + /// + internal static string CollectionStatusLineInc { + get { + return ResourceManager.GetString("CollectionStatusLineInc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Control files. + /// + internal static string ControlFilesHeader { + get { + return ResourceManager.GetString("ControlFilesHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleted files. + /// + internal static string DeletedFilesHeader { + get { + return ResourceManager.GetString("DeletedFilesHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleted folders. + /// + internal static string DeletedFoldersHeader { + get { + return ResourceManager.GetString("DeletedFoldersHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicati options:. + /// + internal static string DuplicatiOptionsHeader { + get { + return ResourceManager.GetString("DuplicatiOptionsHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to read the parameters file "{0}", reason: {1}. + /// + internal static string FailedToParseParametersFileError { + get { + return ResourceManager.GetString("FailedToParseParametersFileError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to < not found >. + /// + internal static string FileEntryNotFound { + get { + return ResourceManager.GetString("FileEntryNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Filters cannot be specified on the commandline if filters are also present in the parameter file. + /// + internal static string FiltersCannotBeUsedWithFileError { + get { + return ResourceManager.GetString("FiltersCannotBeUsedWithFileError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}\t{1}. + /// + internal static string FindLastVersionEntry { + get { + return ResourceManager.GetString("FindLastVersionEntry", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Backuptime\t\tFilename. + /// + internal static string FindLastVersionHeader { + get { + return ResourceManager.GetString("FindLastVersionHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The folder {0} was created. + /// + internal static string FolderCreatedMessage { + get { + return ResourceManager.GetString("FolderCreatedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Supported generic modules:. + /// + internal static string GenericModulesHeader { + get { + return ResourceManager.GetString("GenericModulesHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to parse "{0}" into a number. + /// + internal static string IntegerParseError { + get { + return ResourceManager.GetString("IntegerParseError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The option --{0} was supplied, but it is reserved for internal use and may not be set on the commandline. + /// + internal static string InternalOptionUsedError { + get { + return ResourceManager.GetString("InternalOptionUsedError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Modified files. + /// + internal static string ModifiedFilesHeader { + get { + return ResourceManager.GetString("ModifiedFilesHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Module is loaded atomatically, use --disable-module to prevent this. + /// + internal static string ModuleIsLoadedAutomatically { + get { + return ResourceManager.GetString("ModuleIsLoadedAutomatically", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Module is not loaded atomatically, use --enable-module to load it. + /// + internal static string ModuleIsNotLoadedAutomatically { + get { + return ResourceManager.GetString("ModuleIsNotLoadedAutomatically", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New files. + /// + internal static string NewFilesHeader { + get { + return ResourceManager.GetString("NewFilesHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New/Modified files. + /// + internal static string NewOrModifiedFilesHeader { + get { + return ResourceManager.GetString("NewOrModifiedFilesHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This option can be used to store some or all of the options given to the commandline client. The file must be a plain text file, UTF-8 encoding is preferred. Each line in the file should be of the format --option=value. The special options --source and --target can be used to override the localpath and the remote destination uri, respectively. The options in this file take precedence over the options provided on the commandline. You cannot specify filters in both the file and on the commandline.. + /// + internal static string ParametersFileOptionLong { + get { + return ResourceManager.GetString("ParametersFileOptionLong", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Path to a file with parameters. + /// + internal static string ParametersFileOptionShort { + get { + return ResourceManager.GetString("ParametersFileOptionShort", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A <backend> is identified by an url like ftp://host/ or ssh://server/. + /// Using this system, Duplicati can detect if you want to backup or restore. + /// The cleanup and delete commands do not delete files, unless the --force option is specified, so you may examine what files are affected, before actually deleting the files. + /// The cleanup command should not be used unless a backup was interrupted and has left partial files. Duplicati will inform you if this happens. + /// The delete command can be used to remove bac [rest of string was truncated]";. + /// + internal static string ProgramUsageBackend { + get { + return ResourceManager.GetString("ProgramUsageBackend", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Backup (make a full or incremental backup): + /// Duplicati.CommandLine [full] [options] <sourcefolder> <backend>. + /// + internal static string ProgramUsageBackup { + get { + return ResourceManager.GetString("ProgramUsageBackup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cleanup (remove partial and unused files): + /// Duplicati.CommandLine cleanup [options] <backend>. + /// + internal static string ProgramUsageCleanup { + get { + return ResourceManager.GetString("ProgramUsageCleanup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create folder (usually done automatically): + /// Duplicati.CommandLine create-folder [options] <backend>. + /// + internal static string ProgramUsageCreateFolders { + get { + return ResourceManager.GetString("ProgramUsageCreateFolders", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete old backups: + /// Duplicati.CommandLine delete-all-but-n-full <number of full backups to keep> [options] <backend> + /// Duplicati.CommandLine delete-older-than <max allowed age> [options] <backend>. + /// + internal static string ProgramUsageDeleteOld { + get { + return ResourceManager.GetString("ProgramUsageDeleteOld", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Filters: + /// Duplicati uses filters to include and exclude files. + /// Duplicati uses a "first-touch" filter where the first rule that matches a file determines if the file is included or excluded. Internally Duplicati uses regular expression filters, but supports filters in the form of filename globbing. The order of the commandline arguments also determine what order they are applied in. An example: + /// --include=*.txt --exclude=*\Thumbs.db --include=* + /// + /// Even though the last filter includes everything, no [rest of string was truncated]";. + /// + internal static string ProgramUsageFilters { + get { + return ResourceManager.GetString("ProgramUsageFilters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Find the last version of a file: + /// Duplicati.CommandLine find-last-version [options] --file-to-restore=<files to find> <backend>. + /// + internal static string ProgramUsageFindLastVersion { + get { + return ResourceManager.GetString("ProgramUsageFindLastVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ********** Duplicati v. {0} ********** + /// + ///Usage:. + /// + internal static string ProgramUsageHeader { + get { + return ResourceManager.GetString("ProgramUsageHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List content files (files that can be restored): + /// Duplicati.CommandLine list-current-files [options] <backend>. + /// + internal static string ProgramUsageListContentFiles { + get { + return ResourceManager.GetString("ProgramUsageListContentFiles", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List files: + /// Duplicati.CommandLine list [options] <backend>. + /// + internal static string ProgramUsageListFiles { + get { + return ResourceManager.GetString("ProgramUsageListFiles", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List backup sets: + /// Duplicati.CommandLine collection-status [options] <backend>. + /// + internal static string ProgramUsageListSets { + get { + return ResourceManager.GetString("ProgramUsageListSets", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List signature files (files in a single backup volume set): + /// Duplicati.CommandLine list-actual-signature-files [options] <backend>. + /// + internal static string ProgramUsageListSignatureFiles { + get { + return ResourceManager.GetString("ProgramUsageListSignatureFiles", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List source folders (folders backed up from): + /// Duplicati.CommandLine list-source-folders [options] <backend>. + /// + internal static string ProgramUsageListSourceFolders { + get { + return ResourceManager.GetString("ProgramUsageListSourceFolders", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Option types: + /// The following option types are available: + /// Integer: a numerical value + /// Boolean: a truth value, --force and --force=true are equivalent. --force=false is the opposite + /// Timespan: a time in the special time format (explained below) + /// Size: a size like 5mb or 200kb + /// Enumeration: any of the listed values + /// Path: the path to a folder or file + /// String: any other type. + /// + internal static string ProgramUsageOptionTypes { + get { + return ResourceManager.GetString("ProgramUsageOptionTypes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Purge signature cache: + /// Duplicati.CommandLine purge-signature-cache [options]. + /// + internal static string ProgramUsagePurgeCache { + get { + return ResourceManager.GetString("ProgramUsagePurgeCache", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Restore (restore all or some files): + /// Duplicati.CommandLine [options] <backend> <destinationfolder>. + /// + internal static string ProgramUsageRestore { + get { + return ResourceManager.GetString("ProgramUsageRestore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Times: + /// Duplicati uses the time system from duplicity, where times may be presented as: + /// 1: the string "now", indicating the current time + /// 2: the number of seconds after epoch, eg: 123456890 + /// 3: a string like "2009-03-26T08:30:00+01:00" + /// 4: an interval string, using Y, M, W, D, h, m, s for Year, Month, Week, Day, hour, minute or second, eg: "1M4D" for one month and four days, or "5m" for five minutes.. + /// + internal static string ProgramUsageTimes { + get { + return ResourceManager.GetString("ProgramUsageTimes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Verify backup integrity: + /// Duplicati.CommandLine verify <backend> --verification-level=manifest|signatures|full. + /// + internal static string ProgramUsageVerify { + get { + return ResourceManager.GetString("ProgramUsageVerify", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Supported backends:. + /// + internal static string SupportedBackendsHeader { + get { + return ResourceManager.GetString("SupportedBackendsHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Supported compression modules:. + /// + internal static string SupportedCompressionModulesHeader { + get { + return ResourceManager.GetString("SupportedCompressionModulesHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Supported encryption modules:. + /// + internal static string SupportedEncryptionModulesHeader { + get { + return ResourceManager.GetString("SupportedEncryptionModulesHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Supported options:. + /// + internal static string SupportedOptionsHeader { + get { + return ResourceManager.GetString("SupportedOptionsHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Symbolic links. + /// + internal static string SymlinksHeader { + get { + return ResourceManager.GetString("SymlinksHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to parse "{0}" into a time offset: {1}. + /// + internal static string TimeParseError { + get { + return ResourceManager.GetString("TimeParseError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occured: {0}. + /// + internal static string UnhandledException { + get { + return ResourceManager.GetString("UnhandledException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The inner error message is: {0}. + /// + internal static string UnhandledInnerException { + get { + return ResourceManager.GetString("UnhandledInnerException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicati.CommandLine /home/user/ ftp://host/folder --exclude=/file.txt + /// + /// In this example the file "/home/user/file.txt" is excluded.. + /// + internal static string UsageExampleLinux { + get { + return ResourceManager.GetString("UsageExampleLinux", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicati.CommandLine C:\Documents\Files ftp://host/folder --exclude=\file.txt + /// + /// In this example the file "C:\Documents\Files\file.txt" is excluded.. + /// + internal static string UsageExampleWindows { + get { + return ResourceManager.GetString("UsageExampleWindows", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Verification completed, summary: + ///Manifest files verified: {0} + ///Signature files verified: {1} + ///Content files verified: {2} + ///Errors: {3}. + /// + internal static string VerificationCompleted { + get { + return ResourceManager.GetString("VerificationCompleted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reported error messages:. + /// + internal static string VerificationErrorHeader { + get { + return ResourceManager.GetString("VerificationErrorHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found {0} commands but expected {1}, commands: + ///{2}. + /// + internal static string WrongNumberOfCommandsError_v2 { + get { + return ResourceManager.GetString("WrongNumberOfCommandsError_v2", resourceCulture); + } + } + } +} diff --git a/Duplicati/CommandLine/Strings/Program.resx b/Duplicati/CommandLine/Strings/Program.resx new file mode 100644 index 000000000..d6fd6320e --- /dev/null +++ b/Duplicati/CommandLine/Strings/Program.resx @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Added folders + + + Found {0} backup chains on backend + +Type\tTime\t\t\tVolumes\tSize + + + Full\t{0}\t{1}\t{2} + + + Inc\t{0}\t{1}\t{2} + + + Control files + + + Deleted files + + + Deleted folders + + + Duplicati options: + + + Unable to read the parameters file "{0}", reason: {1} + + + < not found > + + + Filters cannot be specified on the commandline if filters are also present in the parameter file + + + {0}\t{1} + + + Backuptime\t\tFilename + + + The folder {0} was created + + + Supported generic modules: + + + Unable to parse "{0}" into a number + + + The option --{0} was supplied, but it is reserved for internal use and may not be set on the commandline + + + Modified files + + + Module is loaded atomatically, use --disable-module to prevent this + + + Module is not loaded atomatically, use --enable-module to load it + + + New files + + + New/Modified files + + + This option can be used to store some or all of the options given to the commandline client. The file must be a plain text file, UTF-8 encoding is preferred. Each line in the file should be of the format --option=value. The special options --source and --target can be used to override the localpath and the remote destination uri, respectively. The options in this file take precedence over the options provided on the commandline. You cannot specify filters in both the file and on the commandline. + + + Path to a file with parameters + + + A <backend> is identified by an url like ftp://host/ or ssh://server/. + Using this system, Duplicati can detect if you want to backup or restore. + The cleanup and delete commands do not delete files, unless the --force option is specified, so you may examine what files are affected, before actually deleting the files. + The cleanup command should not be used unless a backup was interrupted and has left partial files. Duplicati will inform you if this happens. + The delete command can be used to remove backup sets when newer backups are present. + + + Backup (make a full or incremental backup): + Duplicati.CommandLine [full] [options] <sourcefolder> <backend> + + + Cleanup (remove partial and unused files): + Duplicati.CommandLine cleanup [options] <backend> + + + Create folder (usually done automatically): + Duplicati.CommandLine create-folder [options] <backend> + + + Delete old backups: + Duplicati.CommandLine delete-all-but-n-full <number of full backups to keep> [options] <backend> + Duplicati.CommandLine delete-older-than <max allowed age> [options] <backend> + + + Filters: + Duplicati uses filters to include and exclude files. + Duplicati uses a "first-touch" filter where the first rule that matches a file determines if the file is included or excluded. Internally Duplicati uses regular expression filters, but supports filters in the form of filename globbing. The order of the commandline arguments also determine what order they are applied in. An example: + --include=*.txt --exclude=*\Thumbs.db --include=* + + Even though the last filter includes everything, no files named "Thumbs.db" are included because they match the exclude rule before the include rule. Paths are evaluated in full, and as paths that are relative to folder being backed up, but including a leading slash. An example: + +{0} + If a folder is excluded, files in that folder are always excluded, even if there are filters that include files in that folder. If a folder is included with a wildcard at the end, all files are included, if the folder is included without a wildcard, files may be excluded or included with extra rules. + + + Find the last version of a file: + Duplicati.CommandLine find-last-version [options] --file-to-restore=<files to find> <backend> + + + ********** Duplicati v. {0} ********** + +Usage: + + + List content files (files that can be restored): + Duplicati.CommandLine list-current-files [options] <backend> + + + List files: + Duplicati.CommandLine list [options] <backend> + + + List backup sets: + Duplicati.CommandLine collection-status [options] <backend> + + + List signature files (files in a single backup volume set): + Duplicati.CommandLine list-actual-signature-files [options] <backend> + + + List source folders (folders backed up from): + Duplicati.CommandLine list-source-folders [options] <backend> + + + Option types: + The following option types are available: + Integer: a numerical value + Boolean: a truth value, --force and --force=true are equivalent. --force=false is the opposite + Timespan: a time in the special time format (explained below) + Size: a size like 5mb or 200kb + Enumeration: any of the listed values + Path: the path to a folder or file + String: any other type + + + Purge signature cache: + Duplicati.CommandLine purge-signature-cache [options] + + + Restore (restore all or some files): + Duplicati.CommandLine [options] <backend> <destinationfolder> + + + Times: + Duplicati uses the time system from duplicity, where times may be presented as: + 1: the string "now", indicating the current time + 2: the number of seconds after epoch, eg: 123456890 + 3: a string like "2009-03-26T08:30:00+01:00" + 4: an interval string, using Y, M, W, D, h, m, s for Year, Month, Week, Day, hour, minute or second, eg: "1M4D" for one month and four days, or "5m" for five minutes. + + + Verify backup integrity: + Duplicati.CommandLine verify <backend> --verification-level=manifest|signatures|full + + + Supported backends: + + + Supported compression modules: + + + Supported encryption modules: + + + Supported options: + + + Symbolic links + + + Unable to parse "{0}" into a time offset: {1} + + + An error occured: {0} + + + The inner error message is: {0} + + + Duplicati.CommandLine /home/user/ ftp://host/folder --exclude=/file.txt + + In this example the file "/home/user/file.txt" is excluded. + + + Duplicati.CommandLine C:\Documents\Files ftp://host/folder --exclude=\file.txt + + In this example the file "C:\Documents\Files\file.txt" is excluded. + + + Verification completed, summary: +Manifest files verified: {0} +Signature files verified: {1} +Content files verified: {2} +Errors: {3} + + + Reported error messages: + + + Found {0} commands but expected {1}, commands: +{2} + + \ No newline at end of file diff --git a/Duplicati/CommandLine/TrayWarning.ico b/Duplicati/CommandLine/TrayWarning.ico new file mode 100644 index 000000000..4243c89e3 Binary files /dev/null and b/Duplicati/CommandLine/TrayWarning.ico differ diff --git a/Duplicati/CommandLine/UnitTest.cs b/Duplicati/CommandLine/UnitTest.cs new file mode 100644 index 000000000..ec9dcc52e --- /dev/null +++ b/Duplicati/CommandLine/UnitTest.cs @@ -0,0 +1,524 @@ +#region Disclaimer / License +// Copyright (C) 2011, Kenneth Skovhede +// http://www.hexad.dk, opensource@hexad.dk +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion + +#if DEBUG + +using System; +using System.Collections.Generic; +using System.Text; +using Duplicati.Library.Logging; +using Duplicati.Library.Utility; + +namespace Duplicati.CommandLine +{ + /// + /// This class encapsulates a simple method for testing the correctness of duplicati. + /// + public class UnitTest + { + /// + /// A helper class to write debug messages to the log file + /// + private class LogHelper : StreamLog + { + private string m_backupset; + + public string Backupset + { + get { return m_backupset; } + set { m_backupset = value; } + } + + public LogHelper(string file) + : base(file) + { + this.Backupset = "none"; + } + + public override void WriteMessage(string message, LogMessageType type, Exception exception) + { + base.WriteMessage(this.Backupset + ", " + message, type, exception); + } + } + + /// + /// Running the unit test confirms the correctness of duplicati + /// + /// The folders to backup. Folder at index 0 is the base, all others are incrementals + public static void RunTest(string[] folders, Dictionary options) + { + //Place a file called "unittest_target.txt" in the bin folder, and enter a connection string like "ftp://username:password@example.com" + + string ftp_password = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "unittest_ftppassword.txt"); + if (System.IO.File.Exists(ftp_password)) + options["ftp-password"] = System.IO.File.ReadAllText(ftp_password).Trim(); + + string alttarget = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "unittest_target.txt"); + if (System.IO.File.Exists(alttarget)) + RunTest(folders, options, System.IO.File.ReadAllText(alttarget).Trim()); + else + RunTest(folders, options, null); + } + + /// + /// Running the unit test confirms the correctness of duplicati + /// + /// The folders to backup. Folder at index 0 is the base, all others are incrementals + /// The target destination for the backups + public static void RunTest(string[] folders, Dictionary options, string target) + { + LogHelper log = new LogHelper("unittest.log"); + Log.CurrentLog = log; ; + Log.LogLevel = Duplicati.Library.Logging.LogMessageType.Profiling; + + string tempdir = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "tempdir"); + + try + { + if (System.IO.Directory.Exists(tempdir)) + System.IO.Directory.Delete(tempdir, true); + + System.IO.Directory.CreateDirectory(tempdir); + } + catch(Exception ex) + { + Log.WriteMessage("Failed to clean tempdir", LogMessageType.Error, ex); + } + + Duplicati.Library.Utility.TempFolder.SystemTempPath = tempdir; + + //Set some defaults + if (!options.ContainsKey("passphrase")) + options["passphrase"] = "secret password!"; + + if (!options.ContainsKey("backup-prefix")) + options["backup-prefix"] = "duplicati_unittest"; + + //This would break the test, because the data is not modified the normal way + options["disable-filetime-check"] = "true"; + //We do not use the same folder, so we need this option + options["allow-sourcefolder-change"] = "true"; + //We want all messages in the log + options["log-level"] = LogMessageType.Profiling.ToString(); + //We cannot rely on USN numbering, but we can use USN enumeration + //options["disable-usn-diff-check"] = "true"; + + //We use precise times + options["disable-time-tolerance"] = "true"; + + options["verification-level"] = "full"; + + //We need all sets, even if they are unchanged + options["upload-unchanged-backups"] = "true"; + + using(new Timer("Total unittest")) + using(TempFolder tf = new TempFolder()) + { + //The code below tests for a race condition in the ssh backend. + /*string[] list = null; + string[] prevList = null; + + for (int i = 0; i < 1000; i++) + { + Console.WriteLine(string.Format("Listing, test {0}", i)); + list = Duplicati.Library.Main.Interface.List(target, options); + if (i != 0 && list.Length != prevList.Length) + Console.WriteLine(string.Format("Count mismatch {0} vs {1}", list.Length, prevList.Length)); + + prevList = list; + }*/ + + if (options.ContainsKey("fh-dbpath")) + { + options["fh-dbpath"] = System.IO.Path.Combine(tempdir, "fh-unittest.sqlite"); + if (System.IO.File.Exists(options["fh-dbpath"])) + System.IO.File.Delete(options["fh-dbpath"]); + } + + if (string.IsNullOrEmpty(target)) + { + target = "file://" + tf; + } + else + { + Console.WriteLine("Removing old backups"); + Dictionary tmp = new Dictionary(options); + tmp["delete-all-but-n-full"] = "0"; + tmp["force"] = ""; + tmp["allow-full-removal"] = ""; + + using (new Timer("Cleaning up any existing backups")) + Console.WriteLine(Duplicati.Library.Main.Interface.DeleteAllButNFull(target, tmp)); + } + + log.Backupset = "Backup " + folders[0]; + Console.WriteLine("Backing up the full copy: " + folders[0]); + using (new Timer("Full backup of " + folders[0])) + { + options["full"] = ""; + Log.WriteMessage(Duplicati.Library.Main.Interface.Backup(folders[0].Split(System.IO.Path.PathSeparator), target, options), LogMessageType.Information); + options.Remove("full"); + } + + for (int i = 1; i < folders.Length; i++) + { + //options["passphrase"] = "bad password"; + //If the backups are too close, we can't pick the right one :( + System.Threading.Thread.Sleep(1000 * 5); + log.Backupset = "Backup " + folders[i]; + Console.WriteLine("Backing up the incremental copy: " + folders[i]); + using (new Timer("Incremental backup of " + folders[i])) + Log.WriteMessage(Duplicati.Library.Main.Interface.Backup(folders[i].Split(System.IO.Path.PathSeparator), target, options), LogMessageType.Information); + } + + Duplicati.Library.Main.Options opts = new Duplicati.Library.Main.Options(options); + using (Duplicati.Library.Interface.IBackend bk = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(target, options)) + foreach (Duplicati.Library.Interface.IFileEntry fe in bk.List()) + if (fe.Size > opts.VolumeSize) + { + string msg = string.Format("The file {0} is {1} bytes larger than allowed", fe.Name, fe.Size - opts.VolumeSize); + Console.WriteLine(msg); + Log.WriteMessage(msg, LogMessageType.Error); + } + + IList entries; + if (options.ContainsKey("fh-dbpath")) + { + entries = new List(); + foreach (var el in Duplicati.Library.Main.Interface.ParseFhFileList(target, options)) + if (el.FileType == Library.Main.ForestHash.RemoteVolumeType.Files) + entries.Add(el.Time.ToLocalTime()); + + if (entries.Count != folders.Length) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("Entry count: " + entries.Count.ToString()); + sb.Append(string.Format("Found {0} filelists but there were {1} source folders", entries.Count, folders.Length)); + throw new Exception("Filename parsing problem, or corrupt storage: " + sb.ToString()); + } + } + else + { + List mfs = Duplicati.Library.Main.Interface.ParseFileList(target, options); + + if (mfs.Count != 1 || mfs[0].Incrementals.Count != folders.Length - 1) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("Entry count: " + mfs.Count.ToString()); + if (mfs.Count == 1) + sb.Append(string.Format("Found {0} incrementals but there were {1} source folders", mfs[0].Incrementals.Count, folders.Length)); + throw new Exception("Filename parsing problem, or corrupt storage: " + sb.ToString()); + } + + Console.WriteLine("Verifying the backup chain"); + using (new Timer("Verify backup")) + { + List> results = Duplicati.Library.Main.Interface.VerifyBackup(target, options); + foreach (KeyValuePair x in results) + if (x.Value != null) + Console.WriteLine(string.Format("Error: {0}: {1}", x.Key.Filename, x.Value.ToString())); + } + + entries = new List(); + entries.Add(mfs[0].Time); + foreach (Duplicati.Library.Main.ManifestEntry mf in mfs[0].Incrementals) + entries.Add(mf.Time); + } + + + + for (int i = 0; i < entries.Count; i++) + { + using (TempFolder ttf = new TempFolder()) + { + log.Backupset = "Restore " + folders[i]; + Console.WriteLine("Restoring the copy: " + folders[i]); + + options["restore-time"] = entries[i].ToString(); + + string[] actualfolders = folders[i].Split(System.IO.Path.PathSeparator); + string[] restorefoldernames; + if (actualfolders.Length == 1) + restorefoldernames = new string[] { ttf }; + else + { + restorefoldernames = new string[actualfolders.Length]; + for (int j = 0; j < actualfolders.Length; j++) + restorefoldernames[j] = System.IO.Path.Combine(ttf, System.IO.Path.GetFileName(actualfolders[j])); + } + + Console.WriteLine("Partial restore of: " + folders[i]); + using (TempFolder ptf = new TempFolder()) + { + List testfiles = new List(); + using (new Timer("Extract list of files from" + folders[i])) + { + IList sourcefiles = Duplicati.Library.Main.Interface.ListCurrentFiles(target, options); + + //Remove all folders from list + for (int j = 0; j < sourcefiles.Count; j++) + if (sourcefiles[j].EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) + { + sourcefiles.RemoveAt(j); + j--; + } + + + int testfilecount = 15; + Random r = new Random(); + while (testfilecount-- > 0 && sourcefiles.Count > 0) + { + int rn = r.Next(0, sourcefiles.Count); + testfiles.Add(sourcefiles[rn]); + sourcefiles.RemoveAt(rn); + } + + } + + + //Add all folders to avoid warnings in restore log + int c = testfiles.Count; + Dictionary partialFolders = new Dictionary(Utility.ClientFilenameStringComparer); + + for (int j = 0; j < c; j++) + { + string f = testfiles[j]; + + if (options.ContainsKey("fh-dbpath")) + { + if (!f.StartsWith(folders[i], Utility.ClientFilenameStringComparision)) + throw new Exception(string.Format("Unexpected file found: {0}, path is not a subfolder for {1}", f, folders[i])); + + f = f.Substring(Utility.AppendDirSeparator(folders[i]).Length); + } + + do + { + f = System.IO.Path.GetDirectoryName(f); + partialFolders[Utility.AppendDirSeparator(f)] = null; + } while (f.IndexOf(System.IO.Path.DirectorySeparatorChar) > 0); + } + + if (partialFolders.ContainsKey("")) + partialFolders.Remove(""); + + Dictionary tops = new Dictionary(options); + List filterlist = new List(); + filterlist.AddRange(partialFolders.Keys); + filterlist.AddRange(testfiles); + + if (options.ContainsKey("fh-dbpath")) + { + //We need a minor tweak here because the two models + // use releative vs. absolute paths + List t = new List(); + foreach(var s in filterlist) + t.Add(System.IO.Path.Combine(folders[i], s)); + + filterlist = t; + if (!filterlist.Contains(Utility.AppendDirSeparator(folders[i]))) + filterlist.Add(Utility.AppendDirSeparator(folders[i])); + + t = new List(); + foreach (var s in testfiles) + t.Add(s.Substring(Utility.AppendDirSeparator(folders[i]).Length)); + testfiles = t; + } + tops["file-to-restore"] = String.Join(System.IO.Path.PathSeparator.ToString(), filterlist.ToArray()); + + using (new Timer("Partial restore of " + folders[i])) + Log.WriteMessage(Duplicati.Library.Main.Interface.Restore(target, new string[] { ptf }, tops), LogMessageType.Information); + + Console.WriteLine("Verifying partial restore of: " + folders[i]); + using (new Timer("Verification of partial restore from " + folders[i])) + foreach (string s in testfiles) + { + string restoredname; + string sourcename; + + if (actualfolders.Length == 1) + { + sourcename = System.IO.Path.Combine(actualfolders[0], s); + restoredname = System.IO.Path.Combine(ptf, s);; + } + else + { + int six = s.IndexOf(System.IO.Path.DirectorySeparatorChar); + sourcename = System.IO.Path.Combine(actualfolders[int.Parse(s.Substring(0, six))], s.Substring(six + 1)); + restoredname = System.IO.Path.Combine(System.IO.Path.Combine(ptf, System.IO.Path.GetFileName(folders[0].Split(System.IO.Path.PathSeparator)[int.Parse(s.Substring(0, six))])), s.Substring(six + 1)); + } + + if (!System.IO.File.Exists(restoredname)) + { + Log.WriteMessage("Partial restore missing file: " + restoredname, LogMessageType.Error); + Console.WriteLine("Partial restore missing file: " + restoredname); + } + else + { + if (!System.IO.File.Exists(sourcename)) + { + Log.WriteMessage("Partial restore missing file: " + sourcename, LogMessageType.Error); + Console.WriteLine("Partial restore missing file: " + sourcename); + throw new Exception("Unittest is broken"); + } + + if (!CompareFiles(sourcename, restoredname, s)) + { + Log.WriteMessage("Partial restore file differs: " + s, LogMessageType.Error); + Console.WriteLine("Partial restore file differs: " + s); + } + } + } + } + + using (new Timer("Restore of " + folders[i])) + Log.WriteMessage(Duplicati.Library.Main.Interface.Restore(target, restorefoldernames, options), LogMessageType.Information); + + Console.WriteLine("Verifying the copy: " + folders[i]); + + using (new Timer("Verification of " + folders[i])) + { + for (int j = 0; j < actualfolders.Length; j++) + VerifyDir(actualfolders[j], restorefoldernames[j]); + } + } + } + + } + + (Log.CurrentLog as StreamLog).Dispose(); + Log.CurrentLog = null; + } + + /// + /// Returns the index of a given string, using the file system case sensitivity + /// + /// The index of the entry or -1 if no entry was found + /// The list to search + /// The string to find + private static int IndexOf(List lst, string m) + { + StringComparison sc = Duplicati.Library.Utility.Utility.ClientFilenameStringComparision; + for(int i = 0; i < lst.Count; i++) + if (lst[i].Equals(m, sc)) + return i; + + return -1; + } + + /// + /// Verifies the existence of all files and folders, and ensures that all + /// files are binary equal. + /// + /// One folder + /// Another folder + private static void VerifyDir(string f1, string f2) + { + f1 = Utility.AppendDirSeparator(f1); + f2 = Utility.AppendDirSeparator(f2); + + List folders1 = Utility.EnumerateFolders(f1); + List folders2 = Utility.EnumerateFolders(f2); + + foreach (string s in folders1) + { + string relpath = s.Substring(f1.Length); + string target = System.IO.Path.Combine(f2, relpath); + int ix = IndexOf(folders2, target); + if (ix < 0) + { + Log.WriteMessage("Missing folder: " + relpath, LogMessageType.Error); + Console.WriteLine("Missing folder: " + relpath); + } + else + folders2.RemoveAt(ix); + } + + foreach (string s in folders2) + { + Log.WriteMessage("Extra folder: " + s.Substring(f2.Length), LogMessageType.Error); + Console.WriteLine("Extra folder: " + s.Substring(f2.Length)); + } + + List files1 = Utility.EnumerateFiles(f1); + List files2 = Utility.EnumerateFiles(f2); + foreach (string s in files1) + { + string relpath = s.Substring(f1.Length); + string target = System.IO.Path.Combine(f2, relpath); + int ix = IndexOf(files2, target); + if (ix < 0) + { + Log.WriteMessage("Missing file: " + relpath, LogMessageType.Error); + Console.WriteLine("Missing file: " + relpath); + } + else + { + files2.RemoveAt(ix); + if (!CompareFiles(s, target, relpath)) + { + Log.WriteMessage("File differs: " + relpath, LogMessageType.Error); + Console.WriteLine("File differs: " + relpath); + } + } + } + + foreach (string s in files2) + { + Log.WriteMessage("Extra file: " + s.Substring(f2.Length), LogMessageType.Error); + Console.WriteLine("Extra file: " + s.Substring(f2.Length)); + } + } + + /// + /// Compares two files by reading all bytes, and comparing one by one + /// + /// One file + /// Another file + /// File display name + /// True if they are equal, false otherwise + private static bool CompareFiles(string f1, string f2, string display) + { + using (System.IO.FileStream fs1 = System.IO.File.OpenRead(f1)) + using (System.IO.FileStream fs2 = System.IO.File.OpenRead(f2)) + if (fs1.Length != fs2.Length) + { + Log.WriteMessage("Lengths differ: " + display + ", " + fs1.Length.ToString() + " vs. " + fs2.Length.ToString(), LogMessageType.Error); + Console.WriteLine("Lengths differ: " + display + ", " + fs1.Length.ToString() + " vs. " + fs2.Length.ToString()); + return false; + } + else + { + long len = fs1.Length; + for (long l = 0; l < len; l++) + if (fs1.ReadByte() != fs2.ReadByte()) + { + Log.WriteMessage("Mismatch in byte " + l.ToString() + " in file " + display, LogMessageType.Error); + Console.WriteLine("Mismatch in byte " + l.ToString() + " in file " + display); + return false; + } + } + + return true; + } + } +} + +#endif \ No newline at end of file diff --git a/Duplicati/CommandLine/app.config b/Duplicati/CommandLine/app.config new file mode 100644 index 000000000..1542bc384 --- /dev/null +++ b/Duplicati/CommandLine/app.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Duplicati/CommandLine/help.txt b/Duplicati/CommandLine/help.txt new file mode 100644 index 000000000..4e0e83427 --- /dev/null +++ b/Duplicati/CommandLine/help.txt @@ -0,0 +1,411 @@ +# This file is the help for the Duplicati Command Line Client. It shows what +# commands the CLI understands and what the outputs are. Sometimes we used +# placeholders for options. In that case descriptions are taken directly from +# the modules. Sometimes we described options here. The reason for that is, +# that we found the new description much better but did not want to create a +# need for translation as many options are described in the UI as well and +# they are translated there. +# ============================================================================= +> duplicati.commandline.exe +> duplicati.commandline.exe help + +See duplicati.commandline.exe help for more information. + + Examples: example + + Commands: backup, restore, delete-all-but-n-full, delete-all-but-n, cleanup, delete-older-than, list, list-current-files, list-source-folders, list-actual-signature-files, collection-status, create-folder, find-last-version, verify + + Debugging: debug, logging + + Targets: %BACKENDS% + + Modules: %ENCRYPTIONMODULES%, %COMPRESSIONMODULES%, %GENERICMODULES% + + Formats: date, time, size, encryption, compression + + Advanced options: advanced, returncodes, filter,