From 02d0318bf249616f6de45607b08e629f59899340 Mon Sep 17 00:00:00 2001 From: Steve Ives Date: Thu, 22 Jan 2026 13:18:30 -0800 Subject: [PATCH 1/7] Working on real-time sync issue and simplified log messages. --- SFTPSyncLib/RemoteSync.cs | 35 +++++++++++++++++++++++++---------- SFTPSyncLib/SyncDirector.cs | 6 ++++-- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/SFTPSyncLib/RemoteSync.cs b/SFTPSyncLib/RemoteSync.cs index 71e015b..cd4a108 100644 --- a/SFTPSyncLib/RemoteSync.cs +++ b/SFTPSyncLib/RemoteSync.cs @@ -33,8 +33,9 @@ public RemoteSync(string host, string username, string password, _username = username; _password = password; _searchPattern = searchPattern; - _localRootDirectory = localRootDirectory; - _remoteRootDirectory = remoteRootDirectory; + _localRootDirectory = Path.GetFullPath(localRootDirectory) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + _remoteRootDirectory = remoteRootDirectory.TrimEnd('/', '\\'); _director = director; _excludedFolders = excludedFolders ?? new List(); _sftp = new SftpClient(host, username, password); @@ -62,8 +63,9 @@ public RemoteSync(string host, string username, string password, _username = username; _password = password; _searchPattern = searchPattern; - _localRootDirectory = localRootDirectory; - _remoteRootDirectory = remoteRootDirectory; + _localRootDirectory = Path.GetFullPath(localRootDirectory) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + _remoteRootDirectory = remoteRootDirectory.TrimEnd('/', '\\'); _director = director; _excludedFolders = excludedFolders ?? new List(); _sftp = new SftpClient(host, username, password); @@ -150,7 +152,7 @@ public static async Task RunSharedInitialSyncAsync( /// Path to the destination file public static void SyncFile(SftpClient sftp, string sourcePath, string destinationPath) { - Logger.LogInfo($"Syncing {sourcePath} -> {destinationPath}"); + Logger.LogInfo($"Syncing {sourcePath}"); int retryCount = 0; const int maxRetries = 5; @@ -214,7 +216,7 @@ private string[] FilteredDirectories(string localPath) public async Task CreateDirectories(string localPath, string remotePath) { - Logger.LogInfo($"Creating directory {localPath} -> {remotePath}"); + Logger.LogInfo($"Creating directory {remotePath}"); try { @@ -383,6 +385,19 @@ public static bool IsFileReady(String sFilename) } } + private string GetRemotePathForLocal(string localPath) + { + var relativePath = Path.GetRelativePath(_localRootDirectory, localPath); + if (relativePath == "." || string.IsNullOrEmpty(relativePath)) + return _remoteRootDirectory; + + relativePath = relativePath.Replace('\\', '/').TrimStart('/'); + if (relativePath.Length == 0) + return _remoteRootDirectory; + + return _remoteRootDirectory + "/" + relativePath; + } + private void EnsureConnected() { if (_disposed) @@ -417,8 +432,8 @@ private async void Fsw_Changed(object? sender, FileSystemEventArgs arg) || arg.ChangeType == WatcherChangeTypes.Renamed) { var changedPath = Path.GetDirectoryName(arg.FullPath); - var relativePath = _localRootDirectory == changedPath ? "" : changedPath?.Substring(_localRootDirectory.Length).Replace('\\', '/'); - var fullRemotePath = _remoteRootDirectory + relativePath; + var fullRemotePath = GetRemotePathForLocal(changedPath ?? _localRootDirectory); + var fullRemoteFilePath = GetRemotePathForLocal(arg.FullPath); await Task.Yield(); bool makeDirectory = true; lock (_activeDirSync) @@ -439,7 +454,7 @@ private async void Fsw_Changed(object? sender, FileSystemEventArgs arg) if (connectionOk) { //check if we're a new directory - if (makeDirectory && Directory.Exists(arg.FullPath) && !_sftp.Exists(fullRemotePath)) + if (makeDirectory && changedPath != null && Directory.Exists(changedPath) && !_sftp.Exists(fullRemotePath)) { _sftp.CreateDirectory(fullRemotePath); } @@ -501,7 +516,7 @@ private async void Fsw_Changed(object? sender, FileSystemEventArgs arg) fileConnectionOk = EnsureConnectedSafe(); if (fileConnectionOk) { - SyncFile(_sftp, arg.FullPath, fullRemotePath + "/" + Path.GetFileName(arg.FullPath)); + SyncFile(_sftp, arg.FullPath, fullRemoteFilePath); } } finally diff --git a/SFTPSyncLib/SyncDirector.cs b/SFTPSyncLib/SyncDirector.cs index 4d3a25f..7bc2a88 100644 --- a/SFTPSyncLib/SyncDirector.cs +++ b/SFTPSyncLib/SyncDirector.cs @@ -50,8 +50,10 @@ private void Fsw_Created(object sender, FileSystemEventArgs e) public void AddCallback(string match, Action handler) { - string regexPattern = "^" + Regex.Escape(match).Replace("\\*", ".*") + "$"; - callbacks.Add((new Regex(regexPattern), handler)); + string regexPattern = "^" + Regex.Escape(match) + .Replace("\\*", ".*") + .Replace("\\?", ".") + "$"; + callbacks.Add((new Regex(regexPattern, RegexOptions.IgnoreCase), handler)); } private void Fsw_Changed(object sender, FileSystemEventArgs e) From 5aa0134fce8bfffec5b5e80727d2fe0a43bfe75b Mon Sep 17 00:00:00 2001 From: Steve Ives Date: Thu, 22 Jan 2026 14:32:30 -0800 Subject: [PATCH 2/7] WIP: Getting real-time sync working again. --- SFTPSyncLib/RemoteSync.cs | 11 +-- SFTPSyncLib/SyncDirector.cs | 9 +- SFTPSyncUI/SFTPSyncUI.cs | 160 +++++++++++++++++++++++------------- 3 files changed, 113 insertions(+), 67 deletions(-) diff --git a/SFTPSyncLib/RemoteSync.cs b/SFTPSyncLib/RemoteSync.cs index cd4a108..05524c7 100644 --- a/SFTPSyncLib/RemoteSync.cs +++ b/SFTPSyncLib/RemoteSync.cs @@ -33,8 +33,8 @@ public RemoteSync(string host, string username, string password, _username = username; _password = password; _searchPattern = searchPattern; - _localRootDirectory = Path.GetFullPath(localRootDirectory) - .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + _localRootDirectory = Path.TrimEndingDirectorySeparator( + Path.GetFullPath(localRootDirectory)); _remoteRootDirectory = remoteRootDirectory.TrimEnd('/', '\\'); _director = director; _excludedFolders = excludedFolders ?? new List(); @@ -63,8 +63,8 @@ public RemoteSync(string host, string username, string password, _username = username; _password = password; _searchPattern = searchPattern; - _localRootDirectory = Path.GetFullPath(localRootDirectory) - .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + _localRootDirectory = Path.TrimEndingDirectorySeparator( + Path.GetFullPath(localRootDirectory)); _remoteRootDirectory = remoteRootDirectory.TrimEnd('/', '\\'); _director = director; _excludedFolders = excludedFolders ?? new List(); @@ -197,7 +197,7 @@ public static Task> SyncDirectoryAsync(SftpClient sftp, st { if (new DirectoryInfo(sourcePath).EnumerateFiles(searchPattern, SearchOption.TopDirectoryOnly).Any()) { - Logger.LogInfo($"Sync started for {sourcePath}\\{searchPattern} -> {destinationPath}"); + Logger.LogInfo($"Sync started for {sourcePath}\\{searchPattern}"); return Task>.Factory.FromAsync(sftp.BeginSynchronizeDirectories, sftp.EndSynchronizeDirectories, sourcePath, @@ -339,6 +339,7 @@ private static async Task CreateDirectoriesInternal( var directoryName = item.Split(Path.DirectorySeparatorChar).Last(); if (!remoteDirectories.ContainsKey(directoryName)) { + Logger.LogInfo($"Creating remote directory {remotePath}{directoryName}"); sftp.CreateDirectory(remotePath + "/" + directoryName); } await CreateDirectoriesInternal(sftp, localRootDirectory, localPath + "\\" + directoryName, remotePath + "/" + directoryName, excludedFolders); diff --git a/SFTPSyncLib/SyncDirector.cs b/SFTPSyncLib/SyncDirector.cs index 7bc2a88..73c3cda 100644 --- a/SFTPSyncLib/SyncDirector.cs +++ b/SFTPSyncLib/SyncDirector.cs @@ -28,9 +28,10 @@ public SyncDirector(string rootFolder) private void Fsw_Renamed(object sender, RenamedEventArgs e) { + var name = Path.GetFileName(e.FullPath); foreach (var (regex, callback) in callbacks) { - if (regex.IsMatch(e.FullPath)) + if (regex.IsMatch(name)) { callback(e); } @@ -39,9 +40,10 @@ private void Fsw_Renamed(object sender, RenamedEventArgs e) private void Fsw_Created(object sender, FileSystemEventArgs e) { + var name = Path.GetFileName(e.FullPath); foreach (var (regex, callback) in callbacks) { - if (regex.IsMatch(e.FullPath)) + if (regex.IsMatch(name)) { callback(e); } @@ -58,9 +60,10 @@ public void AddCallback(string match, Action handler) private void Fsw_Changed(object sender, FileSystemEventArgs e) { + var name = Path.GetFileName(e.FullPath); foreach (var (regex, callback) in callbacks) { - if (regex.IsMatch(e.FullPath)) + if (regex.IsMatch(name)) { callback(e); } diff --git a/SFTPSyncUI/SFTPSyncUI.cs b/SFTPSyncUI/SFTPSyncUI.cs index 296e91f..c85d05d 100644 --- a/SFTPSyncUI/SFTPSyncUI.cs +++ b/SFTPSyncUI/SFTPSyncUI.cs @@ -210,76 +210,118 @@ public static async void StartSync(Action loggerAction) Logger.LogInfo("Starting sync workers..."); mainForm?.SetStatusBarText("Performing initial sync..."); - var director = new SyncDirector(settings.LocalPath); - - var patterns = settings.LocalSearchPattern - .Split(';', StringSplitOptions.RemoveEmptyEntries) - .Select(pattern => pattern.Trim()) - .Where(pattern => pattern.Length > 0) - .ToArray(); - - if (patterns.Length == 0) + var capturedSettings = settings; + var capturedMainForm = mainForm; + var setStatus = (string text) => { - Logger.LogError("No valid search patterns were configured."); - return; - } + if (capturedMainForm == null) + return; + capturedMainForm.BeginInvoke((Action)(() => capturedMainForm.SetStatusBarText(text))); + }; - Task initialSyncTask; try { - initialSyncTask = RemoteSync.RunSharedInitialSyncAsync( - settings.RemoteHost, - settings.RemoteUsername, - DPAPIEncryption.Decrypt(settings.RemotePassword), - settings.LocalPath, - settings.RemotePath, - patterns, - settings.ExcludedDirectories, - patterns.Length); - } - catch (Exception ex) - { - Logger.LogError($"Failed to start initial sync. Exception: {ex.Message}"); - return; - } - - foreach (var pattern in patterns) - { - try + await Task.Run(async () => { - RemoteSyncWorkers.Add(new RemoteSync( - settings.RemoteHost, - settings.RemoteUsername, - DPAPIEncryption.Decrypt(settings.RemotePassword), - settings.LocalPath, - settings.RemotePath, - pattern, - director, - settings.ExcludedDirectories, - initialSyncTask)); - - Logger.LogInfo($"Started sync worker {RemoteSyncWorkers.Count} for pattern {pattern}"); - } - catch (Exception) - { - Logger.LogError($"Failed to start sync worker for pattern {pattern}"); - } - } + var director = new SyncDirector(capturedSettings.LocalPath); - //Wait for all sync workers to finish initial sync then tell the user - try - { - await initialSyncTask; + var patterns = capturedSettings.LocalSearchPattern + .Split(';', StringSplitOptions.RemoveEmptyEntries) + .Select(pattern => pattern.Trim()) + .Where(pattern => pattern.Length > 0) + .ToArray(); + + if (patterns.Length == 0) + { + Logger.LogError("No valid search patterns were configured."); + setStatus("Invalid search patterns"); + return; + } + + var initialSyncTcs = new TaskCompletionSource(); + var initialSyncTask = initialSyncTcs.Task; + + foreach (var pattern in patterns) + { + try + { + RemoteSyncWorkers.Add(new RemoteSync( + capturedSettings.RemoteHost, + capturedSettings.RemoteUsername, + DPAPIEncryption.Decrypt(capturedSettings.RemotePassword), + capturedSettings.LocalPath, + capturedSettings.RemotePath, + pattern, + director, + capturedSettings.ExcludedDirectories, + initialSyncTask)); + + Logger.LogInfo($"Started sync worker {RemoteSyncWorkers.Count} for pattern {pattern}"); + } + catch (Exception) + { + Logger.LogError($"Failed to start sync worker for pattern {pattern}"); + } + } + + Task runInitialSyncTask; + try + { + runInitialSyncTask = RemoteSync.RunSharedInitialSyncAsync( + capturedSettings.RemoteHost, + capturedSettings.RemoteUsername, + DPAPIEncryption.Decrypt(capturedSettings.RemotePassword), + capturedSettings.LocalPath, + capturedSettings.RemotePath, + patterns, + capturedSettings.ExcludedDirectories, + patterns.Length); + } + catch (Exception ex) + { + Logger.LogError($"Failed to start initial sync. Exception: {ex.Message}"); + setStatus("Initial sync failed"); + return; + } + + runInitialSyncTask.ContinueWith(t => + { + if (t.IsFaulted && t.Exception != null) + { + initialSyncTcs.TrySetException(t.Exception.InnerExceptions); + } + else if (t.IsCanceled) + { + initialSyncTcs.TrySetCanceled(); + } + else + { + initialSyncTcs.TrySetResult(null); + } + }, TaskScheduler.Default); + + //Wait for all sync workers to finish initial sync then tell the user + try + { + await runInitialSyncTask; + } + catch (Exception ex) + { + Logger.LogError($"Initial sync failed. Exception: {ex.Message}"); + setStatus("Initial sync failed"); + return; + } + + Logger.LogInfo("Initial sync complete, real-time sync active"); + setStatus("Real time sync active"); + }); } catch (Exception ex) { - Logger.LogError($"Initial sync failed. Exception: {ex.Message}"); - mainForm?.SetStatusBarText("Initial sync failed"); + Logger.LogError($"Failed to start sync. Exception: {ex.Message}"); + mainForm?.SetStatusBarText("Sync failed"); return; } - - Logger.LogInfo("Initial sync complete, real-time sync active"); - mainForm?.SetStatusBarText("Real time sync active"); } /// From c5b8877b51ab775297e796ddf66c3fb367270138 Mon Sep 17 00:00:00 2001 From: Steve Ives Date: Thu, 22 Jan 2026 15:20:15 -0800 Subject: [PATCH 3/7] More work on improving perf and reinstating real-time sync. --- SFTPSyncLib/Logger.cs | 4 ++-- SFTPSyncLib/RemoteSync.cs | 7 +++++-- SFTPSyncUI/SFTPSyncUI.cs | 14 +++++++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/SFTPSyncLib/Logger.cs b/SFTPSyncLib/Logger.cs index a674394..e3e423c 100644 --- a/SFTPSyncLib/Logger.cs +++ b/SFTPSyncLib/Logger.cs @@ -40,11 +40,11 @@ private static void Log(string message) switch (_mode) { case LoggerMode.Console: - Console.WriteLine($"{DateTime.Now.ToString()} {message}"); + Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {message}"); break; case LoggerMode.RaiseEvent: - LogUpdated?.Invoke($"{DateTime.Now.ToString()} {message}"); + LogUpdated?.Invoke($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {message}"); break; } } diff --git a/SFTPSyncLib/RemoteSync.cs b/SFTPSyncLib/RemoteSync.cs index 05524c7..f826aff 100644 --- a/SFTPSyncLib/RemoteSync.cs +++ b/SFTPSyncLib/RemoteSync.cs @@ -39,7 +39,6 @@ public RemoteSync(string host, string username, string password, _director = director; _excludedFolders = excludedFolders ?? new List(); _sftp = new SftpClient(host, username, password); - _sftp.Connect(); //Our first instance is responsible for creating all of the the directories //Subsequent instances will not be created until this is done @@ -69,7 +68,6 @@ public RemoteSync(string host, string username, string password, _director = director; _excludedFolders = excludedFolders ?? new List(); _sftp = new SftpClient(host, username, password); - _sftp.Connect(); DoneMakingFolders = Task.CompletedTask; DoneInitialSync = initialSyncTask; @@ -424,6 +422,11 @@ private bool EnsureConnectedSafe() } } + public Task ConnectAsync() + { + return Task.Run(() => EnsureConnectedSafe()); + } + private async void Fsw_Changed(object? sender, FileSystemEventArgs arg) { diff --git a/SFTPSyncUI/SFTPSyncUI.cs b/SFTPSyncUI/SFTPSyncUI.cs index c85d05d..6124146 100644 --- a/SFTPSyncUI/SFTPSyncUI.cs +++ b/SFTPSyncUI/SFTPSyncUI.cs @@ -264,7 +264,18 @@ await Task.Run(async () => } } + var connectTasks = new List(); + + Logger.LogInfo($"Establishing SFTP connections for {RemoteSyncWorkers.Count} workers"); + + for (int i = 0; i < RemoteSyncWorkers.Count; i++) + { + connectTasks.Add(RemoteSyncWorkers[i].ConnectAsync()); + } + await Task.WhenAll(connectTasks); + Task runInitialSyncTask; + try { runInitialSyncTask = RemoteSync.RunSharedInitialSyncAsync( @@ -284,7 +295,7 @@ await Task.Run(async () => return; } - runInitialSyncTask.ContinueWith(t => + await runInitialSyncTask.ContinueWith(t => { if (t.IsFaulted && t.Exception != null) { @@ -301,6 +312,7 @@ await Task.Run(async () => }, TaskScheduler.Default); //Wait for all sync workers to finish initial sync then tell the user + try { await runInitialSyncTask; From 8b2c28d0a5acf0e863ea3712eb10e24d8d75f080 Mon Sep 17 00:00:00 2001 From: Steve Ives Date: Thu, 22 Jan 2026 15:34:52 -0800 Subject: [PATCH 4/7] Switch log to scroll down with latest message at top. --- SFTPSyncUI/MainForm.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SFTPSyncUI/MainForm.cs b/SFTPSyncUI/MainForm.cs index 010a841..1310532 100644 --- a/SFTPSyncUI/MainForm.cs +++ b/SFTPSyncUI/MainForm.cs @@ -159,15 +159,15 @@ private void AppendLog(string message) private void AddMessage(string message) { - listBoxMessages.Items.Add(message); + listBoxMessages.Items.Insert(0, message); - // Optional: auto-scroll to bottom - listBoxMessages.TopIndex = listBoxMessages.Items.Count - 1; + // Keep newest at top + listBoxMessages.TopIndex = 0; // Optional: trim excess from UI if _log did a dequeue if (listBoxMessages.Items.Count > 1000) { - listBoxMessages.Items.RemoveAt(0); + listBoxMessages.Items.RemoveAt(listBoxMessages.Items.Count - 1); } } From 3e6ba078ff7f4ee2c7e3ef807bb789aaf7428934 Mon Sep 17 00:00:00 2001 From: Steve Ives Date: Thu, 22 Jan 2026 16:15:31 -0800 Subject: [PATCH 5/7] Code cleanup --- SFTPSyncLib/RemoteSync.cs | 31 ++++++++++++++++--------------- SFTPSyncLib/SyncDirector.cs | 24 ++++++++++++------------ 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/SFTPSyncLib/RemoteSync.cs b/SFTPSyncLib/RemoteSync.cs index f826aff..773df53 100644 --- a/SFTPSyncLib/RemoteSync.cs +++ b/SFTPSyncLib/RemoteSync.cs @@ -33,21 +33,25 @@ public RemoteSync(string host, string username, string password, _username = username; _password = password; _searchPattern = searchPattern; - _localRootDirectory = Path.TrimEndingDirectorySeparator( - Path.GetFullPath(localRootDirectory)); + _localRootDirectory = Path.TrimEndingDirectorySeparator(Path.GetFullPath(localRootDirectory)); _remoteRootDirectory = remoteRootDirectory.TrimEnd('/', '\\'); _director = director; _excludedFolders = excludedFolders ?? new List(); _sftp = new SftpClient(host, username, password); - //Our first instance is responsible for creating all of the the directories - //Subsequent instances will not be created until this is done - DoneMakingFolders = createFolders ? CreateDirectories(_localRootDirectory, _remoteRootDirectory) : Task.CompletedTask; + //The first instance is responsible for creating ALL of the the directories. + //Subsequent instances will not be created until this one completes. + + DoneMakingFolders = createFolders + ? CreateDirectories(_localRootDirectory, _remoteRootDirectory) + : Task.CompletedTask; //Now perform the initial sync for the pattern this instance is responsible for + DoneInitialSync = InitialSync(_localRootDirectory, _remoteRootDirectory); //Once the initial sync is done, we can start watching the file system for changes + DoneInitialSync.ContinueWith((tmp) => { _director.AddCallback(searchPattern, (args) => Fsw_Changed(null, args)); @@ -62,14 +66,14 @@ public RemoteSync(string host, string username, string password, _username = username; _password = password; _searchPattern = searchPattern; - _localRootDirectory = Path.TrimEndingDirectorySeparator( - Path.GetFullPath(localRootDirectory)); + _localRootDirectory = Path.TrimEndingDirectorySeparator(Path.GetFullPath(localRootDirectory)); _remoteRootDirectory = remoteRootDirectory.TrimEnd('/', '\\'); _director = director; _excludedFolders = excludedFolders ?? new List(); _sftp = new SftpClient(host, username, password); DoneMakingFolders = Task.CompletedTask; + DoneInitialSync = initialSyncTask; DoneInitialSync.ContinueWith((tmp) => @@ -79,14 +83,9 @@ public RemoteSync(string host, string username, string password, } public static async Task RunSharedInitialSyncAsync( - string host, - string username, - string password, - string localRootDirectory, - string remoteRootDirectory, - string[] searchPatterns, - List? excludedFolders, - int workerCount) + string host, string username, string password, + string localRootDirectory, string remoteRootDirectory, + string[] searchPatterns, List? excludedFolders, int workerCount) { if (workerCount <= 0 || searchPatterns.Length == 0) { @@ -108,6 +107,7 @@ public static async Task RunSharedInitialSyncAsync( } var workQueue = new ConcurrentQueue(); + foreach (var pair in EnumerateLocalDirectories(localRootDirectory, remoteRootDirectory, excludedFolders)) { foreach (var pattern in searchPatterns) @@ -117,6 +117,7 @@ public static async Task RunSharedInitialSyncAsync( } var workers = new List(); + for (int i = 0; i < workerCount; i++) { workers.Add(Task.Run(async () => diff --git a/SFTPSyncLib/SyncDirector.cs b/SFTPSyncLib/SyncDirector.cs index 73c3cda..813d695 100644 --- a/SFTPSyncLib/SyncDirector.cs +++ b/SFTPSyncLib/SyncDirector.cs @@ -18,15 +18,23 @@ public SyncDirector(string rootFolder) InternalBufferSize = 64 * 1024 }; - _fsw.Changed += Fsw_Changed; _fsw.Created += Fsw_Created; + _fsw.Changed += Fsw_Changed; _fsw.Renamed += Fsw_Renamed; _fsw.Error += Fsw_Error; _fsw.EnableRaisingEvents = true; } - private void Fsw_Renamed(object sender, RenamedEventArgs e) + public void AddCallback(string match, Action handler) + { + string regexPattern = "^" + Regex.Escape(match) + .Replace("\\*", ".*") + .Replace("\\?", ".") + "$"; + callbacks.Add((new Regex(regexPattern, RegexOptions.IgnoreCase), handler)); + } + + private void Fsw_Created(object sender, FileSystemEventArgs e) { var name = Path.GetFileName(e.FullPath); foreach (var (regex, callback) in callbacks) @@ -38,7 +46,7 @@ private void Fsw_Renamed(object sender, RenamedEventArgs e) } } - private void Fsw_Created(object sender, FileSystemEventArgs e) + private void Fsw_Changed(object sender, FileSystemEventArgs e) { var name = Path.GetFileName(e.FullPath); foreach (var (regex, callback) in callbacks) @@ -50,15 +58,7 @@ private void Fsw_Created(object sender, FileSystemEventArgs e) } } - public void AddCallback(string match, Action handler) - { - string regexPattern = "^" + Regex.Escape(match) - .Replace("\\*", ".*") - .Replace("\\?", ".") + "$"; - callbacks.Add((new Regex(regexPattern, RegexOptions.IgnoreCase), handler)); - } - - private void Fsw_Changed(object sender, FileSystemEventArgs e) + private void Fsw_Renamed(object sender, RenamedEventArgs e) { var name = Path.GetFileName(e.FullPath); foreach (var (regex, callback) in callbacks) From da2519e695b62216eed9f7609b3c7152ba0e8489 Mon Sep 17 00:00:00 2001 From: Steve Ives Date: Thu, 22 Jan 2026 16:53:10 -0800 Subject: [PATCH 6/7] WIP: First pattern regex? --- SFTPSyncLib/RemoteSync.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/SFTPSyncLib/RemoteSync.cs b/SFTPSyncLib/RemoteSync.cs index 773df53..6d0389a 100644 --- a/SFTPSyncLib/RemoteSync.cs +++ b/SFTPSyncLib/RemoteSync.cs @@ -50,12 +50,8 @@ public RemoteSync(string host, string username, string password, DoneInitialSync = InitialSync(_localRootDirectory, _remoteRootDirectory); - //Once the initial sync is done, we can start watching the file system for changes - - DoneInitialSync.ContinueWith((tmp) => - { - _director.AddCallback(searchPattern, (args) => Fsw_Changed(null, args)); - }); + // Register callbacks immediately; handler will ignore events until initial sync completes. + _director.AddCallback(searchPattern, (args) => Fsw_Changed(null, args)); } public RemoteSync(string host, string username, string password, @@ -76,10 +72,8 @@ public RemoteSync(string host, string username, string password, DoneInitialSync = initialSyncTask; - DoneInitialSync.ContinueWith((tmp) => - { - _director.AddCallback(searchPattern, (args) => Fsw_Changed(null, args)); - }); + // Register callbacks immediately; handler will ignore events until initial sync completes. + _director.AddCallback(searchPattern, (args) => Fsw_Changed(null, args)); } public static async Task RunSharedInitialSyncAsync( @@ -433,6 +427,9 @@ private async void Fsw_Changed(object? sender, FileSystemEventArgs arg) { try { + if (!DoneInitialSync.IsCompleted) + return; + if (arg.ChangeType == WatcherChangeTypes.Changed || arg.ChangeType == WatcherChangeTypes.Created || arg.ChangeType == WatcherChangeTypes.Renamed) { From 3e663e182cdab4fab34b07963507100a7e1b43a5 Mon Sep 17 00:00:00 2001 From: Steve Ives Date: Thu, 22 Jan 2026 17:02:40 -0800 Subject: [PATCH 7/7] Bump version number to 1.6. --- SFTPSyncSetup/Product.wxs | 2 +- SFTPSyncSetup/SFTPSyncSetup.wixproj | 2 +- SFTPSyncUI/SFTPSyncUI.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SFTPSyncSetup/Product.wxs b/SFTPSyncSetup/Product.wxs index 927e67c..26196c9 100644 --- a/SFTPSyncSetup/Product.wxs +++ b/SFTPSyncSetup/Product.wxs @@ -5,7 +5,7 @@ Id="*" Name="SFTPSync" Language="1033" - Version="1.5" + Version="1.6" Manufacturer="Synergex International Corporation" UpgradeCode="6000f870-b811-4e22-b80b-5b8956317d09"> diff --git a/SFTPSyncSetup/SFTPSyncSetup.wixproj b/SFTPSyncSetup/SFTPSyncSetup.wixproj index 33fabd6..54c01f4 100644 --- a/SFTPSyncSetup/SFTPSyncSetup.wixproj +++ b/SFTPSyncSetup/SFTPSyncSetup.wixproj @@ -6,7 +6,7 @@ 3.10 5074cbcb-641b-4a9c-b3bc-8dd0b78810a6 2.0 - SFTPSync-1.5 + SFTPSync-1.6 Package SFTPSyncSetup diff --git a/SFTPSyncUI/SFTPSyncUI.csproj b/SFTPSyncUI/SFTPSyncUI.csproj index a864dd6..f2d3759 100644 --- a/SFTPSyncUI/SFTPSyncUI.csproj +++ b/SFTPSyncUI/SFTPSyncUI.csproj @@ -20,10 +20,10 @@ LICENSE.md True Synergex International, Inc. - 1.5 - 1.5 + 1.6 + 1.6 preview - 1.5 + 1.6