Skip to content
Closed
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
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
--configuration Release `
--runtime win-x64 `
--self-contained true `
--output SFTPSync/bin/Release/net8.0-windows/publish/win-x64/ `
--output SFTPSync/bin/Release/net8.0-windows `
/p:PublishSingleFile=true `
/p:PublishReadyToRun=true

Expand All @@ -51,7 +51,7 @@ jobs:
--configuration Release `
--runtime win-x64 `
--self-contained true `
--output SFTPSyncStop/bin/Release/net8.0-windows/publish/win-x64/ `
--output SFTPSyncStop/bin/Release/net8.0-windows `
/p:PublishSingleFile=true `
/p:PublishReadyToRun=true

Expand All @@ -61,7 +61,7 @@ jobs:
--configuration Release `
--runtime win-x64 `
--self-contained true `
--output SFTPSyncUI/bin/Release/net8.0-windows/publish/win-x64/ `
--output SFTPSyncUI/bin/Release/net8.0-windows `
/p:PublishSingleFile=true `
/p:PublishReadyToRun=true

Expand All @@ -75,7 +75,7 @@ jobs:
AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }}
shell: pwsh
run: |
$publishDirs = @("SFTPSync/bin/Release/net8.0-windows/publish/win-x64", "SFTPSyncStop/bin/Release/net8.0-windows/publish/win-x64", "SFTPSyncUI/bin/Release/net8.0-windows/publish/win-x64")
$publishDirs = @("SFTPSync/bin/Release/net8.0-windows", "SFTPSyncStop/bin/Release/net8.0-windows", "SFTPSyncUI/bin/Release/net8.0-windows")
foreach ($dir in $publishDirs) {
if (Test-Path $dir) {
$files = Get-ChildItem -Path $dir -Include *.exe, *.dll -Recurse
Expand Down
Binary file added SFTPSync.chm-keep
Binary file not shown.
170 changes: 132 additions & 38 deletions SFTPSyncLib/RemoteSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public class RemoteSync : IDisposable
SftpClient _sftp;
SyncDirector _director;
HashSet<string> _activeDirSync = new HashSet<string>();
readonly SemaphoreSlim _sftpLock = new SemaphoreSlim(1, 1);
bool _disposed;


public Task DoneMakingFolders { get; }
Expand Down Expand Up @@ -85,7 +87,7 @@ public static void SyncFile(SftpClient sftp, string sourcePath, string destinati

return;
}
catch (Exception ex) when (ex is IOException || ex is FileNotFoundException)
catch (Exception ex)
{
retryCount++;
if (retryCount >= maxRetries)
Expand Down Expand Up @@ -150,6 +152,9 @@ public async Task CreateDirectories(string localPath, string remotePath)

try
{
if (!EnsureConnectedSafe())
return;

//Got local directories to sync
var localDirectories = FilteredDirectories(localPath);

Expand Down Expand Up @@ -194,6 +199,9 @@ public async Task InitialSync(string localPath, string remotePath)
//Wait for the folders to be created before starting the initial sync
await DoneMakingFolders;

if (!EnsureConnectedSafe())
return;

//Get the local directories to sync
var localDirectories = FilteredDirectories(localPath);

Expand Down Expand Up @@ -256,65 +264,151 @@ public static bool IsFileReady(String sFilename)
}
}

private void EnsureConnected()
{
if (_disposed)
throw new ObjectDisposedException(nameof(RemoteSync));

private async void Fsw_Changed(object? sender, FileSystemEventArgs arg)
if (_sftp.IsConnected)
return;

_sftp.Connect();
}

private bool EnsureConnectedSafe()
{
if (arg.ChangeType == WatcherChangeTypes.Changed || arg.ChangeType == WatcherChangeTypes.Created
|| arg.ChangeType == WatcherChangeTypes.Renamed)
try
{
var changedPath = Path.GetDirectoryName(arg.FullPath);
var relativePath = _localRootDirectory == changedPath ? "" : changedPath?.Substring(_localRootDirectory.Length).Replace('\\', '/');
var fullRemotePath = _remoteRootDirectory + relativePath;
await Task.Yield();
bool makeDirectory = true;
lock (_activeDirSync)
{
if (changedPath == null)
return;
if (_activeDirSync.Contains(changedPath))
makeDirectory = false;
else
_activeDirSync.Add(changedPath);
}
EnsureConnected();
return true;
}
catch (Exception ex)
{
Logger.LogError($"SFTP connection error: {ex.Message}");
return false;
}
}

//check if we're a new directory
if (makeDirectory && Directory.Exists(arg.FullPath) && !_sftp.Exists(arg.FullPath))
{
_sftp.CreateDirectory(fullRemotePath);
}

if (makeDirectory)
private async void Fsw_Changed(object? sender, FileSystemEventArgs arg)
{
try
{
if (arg.ChangeType == WatcherChangeTypes.Changed || arg.ChangeType == WatcherChangeTypes.Created
|| arg.ChangeType == WatcherChangeTypes.Renamed)
{
var changedPath = Path.GetDirectoryName(arg.FullPath);
var relativePath = _localRootDirectory == changedPath ? "" : changedPath?.Substring(_localRootDirectory.Length).Replace('\\', '/');
var fullRemotePath = _remoteRootDirectory + relativePath;
await Task.Yield();
bool makeDirectory = true;
lock (_activeDirSync)
{
_activeDirSync.Remove(changedPath);
if (changedPath == null)
return;
if (_activeDirSync.Contains(changedPath))
makeDirectory = false;
else
_activeDirSync.Add(changedPath);
}
}

while (!IsFileReady(arg.FullPath))
await Task.Delay(25);
bool connectionOk;
await _sftpLock.WaitAsync();
try
{
connectionOk = EnsureConnectedSafe();
if (connectionOk)
{
//check if we're a new directory
if (makeDirectory && Directory.Exists(arg.FullPath) && !_sftp.Exists(fullRemotePath))
{
_sftp.CreateDirectory(fullRemotePath);
}
}
}
finally
{
_sftpLock.Release();
}

if (!connectionOk)
{
if (makeDirectory)
{
lock (_activeDirSync)
{
_activeDirSync.Remove(changedPath);
}
}
return;
}

lock (_activeDirSync)
{
if (_activeDirSync.Contains(arg.FullPath))
if (makeDirectory)
{
lock (_activeDirSync)
{
_activeDirSync.Remove(changedPath);
}
}

if (Directory.Exists(arg.FullPath))
return;
else
_activeDirSync.Add(arg.FullPath);
}
SyncFile(_sftp, arg.FullPath, fullRemotePath + "/" + Path.GetFileName(arg.FullPath) );

lock (_activeDirSync)
{
_activeDirSync.Remove(arg.FullPath);
var waitStart = DateTime.UtcNow;
while (!IsFileReady(arg.FullPath))
{
if (!File.Exists(arg.FullPath))
return;
if (DateTime.UtcNow - waitStart > TimeSpan.FromSeconds(30))
{
Logger.LogWarnig($"Timed out waiting for file to be ready: {arg.FullPath}");
return;
}
await Task.Delay(25);
}

lock (_activeDirSync)
{
if (_activeDirSync.Contains(arg.FullPath))
return;
else
_activeDirSync.Add(arg.FullPath);
}

bool fileConnectionOk;
await _sftpLock.WaitAsync();
try
{
fileConnectionOk = EnsureConnectedSafe();
if (fileConnectionOk)
{
SyncFile(_sftp, arg.FullPath, fullRemotePath + "/" + Path.GetFileName(arg.FullPath));
}
}
finally
{
_sftpLock.Release();
lock (_activeDirSync)
{
_activeDirSync.Remove(arg.FullPath);
}
}

if (!fileConnectionOk)
return;
}
}
catch (Exception ex)
{
Logger.LogError($"Unhandled exception in file sync handler: {ex.Message}");
}
}

public void Dispose()
{
if (_sftp != null)
{
_disposed = true;
_sftp.Dispose();
}
}
Expand Down
10 changes: 9 additions & 1 deletion SFTPSyncLib/SyncDirector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ public SyncDirector(string rootFolder)
_fsw = new FileSystemWatcher(rootFolder, "*.*")
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
// Max allowed size; helps reduce missed events during bursts.
InternalBufferSize = 64 * 1024
};

_fsw.Changed += Fsw_Changed;
_fsw.Created += Fsw_Created;
_fsw.Renamed += Fsw_Renamed;
_fsw.Error += Fsw_Error;

_fsw.EnableRaisingEvents = true;
}
Expand Down Expand Up @@ -61,5 +64,10 @@ private void Fsw_Changed(object sender, FileSystemEventArgs e)
}
}
}

private void Fsw_Error(object sender, ErrorEventArgs e)
{
Logger.LogError($"FileSystemWatcher error: {e.GetException()?.Message}");
}
}
}
Loading
Loading