Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5ff73cf4e | ||
|
|
056307961b |
@@ -20,7 +20,10 @@
|
||||
|
||||
using Duplicati.Library.Common.IO;
|
||||
using Duplicati.Library.Interface;
|
||||
using Duplicati.Library.Utility;
|
||||
using FluentFTP;
|
||||
using FluentFTP.Client.BaseClient;
|
||||
using FluentFTP.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -61,9 +64,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
|
||||
private readonly string _url;
|
||||
private readonly bool _listVerify = true;
|
||||
private readonly FtpEncryptionMode _encryptionMode;
|
||||
private readonly FtpDataConnectionType _dataConnectionType;
|
||||
private readonly SslProtocols _sslProtocols;
|
||||
private readonly FtpConfig _ftpConfig;
|
||||
private readonly TimeSpan _uploadWaitTime;
|
||||
|
||||
private readonly byte[] _copybuffer = new byte[CoreUtility.DEFAULT_BUFFER_SIZE];
|
||||
@@ -86,7 +87,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
get { return "aftp"; }
|
||||
}
|
||||
|
||||
private FtpClient Client
|
||||
private AsyncFtpClient Client
|
||||
{ get; set; }
|
||||
|
||||
public IList<ICommandLineArgument> SupportedCommands
|
||||
@@ -162,42 +163,52 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
|
||||
// Process the aftp-data-connection-type option
|
||||
string dataConnectionTypeString;
|
||||
FtpDataConnectionType dataConnectionType;
|
||||
|
||||
if (!options.TryGetValue(CONFIG_KEY_AFTP_DATA_CONNECTION_TYPE, out dataConnectionTypeString) || string.IsNullOrWhiteSpace(dataConnectionTypeString))
|
||||
{
|
||||
dataConnectionTypeString = null;
|
||||
}
|
||||
|
||||
if (dataConnectionTypeString == null || !Enum.TryParse(dataConnectionTypeString, true, out _dataConnectionType))
|
||||
if (dataConnectionTypeString == null || !Enum.TryParse(dataConnectionTypeString, true, out dataConnectionType))
|
||||
{
|
||||
_dataConnectionType = DEFAULT_DATA_CONNECTION_TYPE;
|
||||
dataConnectionType = DEFAULT_DATA_CONNECTION_TYPE;
|
||||
}
|
||||
|
||||
// Process the aftp-encryption-mode option
|
||||
string encryptionModeString;
|
||||
FtpEncryptionMode encryptionMode;
|
||||
|
||||
if (!options.TryGetValue(CONFIG_KEY_AFTP_ENCRYPTION_MODE, out encryptionModeString) || string.IsNullOrWhiteSpace(encryptionModeString))
|
||||
{
|
||||
encryptionModeString = null;
|
||||
}
|
||||
|
||||
if (encryptionModeString == null || !Enum.TryParse(encryptionModeString, true, out _encryptionMode))
|
||||
if (encryptionModeString == null || !Enum.TryParse(encryptionModeString, true, out encryptionMode))
|
||||
{
|
||||
_encryptionMode = DEFAULT_ENCRYPTION_MODE;
|
||||
encryptionMode = DEFAULT_ENCRYPTION_MODE;
|
||||
}
|
||||
|
||||
// Process the aftp-ssl-protocols option
|
||||
string sslProtocolsString;
|
||||
SslProtocols sslProtocols;
|
||||
|
||||
if (!options.TryGetValue(CONFIG_KEY_AFTP_SSL_PROTOCOLS, out sslProtocolsString) || string.IsNullOrWhiteSpace(sslProtocolsString))
|
||||
{
|
||||
sslProtocolsString = null;
|
||||
}
|
||||
|
||||
if (sslProtocolsString == null || !Enum.TryParse(sslProtocolsString, true, out _sslProtocols))
|
||||
if (sslProtocolsString == null || !Enum.TryParse(sslProtocolsString, true, out sslProtocols))
|
||||
{
|
||||
_sslProtocols = DEFAULT_SSL_PROTOCOLS;
|
||||
sslProtocols = DEFAULT_SSL_PROTOCOLS;
|
||||
}
|
||||
|
||||
_ftpConfig = new FtpConfig
|
||||
{
|
||||
DataConnectionType = dataConnectionType,
|
||||
EncryptionMode = encryptionMode,
|
||||
SslProtocols = sslProtocols,
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<IFileEntry> List()
|
||||
@@ -215,99 +226,87 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
var list = new List<IFileEntry>();
|
||||
string remotePath = filename;
|
||||
|
||||
try
|
||||
var ftpClient = CreateClient();
|
||||
|
||||
// Get the remote path
|
||||
var url = new Uri(this._url);
|
||||
remotePath = "/" + this.GetUnescapedAbsolutePath(url);
|
||||
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
{
|
||||
var ftpClient = CreateClient();
|
||||
|
||||
// Get the remote path
|
||||
var url = new Uri(this._url);
|
||||
remotePath = "/" + this.GetUnescapedAbsolutePath(url);
|
||||
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
if (!stripFile)
|
||||
{
|
||||
if (!stripFile)
|
||||
{
|
||||
// Append the filename
|
||||
remotePath += filename;
|
||||
}
|
||||
else if (filename.Contains("/"))
|
||||
{
|
||||
remotePath += filename.Substring(0, filename.LastIndexOf("/", StringComparison.Ordinal));
|
||||
}
|
||||
// else: stripping the filename in this case ignoring it
|
||||
// Append the filename
|
||||
remotePath += filename;
|
||||
}
|
||||
|
||||
foreach (FtpListItem item in ftpClient.GetListing(remotePath, FtpListOption.Modify | FtpListOption.Size | FtpListOption.DerefLinks))
|
||||
else if (filename.Contains("/"))
|
||||
{
|
||||
switch (item.Type)
|
||||
{
|
||||
case FtpFileSystemObjectType.Directory:
|
||||
{
|
||||
if (item.Name == "." || item.Name == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(new FileEntry(item.Name, -1, new DateTime(), item.Modified)
|
||||
{
|
||||
IsFolder = true,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case FtpFileSystemObjectType.File:
|
||||
{
|
||||
list.Add(new FileEntry(item.Name, item.Size, new DateTime(), item.Modified));
|
||||
|
||||
break;
|
||||
}
|
||||
case FtpFileSystemObjectType.Link:
|
||||
{
|
||||
if (item.Name == "." || item.Name == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.LinkObject != null)
|
||||
{
|
||||
switch (item.LinkObject.Type)
|
||||
{
|
||||
case FtpFileSystemObjectType.Directory:
|
||||
{
|
||||
if (item.Name == "." || item.Name == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(new FileEntry(item.Name, -1, new DateTime(), item.Modified)
|
||||
{
|
||||
IsFolder = true,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case FtpFileSystemObjectType.File:
|
||||
{
|
||||
list.Add(new FileEntry(item.Name, item.Size, new DateTime(), item.Modified));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
remotePath += filename.Substring(0, filename.LastIndexOf("/", StringComparison.Ordinal));
|
||||
}
|
||||
}// Message "Directory not found." string
|
||||
catch (FtpCommandException ex)
|
||||
// else: stripping the filename in this case ignoring it
|
||||
}
|
||||
|
||||
foreach (FtpListItem item in ftpClient.GetListing(remotePath, FtpListOption.Modify | FtpListOption.Size).Await())
|
||||
{
|
||||
if (ex.Message == "Directory not found.")
|
||||
switch (item.Type)
|
||||
{
|
||||
throw new FolderMissingException(Strings.MissingFolderError(remotePath, ex.Message), ex);
|
||||
}
|
||||
case FtpObjectType.Directory:
|
||||
{
|
||||
if (item.Name == "." || item.Name == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw;
|
||||
list.Add(new FileEntry(item.Name, -1, new DateTime(), item.Modified)
|
||||
{
|
||||
IsFolder = true,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case FtpObjectType.File:
|
||||
{
|
||||
list.Add(new FileEntry(item.Name, item.Size, new DateTime(), item.Modified));
|
||||
|
||||
break;
|
||||
}
|
||||
case FtpObjectType.Link:
|
||||
{
|
||||
if (item.Name == "." || item.Name == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.LinkObject != null)
|
||||
{
|
||||
switch (item.LinkObject.Type)
|
||||
{
|
||||
case FtpObjectType.Directory:
|
||||
{
|
||||
if (item.Name == "." || item.Name == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(new FileEntry(item.Name, -1, new DateTime(), item.Modified)
|
||||
{
|
||||
IsFolder = true,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case FtpObjectType.File:
|
||||
{
|
||||
list.Add(new FileEntry(item.Name, item.Size, new DateTime(), item.Modified));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
@@ -318,55 +317,43 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
string remotePath = remotename;
|
||||
long streamLen;
|
||||
|
||||
var ftpClient = CreateClient();
|
||||
|
||||
try
|
||||
{
|
||||
var ftpClient = CreateClient();
|
||||
|
||||
try
|
||||
{
|
||||
streamLen = input.Length;
|
||||
}
|
||||
catch (NotSupportedException) { streamLen = -1; }
|
||||
|
||||
// Get the remote path
|
||||
remotePath = "";
|
||||
|
||||
if (!string.IsNullOrEmpty(remotename))
|
||||
{
|
||||
// Append the filename
|
||||
remotePath += remotename;
|
||||
}
|
||||
|
||||
var success = await ftpClient.UploadAsync(input, remotePath, FtpExists.Overwrite, createRemoteDir: false, token: cancelToken, progress: null).ConfigureAwait(false);
|
||||
if (!success)
|
||||
{
|
||||
throw new UserInformationException(string.Format(Strings.ErrorWriteFile, remotename), "AftpPutFailure");
|
||||
}
|
||||
|
||||
// Wait for the upload, if required
|
||||
if (_uploadWaitTime.Ticks > 0)
|
||||
{
|
||||
Thread.Sleep(_uploadWaitTime);
|
||||
}
|
||||
|
||||
if (_listVerify)
|
||||
{
|
||||
// check remote file size; matching file size indicates completion
|
||||
var remoteSize = await ftpClient.GetFileSizeAsync(remotePath, cancelToken);
|
||||
if (streamLen != remoteSize)
|
||||
{
|
||||
throw new UserInformationException(Strings.ListVerifySizeFailure(remotename, remoteSize, streamLen), "AftpListVerifySizeFailure");
|
||||
}
|
||||
}
|
||||
streamLen = input.Length;
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
if (ex.Message == "Directory not found.")
|
||||
{
|
||||
throw new FolderMissingException(Strings.MissingFolderError(remotePath, ex.Message), ex);
|
||||
}
|
||||
catch (NotSupportedException) { streamLen = -1; }
|
||||
|
||||
throw;
|
||||
// Get the remote path
|
||||
remotePath = "";
|
||||
|
||||
if (!string.IsNullOrEmpty(remotename))
|
||||
{
|
||||
// Append the filename
|
||||
remotePath += remotename;
|
||||
}
|
||||
|
||||
var status = await ftpClient.UploadStream(input, remotePath, FtpRemoteExists.Overwrite, createRemoteDir: false, token: cancelToken, progress: null).ConfigureAwait(false);
|
||||
if (status != FtpStatus.Success)
|
||||
{
|
||||
throw new UserInformationException(string.Format(Strings.ErrorWriteFile, remotename), "AftpPutFailure");
|
||||
}
|
||||
|
||||
// Wait for the upload, if required
|
||||
if (_uploadWaitTime.Ticks > 0)
|
||||
{
|
||||
Thread.Sleep(_uploadWaitTime);
|
||||
}
|
||||
|
||||
if (_listVerify)
|
||||
{
|
||||
// check remote file size; matching file size indicates completion
|
||||
var remoteSize = await ftpClient.GetFileSize(remotePath, -1, cancelToken);
|
||||
if (streamLen != remoteSize)
|
||||
{
|
||||
throw new UserInformationException(Strings.ListVerifySizeFailure(remotename, remoteSize, streamLen), "AftpListVerifySizeFailure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +378,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
remotePath += remotename;
|
||||
}
|
||||
|
||||
using (var inputStream = ftpClient.OpenRead(remotePath))
|
||||
using (var inputStream = ftpClient.OpenRead(remotePath).Await())
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -426,7 +413,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
remotePath += remotename;
|
||||
}
|
||||
|
||||
ftpClient.DeleteFile(remotePath);
|
||||
ftpClient.DeleteFile(remotePath).Await();
|
||||
|
||||
}
|
||||
|
||||
@@ -462,6 +449,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e.InnerException != null) { e = e.InnerException; }
|
||||
throw new Exception(string.Format(Strings.ErrorDeleteFile, e.Message), e);
|
||||
}
|
||||
}
|
||||
@@ -471,10 +459,11 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
{
|
||||
try
|
||||
{
|
||||
PutAsync(TEST_FILE_NAME, testStream, CancellationToken.None).Wait();
|
||||
PutAsync(TEST_FILE_NAME, testStream, CancellationToken.None).Await();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e.InnerException != null) { e = e.InnerException; }
|
||||
throw new Exception(string.Format(Strings.ErrorWriteFile, e.Message), e);
|
||||
}
|
||||
}
|
||||
@@ -491,6 +480,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e.InnerException != null) { e = e.InnerException; }
|
||||
throw new Exception(string.Format(Strings.ErrorReadFile, e.Message), e);
|
||||
}
|
||||
}
|
||||
@@ -502,13 +492,14 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e.InnerException != null) { e = e.InnerException; }
|
||||
throw new Exception(string.Format(Strings.ErrorDeleteFile, e.Message), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateFolder()
|
||||
{
|
||||
var client = CreateClient();
|
||||
var client = CreateClient(false);
|
||||
|
||||
var url = new Uri(_url);
|
||||
|
||||
@@ -516,7 +507,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
var remotePath = this.GetUnescapedAbsolutePath(url);
|
||||
|
||||
// Try to create the directory
|
||||
client.CreateDirectory(remotePath, true);
|
||||
client.CreateDirectory(remotePath, true).Await();
|
||||
|
||||
}
|
||||
|
||||
@@ -529,23 +520,18 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
_userInfo = null;
|
||||
}
|
||||
|
||||
private FtpClient CreateClient()
|
||||
private AsyncFtpClient CreateClient(bool setWorkingDirectory = true)
|
||||
{
|
||||
var uri = new Uri(_url);
|
||||
|
||||
if (this.Client == null) // Create connection if it doesn't exist yet
|
||||
{
|
||||
var ftpClient = new FtpClient
|
||||
var ftpClient = new AsyncFtpClient
|
||||
{
|
||||
Host = uri.Host,
|
||||
Port = uri.Port == -1 ? 21 : uri.Port,
|
||||
Credentials = _userInfo,
|
||||
EncryptionMode = _encryptionMode,
|
||||
DataConnectionType = _dataConnectionType,
|
||||
SslProtocols = _sslProtocols,
|
||||
|
||||
// We do not support parallel uploads, and the feature is buggy
|
||||
EnableThreadSafeDataConnections = false,
|
||||
Config = _ftpConfig,
|
||||
};
|
||||
|
||||
ftpClient.ValidateCertificate += HandleValidateCertificate;
|
||||
@@ -553,10 +539,25 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
this.Client = ftpClient;
|
||||
} // else reuse existing connection
|
||||
|
||||
// Change working directory to the remote path
|
||||
// Do this every time to prevent issues when FtpClient silently reconnects after failure.
|
||||
var remotePath = this.GetUnescapedAbsolutePath(uri);
|
||||
this.Client.SetWorkingDirectory(remotePath);
|
||||
if (setWorkingDirectory)
|
||||
{
|
||||
// Change working directory to the remote path
|
||||
// Do this every time to prevent issues when FtpClient silently reconnects after failure.
|
||||
var remotePath = this.GetUnescapedAbsolutePath(uri);
|
||||
try
|
||||
{
|
||||
this.Client.SetWorkingDirectory(remotePath).Await();
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
if (ex.CompletionCode == "550")
|
||||
{
|
||||
throw new FolderMissingException(Strings.MissingFolderError(remotePath, ex.Message), ex);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return this.Client;
|
||||
}
|
||||
@@ -567,7 +568,7 @@ namespace Duplicati.Library.Backend.AlternativeFTP
|
||||
return absolutePath.EndsWith("/", StringComparison.Ordinal) ? absolutePath.Substring(0, absolutePath.Length - 1) : absolutePath;
|
||||
}
|
||||
|
||||
private void HandleValidateCertificate(FtpClient control, FtpSslValidationEventArgs e)
|
||||
private void HandleValidateCertificate(BaseFtpClient control, FtpSslValidationEventArgs e)
|
||||
{
|
||||
if (e.PolicyErrors == SslPolicyErrors.None || _accepAllCertificates)
|
||||
{
|
||||
|
||||
+5
-2
@@ -35,8 +35,11 @@
|
||||
<AssemblyOriginatorKeyFile>Duplicati.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentFTP, Version=27.1.1.0, Culture=neutral, PublicKeyToken=f4af092b1d8df44f, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\..\packages\FluentFTP.27.1.1\lib\net45\FluentFTP.dll</HintPath>
|
||||
<Reference Include="FluentFTP, Version=46.0.2.0, Culture=neutral, PublicKeyToken=f4af092b1d8df44f, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\..\packages\FluentFTP.46.0.2\lib\net462\FluentFTP.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\..\packages\Microsoft.Extensions.Logging.Abstractions.2.1.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentFTP" version="27.1.1" targetFramework="net471" />
|
||||
</packages>
|
||||
<package id="FluentFTP" version="46.0.2" targetFramework="net471" />
|
||||
<package id="Microsoft.Extensions.Logging.Abstractions" version="2.1.0" targetFramework="net471" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user