Update issue #160

Status: Fixed

Changed to the AWSSDK library, which fixes a few quirks, but basically seems to be the Affirma version slightly renamed.



git-svn-id: https://duplicati.googlecode.com/svn/trunk@811 59da171f-624f-0410-aa54-27559c288bec
This commit is contained in:
kenneth.skovhede@gmail.com
2011-06-23 21:37:14 +00:00
parent 9da618aa67
commit db8cdd0dd2
29 changed files with 96454 additions and 961 deletions
@@ -47,18 +47,6 @@ namespace Duplicati.CommandLine.BackendTester
throw new NotImplementedException();
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override void SetLength(long value)
{
throw new NotImplementedException();
@@ -351,31 +351,6 @@ namespace Duplicati.GUI.Wizard_pages
set { SetItem("BackendSettings", value); }
}
/// <summary>
/// Returns a customized settings object describing settings for a file-based backend
/// </summary>
public FileSettings FileSettings { get { return new FileSettings(this); } }
/// <summary>
/// Returns a customized settings object describing settings for a ssh-based backend
/// </summary>
public SSHSettings SSHSettings { get { return new SSHSettings(this); } }
/// <summary>
/// Returns a customized settings object describing settings for a ftp-based backend
/// </summary>
public FTPSettings FTPSettings { get { return new FTPSettings(this); } }
/// <summary>
/// Returns a customized settings object describing settings for a S3-based backend
/// </summary>
public S3Settings S3Settings { get { return new S3Settings(this); } }
/// <summary>
/// Returns a customized settings object describing settings for a WEBDAV-based backend
/// </summary>
public WEBDAVSettings WEBDAVSettings { get { return new WEBDAVSettings(this); } }
/// <summary>
/// The offset for running backups
/// </summary>
@@ -722,198 +697,6 @@ namespace Duplicati.GUI.Wizard_pages
}
/// <summary>
/// Class that represents the settings for a backend
/// </summary>
public class BackendSettings
{
protected WizardSettingsWrapper m_parent;
public BackendSettings(WizardSettingsWrapper parent)
{
m_parent = parent;
}
/// <summary>
/// The username used to authenticate towards the remote path
/// </summary>
public string Username
{
get { return m_parent.GetItem<string>("Backend:Username", ""); }
set { m_parent.SetItem("Backend:Username", value); }
}
/// <summary>
/// The password used to authenticate towards the remote path
/// </summary>
public string Password
{
get { return m_parent.GetItem<string>("Backend:Password", ""); }
set { m_parent.SetItem("Backend:Password", value); }
}
/// <summary>
/// The path used on the server
/// </summary>
public string Path
{
get { return m_parent.GetItem<string>("Backend:Path", ""); }
set { m_parent.SetItem("Backend:Path", value); }
}
}
/// <summary>
/// Class that represents the settings for a file backend
/// </summary>
public class FileSettings : BackendSettings
{
public FileSettings(WizardSettingsWrapper parent)
: base(parent)
{
}
}
/// <summary>
/// Class that represents the settings for a web based backend
/// </summary>
public class WebSettings : BackendSettings
{
protected int m_defaultPort = 0;
public WebSettings(WizardSettingsWrapper parent)
: base(parent)
{
}
/// <summary>
/// The hostname of the server
/// </summary>
public string Server
{
get { return m_parent.GetItem<string>("WEB:Server", ""); }
set { m_parent.SetItem("WEB:Server", value); }
}
/// <summary>
/// The port used to communicate with the server
/// </summary>
public int Port
{
get { return m_parent.GetItem<int>("WEB:Port", m_defaultPort); }
set { m_parent.SetItem("WEB:Port", value); }
}
}
/// <summary>
/// Class that represents the settings for a ftp backend
/// </summary>
public class FTPSettings : WebSettings
{
public FTPSettings(WizardSettingsWrapper parent)
: base(parent)
{
m_defaultPort = 21;
}
/// <summary>
/// A value indicating if the connection should be passive
/// </summary>
public bool Passive
{
get { return m_parent.GetItem<bool>("FTP:Passive", true); }
set { m_parent.SetItem("FTP:Passive", value); }
}
}
/// <summary>
/// Class that represents the settings for a ssh backend
/// </summary>
public class SSHSettings : WebSettings
{
public SSHSettings(WizardSettingsWrapper parent)
: base(parent)
{
m_defaultPort = 22;
}
/// <summary>
/// A value indicating if the connection is passwordless
/// </summary>
public bool Passwordless
{
get { return m_parent.GetItem<bool>("SSH:Passwordless", false); }
set { m_parent.SetItem("SSH:Passwordless", value); }
}
/// <summary>
/// A value indicating if debug output will be generated
/// </summary>
public bool DebugEnabled
{
get { return m_parent.GetItem<bool>("SSH:DebugEnabled", false); }
set { m_parent.SetItem("SSH:DebugEnabled", value); }
}
}
/// <summary>
/// Class that represents the settings for a ssh backend
/// </summary>
public class S3Settings : BackendSettings
{
public S3Settings(WizardSettingsWrapper parent)
: base(parent)
{
}
/// <summary>
/// A value indicating if the server should be placed in europe
/// </summary>
public bool UseEuroServer
{
get { return m_parent.GetItem<bool>("S3:UseEuroServer", false); }
set { m_parent.SetItem("S3:UseEuroServer", value); }
}
/// <summary>
/// A value indicating if the connection should use subdomain access
/// </summary>
public bool UseSubDomains
{
get { return m_parent.GetItem<bool>("S3:UseSubDomains", false); }
set { m_parent.SetItem("S3:UseSubDomains", value); }
}
}
public class WEBDAVSettings : WebSettings
{
public WEBDAVSettings(WizardSettingsWrapper parent)
: base(parent)
{
m_defaultPort = 80;
}
/// <summary>
/// A value indicating if the connection should use integrated authentication
/// </summary>
public bool IntegratedAuthentication
{
get { return m_parent.GetItem<bool>("WEBDAV:IntegratedAuth", false); }
set { m_parent.SetItem("WEBDAV:IntegratedAuth", value); }
}
/// <summary>
/// A value indicating if the connection should only allow digest authentication
/// </summary>
public bool ForceDigestAuthentication
{
get { return m_parent.GetItem<bool>("WEBDAV:DigestAuth", false); }
set { m_parent.SetItem("WEBDAV:DigestAuth", value); }
}
}
/// <summary>
/// Represents settings that are required to give a consistent view of the file selection UI
@@ -54,9 +54,9 @@
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="Affirma.ThreeSharp, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8bfe994a39631a7b, processorArchitecture=MSIL">
<Reference Include="AWSSDK, Version=1.3.6.0, Culture=neutral, PublicKeyToken=cd2d24cd2bace800, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\thirdparty\Affirma.ThreeSharp\Affirma.ThreeSharp.dll</HintPath>
<HintPath>..\..\..\..\thirdparty\AWS SDK\AWSSDK.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
@@ -97,11 +97,6 @@
<DesignTime>True</DesignTime>
<DependentUpon>S3UI.resx</DependentUpon>
</Compile>
<Compile Include="Strings\S3Wrapper.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>S3Wrapper.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Winforms\Controls\Duplicati.Winforms.Controls.csproj">
@@ -141,11 +136,6 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>S3UI.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Strings\S3Wrapper.resx">
<SubType>Designer</SubType>
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>S3Wrapper.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+148 -140
View File
@@ -30,18 +30,36 @@ namespace Duplicati.Library.Backend
public const string RRS_OPTION = "s3-use-rrs";
public const string EU_BUCKETS_OPTION = "s3-european-buckets";
public const string SUBDOMAIN_OPTION = "s3-use-new-style";
public const string SERVER_NAME = "s3-server-name";
public const string LOCATION_OPTION = "s3-location-constraint";
public static readonly KeyValuePair<string, string>[] KNOWN_S3_PROVIDERS = new KeyValuePair<string,string>[] {
new KeyValuePair<string, string>("Amazon S3", "s3.amazonaws.com"),
new KeyValuePair<string, string>("Hosteurope", "cs.hosteurope.de"),
new KeyValuePair<string, string>("Dunkel", "dcs.dunkel.de"),
};
public static readonly KeyValuePair<string, string>[] KNOWN_S3_LOCATIONS = new KeyValuePair<string, string>[] {
new KeyValuePair<string, string>("(default)", ""),
new KeyValuePair<string, string>("Europe", "eu-west-1"),
new KeyValuePair<string, string>("US East", "us-east-1"),
new KeyValuePair<string, string>("US West", "us-west-1"),
new KeyValuePair<string, string>("AP Southeast", "ap-southeast-1"),
new KeyValuePair<string, string>("AP Northeast", "ap-northeast-1"),
};
private string m_awsID;
private string m_awsKey;
private string m_url;
private string m_host;
private string m_bucket;
private string m_prefix;
private Affirma.ThreeSharp.CallingFormat m_format;
private bool m_euBuckets;
private string m_locationConstraint;
private bool m_useRRS;
private const string S3_HOST = "s3.amazonaws.com";
private readonly System.Text.RegularExpressions.Regex URL_PARSING = new Regex("s3://(?<hostname>[^/]+)(/(?<prefix>.+))?");
public const string DEFAULT_S3_HOST = "s3.amazonaws.com";
public const string S3_EU_REGION_NAME = "eu-west-1";
Dictionary<string, string> m_options;
@@ -49,37 +67,87 @@ namespace Duplicati.Library.Backend
{
}
public S3(string url, Dictionary<string, string> options)
{
Uri u = new Uri(url);
//We need to do custom parsing because we allow non-valid urls
System.Text.RegularExpressions.Match m = URL_PARSING.Match(url);
if (!m.Success)
throw new Exception(string.Format(Strings.S3Backend.UnableToParseURLError, url));
if (!string.IsNullOrEmpty(u.UserInfo))
m_host = m.Groups["hostname"].Value;
m_prefix = m.Groups["prefix"].Value;
if (options.ContainsKey("ftp-username"))
m_awsID = options["ftp-username"];
if (options.ContainsKey("ftp-password"))
m_awsKey = options["ftp-password"];
if (options.ContainsKey("aws_access_key_id"))
m_awsID = options["aws_access_key_id"];
if (options.ContainsKey("aws_secret_access_key"))
m_awsKey = options["aws_secret_access_key"];
string s3host;
options.TryGetValue(SERVER_NAME, out s3host);
if (string.IsNullOrEmpty(s3host))
s3host = DEFAULT_S3_HOST;
bool euBuckets = Utility.Utility.ParseBoolOption(options, EU_BUCKETS_OPTION);
m_useRRS = Utility.Utility.ParseBoolOption(options, RRS_OPTION);
options.TryGetValue(LOCATION_OPTION, out m_locationConstraint);
if (!string.IsNullOrEmpty(m_locationConstraint) && euBuckets)
throw new Exception(string.Format(Strings.S3Backend.OptionsAreMutuallyExclusiveError, LOCATION_OPTION, EU_BUCKETS_OPTION));
if (euBuckets)
m_locationConstraint = S3_EU_REGION_NAME;
//Fallback to previous formats
if (m_host.Contains(DEFAULT_S3_HOST))
{
if (u.UserInfo.IndexOf(":") >= 0)
Uri u = new Uri(url);
m_host = u.Host;
m_prefix = "";
if (m_host.ToLower() == s3host)
{
m_awsID = u.UserInfo.Substring(0, u.UserInfo.IndexOf(":"));
m_awsKey = u.UserInfo.Substring(u.UserInfo.IndexOf(":") + 1);
m_bucket = System.Web.HttpUtility.UrlDecode(u.PathAndQuery);
if (m_bucket.StartsWith("/"))
m_bucket = m_bucket.Substring(1);
if (m_bucket.Contains("/"))
{
m_prefix = m_bucket.Substring(m_bucket.IndexOf("/") + 1);
m_bucket = m_bucket.Substring(0, m_bucket.IndexOf("/"));
}
}
else
{
m_awsID = u.UserInfo;
if (options.ContainsKey("aws_secret_access_key"))
m_awsKey = options["aws_secret_access_key"];
else if (options.ContainsKey("ftp-password"))
m_awsKey = options["ftp-password"];
//Subdomain type lookup
if (m_host.ToLower().EndsWith("." + s3host))
{
m_bucket = m_host.Substring(0, m_host.Length - ("." + s3host).Length);
m_host = s3host;
m_prefix = System.Web.HttpUtility.UrlDecode(u.PathAndQuery);
if (m_prefix.StartsWith("/"))
m_prefix = m_prefix.Substring(1);
}
else
throw new Exception(string.Format(Strings.S3Backend.UnableToDecodeBucketnameError, m_url));
}
try { Console.Error.WriteLine(string.Format(Strings.S3Backend.DeprecatedUrlFormat, "s3://" + m_bucket + "/" + m_prefix)); }
catch { }
}
else
{
if (options.ContainsKey("ftp-username"))
m_awsID = options["ftp-username"];
if (options.ContainsKey("ftp-password"))
m_awsKey = options["ftp-password"];
if (options.ContainsKey("aws_access_key_id"))
m_awsID = options["aws_access_key_id"];
if (options.ContainsKey("aws_secret_access_key"))
m_awsKey = options["aws_secret_access_key"];
//The new simplified url style s3://bucket/prefix
m_bucket = m_host;
m_host = s3host;
}
if (string.IsNullOrEmpty(m_awsID))
@@ -87,68 +155,6 @@ namespace Duplicati.Library.Backend
if (string.IsNullOrEmpty(m_awsKey))
throw new Exception(Strings.S3Backend.NoAMZKeyError);
m_prefix = "";
m_host = u.Host;
bool use_subdomain = options.ContainsKey(SUBDOMAIN_OPTION);
m_euBuckets = options.ContainsKey(EU_BUCKETS_OPTION);
m_format = (use_subdomain || m_euBuckets) ? Affirma.ThreeSharp.CallingFormat.SUBDOMAIN : Affirma.ThreeSharp.CallingFormat.REGULAR;
m_useRRS = options.ContainsKey(RRS_OPTION);
if (m_host.ToLower() == S3_HOST)
{
m_bucket = System.Web.HttpUtility.UrlDecode(u.PathAndQuery);
if (m_bucket.StartsWith("/"))
m_bucket = m_bucket.Substring(1);
if (m_bucket.Contains("/"))
{
m_prefix = m_bucket.Substring(m_bucket.IndexOf("/") + 1);
m_bucket = m_bucket.Substring(0, m_bucket.IndexOf("/"));
}
}
else
{
//If it is vanity style, do a CNAME lookup
if (!m_host.ToLower().EndsWith("." + S3_HOST))
{
System.Net.IPHostEntry ent = System.Net.Dns.GetHostEntry(m_host);
foreach (string s in ent.Aliases)
if (s.EndsWith("." + S3_HOST, StringComparison.InvariantCultureIgnoreCase))
{
m_host = s;
break;
}
}
//Subdomain type lookup
if (m_host.ToLower().EndsWith("." + S3_HOST))
{
m_format = Affirma.ThreeSharp.CallingFormat.SUBDOMAIN;
m_bucket = m_host.Substring(0, m_host.Length - ("." + S3_HOST).Length);
m_host = S3_HOST;
m_prefix = System.Web.HttpUtility.UrlDecode(u.PathAndQuery);
use_subdomain = true;
if (m_prefix.StartsWith("/"))
m_prefix = m_prefix.Substring(1);
}
else
throw new Exception(string.Format(Strings.S3Backend.UnableToDecodeBucketnameError, m_url));
}
if (!IsValidHostname(m_bucket))
{
if (use_subdomain || m_euBuckets)
throw new Exception(string.Format(Strings.S3Backend.InvalidHostnameForSubdomainError, m_bucket, SUBDOMAIN_OPTION, EU_BUCKETS_OPTION));
else //Not specified, just switch
m_format = Affirma.ThreeSharp.CallingFormat.REGULAR;
}
m_options = options;
m_url = url;
m_prefix = m_prefix.Trim();
@@ -158,7 +164,10 @@ namespace Duplicati.Library.Backend
public static bool IsValidHostname(string bucketname)
{
return !new System.Text.RegularExpressions.Regex(@"[^\w\-]").Match(bucketname).Success;
if (string.IsNullOrEmpty(bucketname))
return false;
else
return Amazon.S3.Util.AmazonS3Util.ValidateV2Bucket(bucketname);
}
#region IBackend Members
@@ -182,32 +191,26 @@ namespace Duplicati.Library.Backend
{
try
{
S3Wrapper con = CreateRequest();
List<IFileEntry> lst = con.ListBucket(m_bucket, m_prefix);
for (int i = 0; i < lst.Count; i++)
using (S3Wrapper con = CreateRequest())
{
((FileEntry)lst[i]).Name = lst[i].Name.Substring(m_prefix.Length);
List<IFileEntry> lst = con.ListBucket(m_bucket, m_prefix);
for (int i = 0; i < lst.Count; i++)
{
((FileEntry)lst[i]).Name = lst[i].Name.Substring(m_prefix.Length);
//Fix for a bug in Duplicati 1.0 beta 3 and earlier, where filenames are incorrectly prefixed with a slash
if (lst[i].Name.StartsWith("/") && !m_prefix.StartsWith("/"))
((FileEntry)lst[i]).Name = lst[i].Name.Substring(1);
//Fix for a bug in Duplicati 1.0 beta 3 and earlier, where filenames are incorrectly prefixed with a slash
if (lst[i].Name.StartsWith("/") && !m_prefix.StartsWith("/"))
((FileEntry)lst[i]).Name = lst[i].Name.Substring(1);
}
return lst;
}
return lst;
}
catch (Exception ex)
{
System.Net.WebException wex = ex as System.Net.WebException;
Affirma.ThreeSharp.ThreeSharpException tex = ex as Affirma.ThreeSharp.ThreeSharpException;
if (wex == null && tex != null)
wex = tex.InnerException as System.Net.WebException;
//Catch "non-existing" buckets
if (wex != null && wex.Status == System.Net.WebExceptionStatus.ProtocolError && wex.Response is System.Net.HttpWebResponse && ((System.Net.HttpWebResponse)wex.Response).StatusCode == System.Net.HttpStatusCode.NotFound)
throw new Interface.FolderMissingException(wex);
if (tex != null && tex.StatusCode == System.Net.HttpStatusCode.NotFound)
throw new Interface.FolderMissingException(tex);
Amazon.S3.AmazonS3Exception s3ex = ex as Amazon.S3.AmazonS3Exception;
if (s3ex != null && (s3ex.StatusCode == System.Net.HttpStatusCode.NotFound || "NoSuchBucket".Equals(s3ex.ErrorCode)))
throw new Interface.FolderMissingException(ex);
throw;
}
@@ -221,25 +224,19 @@ namespace Duplicati.Library.Backend
public void Put(string remotename, System.IO.Stream input)
{
S3Wrapper con = CreateRequest();
try
{
con.AddFileStream(m_bucket, GetFullKey(remotename), input);
using (S3Wrapper con = CreateRequest())
con.AddFileStream(m_bucket, GetFullKey(remotename), input);
}
catch (Exception ex)
{
bool isBucketMissingError = false;
System.Net.WebException wex = ex as System.Net.WebException;
Affirma.ThreeSharp.ThreeSharpException tex = ex as Affirma.ThreeSharp.ThreeSharpException;
if (wex == null && tex != null)
wex = tex.InnerException as System.Net.WebException;
if (wex != null && wex.Status == System.Net.WebExceptionStatus.ProtocolError && wex.Response is System.Net.HttpWebResponse && ((System.Net.HttpWebResponse)wex.Response).StatusCode == System.Net.HttpStatusCode.NotFound)
throw new Interface.FolderMissingException(wex);
if (!isBucketMissingError && tex != null && tex.StatusCode == System.Net.HttpStatusCode.NotFound)
throw new Interface.FolderMissingException(wex);
//Catch "non-existing" buckets
Amazon.S3.AmazonS3Exception s3ex = ex as Amazon.S3.AmazonS3Exception;
if (s3ex != null && (s3ex.StatusCode == System.Net.HttpStatusCode.NotFound || "NoSuchBucket".Equals(s3ex.ErrorCode)))
throw new Interface.FolderMissingException(ex);
throw;
throw;
}
}
@@ -251,46 +248,57 @@ namespace Duplicati.Library.Backend
public void Get(string remotename, System.IO.Stream output)
{
S3Wrapper con = CreateRequest();
try
using (S3Wrapper con = CreateRequest())
{
con.GetFileStream(m_bucket, GetFullKey(remotename), output);
}
catch
{
//This is a fix for the S3 backend prior to beta 3, where the filenames had a slash suffix
bool fallbackFix = false;
try
{
if (!remotename.StartsWith("/"))
con.GetFileStream(m_bucket, GetFullKey("/" + remotename), output);
fallbackFix = true;
con.GetFileStream(m_bucket, GetFullKey(remotename), output);
}
catch
{
}
//This is a fix for the S3 backend prior to beta 3, where the filenames had a slash prefixed
try
{
if (!remotename.StartsWith("/"))
con.GetFileStream(m_bucket, GetFullKey("/" + remotename), output);
return;
}
catch
{
}
if (!fallbackFix)
//Throw original error
throw;
}
}
}
public void Delete(string remotename)
{
S3Wrapper con = CreateRequest();
con.DeleteObject(m_bucket, GetFullKey(remotename));
using(S3Wrapper con = CreateRequest())
con.DeleteObject(m_bucket, GetFullKey(remotename));
}
public IList<ICommandLineArgument> SupportedCommands
{
get
{
StringBuilder hostnames = new StringBuilder();
StringBuilder locations = new StringBuilder();
foreach(KeyValuePair<string, string> s in KNOWN_S3_PROVIDERS)
hostnames.AppendLine(string.Format("{0}: {1}", s.Key, s.Value));
foreach (KeyValuePair<string, string> s in KNOWN_S3_LOCATIONS)
locations.AppendLine(string.Format("{0}: {1}", s.Key, s.Value));
return new List<ICommandLineArgument>(new ICommandLineArgument[] {
new CommandLineArgument("aws_secret_access_key", CommandLineArgument.ArgumentType.Path, Strings.S3Backend.AMZKeyDescriptionShort, Strings.S3Backend.AMZKeyDescriptionLong, null, new string[] {"ftp-password"}, null),
new CommandLineArgument("aws_access_key_id", CommandLineArgument.ArgumentType.Path, Strings.S3Backend.AMZUserIDDescriptionShort, Strings.S3Backend.AMZUserIDDescriptionLong, null, new string[] {"ftp-username"}, null),
new CommandLineArgument(SUBDOMAIN_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.S3Backend.S3NewStyleDescriptionShort, Strings.S3Backend.S3NewStyleDescriptionLong, "true"),
new CommandLineArgument(EU_BUCKETS_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.S3Backend.S3EurobucketDescriptionShort, Strings.S3Backend.S3EurobucketDescriptionLong, "false"),
new CommandLineArgument(SUBDOMAIN_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.S3Backend.S3NewStyleDescriptionShort, Strings.S3Backend.S3NewStyleDescriptionLong, "true", null, null, Strings.S3Backend.S3NewStyleDeprecation),
new CommandLineArgument(EU_BUCKETS_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.S3Backend.S3EurobucketDescriptionShort, Strings.S3Backend.S3EurobucketDescriptionLong, "false", null, null, string.Format(Strings.S3Backend.S3EurobucketDeprecationDescription, LOCATION_OPTION, S3_EU_REGION_NAME)),
new CommandLineArgument(RRS_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.S3Backend.S3UseRRSDescriptionShort, Strings.S3Backend.S3UseRRSDescriptionLong, "false"),
new CommandLineArgument(SERVER_NAME, CommandLineArgument.ArgumentType.String, Strings.S3Backend.S3ServerNameDescriptionShort, string.Format(Strings.S3Backend.S3ServerNameDescriptionLong, hostnames.ToString()), DEFAULT_S3_HOST),
new CommandLineArgument(LOCATION_OPTION, CommandLineArgument.ArgumentType.String, Strings.S3Backend.S3LocationDescriptionShort, string.Format(Strings.S3Backend.S3LocationDescriptionLong, locations.ToString())),
new CommandLineArgument("ftp-password", CommandLineArgument.ArgumentType.String, Strings.S3Backend.FTPPasswordDescriptionShort, Strings.S3Backend.FTPPasswordDescriptionLong),
new CommandLineArgument("ftp-username", CommandLineArgument.ArgumentType.String, Strings.S3Backend.DescriptionFTPUsernameShort, Strings.S3Backend.DescriptionFTPUsernameLong)
});
@@ -302,7 +310,7 @@ namespace Duplicati.Library.Backend
{
get
{
return Strings.S3Backend.Description;
return Strings.S3Backend.Description_v2;
}
}
@@ -317,9 +325,9 @@ namespace Duplicati.Library.Backend
public void CreateFolder()
{
S3Wrapper con = CreateRequest();
//S3 does not complain if the bucket already exists
con.AddBucket(m_bucket);
using (S3Wrapper con = CreateRequest())
con.AddBucket(m_bucket);
}
#endregion
@@ -340,13 +348,13 @@ namespace Duplicati.Library.Backend
private S3Wrapper CreateRequest()
{
return new S3Wrapper(m_awsID, m_awsKey, m_format, m_euBuckets, m_useRRS);
return new S3Wrapper(m_awsID, m_awsKey, m_locationConstraint, m_host, m_useRRS);
}
private string GetFullKey(string name)
{
//Url encode special chars, but keep slashes
return System.Web.HttpUtility.UrlEncode(m_prefix + name).Replace("%2f", "/");
//AWS SDK encodes the filenames correctly
return m_prefix + name;
}
+41 -14
View File
@@ -34,9 +34,12 @@
this.RemoveAllButton = new System.Windows.Forms.Button();
this.CredentialList = new System.Windows.Forms.ListBox();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.UseEUBuckets = new System.Windows.Forms.CheckBox();
this.AllowCredentialStorage = new System.Windows.Forms.CheckBox();
this.DefaultServername = new System.Windows.Forms.ComboBox();
this.label2 = new System.Windows.Forms.Label();
this.DefaultBucketRegion = new System.Windows.Forms.ComboBox();
this.label1 = new System.Windows.Forms.Label();
this.UseRRS = new System.Windows.Forms.CheckBox();
this.AllowCredentialStorage = new System.Windows.Forms.CheckBox();
this.CredentialGroup.SuspendLayout();
this.groupBox2.SuspendLayout();
this.SuspendLayout();
@@ -74,16 +77,43 @@
// groupBox2
//
resources.ApplyResources(this.groupBox2, "groupBox2");
this.groupBox2.Controls.Add(this.DefaultServername);
this.groupBox2.Controls.Add(this.label2);
this.groupBox2.Controls.Add(this.DefaultBucketRegion);
this.groupBox2.Controls.Add(this.label1);
this.groupBox2.Controls.Add(this.UseRRS);
this.groupBox2.Controls.Add(this.UseEUBuckets);
this.groupBox2.Name = "groupBox2";
this.groupBox2.TabStop = false;
//
// UseEUBuckets
// DefaultServername
//
resources.ApplyResources(this.UseEUBuckets, "UseEUBuckets");
this.UseEUBuckets.Name = "UseEUBuckets";
this.UseEUBuckets.UseVisualStyleBackColor = true;
resources.ApplyResources(this.DefaultServername, "DefaultServername");
this.DefaultServername.FormattingEnabled = true;
this.DefaultServername.Name = "DefaultServername";
this.DefaultServername.SelectedIndexChanged += new System.EventHandler(this.DefaultServername_SelectedIndexChanged);
//
// label2
//
resources.ApplyResources(this.label2, "label2");
this.label2.Name = "label2";
//
// DefaultBucketRegion
//
resources.ApplyResources(this.DefaultBucketRegion, "DefaultBucketRegion");
this.DefaultBucketRegion.FormattingEnabled = true;
this.DefaultBucketRegion.Name = "DefaultBucketRegion";
this.DefaultBucketRegion.SelectedIndexChanged += new System.EventHandler(this.DefaultBucketRegion_SelectedIndexChanged);
//
// label1
//
resources.ApplyResources(this.label1, "label1");
this.label1.Name = "label1";
//
// UseRRS
//
resources.ApplyResources(this.UseRRS, "UseRRS");
this.UseRRS.Name = "UseRRS";
this.UseRRS.UseVisualStyleBackColor = true;
//
// AllowCredentialStorage
//
@@ -94,12 +124,6 @@
this.AllowCredentialStorage.UseVisualStyleBackColor = true;
this.AllowCredentialStorage.CheckedChanged += new System.EventHandler(this.AllowCredentialStorage_CheckedChanged);
//
// UseRRS
//
resources.ApplyResources(this.UseRRS, "UseRRS");
this.UseRRS.Name = "UseRRS";
this.UseRRS.UseVisualStyleBackColor = true;
//
// S3CommonOptions
//
resources.ApplyResources(this, "$this");
@@ -124,8 +148,11 @@
private System.Windows.Forms.Button RemoveAllButton;
private System.Windows.Forms.ListBox CredentialList;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.CheckBox UseEUBuckets;
private System.Windows.Forms.CheckBox AllowCredentialStorage;
private System.Windows.Forms.CheckBox UseRRS;
private System.Windows.Forms.ComboBox DefaultBucketRegion;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.ComboBox DefaultServername;
private System.Windows.Forms.Label label2;
}
}
@@ -33,6 +33,8 @@ namespace Duplicati.Library.Backend
private const string SAVED_CREDENTIALS = "S3: Available Accounts";
private const string DEFAULT_EU_BUCKET = "S3: Default Use Euro";
private const string DEFAULT_RRS = "S3: Default Use RRS";
private const string DEFAULT_BUCKET_REGION = "S3: Default Bucket Region";
private const string DEFAULT_SERVERNAME = "S3: Default Servername";
private const string XML_ROOT = "root";
private const string XML_ACCOUNT = "account";
@@ -51,13 +53,25 @@ namespace Duplicati.Library.Backend
{
m_settings = options;
UseEUBuckets.Checked = ExtractDefaultEUBuckets(m_settings);
string region = ExtractDefaultBucketRegion(m_settings);
string server = ExtractDefaultServername(m_settings);
AllowCredentialStorage.Checked = ExtractAllowCredentialStorage(m_settings);
UseRRS.Checked = ExtractDefaultRRS(m_settings);
CredentialList.Items.Clear();
foreach(KeyValuePair<string, string> k in ExtractAccounts(m_settings))
CredentialList.Items.Add(new Utility.ComboBoxItemPair<string>(k.Key, k.Value));
DefaultServername.Items.Clear();
DefaultBucketRegion.Items.Clear();
foreach (KeyValuePair<string, string> s in S3.KNOWN_S3_LOCATIONS)
DefaultBucketRegion.Items.Add(new Utility.ComboBoxItemPair<string>(string.Format("{0} ({1})", s.Key, string.IsNullOrEmpty(s.Value) ? "-none-" : s.Value), s.Value));
foreach (KeyValuePair<string, string> s in S3.KNOWN_S3_PROVIDERS)
DefaultServername.Items.Add(new Utility.ComboBoxItemPair<string>(string.Format("{0} ({1})", s.Key, s.Value), s.Value));
DefaultServername.Text = server;
DefaultBucketRegion.Text = region;
}
private void RemoveAllButton_Click(object sender, EventArgs e)
@@ -88,7 +102,16 @@ namespace Duplicati.Library.Backend
if (!validate)
{
m_settings[ALLOW_SAVED_CREDENTIALS] = AllowCredentialStorage.Checked.ToString();
m_settings[DEFAULT_EU_BUCKET] = UseEUBuckets.Checked.ToString();
if (DefaultServername.SelectedItem as Utility.ComboBoxItemPair<string> == null)
m_settings[DEFAULT_SERVERNAME] = DefaultServername.Text;
else
m_settings[DEFAULT_SERVERNAME] = (DefaultServername.SelectedItem as Utility.ComboBoxItemPair<string>).Value;
if (DefaultBucketRegion.SelectedItem as Utility.ComboBoxItemPair<string> == null)
m_settings[DEFAULT_BUCKET_REGION] = DefaultBucketRegion.Text;
else
m_settings[DEFAULT_BUCKET_REGION] = (DefaultBucketRegion.SelectedItem as Utility.ComboBoxItemPair<string>).Value;
m_settings[DEFAULT_RRS] = UseRRS.Checked.ToString();
Dictionary<string, string> tmp = new Dictionary<string, string>();
@@ -106,6 +129,14 @@ namespace Duplicati.Library.Backend
if (guiOptions.ContainsKey(SAVED_CREDENTIALS))
applicationSettings[SAVED_CREDENTIALS] = guiOptions[SAVED_CREDENTIALS];
if (guiOptions.ContainsKey(DEFAULT_BUCKET_REGION))
applicationSettings[DEFAULT_BUCKET_REGION] = guiOptions[DEFAULT_BUCKET_REGION];
else if (guiOptions.ContainsKey(DEFAULT_EU_BUCKET))
applicationSettings[DEFAULT_EU_BUCKET] = guiOptions[DEFAULT_EU_BUCKET];
if (guiOptions.ContainsKey(DEFAULT_SERVERNAME))
applicationSettings[DEFAULT_SERVERNAME] = guiOptions[DEFAULT_SERVERNAME];
if (guiOptions.ContainsKey(DEFAULT_EU_BUCKET))
applicationSettings[DEFAULT_EU_BUCKET] = guiOptions[DEFAULT_EU_BUCKET];
@@ -178,11 +209,12 @@ namespace Duplicati.Library.Backend
}
/// <summary>
/// Extracts a boolean from <paramref name="options"/> that indicates if the user wants the EU buckets option on when creating a new backup
/// Extracts a boolean from <paramref name="options"/> that indicates if the user wants the EU buckets option on when creating a new backup.
/// DEPRECATED
/// </summary>
/// <param name="options">The list of options</param>
/// <returns>True if the user wants the EU buckets option on when creating a new backup, false otherwise</returns>
public static bool ExtractDefaultEUBuckets(IDictionary<string, string> options)
private static bool Old_ExtractDefaultEUBuckets(IDictionary<string, string> options)
{
string keyv;
@@ -190,6 +222,35 @@ namespace Duplicati.Library.Backend
return Utility.Utility.ParseBool(keyv, false);
}
/// <summary>
/// Extracts a string from <paramref name="options"/> that indicates the default region to use for creating buckets.
/// </summary>
/// <param name="options">The list of options</param>
/// <returns>Null if the default bucket location should be used, otherwise the bucket location identifier</returns>
public static string ExtractDefaultBucketRegion(IDictionary<string, string> options)
{
string keyv;
if (options.TryGetValue(DEFAULT_BUCKET_REGION, out keyv))
return keyv;
else
return Old_ExtractDefaultEUBuckets(options) ? S3.S3_EU_REGION_NAME : null;
}
/// <summary>
/// Extracts a string from <paramref name="options"/> that indicates the default server to use.
/// </summary>
/// <param name="options">The list of options</param>
/// <returns>Null if the default server should be used, otherwise the server name identifier</returns>
public static string ExtractDefaultServername(IDictionary<string, string> options)
{
string keyv;
options.TryGetValue(DEFAULT_SERVERNAME, out keyv);
if (string.IsNullOrEmpty(keyv))
return null;
else
return keyv;
}
/// <summary>
/// Extracts a boolean from <paramref name="options"/> that indicates if the user wants the RRS option on when creating a new backup
/// </summary>
@@ -204,5 +265,23 @@ namespace Duplicati.Library.Backend
}
#endregion
private delegate void SetComboTextDelegate(ComboBox el, string text);
private void SetComboText(ComboBox el, string text)
{
el.Text = text;
}
private void DefaultServername_SelectedIndexChanged(object sender, EventArgs e)
{
if (DefaultServername.SelectedItem as Utility.ComboBoxItemPair<string> != null)
BeginInvoke(new SetComboTextDelegate(SetComboText), DefaultServername, (DefaultServername.SelectedItem as Utility.ComboBoxItemPair<string>).Value);
}
private void DefaultBucketRegion_SelectedIndexChanged(object sender, EventArgs e)
{
if (DefaultBucketRegion.SelectedItem as Utility.ComboBoxItemPair<string> != null)
BeginInvoke(new SetComboTextDelegate(SetComboText), DefaultBucketRegion, (DefaultBucketRegion.SelectedItem as Utility.ComboBoxItemPair<string>).Value);
}
}
}
+113 -35
View File
@@ -130,7 +130,7 @@
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="RemoveSelectedButton.Location" type="System.Drawing.Point, System.Drawing">
<value>54, 104</value>
<value>54, 107</value>
</data>
<data name="RemoveSelectedButton.Size" type="System.Drawing.Size, System.Drawing">
<value>99, 23</value>
@@ -157,7 +157,7 @@
<value>Bottom, Right</value>
</data>
<data name="RemoveAllButton.Location" type="System.Drawing.Point, System.Drawing">
<value>158, 104</value>
<value>158, 107</value>
</data>
<data name="RemoveAllButton.Size" type="System.Drawing.Size, System.Drawing">
<value>75, 23</value>
@@ -208,7 +208,7 @@
<value>8, 32</value>
</data>
<data name="CredentialGroup.Size" type="System.Drawing.Size, System.Drawing">
<value>246, 136</value>
<value>246, 139</value>
</data>
<data name="CredentialGroup.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
@@ -231,6 +231,111 @@
<data name="groupBox2.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Bottom, Left, Right</value>
</data>
<data name="DefaultServername.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Left, Right</value>
</data>
<data name="DefaultServername.Location" type="System.Drawing.Point, System.Drawing">
<value>104, 24</value>
</data>
<data name="DefaultServername.Size" type="System.Drawing.Size, System.Drawing">
<value>120, 21</value>
</data>
<data name="DefaultServername.TabIndex" type="System.Int32, mscorlib">
<value>5</value>
</data>
<data name="&gt;&gt;DefaultServername.Name" xml:space="preserve">
<value>DefaultServername</value>
</data>
<data name="&gt;&gt;DefaultServername.Type" xml:space="preserve">
<value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;DefaultServername.Parent" xml:space="preserve">
<value>groupBox2</value>
</data>
<data name="&gt;&gt;DefaultServername.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="label2.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="label2.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="label2.Location" type="System.Drawing.Point, System.Drawing">
<value>16, 24</value>
</data>
<data name="label2.Size" type="System.Drawing.Size, System.Drawing">
<value>64, 13</value>
</data>
<data name="label2.TabIndex" type="System.Int32, mscorlib">
<value>4</value>
</data>
<data name="label2.Text" xml:space="preserve">
<value>Servername</value>
</data>
<data name="&gt;&gt;label2.Name" xml:space="preserve">
<value>label2</value>
</data>
<data name="&gt;&gt;label2.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;label2.Parent" xml:space="preserve">
<value>groupBox2</value>
</data>
<data name="&gt;&gt;label2.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="DefaultBucketRegion.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Left, Right</value>
</data>
<data name="DefaultBucketRegion.Location" type="System.Drawing.Point, System.Drawing">
<value>104, 48</value>
</data>
<data name="DefaultBucketRegion.Size" type="System.Drawing.Size, System.Drawing">
<value>120, 21</value>
</data>
<data name="DefaultBucketRegion.TabIndex" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="&gt;&gt;DefaultBucketRegion.Name" xml:space="preserve">
<value>DefaultBucketRegion</value>
</data>
<data name="&gt;&gt;DefaultBucketRegion.Type" xml:space="preserve">
<value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;DefaultBucketRegion.Parent" xml:space="preserve">
<value>groupBox2</value>
</data>
<data name="&gt;&gt;DefaultBucketRegion.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="label1.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="label1.Location" type="System.Drawing.Point, System.Drawing">
<value>16, 48</value>
</data>
<data name="label1.Size" type="System.Drawing.Size, System.Drawing">
<value>73, 13</value>
</data>
<data name="label1.TabIndex" type="System.Int32, mscorlib">
<value>2</value>
</data>
<data name="label1.Text" xml:space="preserve">
<value>Bucket region</value>
</data>
<data name="&gt;&gt;label1.Name" xml:space="preserve">
<value>label1</value>
</data>
<data name="&gt;&gt;label1.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;label1.Parent" xml:space="preserve">
<value>groupBox2</value>
</data>
<data name="&gt;&gt;label1.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="UseRRS.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
@@ -238,7 +343,7 @@
<value>NoControl</value>
</data>
<data name="UseRRS.Location" type="System.Drawing.Point, System.Drawing">
<value>16, 48</value>
<value>16, 72</value>
</data>
<data name="UseRRS.Size" type="System.Drawing.Size, System.Drawing">
<value>196, 17</value>
@@ -259,40 +364,13 @@
<value>groupBox2</value>
</data>
<data name="&gt;&gt;UseRRS.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="UseEUBuckets.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="UseEUBuckets.Location" type="System.Drawing.Point, System.Drawing">
<value>16, 24</value>
</data>
<data name="UseEUBuckets.Size" type="System.Drawing.Size, System.Drawing">
<value>135, 17</value>
</data>
<data name="UseEUBuckets.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="UseEUBuckets.Text" xml:space="preserve">
<value>Use European buckets</value>
</data>
<data name="&gt;&gt;UseEUBuckets.Name" xml:space="preserve">
<value>UseEUBuckets</value>
</data>
<data name="&gt;&gt;UseEUBuckets.Type" xml:space="preserve">
<value>System.Windows.Forms.CheckBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;UseEUBuckets.Parent" xml:space="preserve">
<value>groupBox2</value>
</data>
<data name="&gt;&gt;UseEUBuckets.ZOrder" xml:space="preserve">
<value>1</value>
<value>4</value>
</data>
<data name="groupBox2.Location" type="System.Drawing.Point, System.Drawing">
<value>8, 176</value>
<value>8, 179</value>
</data>
<data name="groupBox2.Size" type="System.Drawing.Size, System.Drawing">
<value>246, 73</value>
<value>246, 100</value>
</data>
<data name="groupBox2.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
@@ -352,7 +430,7 @@
<value>True</value>
</data>
<data name="$this.Size" type="System.Drawing.Size, System.Drawing">
<value>265, 257</value>
<value>265, 284</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>S3CommonOptions</value>
+41 -10
View File
@@ -36,12 +36,15 @@ namespace Duplicati.Library.Backend
this.BucketName = new System.Windows.Forms.TextBox();
this.SignUpLink = new System.Windows.Forms.LinkLabel();
this.label4 = new System.Windows.Forms.Label();
this.UseEuroBuckets = new System.Windows.Forms.CheckBox();
this.TestConnection = new System.Windows.Forms.Button();
this.CreateBucket = new System.Windows.Forms.Button();
this.AWS_ID = new System.Windows.Forms.ComboBox();
this.UseRRS = new System.Windows.Forms.CheckBox();
this.label5 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
this.Bucketregions = new System.Windows.Forms.ComboBox();
this.Servernames = new System.Windows.Forms.ComboBox();
this.label7 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
@@ -62,6 +65,7 @@ namespace Duplicati.Library.Backend
// AWS_KEY
//
this.AWS_KEY.AskToEnterNewPassword = false;
this.AWS_KEY.InitialPassword = null;
this.AWS_KEY.IsPasswordVisible = false;
resources.ApplyResources(this.AWS_KEY, "AWS_KEY");
this.AWS_KEY.MaximumSize = new System.Drawing.Size(5000, 20);
@@ -87,13 +91,6 @@ namespace Duplicati.Library.Backend
resources.ApplyResources(this.label4, "label4");
this.label4.Name = "label4";
//
// UseEuroBuckets
//
resources.ApplyResources(this.UseEuroBuckets, "UseEuroBuckets");
this.UseEuroBuckets.Name = "UseEuroBuckets";
this.UseEuroBuckets.UseVisualStyleBackColor = true;
this.UseEuroBuckets.CheckedChanged += new System.EventHandler(this.UseEuroBuckets_CheckedChanged);
//
// TestConnection
//
resources.ApplyResources(this.TestConnection, "TestConnection");
@@ -127,16 +124,47 @@ namespace Duplicati.Library.Backend
resources.ApplyResources(this.label5, "label5");
this.label5.Name = "label5";
//
// label6
//
resources.ApplyResources(this.label6, "label6");
this.label6.Name = "label6";
//
// Bucketregions
//
this.Bucketregions.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend;
this.Bucketregions.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;
this.Bucketregions.FormattingEnabled = true;
resources.ApplyResources(this.Bucketregions, "Bucketregions");
this.Bucketregions.Name = "Bucketregions";
this.Bucketregions.SelectedIndexChanged += new System.EventHandler(this.Bucketregions_SelectedIndexChanged);
//
// Servernames
//
this.Servernames.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend;
this.Servernames.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;
this.Servernames.FormattingEnabled = true;
resources.ApplyResources(this.Servernames, "Servernames");
this.Servernames.Name = "Servernames";
this.Servernames.SelectedIndexChanged += new System.EventHandler(this.Servernames_SelectedIndexChanged);
//
// label7
//
resources.ApplyResources(this.label7, "label7");
this.label7.Name = "label7";
//
// S3UI
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.Servernames);
this.Controls.Add(this.label7);
this.Controls.Add(this.Bucketregions);
this.Controls.Add(this.label6);
this.Controls.Add(this.label5);
this.Controls.Add(this.UseRRS);
this.Controls.Add(this.AWS_ID);
this.Controls.Add(this.CreateBucket);
this.Controls.Add(this.TestConnection);
this.Controls.Add(this.UseEuroBuckets);
this.Controls.Add(this.label4);
this.Controls.Add(this.SignUpLink);
this.Controls.Add(this.BucketName);
@@ -160,11 +188,14 @@ namespace Duplicati.Library.Backend
private System.Windows.Forms.TextBox BucketName;
private System.Windows.Forms.LinkLabel SignUpLink;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.CheckBox UseEuroBuckets;
private System.Windows.Forms.Button TestConnection;
private System.Windows.Forms.Button CreateBucket;
private System.Windows.Forms.ComboBox AWS_ID;
private System.Windows.Forms.CheckBox UseRRS;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.ComboBox Bucketregions;
private System.Windows.Forms.ComboBox Servernames;
private System.Windows.Forms.Label label7;
}
}
+99 -36
View File
@@ -34,8 +34,8 @@ namespace Duplicati.Library.Backend
private const string BUCKET_NAME = "Bucketname";
private const string EUROBUCKET = "UseEuroBucket";
private const string RRS = "UseRRS";
private const string SUBDOMAIN = "UseSubdomainStrategy";
private const string SERVER_URL = "ServerUrl";
private const string SERVER_HOSTNAME = "ServerHostname";
private const string BUCKET_LOCATION = "BucketRegion";
private const string PREFIX = "Prefix";
private const string HASTESTED = "UI: HasTested";
private const string HASCREATEDBUCKET = "UI: Has created bucket";
@@ -44,10 +44,21 @@ namespace Duplicati.Library.Backend
private const string HASWARNEDINVALIDBUCKETNAME = "UI: Has warned invalid bucket name";
private const string INITIALPASSWORD = "UI: Temp password";
private const string S3_PATH = "s3.amazonaws.com";
private const string SIGNUP_PAGE_AWS = "http://aws.amazon.com/s3";
private const string SIGNUP_PAGE_HOSTEUROPE = "http://www.hosteurope.de/produkt/Cloud-Storage";
private const string SIGNUP_PAGE_DUNKEL = "http://dunkel.de/s3/";
private static string GetSignupLink(string servername)
{
if (!string.IsNullOrEmpty(servername))
{
if (servername.Equals("cs.hosteurope.de")) return SIGNUP_PAGE_HOSTEUROPE;
if (servername.Equals("dcs.dunkel.de")) return SIGNUP_PAGE_DUNKEL;
}
return SIGNUP_PAGE_AWS;
}
private const string LOGIN_PAGE = "http://aws.amazon.com/s3";
private IDictionary<string, string> m_options;
private IDictionary<string, string> m_applicationSettings;
@@ -122,8 +133,18 @@ namespace Duplicati.Library.Backend
m_options[ACCESS_ID] = AWS_ID.Text;
m_options[ACCESS_KEY] = AWS_KEY.Text;
m_options[BUCKET_NAME] = BucketName.Text;
m_options[EUROBUCKET] = UseEuroBuckets.Checked.ToString();
m_options[RRS] = UseRRS.Checked.ToString();
if (Servernames.SelectedItem as Utility.ComboBoxItemPair<string> == null)
m_options[SERVER_HOSTNAME] = Servernames.Text;
else
m_options[SERVER_HOSTNAME] = (Servernames.SelectedItem as Utility.ComboBoxItemPair<string>).Value;
if (Bucketregions.SelectedItem as Utility.ComboBoxItemPair<string> == null)
m_options[BUCKET_LOCATION] = Bucketregions.Text;
else
m_options[BUCKET_LOCATION] = (Bucketregions.SelectedItem as Utility.ComboBoxItemPair<string>).Value;
string bucketname = BucketName.Text;
int ix = bucketname.IndexOf("/");
if (ix > 0)
@@ -138,7 +159,6 @@ namespace Duplicati.Library.Backend
}
bucketname = m_options[BUCKET_NAME];
m_options[SUBDOMAIN] = (bucketname.ToLower() == bucketname && S3.IsValidHostname(bucketname)).ToString();
if (hasInitial)
m_options[INITIALPASSWORD] = initialPwd;
@@ -150,6 +170,15 @@ namespace Duplicati.Library.Backend
foreach (KeyValuePair<string, string> x in S3CommonOptions.ExtractAccounts(m_applicationSettings))
AWS_ID.Items.Add(new Utility.ComboBoxItemPair<string>(x.Key, x.Value));
Bucketregions.Items.Clear();
Servernames.Items.Clear();
foreach (KeyValuePair<string, string> s in S3.KNOWN_S3_LOCATIONS)
Bucketregions.Items.Add(new Utility.ComboBoxItemPair<string>(string.Format("{0} ({1})", s.Key, string.IsNullOrEmpty(s.Value) ? "-none-" : s.Value), s.Value));
foreach (KeyValuePair<string, string> s in S3.KNOWN_S3_PROVIDERS)
Servernames.Items.Add(new Utility.ComboBoxItemPair<string>(string.Format("{0} ({1})", s.Key, s.Value), s.Value));
if (m_options.ContainsKey(ACCESS_ID))
AWS_ID.Text = m_options[ACCESS_ID];
if (m_options.ContainsKey(ACCESS_KEY))
@@ -164,19 +193,35 @@ namespace Duplicati.Library.Backend
AWS_KEY.AskToEnterNewPassword = !string.IsNullOrEmpty(m_options[INITIALPASSWORD]);
AWS_KEY.InitialPassword = m_options[INITIALPASSWORD];
bool b;
bool b = false;
if (m_options.ContainsKey(EUROBUCKET))
{
if (!bool.TryParse(m_options[EUROBUCKET], out b))
b = false;
}
string region;
if (m_options.ContainsKey(BUCKET_LOCATION))
region = m_options[BUCKET_LOCATION];
else if (m_options.ContainsKey(EUROBUCKET))
region = b ? S3.S3_EU_REGION_NAME : null;
else
{
b = S3CommonOptions.ExtractDefaultEUBuckets(m_applicationSettings);
}
UseEuroBuckets.Checked = b;
region = S3CommonOptions.ExtractDefaultBucketRegion(m_applicationSettings);
string server;
if (m_options.ContainsKey(SERVER_HOSTNAME))
server = m_options[SERVER_HOSTNAME];
else
server = S3CommonOptions.ExtractDefaultServername(m_applicationSettings);
if (string.IsNullOrEmpty(server))
server = S3.DEFAULT_S3_HOST;
Servernames.Text = server;
Bucketregions.Text = region;
if (m_options.ContainsKey(RRS))
{
@@ -288,10 +333,19 @@ namespace Duplicati.Library.Backend
prefix = null;
}
string region;
if (Bucketregions.SelectedItem as Utility.ComboBoxItemPair<string> == null)
region = Bucketregions.Text;
else
region = (Bucketregions.SelectedItem as Utility.ComboBoxItemPair<string>).Value;
//TODO: Figure out if the AWSSDK even supports old bucket names
bool isNonDefaultBucket = !(string.IsNullOrEmpty(region) || region.Equals("us-west-1"));
if (!m_hasSuggestedLowerCase && bucketname.ToLower() != bucketname)
{
string reason = UseEuroBuckets.Checked ?
Strings.S3UI.EuroBucketsRequireLowerCaseError :
string reason = isNonDefaultBucket ?
Strings.S3UI.NonUSBucketsRequireLowerCaseError :
Strings.S3UI.NewS3RequiresLowerCaseError;
DialogResult res = MessageBox.Show(this, string.Format(Strings.S3UI.BucketnameCaseWarning, reason), Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
@@ -300,7 +354,7 @@ namespace Duplicati.Library.Backend
bucketname = bucketname.ToLower();
BucketName.Text = bucketname + "/" + prefix;
}
else if (res == DialogResult.Cancel || UseEuroBuckets.Checked)
else if (res == DialogResult.Cancel || isNonDefaultBucket)
{
return false;
}
@@ -310,9 +364,9 @@ namespace Duplicati.Library.Backend
if (!S3.IsValidHostname(bucketname))
{
if (UseEuroBuckets.Checked)
if (isNonDefaultBucket)
{
MessageBox.Show(this, string.Format(Strings.S3UI.HostnameInvalidWithEuBucketOptionError, bucketname), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show(this, string.Format(Strings.S3UI.HostnameInvalidWithNonUSBucketOptionError, bucketname), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
else
@@ -335,7 +389,7 @@ namespace Duplicati.Library.Backend
private void SignUpLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Duplicati.Library.Utility.UrlUtillity.OpenUrl(LOGIN_PAGE);
Duplicati.Library.Utility.UrlUtillity.OpenUrl(GetSignupLink(Servernames.Text));
}
private void TestConnection_Click(object sender, EventArgs e)
@@ -425,7 +479,6 @@ namespace Duplicati.Library.Backend
commandlineOptions["aws_secret_access_key"] = guiOptions[ACCESS_KEY];
bool useEuroBucket;
bool useSubDomain;
bool useRRS;
if (!guiOptions.ContainsKey(EUROBUCKET) || !bool.TryParse(guiOptions[EUROBUCKET], out useEuroBucket))
@@ -434,31 +487,23 @@ namespace Duplicati.Library.Backend
if (!guiOptions.ContainsKey(RRS) || !bool.TryParse(guiOptions[RRS], out useRRS))
useRRS = false;
if (!guiOptions.ContainsKey(SUBDOMAIN) || !bool.TryParse(guiOptions[SUBDOMAIN], out useSubDomain))
useSubDomain = false;
if (!guiOptions.ContainsKey(BUCKET_NAME))
throw new Exception(string.Format(Interface.CommonStrings.ConfigurationIsMissingItemError, BUCKET_NAME));
string bucketName = guiOptions[BUCKET_NAME];
string host = guiOptions.ContainsKey(SERVER_URL) ? guiOptions[SERVER_URL] : "";
string host = guiOptions.ContainsKey(SERVER_HOSTNAME) ? guiOptions[SERVER_HOSTNAME] : "";
string prefix = guiOptions.ContainsKey(PREFIX) ? guiOptions[PREFIX] : "";
if (string.IsNullOrEmpty(host))
{
if (useEuroBucket || useSubDomain)
host = bucketName + "." + S3_PATH;
else
host = S3_PATH;
}
string region = guiOptions.ContainsKey(BUCKET_LOCATION) ? guiOptions[BUCKET_LOCATION] : (useEuroBucket ? S3.S3_EU_REGION_NAME : null);
if (useRRS)
commandlineOptions[S3.RRS_OPTION] = "";
if (useEuroBucket || useSubDomain)
return "s3://" + host + "/" + (string.IsNullOrEmpty(prefix) ? "" : prefix);
else
return "s3://" + host + "/" + bucketName + (string.IsNullOrEmpty(prefix) ? "" : "/" + prefix);
if (!string.IsNullOrEmpty(host))
commandlineOptions[S3.SERVER_NAME] = host;
if (!string.IsNullOrEmpty(region))
commandlineOptions[S3.LOCATION_OPTION] = region;
return "s3://" + bucketName + "/" + (string.IsNullOrEmpty(prefix) ? "" : prefix);
}
public static string PageTitle
@@ -501,5 +546,23 @@ namespace Duplicati.Library.Backend
}
private delegate void SetComboTextDelegate(ComboBox el, string text);
private void SetComboText(ComboBox el, string text)
{
el.Text = text;
}
private void Servernames_SelectedIndexChanged(object sender, EventArgs e)
{
if (Servernames.SelectedItem as Utility.ComboBoxItemPair<string> != null)
BeginInvoke(new SetComboTextDelegate(SetComboText), Servernames, (Servernames.SelectedItem as Utility.ComboBoxItemPair<string>).Value);
}
private void Bucketregions_SelectedIndexChanged(object sender, EventArgs e)
{
if (Bucketregions.SelectedItem as Utility.ComboBoxItemPair<string> != null)
BeginInvoke(new SetComboTextDelegate(SetComboText), Bucketregions, (Bucketregions.SelectedItem as Utility.ComboBoxItemPair<string>).Value);
}
}
}
+121 -49
View File
@@ -123,7 +123,7 @@
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="label1.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 8</value>
<value>24, 32</value>
</data>
<data name="label1.Size" type="System.Drawing.Size, System.Drawing">
<value>84, 13</value>
@@ -144,13 +144,13 @@
<value>$this</value>
</data>
<data name="&gt;&gt;label1.ZOrder" xml:space="preserve">
<value>12</value>
<value>15</value>
</data>
<data name="label2.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="label2.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 32</value>
<value>24, 56</value>
</data>
<data name="label2.Size" type="System.Drawing.Size, System.Drawing">
<value>87, 13</value>
@@ -171,13 +171,13 @@
<value>$this</value>
</data>
<data name="&gt;&gt;label2.ZOrder" xml:space="preserve">
<value>11</value>
<value>14</value>
</data>
<data name="label3.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="label3.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 56</value>
<value>24, 80</value>
</data>
<data name="label3.Size" type="System.Drawing.Size, System.Drawing">
<value>86, 13</value>
@@ -198,10 +198,10 @@
<value>$this</value>
</data>
<data name="&gt;&gt;label3.ZOrder" xml:space="preserve">
<value>10</value>
<value>13</value>
</data>
<data name="AWS_KEY.Location" type="System.Drawing.Point, System.Drawing">
<value>136, 32</value>
<value>136, 56</value>
</data>
<data name="AWS_KEY.Size" type="System.Drawing.Size, System.Drawing">
<value>152, 20</value>
@@ -219,10 +219,10 @@
<value>$this</value>
</data>
<data name="&gt;&gt;AWS_KEY.ZOrder" xml:space="preserve">
<value>9</value>
<value>12</value>
</data>
<data name="BucketName.Location" type="System.Drawing.Point, System.Drawing">
<value>136, 56</value>
<value>136, 80</value>
</data>
<data name="BucketName.Size" type="System.Drawing.Size, System.Drawing">
<value>152, 20</value>
@@ -240,13 +240,13 @@
<value>$this</value>
</data>
<data name="&gt;&gt;BucketName.ZOrder" xml:space="preserve">
<value>8</value>
<value>11</value>
</data>
<data name="SignUpLink.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="SignUpLink.Location" type="System.Drawing.Point, System.Drawing">
<value>296, 8</value>
<value>328, 8</value>
</data>
<data name="SignUpLink.Size" type="System.Drawing.Size, System.Drawing">
<value>151, 13</value>
@@ -267,13 +267,13 @@
<value>$this</value>
</data>
<data name="&gt;&gt;SignUpLink.ZOrder" xml:space="preserve">
<value>7</value>
<value>10</value>
</data>
<data name="label4.Font" type="System.Drawing.Font, System.Drawing">
<value>Microsoft Sans Serif, 8.25pt, style=Italic</value>
</data>
<data name="label4.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 80</value>
<value>24, 104</value>
</data>
<data name="label4.Size" type="System.Drawing.Size, System.Drawing">
<value>440, 32</value>
@@ -294,37 +294,10 @@
<value>$this</value>
</data>
<data name="&gt;&gt;label4.ZOrder" xml:space="preserve">
<value>6</value>
</data>
<data name="UseEuroBuckets.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="UseEuroBuckets.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 128</value>
</data>
<data name="UseEuroBuckets.Size" type="System.Drawing.Size, System.Drawing">
<value>176, 17</value>
</data>
<data name="UseEuroBuckets.TabIndex" type="System.Int32, mscorlib">
<value>7</value>
</data>
<data name="UseEuroBuckets.Text" xml:space="preserve">
<value>Store files on a european server</value>
</data>
<data name="&gt;&gt;UseEuroBuckets.Name" xml:space="preserve">
<value>UseEuroBuckets</value>
</data>
<data name="&gt;&gt;UseEuroBuckets.Type" xml:space="preserve">
<value>System.Windows.Forms.CheckBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;UseEuroBuckets.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;UseEuroBuckets.ZOrder" xml:space="preserve">
<value>5</value>
<value>9</value>
</data>
<data name="TestConnection.Location" type="System.Drawing.Point, System.Drawing">
<value>360, 120</value>
<value>360, 184</value>
</data>
<data name="TestConnection.Size" type="System.Drawing.Size, System.Drawing">
<value>112, 24</value>
@@ -345,7 +318,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;TestConnection.ZOrder" xml:space="preserve">
<value>4</value>
<value>8</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="CreateBucket.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
@@ -373,10 +346,10 @@
<value>$this</value>
</data>
<data name="&gt;&gt;CreateBucket.ZOrder" xml:space="preserve">
<value>3</value>
<value>7</value>
</data>
<data name="AWS_ID.Location" type="System.Drawing.Point, System.Drawing">
<value>136, 8</value>
<value>136, 32</value>
</data>
<data name="AWS_ID.Size" type="System.Drawing.Size, System.Drawing">
<value>152, 21</value>
@@ -394,7 +367,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;AWS_ID.ZOrder" xml:space="preserve">
<value>2</value>
<value>6</value>
</data>
<data name="UseRRS.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
@@ -403,7 +376,7 @@
<value>NoControl</value>
</data>
<data name="UseRRS.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 152</value>
<value>24, 184</value>
</data>
<data name="UseRRS.Size" type="System.Drawing.Size, System.Drawing">
<value>228, 17</value>
@@ -424,7 +397,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;UseRRS.ZOrder" xml:space="preserve">
<value>1</value>
<value>5</value>
</data>
<data name="label5.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
@@ -433,7 +406,7 @@
<value>Microsoft Sans Serif, 8.25pt, style=Italic</value>
</data>
<data name="label5.Location" type="System.Drawing.Point, System.Drawing">
<value>40, 176</value>
<value>40, 208</value>
</data>
<data name="label5.Size" type="System.Drawing.Size, System.Drawing">
<value>157, 13</value>
@@ -454,8 +427,107 @@
<value>$this</value>
</data>
<data name="&gt;&gt;label5.ZOrder" xml:space="preserve">
<value>4</value>
</data>
<data name="label6.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="label6.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 144</value>
</data>
<data name="label6.Size" type="System.Drawing.Size, System.Drawing">
<value>106, 13</value>
</data>
<data name="label6.TabIndex" type="System.Int32, mscorlib">
<value>13</value>
</data>
<data name="label6.Text" xml:space="preserve">
<value>Bucket create region</value>
</data>
<data name="&gt;&gt;label6.Name" xml:space="preserve">
<value>label6</value>
</data>
<data name="&gt;&gt;label6.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;label6.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;label6.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="Bucketregions.Location" type="System.Drawing.Point, System.Drawing">
<value>136, 144</value>
</data>
<data name="Bucketregions.Size" type="System.Drawing.Size, System.Drawing">
<value>152, 21</value>
</data>
<data name="Bucketregions.TabIndex" type="System.Int32, mscorlib">
<value>14</value>
</data>
<data name="&gt;&gt;Bucketregions.Name" xml:space="preserve">
<value>Bucketregions</value>
</data>
<data name="&gt;&gt;Bucketregions.Type" xml:space="preserve">
<value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;Bucketregions.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;Bucketregions.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<data name="Servernames.Location" type="System.Drawing.Point, System.Drawing">
<value>136, 8</value>
</data>
<data name="Servernames.Size" type="System.Drawing.Size, System.Drawing">
<value>152, 21</value>
</data>
<data name="Servernames.TabIndex" type="System.Int32, mscorlib">
<value>16</value>
</data>
<data name="&gt;&gt;Servernames.Name" xml:space="preserve">
<value>Servernames</value>
</data>
<data name="&gt;&gt;Servernames.Type" xml:space="preserve">
<value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;Servernames.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;Servernames.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="label7.AutoSize" type="System.Boolean, mscorlib">
<value>True</value>
</data>
<data name="label7.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
<value>NoControl</value>
</data>
<data name="label7.Location" type="System.Drawing.Point, System.Drawing">
<value>24, 8</value>
</data>
<data name="label7.Size" type="System.Drawing.Size, System.Drawing">
<value>80, 13</value>
</data>
<data name="label7.TabIndex" type="System.Int32, mscorlib">
<value>15</value>
</data>
<data name="label7.Text" xml:space="preserve">
<value>S3 Servername</value>
</data>
<data name="&gt;&gt;label7.Name" xml:space="preserve">
<value>label7</value>
</data>
<data name="&gt;&gt;label7.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;label7.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;label7.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
+73 -114
View File
@@ -20,120 +20,95 @@
using System;
using System.Collections.Generic;
using System.Text;
using Affirma.ThreeSharp;
using Affirma.ThreeSharp.Query;
using Affirma.ThreeSharp.Model;
using Amazon.S3;
using Amazon.S3.Model;
using System.Xml;
using Duplicati.Library.Interface;
namespace Duplicati.Library.Backend
{
/// <summary>
/// Helper class that allows a little more configuration than the original wrapper,
/// and fixes various problems with it, such as EU bucket support and long lists
/// Helper class that fixes long list support and injects location headers, includes using directives etc.
/// </summary>
public class S3Wrapper
public class S3Wrapper : IDisposable
{
private const int ITEM_LIST_LIMIT = 1000;
private static Dictionary<string, KeyValuePair<DateTime, string>> RedirectCache = new Dictionary<string, KeyValuePair<DateTime, string>>();
protected const string EU_LOCATION_CONSTRAINT = "<CreateBucketConfiguration><LocationConstraint>EU</LocationConstraint></CreateBucketConfiguration>";
protected bool m_euBucket;
protected string m_locationConstraint;
protected bool m_useRRS;
private ThreeSharpConfig m_config;
private ThreeSharpQuery m_service;
protected AmazonS3Client m_client;
public S3Wrapper(string awsID, string awsKey, CallingFormat format, bool euBuckets, bool useRRS)
public S3Wrapper(string awsID, string awsKey, string locationConstraint, string servername, bool useRRS)
{
m_config = new ThreeSharpConfig();
m_config.AwsAccessKeyID = awsID;
m_config.AwsSecretAccessKey = awsKey;
m_config.Format = format;
m_config.IsSecure = false;
AmazonS3Config cfg = new AmazonS3Config();
cfg.CommunicationProtocol = Amazon.S3.Model.Protocol.HTTPS;
cfg.ServiceURL = servername;
cfg.UserAgent = "Duplicati v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + " S3 client with AWS SDK v" + cfg.GetType().Assembly.GetName().Version.ToString();
cfg.UseSecureStringForAwsSecretKey = false;
m_euBucket = euBuckets;
m_client = new Amazon.S3.AmazonS3Client(awsID, awsKey, cfg);
m_locationConstraint = locationConstraint;
m_useRRS = useRRS;
if (euBuckets && format == CallingFormat.REGULAR)
throw new Exception(Strings.S3Wrapper.EuroBucketsRequireSubDomainError);
m_service = new ThreeSharpQuery(m_config);
}
public void AddBucket(string bucketName)
{
//Due to a resource leak in the S3 code, we flush it here
GC.Collect();
using (BucketAddRequest request = new BucketAddRequest(bucketName))
{
if (m_euBucket)
request.LoadStreamWithString(EU_LOCATION_CONSTRAINT);
PutBucketRequest request = new PutBucketRequest();
request.BucketName = bucketName;
using (BucketAddResponse response = m_service.BucketAdd(request))
{ }
}
if (!string.IsNullOrEmpty(m_locationConstraint))
request.BucketRegionName = m_locationConstraint;
using (PutBucketResponse response = m_client.PutBucket(request))
{ }
}
public virtual void GetFileStream(string bucketName, string keyName, System.IO.Stream target)
{
//Due to a resource leak in the S3 code, we flush it here
GC.Collect();
using (ObjectGetRequest objectGetRequest = new ObjectGetRequest(bucketName, keyName))
{
objectGetRequest.RedirectUrl = GetRedirectUrl(bucketName, keyName);
using (ObjectGetResponse objectGetResponse = m_service.ObjectGet(objectGetRequest))
Utility.Utility.CopyStream(objectGetResponse.DataStream, target);
}
GetObjectRequest objectGetRequest = new GetObjectRequest();
objectGetRequest.BucketName = bucketName;
objectGetRequest.Key = keyName;
using (GetObjectResponse objectGetResponse = m_client.GetObject(objectGetRequest))
using (System.IO.Stream s = objectGetResponse.ResponseStream)
Utility.Utility.CopyStream(s, target);
}
public void GetFileObject(string bucketName, string keyName, string localfile)
{
//Due to a resource leak in the S3 code, we flush it here
GC.Collect();
using (System.IO.FileStream fs = System.IO.File.Open(localfile, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
GetFileStream(bucketName, keyName, fs);
}
public void AddFileObject(string bucketName, string keyName, string localfile)
{
//Due to a resource leak in the S3 code, we flush it here
GC.Collect();
using (System.IO.FileStream fs = System.IO.File.Open(localfile, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
AddFileStream(bucketName, keyName, fs);
}
public virtual void AddFileStream(string bucketName, string keyName, System.IO.Stream source)
{
//Due to a resource leak in the S3 code, we flush it here
GC.Collect();
using (ObjectAddRequest objectAddRequest = new ObjectAddRequest(bucketName, keyName))
{
objectAddRequest.DataStream = source;
PutObjectRequest objectAddRequest = new PutObjectRequest();
objectAddRequest.BucketName = bucketName;
objectAddRequest.Key = keyName;
objectAddRequest.InputStream = source;
objectAddRequest.StorageClass = m_useRRS ? S3StorageClass.ReducedRedundancy : S3StorageClass.Standard;
objectAddRequest.GenerateMD5Digest = false;
//objectAddRequest.ContentType = "application/octet-stream";
objectAddRequest.BytesTotal = source.Length; //The source length MUST be readable
objectAddRequest.RedirectUrl = GetRedirectUrl(bucketName, keyName);
if (m_useRRS)
objectAddRequest.Headers.Add("x-amz-storage-class", "REDUCED_REDUNDANCY");
using (ObjectAddResponse objectAddResponse = m_service.ObjectAdd(objectAddRequest))
{ }
}
using (PutObjectResponse objectAddResponse = m_client.PutObject(objectAddRequest))
{ }
}
public void DeleteObject(string bucketName, string keyName)
{
//Due to a resource leak in the S3 code, we flush it here
GC.Collect();
using (ObjectDeleteRequest objectDeleteRequest = new ObjectDeleteRequest(bucketName, keyName))
{
objectDeleteRequest.RedirectUrl = GetRedirectUrl(bucketName, keyName);
using (ObjectDeleteResponse objectDeleteResponse = m_service.ObjectDelete(objectDeleteRequest))
{ }
}
DeleteObjectRequest objectDeleteRequest = new DeleteObjectRequest();
objectDeleteRequest.BucketName = bucketName;
objectDeleteRequest.Key = keyName;
using (DeleteObjectResponse objectDeleteResponse = m_client.DeleteObject(objectDeleteRequest))
{ }
}
public virtual List<IFileEntry> ListBucket(string bucketName, string prefix)
@@ -141,42 +116,41 @@ namespace Duplicati.Library.Backend
bool isTruncated = true;
string filename = null;
string redirUrl = GetRedirectUrl(bucketName, null);
List<IFileEntry> files = new List<IFileEntry>();
//We truncate after ITEM_LIST_LIMIT elements, and then repeat
while (isTruncated)
{
//Due to a resource leak in the S3 code, we flush it here
GC.Collect();
using (BucketListRequest listRequest = new BucketListRequest(bucketName))
ListObjectsRequest listRequest = new ListObjectsRequest();
listRequest.BucketName = bucketName;
if (!string.IsNullOrEmpty(filename))
listRequest.Marker = filename;
listRequest.MaxKeys = ITEM_LIST_LIMIT;
if (!string.IsNullOrEmpty(prefix))
listRequest.Prefix = prefix;
using (ListObjectsResponse listResponse = m_client.ListObjects(listRequest))
{
listRequest.RedirectUrl = redirUrl;
if (!string.IsNullOrEmpty(filename))
listRequest.QueryList.Add("marker", filename);
isTruncated = listResponse.IsTruncated;
filename = listResponse.NextMarker;
listRequest.QueryList.Add("max-keys", ITEM_LIST_LIMIT.ToString());
if (!string.IsNullOrEmpty(prefix))
listRequest.QueryList.Add("prefix", prefix);
using (BucketListResponse listResponse = m_service.BucketList(listRequest))
foreach (S3Object obj in listResponse.S3Objects)
{
XmlDocument bucketXml = listResponse.StreamResponseToXmlDocument();
XmlNodeList objects = bucketXml.SelectNodes("//*[local-name()='Contents']");
foreach (XmlNode obj in objects)
{
filename = obj["Key"].InnerText;
long size = long.Parse(obj["Size"].InnerText);
DateTime lastModified = DateTime.Parse(obj["LastModified"].InnerText);
files.Add(new FileEntry(filename, size, lastModified, lastModified));
}
isTruncated = bool.Parse(bucketXml.SelectSingleNode("//*[local-name()='IsTruncated']").InnerText);
files.Add(new FileEntry(
obj.Key,
obj.Size,
DateTime.Parse(obj.LastModified),
DateTime.Parse(obj.LastModified)
));
}
//filename = files[files.Count - 1].Name;
}
}
//TODO: Figure out if this is the case with AWSSDK too
//Unfortunately S3 sometimes reports duplicate values when requesting more than one page of results
Dictionary<string, string> tmp = new Dictionary<string, string>();
for (int i = 0; i < files.Count; i++)
@@ -191,30 +165,15 @@ namespace Duplicati.Library.Backend
return files;
}
protected string GetRedirectUrl(string bucketName, string filename)
{
if (!RedirectCache.ContainsKey(bucketName) || RedirectCache[bucketName].Key < DateTime.Now)
{
//Due to a resource leak in the S3 code, we flush it here
GC.Collect();
using (BucketListRequest testRequest = new BucketListRequest(bucketName))
{
testRequest.Method = "HEAD";
using (BucketListResponse testResponse = m_service.BucketList(testRequest))
if (testResponse.StatusCode == System.Net.HttpStatusCode.TemporaryRedirect)
RedirectCache[bucketName] = new KeyValuePair<DateTime,string>(DateTime.Now.AddMinutes(5), testResponse.Headers["Location"].ToString());
else
RedirectCache[bucketName] = new KeyValuePair<DateTime,string>(DateTime.Now.AddHours(1), null);
//If there are no temp redirects, the DNS system is updated, and will likely never require temporary redirects
}
}
#region IDisposable Members
string tempurl = RedirectCache[bucketName].Value;
if (tempurl == null)
return null;
else
return RedirectCache[bucketName].Value + (filename == null ? "" : filename);
public void Dispose()
{
if (m_client != null)
m_client.Dispose();
m_client = null;
}
#endregion
}
}
+87 -16
View File
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.4927
// Runtime Version:2.0.50727.5446
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -97,14 +97,20 @@ namespace Duplicati.Library.Backend.Strings {
}
/// <summary>
/// Looks up a localized string similar to This backend can read and write data to an Amazon S3 based backend. Allowed formats are (subdomain type): &quot;s3://bucketname.s3.amazonaws.com/prefix&quot; or &quot;s3://aws_id:aws_key@bucketname.s3.amazonaws.com/prefix&quot;.
///The url suffix model is also accepted: &quot;s3://s3.amazonaws.com/bucketname/prefix&quot;.
///Any hostname not using s3.amazonaws.com is considered a vanity style, and the bucket name is attempted extracted through CNAME lookup.
///Note that if you AWS ID or Key contains either &quot;:&quot;, &quot;/&quot; or &quot;@&quot; you cannot supply t [rest of string was truncated]&quot;;.
/// Looks up a localized string similar to You are using a deprected url format, please change it to: {0}.
/// </summary>
internal static string Description {
internal static string DeprecatedUrlFormat {
get {
return ResourceManager.GetString("Description", resourceCulture);
return ResourceManager.GetString("DeprecatedUrlFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This backend can read and write data to an Amazon S3 compatible server. Allowed formats are: &quot;s3://bucketname/prefix&quot;.
/// </summary>
internal static string Description_v2 {
get {
return ResourceManager.GetString("Description_v2", resourceCulture);
}
}
@@ -153,15 +159,6 @@ namespace Duplicati.Library.Backend.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to The supplied bucket name, &quot;{0}&quot;, is not a valid hostname label, and cannot be used with subdomain urls. The options --{1} or --{2} also toggle subdomain urls..
/// </summary>
internal static string InvalidHostnameForSubdomainError {
get {
return ResourceManager.GetString("InvalidHostnameForSubdomainError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No Amazon S3 secret key given.
/// </summary>
@@ -180,6 +177,24 @@ namespace Duplicati.Library.Backend.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to The options --{0} and --{1} are mutually exclusive.
/// </summary>
internal static string OptionsAreMutuallyExclusiveError {
get {
return ResourceManager.GetString("OptionsAreMutuallyExclusiveError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please use --{0}={1} instead.
/// </summary>
internal static string S3EurobucketDeprecationDescription {
get {
return ResourceManager.GetString("S3EurobucketDeprecationDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This flag is only used when creating new buckets. If the flag is set, the bucket is created on a european server. This flag forces the &quot;s3-use-new-style&quot; flag. Amazon charges slightly more for european buckets..
/// </summary>
@@ -198,6 +213,34 @@ namespace Duplicati.Library.Backend.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to This option is only used when creating new buckets. Use this option to change what region the data is stored in. Amazon charges slightly more for non-US buckets. Known bucket locations:
///{0}.
/// </summary>
internal static string S3LocationDescriptionLong {
get {
return ResourceManager.GetString("S3LocationDescriptionLong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Specifies S3 location constraints.
/// </summary>
internal static string S3LocationDescriptionShort {
get {
return ResourceManager.GetString("S3LocationDescriptionShort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The subdomain calling option does nothing, the library will pick the right calling convention.
/// </summary>
internal static string S3NewStyleDeprecation {
get {
return ResourceManager.GetString("S3NewStyleDeprecation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Specify this argument to make the S3 backend use subdomains rather than the previous url prefix method. See the Amazon S3 documentation for more details..
/// </summary>
@@ -216,6 +259,25 @@ namespace Duplicati.Library.Backend.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Companies other than Amazon are now supporting the S3 API, meaning that this backend can read and write data to those providers as well. Use this option to set the hostname. Currently known providers are:
///{0}.
/// </summary>
internal static string S3ServerNameDescriptionLong {
get {
return ResourceManager.GetString("S3ServerNameDescriptionLong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Specifies an alternate S3 server name.
/// </summary>
internal static string S3ServerNameDescriptionShort {
get {
return ResourceManager.GetString("S3ServerNameDescriptionShort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This flag toggles the use of the special RRS header. Files stored using RRS are more likely to disapear than those stored normally, but also costs less to store. See the full description here: http://aws.amazon.com/about-aws/whats-new/2010/05/19/announcing-amazon-s3-reduced-redundancy-storage/.
/// </summary>
@@ -242,5 +304,14 @@ namespace Duplicati.Library.Backend.Strings {
return ResourceManager.GetString("UnableToDecodeBucketnameError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Could not parse the url: {0}.
/// </summary>
internal static string UnableToParseURLError {
get {
return ResourceManager.GetString("UnableToParseURLError", resourceCulture);
}
}
}
}
@@ -129,12 +129,6 @@
<data name="AMZUserIDDescriptionShort" xml:space="preserve">
<value>The AWS "Access Key ID"</value>
</data>
<data name="Description" xml:space="preserve">
<value>This backend can read and write data to an Amazon S3 based backend. Allowed formats are (subdomain type): "s3://bucketname.s3.amazonaws.com/prefix" or "s3://aws_id:aws_key@bucketname.s3.amazonaws.com/prefix".
The url suffix model is also accepted: "s3://s3.amazonaws.com/bucketname/prefix".
Any hostname not using s3.amazonaws.com is considered a vanity style, and the bucket name is attempted extracted through CNAME lookup.
Note that if you AWS ID or Key contains either ":", "/" or "@" you cannot supply them in the url, but must pass them through arguments.</value>
</data>
<data name="DisplayName" xml:space="preserve">
<value>Amazon S3 based</value>
</data>
@@ -171,13 +165,42 @@ Note that if you AWS ID or Key contains either ":", "/" or "@" you cannot supply
<data name="UnableToDecodeBucketnameError" xml:space="preserve">
<value>Unable to determine the bucket name for host: {0}</value>
</data>
<data name="InvalidHostnameForSubdomainError" xml:space="preserve">
<value>The supplied bucket name, "{0}", is not a valid hostname label, and cannot be used with subdomain urls. The options --{1} or --{2} also toggle subdomain urls.</value>
</data>
<data name="S3UseRRSDescriptionLong" xml:space="preserve">
<value>This flag toggles the use of the special RRS header. Files stored using RRS are more likely to disapear than those stored normally, but also costs less to store. See the full description here: http://aws.amazon.com/about-aws/whats-new/2010/05/19/announcing-amazon-s3-reduced-redundancy-storage/</value>
</data>
<data name="S3UseRRSDescriptionShort" xml:space="preserve">
<value>Use Reduced Redundancy Storage</value>
</data>
<data name="DeprecatedUrlFormat" xml:space="preserve">
<value>You are using a deprected url format, please change it to: {0}</value>
</data>
<data name="Description_v2" xml:space="preserve">
<value>This backend can read and write data to an Amazon S3 compatible server. Allowed formats are: "s3://bucketname/prefix"</value>
</data>
<data name="OptionsAreMutuallyExclusiveError" xml:space="preserve">
<value>The options --{0} and --{1} are mutually exclusive</value>
</data>
<data name="S3EurobucketDeprecationDescription" xml:space="preserve">
<value>Please use --{0}={1} instead</value>
</data>
<data name="S3LocationDescriptionLong" xml:space="preserve">
<value>This option is only used when creating new buckets. Use this option to change what region the data is stored in. Amazon charges slightly more for non-US buckets. Known bucket locations:
{0}</value>
</data>
<data name="S3LocationDescriptionShort" xml:space="preserve">
<value>Specifies S3 location constraints</value>
</data>
<data name="S3ServerNameDescriptionLong" xml:space="preserve">
<value>Companies other than Amazon are now supporting the S3 API, meaning that this backend can read and write data to those providers as well. Use this option to set the hostname. Currently known providers are:
{0}</value>
</data>
<data name="S3ServerNameDescriptionShort" xml:space="preserve">
<value>Specifies an alternate S3 server name</value>
</data>
<data name="UnableToParseURLError" xml:space="preserve">
<value>Could not parse the url: {0}</value>
</data>
<data name="S3NewStyleDeprecation" xml:space="preserve">
<value>The subdomain calling option does nothing, the library will pick the right calling convention</value>
</data>
</root>
+13 -13
View File
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.4927
// Runtime Version:2.0.50727.5446
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -125,15 +125,6 @@ namespace Duplicati.Library.Backend.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to The european buckets require that the bucket name is in lower case..
/// </summary>
internal static string EuroBucketsRequireLowerCaseError {
get {
return ResourceManager.GetString("EuroBucketsRequireLowerCaseError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The supplied bucket name, &quot;{0}&quot;, does not appear to be a valid hostname label.
///This may still work if the non-subdomain calling style is used, but it is not recommended.
@@ -147,11 +138,11 @@ namespace Duplicati.Library.Backend.Strings {
}
/// <summary>
/// Looks up a localized string similar to The EU bucket option requires that the bucket names are valid hostname labels, but the supplied bucket name, &quot;{0}&quot;, is not a valid hostname label. Either change the bucketname or uncheck the EU bucket option..
/// Looks up a localized string similar to Non-US buckets require that the bucket names are valid hostname labels, but the supplied bucket name, &quot;{0}&quot;, is not a valid hostname label. Either change the bucketname or use the default bucket region..
/// </summary>
internal static string HostnameInvalidWithEuBucketOptionError {
internal static string HostnameInvalidWithNonUSBucketOptionError {
get {
return ResourceManager.GetString("HostnameInvalidWithEuBucketOptionError", resourceCulture);
return ResourceManager.GetString("HostnameInvalidWithNonUSBucketOptionError", resourceCulture);
}
}
@@ -176,6 +167,15 @@ namespace Duplicati.Library.Backend.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Non-US buckets require that the bucket name is in lower case..
/// </summary>
internal static string NonUSBucketsRequireLowerCaseError {
get {
return ResourceManager.GetString("NonUSBucketsRequireLowerCaseError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to On this page you can select where to store the backup data..
/// </summary>
@@ -146,17 +146,14 @@ to open the AWS login page, and retrieve it.</value>
You must use a unique name for each backup.
You may enter any name you like.</value>
</data>
<data name="EuroBucketsRequireLowerCaseError" xml:space="preserve">
<value>The european buckets require that the bucket name is in lower case.</value>
</data>
<data name="HostnameInvalidWarning" xml:space="preserve">
<value>The supplied bucket name, "{0}", does not appear to be a valid hostname label.
This may still work if the non-subdomain calling style is used, but it is not recommended.
Do you want to continue anyway?</value>
</data>
<data name="HostnameInvalidWithEuBucketOptionError" xml:space="preserve">
<value>The EU bucket option requires that the bucket names are valid hostname labels, but the supplied bucket name, "{0}", is not a valid hostname label. Either change the bucketname or uncheck the EU bucket option.</value>
<data name="HostnameInvalidWithNonUSBucketOptionError" xml:space="preserve">
<value>Non-US buckets require that the bucket names are valid hostname labels, but the supplied bucket name, "{0}", is not a valid hostname label. Either change the bucketname or use the default bucket region.</value>
</data>
<data name="MonoRequiresExistingBucket" xml:space="preserve">
<value>A bug in Mono versions older than 2.4.3,
@@ -167,6 +164,9 @@ Do you want to create the bucket now?</value>
<data name="NewS3RequiresLowerCaseError" xml:space="preserve">
<value>The new amazon s3 API requires that bucket names are all lower case.</value>
</data>
<data name="NonUSBucketsRequireLowerCaseError" xml:space="preserve">
<value>Non-US buckets require that the bucket name is in lower case.</value>
</data>
<data name="PageDescription" xml:space="preserve">
<value>On this page you can select where to store the backup data.</value>
</data>
@@ -1,72 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.3082
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Duplicati.Library.Backend.Strings {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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", "2.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class S3Wrapper {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal S3Wrapper() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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.Library.Backend.Strings.S3Wrapper", typeof(S3Wrapper).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to EU buckets does not work with regular calling format.
/// </summary>
internal static string EuroBucketsRequireSubDomainError {
get {
return ResourceManager.GetString("EuroBucketsRequireSubDomainError", resourceCulture);
}
}
}
}
@@ -1,123 +0,0 @@
<?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 name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="EuroBucketsRequireSubDomainError" xml:space="preserve">
<value>EU buckets does not work with regular calling format</value>
</data>
</root>
@@ -63,7 +63,12 @@ namespace Duplicati.Library.DynamicLoader
if (string.IsNullOrEmpty(url))
throw new ArgumentNullException("url");
string scheme = new Uri(url).Scheme.ToLower();
string scheme;
//If possible, we avoid parsing the string as a URL to allow flexible string handling
if (url.IndexOf("://") > 0)
scheme = url.Substring(0, url.IndexOf("://"));
else
scheme = new Uri(url).Scheme.ToLower();
LoadInterfaces();
+10 -1
View File
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.5420
// Runtime Version:2.0.50727.5446
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -78,6 +78,15 @@ namespace Duplicati.Library.Main.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Allow system to enter sleep power modes for inactivity during backup/restore operations (Windows only).
/// </summary>
internal static string AllowsleepShort {
get {
return ResourceManager.GetString("AllowsleepShort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This option can be used to disable the check for the same source folders. It is used in unittests, and is NOT intended for everyday use. Do not use this option unless you know what you are doing..
/// </summary>
+16
View File
@@ -502,6 +502,22 @@ namespace Duplicati.Library.Utility
}
}
/// <summary>
/// Parses an option from the option set, using the convention that if the option is set, it is true unless it parses to false, and false otherwise
/// </summary>
/// <param name="options">The set of options to look for the setting in</param>
/// <param name="value">The value to look for in the settings</param>
/// <returns></returns>
public static bool ParseBoolOption(IDictionary<string, string> options, string value)
{
string opt;
if (options.TryGetValue(value, out opt))
return ParseBool(opt, true);
else
return false;
}
/// <summary>
/// A helper for converting byte arrays to hex, vice versa
/// </summary>
+6 -8
View File
@@ -59,14 +59,6 @@
</Target>
-->
<ItemGroup>
<Content Include="..\..\thirdparty\Affirma.ThreeSharp\Homepage.txt">
<Link>licenses\Affirma.ThreeSharp\Homepage.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\thirdparty\Affirma.ThreeSharp\License.txt">
<Link>licenses\Affirma.ThreeSharp\License.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\thirdparty\alphavss\Homepage.txt">
<Link>licenses\alphavss\Homepage.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -75,6 +67,12 @@
<Link>licenses\alphavss\License.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\thirdparty\AWS SDK\download.txt">
<Link>licenses\AWSSDK\download.txt</Link>
</Content>
<Content Include="..\..\thirdparty\AWS SDK\license.txt">
<Link>licenses\AWSSDK\license.txt</Link>
</Content>
<Content Include="..\..\thirdparty\gpg\Homepage.txt">
<Link>licenses\gpg\Homepage.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
BIN
View File
Binary file not shown.
+95539
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
http://aws.amazon.com/sdkfornet/
+18
View File
@@ -0,0 +1,18 @@
Copyright 2009-2011 Amazon Technologies, Inc.
Amazon.com, Inc. or its affiliates. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the “License”).
You may not use this file except in compliance with the License.
A copy of the License is located at
http://aws.amazon.com/apache2.0/
or in the “license” file accompanying this file. This file is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the
License.
Note: Other license terms may apply to certain, identified software files contained within or distributed with the accompanying software if such terms are included in the directory containing the accompanying software. Such other license terms will then apply in lieu of the terms of the software license above.
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
http://www.codeplex.com/ThreeSharp
-69
View File
@@ -1,69 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
2. You must cause any modified files to carry prominent notices stating that You changed the files; and
3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.