Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions CloudFtpBridge.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30717.126
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CA2FA16E-EF5A-42C1-90F4-0A9351CE1A22}"
EndProject
Expand All @@ -25,9 +25,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudFtpBridge.Infrastructure.Smtp", "src\CloudFtpBridge.Infrastructure.Smtp\CloudFtpBridge.Infrastructure.Smtp.csproj", "{AB3C17C0-1FB9-4EB1-9B61-ED55646E4B4B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudFtpBridge.Infrastructure.Smtp", "src\CloudFtpBridge.Infrastructure.Smtp\CloudFtpBridge.Infrastructure.Smtp.csproj", "{AB3C17C0-1FB9-4EB1-9B61-ED55646E4B4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudFtpBridge.Infrastructure.Json", "src\CloudFtpBridge.Infrastructure.Json\CloudFtpBridge.Infrastructure.Json.csproj", "{A6D99988-DB0B-4DCC-9A81-08A802526FFC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudFtpBridge.Infrastructure.Json", "src\CloudFtpBridge.Infrastructure.Json\CloudFtpBridge.Infrastructure.Json.csproj", "{A6D99988-DB0B-4DCC-9A81-08A802526FFC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudFtpBridge.Infrastructure.FTP", "src\CloudFtpBridge.Infrastructure.FTP\CloudFtpBridge.Infrastructure.FTP.csproj", "{0E9CF9DA-7F24-4CEE-9AA7-49AD78ACB75F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -63,6 +65,10 @@ Global
{A6D99988-DB0B-4DCC-9A81-08A802526FFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6D99988-DB0B-4DCC-9A81-08A802526FFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6D99988-DB0B-4DCC-9A81-08A802526FFC}.Release|Any CPU.Build.0 = Release|Any CPU
{0E9CF9DA-7F24-4CEE-9AA7-49AD78ACB75F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E9CF9DA-7F24-4CEE-9AA7-49AD78ACB75F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E9CF9DA-7F24-4CEE-9AA7-49AD78ACB75F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E9CF9DA-7F24-4CEE-9AA7-49AD78ACB75F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -76,6 +82,7 @@ Global
{9BA84E2D-1E91-4F85-854B-90DF0BC5461E} = {CA2FA16E-EF5A-42C1-90F4-0A9351CE1A22}
{AB3C17C0-1FB9-4EB1-9B61-ED55646E4B4B} = {C27977BE-7208-4B2A-8DC9-47EE43386957}
{A6D99988-DB0B-4DCC-9A81-08A802526FFC} = {C27977BE-7208-4B2A-8DC9-47EE43386957}
{0E9CF9DA-7F24-4CEE-9AA7-49AD78ACB75F} = {C27977BE-7208-4B2A-8DC9-47EE43386957}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B3E20D95-3A68-4E34-ADCB-83BD3A715038}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CloudFtpBridge.Infrastructure.FTP\CloudFtpBridge.Infrastructure.FTP.csproj" />
<ProjectReference Include="..\CloudFtpBridge.Core\CloudFtpBridge.Core.csproj" />
<ProjectReference Include="..\CloudFtpBridge.Infrastructure.FluentFTP\CloudFtpBridge.Infrastructure.FluentFTP.csproj" />
<ProjectReference Include="..\CloudFtpBridge.Infrastructure.Json\CloudFtpBridge.Infrastructure.Json.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
<div class="form-group">
<label class="font-weight-bold">Source File System</label>
<InputSelect @bind-Value="Workflow.SourceFileSystemType" class="form-control">
<option value="@SupportedFileSystems.FluentFTP">FTP</option>
<option value="@SupportedFileSystems.FluentFTP">FTP (FluentFTP)</option>
<option value="@SupportedFileSystems.FTP">FTP (FtpWebRequest)</option>
<option value="@SupportedFileSystems.Local">Local File System</option>
</InputSelect>
<small class="form-text text-muted">The source location where files will be transferred FROM.</small>
Expand Down Expand Up @@ -72,6 +73,9 @@
case SupportedFileSystems.FluentFTP:
<FluentFTPOptions Options="Workflow.SourceFileSystemConfig" />
break;
case SupportedFileSystems.FTP:
<FTPOptions Options="Workflow.SourceFileSystemConfig" />
break;
case SupportedFileSystems.Local:
<LocalOptions Options="Workflow.SourceFileSystemConfig" />
break;
Expand All @@ -85,7 +89,8 @@
<div class="form-group">
<label class="font-weight-bold">Destination File System</label>
<InputSelect @bind-Value="Workflow.DestinationFileSystemType" class="form-control">
<option value="@SupportedFileSystems.FluentFTP">FTP</option>
<option value="@SupportedFileSystems.FluentFTP">FTP (FluentFTP)</option>
<option value="@SupportedFileSystems.FTP">FTP (FtpWebRequest)</option>
<option value="@SupportedFileSystems.Local">Local File System</option>
</InputSelect>
<small class="form-text text-muted">The destination location where files will be transferred TO.</small>
Expand All @@ -108,6 +113,9 @@
case SupportedFileSystems.FluentFTP:
<FluentFTPOptions Options="Workflow.DestinationFileSystemConfig" />
break;
case SupportedFileSystems.FTP:
<FTPOptions Options="Workflow.DestinationFileSystemConfig" />
break;
case SupportedFileSystems.Local:
<LocalOptions Options="Workflow.DestinationFileSystemConfig" />
break;
Expand Down
1 change: 1 addition & 0 deletions src/CloudFtpBridge.BlazorApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>

app.UseFluentFTPFileSystem();
app.UseLocalFileSystem();
app.UseFTPFileSystem();
});

services.AddHostedService<Worker>();
Expand Down
143 changes: 143 additions & 0 deletions src/CloudFtpBridge.BlazorApp/Shared/FileSystemOptions/FTPOptions.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
@using CloudFtpBridge.Infrastructure.FTP

<div class="row">
<div class="col-10">
<div class="form-group">
<label>Host</label>
<input @onchange="@(e => Set("Host", e.Value))" class="form-control" type="text" value="@(Get("Host"))" />
</div>
</div>
<div class="col-2">
<div class="form-group">
<label>Port</label>
<input @onchange="@(e => Set("Port", e.Value))" class="form-control" type="number" value="@(Get("Port"))" />
</div>
</div>
</div>

<div class="row">
<div class="col">
<div class="form-group">
<label>Path</label>
<input @onchange="@(e => Set("Path", e.Value))" class="form-control" type="text" value="@(Get("Path"))" />
<small class="form-text text-muted">The full path to the directory. Usually starts with a forward slash (/).</small>
</div>
</div>
</div>

<div class="row">
<div class="col">
<div class="form-group">
<label>Username</label>
<input @onchange="@(e => Set("Username", e.Value))" class="form-control" type="text" value="@(Get("Username"))" />
</div>
</div>
<div class="col">
<div class="form-group">
<label>Password</label>
<input @onchange="@(e => Set("Password", e.Value))" class="form-control" type="password" value="@(Get("Password"))" />
</div>
</div>
</div>

<div class="row">
<div class="col">
<div class="custom-control custom-checkbox">
<input @onchange="@(e => AutoConnect = !AutoConnect)" checked="@(AutoConnect)" class="custom-control-input" id="@($"AutoConnect_{ComponentId}")" type="checkbox" />
<label class="custom-control-label" for="@($"AutoConnect_{ComponentId}")">Auto-Connect</label>
<small class="form-text text-muted">When enabled, FTP will utilize passive connectivity.</small>
</div>
</div>
</div>

<div class="row">
<div class="col">
<div class="custom-control custom-checkbox">
<input @onchange="@(e => UseFtps = !UseFtps)" checked="@(UseFtps)" class="custom-control-input" id="@($"UseFtps_{ComponentId}")" type="checkbox" />
<label class="custom-control-label" for="@($"UseFtps_{ComponentId}")">Use Ftps</label>
</div>
</div>
</div>

@if (!AutoConnect)
{
<div class="row mt-2">
<div class="col">
<div class="form-group">
<label>Data Connection Type</label>
<InputSelect class="form-control" @bind-Value="DataConnectionType">
<option value="Passive">Passive</option>
<option value="Active">Active</option>
</InputSelect>
</div>
</div>
</div>
}

@code
{
public const string ConfigPrefix = "CloudFtpBridge:Infrastructure:FTP:";

[Parameter]
public Dictionary<string, string> Options { get; set; } = new Dictionary<string, string>();

protected string ComponentId { get; } = Guid.NewGuid().ToString();

protected bool AutoConnect
{
get
{
var value = Get("AutoConnect");

return string.IsNullOrWhiteSpace(value) ? false : bool.Parse(value);
}

set => Set("AutoConnect", value);
}

protected bool UseFtps
{
get
{
var value = Get("UseFtps");

return string.IsNullOrWhiteSpace(value) ? false : bool.Parse(value);
}

set => Set("UseFtps", value);
}

protected string DataConnectionType
{
get => Get("DataConnectionType");
set => Set("DataConnectionType", value);
}

protected string Get(string propertyName)
{
var key = $"{ConfigPrefix}{propertyName}";

if (!Options.ContainsKey(key))
{
return string.Empty;
}

return Options[key];
}

protected void Set(string propertyName, object value)
{
var key = $"{ConfigPrefix}{propertyName}";

Options[key] = value?.ToString();
}

protected override void OnParametersSet()
{
if (!Options.Any())
{
// set defaults for new workflows
new FTPFileSystemOptions().ToStringDictionary(ConfigPrefix, Options);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public static class SupportedFileSystems
{
public const string FluentFTP = "CloudFtpBridge.Infrastructure.FluentFTP.FluentFTPFileSystem";
public const string FTP = "CloudFtpBridge.Infrastructure.FTP.FTPFileSystem";
public const string Local = "CloudFtpBridge.Infrastructure.LocalFileSystem.LocalFileSystem";
}
}
1 change: 1 addition & 0 deletions src/CloudFtpBridge.Core/Services/FileSystemActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public IFileSystem Activate(string fileSystemTypeName, IDictionary<string, strin
.Where(t => !t.IsInterface && typeof(IFileSystem).IsAssignableFrom(t))
.FirstOrDefault(t => t.FullName.Equals(fileSystemTypeName));


if (fileSystemType == null)
{
_logger.LogError("No file system implementation could be found matching the name {FileSystemType}.", fileSystemTypeName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\CloudFtpBridge.Core\CloudFtpBridge.Core.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CloudFtpBridge.Infrastructure.FTP;

namespace Microsoft.Extensions.DependencyInjection
{
public static class CloudFtpBridgeAppBuilderExtensions
{
public static CloudFtpBridgeAppBuilder UseFTPFileSystem(this CloudFtpBridgeAppBuilder builder)
{
builder.AddTransient<FTPFileSystem>();
return builder;
}
}
}
85 changes: 85 additions & 0 deletions src/CloudFtpBridge.Infrastructure.FTP/FTPFileSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using CloudFtpBridge.Core.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CloudFtpBridge.Core.Models;
using CloudFtpBridge.Core.Utils;

namespace CloudFtpBridge.Infrastructure.FTP
{
public class FTPFileSystem : IFileSystem
{
private readonly FTPFileSystemOptions _options = new FTPFileSystemOptions();
private readonly FtpWebRequestClient _ftpClient;

public FTPFileSystem(
IConfiguration configuration,
ILogger<FTPFileSystem> logger)
{
configuration.GetSection("CloudFtpBridge:Infrastructure:FTP").Bind(_options);
bool passive = _options.AutoConnect || _options.DataConnectionType.Equals("Passive");

_ftpClient = new FtpWebRequestClient(_options.Host, _options.Path, _options.Port, _options.Username,
_options.Password, logger, passive, _options.UseFtps);
}

public async Task Delete(string fileName)
{
await Task.Run(() =>_ftpClient.Delete(fileName));
}

public async Task<bool> HasFiles()
{
var names = await Task.Run(()=> _ftpClient.GetListOfFiles());
return names.Count > 0;
}

public async Task<IReadOnlyCollection<FileRef>> List()
{
var items = await Task.Run(() => _ftpClient.GetListOfFiles());
return items
.Select(i => new FileRef(i))
.ToArray();
}

public async Task<Stream> Read(string fileName)
{
var memStream = new MemoryStream();

var stream = await Task.Run(() => _ftpClient.GetFile(fileName));

if (stream != null)
{
await stream.CopyToAsync(memStream);
memStream.Seek(0, SeekOrigin.Begin);
}

return memStream;
}

public async Task Rename(string oldFileName, string newFileName, bool overwriteExisting)
{
if (!overwriteExisting && await Task.Run(()=>_ftpClient.FileExists(newFileName)))
{
throw new InvalidOperationException($"Unable to rename {oldFileName}. The file {newFileName} already exists.");
}

await Task.Run(()=>_ftpClient.Rename(oldFileName, newFileName));
}

public async Task Write(string fileName, Stream fromStream)
{
if (fromStream.CanSeek)
{
fromStream.Seek(0, SeekOrigin.Begin);
}

await Task.Run(()=>_ftpClient.SendToFtp(fileName, fromStream));
}
}
}
Loading