From 725a37d9cf40cc77b9f6b668b8b0f69d0b7a3308 Mon Sep 17 00:00:00 2001 From: Mario Steele Date: Thu, 31 Jul 2025 16:13:49 -0500 Subject: [PATCH 1/4] Updated DBSyncWatcher Added new read code for reading data in from a database file. Added Logger to log when a file is created, or the file has been changed. Changed Error to log as error, instead of Console writting. --- FreeTubeSyncer/Library/DBSyncWatcher.cs | 38 ++++++++++++++++++++----- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/FreeTubeSyncer/Library/DBSyncWatcher.cs b/FreeTubeSyncer/Library/DBSyncWatcher.cs index 2abd29f..f7a6bbf 100644 --- a/FreeTubeSyncer/Library/DBSyncWatcher.cs +++ b/FreeTubeSyncer/Library/DBSyncWatcher.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Threading; +using Serilog; namespace FreeTubeSyncer.Library; @@ -44,8 +45,15 @@ public class DBSyncWatcher if (!WatchFiles.Keys.Contains(dbName)) return; - var data = File.ReadAllText(e.FullPath); - foreach (var line in data.Split('\n')) + Log.Information("Database File Changed: {DbName}", dbName); + + var data = new List(); + + using var fh = File.OpenText(e.FullPath); + while (!fh.EndOfStream) + data.Add(fh.ReadLine() ?? string.Empty); + + foreach (var line in data) { if (line == "") continue; var type = WatchFiles[dbName]; @@ -56,11 +64,27 @@ public class DBSyncWatcher private void HandleCreated(object sender, FileSystemEventArgs e) { if (e.ChangeType != WatcherChangeTypes.Created) return; - var dbName = Path.GetFileName(e.FullPath); - if (!WatchFiles.Keys.Contains(dbName)) return; - var data = File.ReadAllText(e.FullPath); - foreach (var line in data.Split('\n')) + + while (Locked) { + Thread.Sleep(100); + } + + var dbName = Path.GetFileName(e.FullPath); + + if (!WatchFiles.Keys.Contains(dbName)) return; + + Log.Information("Database File Created: {DbName}", dbName); + + var data = new List(); + using var fh = File.OpenText(e.FullPath); + + while (!fh.EndOfStream) + data.Add(fh.ReadLine() ?? string.Empty); + + foreach (var line in data) + { + if (line == "") continue; var type = WatchFiles[dbName]; OnDatabaseChange?.Invoke(dbName, line); } @@ -68,6 +92,6 @@ public class DBSyncWatcher private void HandleError(object sender, ErrorEventArgs e) { - Console.WriteLine("Error: {0}\n{1}", e.GetException().Message, e.GetException().StackTrace); + Log.Error("Error: {Message}\n{StackTrace}", e.GetException().Message, e.GetException().StackTrace); } } \ No newline at end of file From c94b7091451cfbfee5186f927296c44dfd62db1c Mon Sep 17 00:00:00 2001 From: Mario Steele Date: Thu, 31 Jul 2025 16:14:21 -0500 Subject: [PATCH 2/4] Updated Syncer Added Logging functionality to write to log files. --- FreeTubeSyncer/REST/Syncer.cs | 49 +++++++++++++---------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/FreeTubeSyncer/REST/Syncer.cs b/FreeTubeSyncer/REST/Syncer.cs index 71ace55..b92c4ed 100644 --- a/FreeTubeSyncer/REST/Syncer.cs +++ b/FreeTubeSyncer/REST/Syncer.cs @@ -10,6 +10,8 @@ using System.Threading.Tasks; using FreeTubeSyncer.Library; using FreeTubeSyncer.Models.DatabaseModels; using RestSharp; +using Serilog; +using Serilog.Core; namespace FreeTubeSyncer.REST; @@ -68,34 +70,29 @@ public class Syncer : ISyncer where T : class, IDataModel, new() public async Task PingApi() { - // TODO: Replace with Logger - Console.WriteLine($"Pinging API at {_client.BuildUri(new RestRequest("/ping"))}..."); + Log.Information("Pinging API at {BuildUri}...", _client.BuildUri(new RestRequest("/ping"))); try { var res = await _client.GetAsync(new RestRequest("/ping")); if (res == null) { - // TODO: Replace with Logger - Console.WriteLine($"Ping returned null, not the server we are looking for!"); + Log.Information("Ping returned null, not the server we are looking for!"); return false; } if (res.AppVersion == "0.1.3") { - // TODO: Replace with Logger - Console.WriteLine($"Server Online! {res.AppVersion}"); + Log.Information("Server Online! {AppVersion}", res.AppVersion); return true; } } catch (Exception ex) { - // TODO: Replace with Logger - Console.WriteLine($"Network Error: {ex.Message}, API not alive."); + Log.Error("API not alive. Network Error: {Message}", ex.Message); return false; } - - // TODO: Replace with Logger - Console.WriteLine("Responded with something other then 404, API not what we expected."); + + Log.Error("Responded with something other then Ping. This is not the server we are looking for..."); return false; } @@ -128,8 +125,7 @@ public class Syncer : ISyncer where T : class, IDataModel, new() if (_entries.Any(x => x.EqualId(item.Id()))) _entries.RemoveAll(x => x.EqualId(item.Id())); _entries.Add(item); - // TODO: Replace with Logger - Console.WriteLine($"Posting to REST: {item.Id()}"); + Log.Information("Posting to REST: {ItemId}", item.Id()); await _client.PostJsonAsync(_restEndpoint, item); } } @@ -147,15 +143,11 @@ public class Syncer : ISyncer where T : class, IDataModel, new() if (data.Equals(entry)) continue; - // TODO: Replace with Logger - Console.WriteLine($"Updated Entry from REST for {_dbName} - {entry.Id()}"); + Log.Information("Updated Entry from REST for {DbName} - {EntryId}", _dbName, entry.Id()); _entries.RemoveAll(x => x.EqualId(entry.Id())); } else - { - // TODO: Replace with Logger - Console.WriteLine($"New Entry from REST for {_dbName} - {entry.Id()}"); - } + Log.Information("New Entry from REST for {DbName} - {EntryId}", _dbName, entry.Id()); _entries.Add(entry); _isDirty = true; @@ -186,8 +178,8 @@ public class Syncer : ISyncer where T : class, IDataModel, new() } catch (Exception iex) { - // TODO: Replace with Logger - Console.WriteLine($"Failed to parse line: {entryObject}\nMessage: {iex.Message}"); + Log.Error("Failed to parse line: {EntryLine}", entryObject); + Log.Error("Error Message: {Messsage}", iex.Message); entry = null; } } @@ -199,15 +191,11 @@ public class Syncer : ISyncer where T : class, IDataModel, new() { var data = _entries.First(x => x.EqualId(entry.Id())); if (data.Equals(entry)) return; - // TODO: Replace with Logger - Console.WriteLine($"File Entry {entry.Id()} updated for {_dbName}"); + Log.Information("Updated File Entry {EntryId} updated for {DbName}", entry.Id(), _dbName); _entries.RemoveAll(x => x.EqualId(entry.Id())); } else - { - // TODO: Replace with Logger - Console.WriteLine($"New File Entry {entry.Id()} for {_dbName}"); - } + Log.Information("New File Entry {EntryId} for {DbName}", entry.Id(), _dbName); _entries.Add(entry); await _client.PostJsonAsync(_restEndpoint, entry); @@ -219,8 +207,8 @@ public class Syncer : ISyncer where T : class, IDataModel, new() if (!_isDirty) return; _syncing = true; - // TODO: Replace with Logger - Console.WriteLine($"Syncing {_dbPath}..."); + Log.Information("Syncing {DbName}...", _dbPath); + var start = DateTime.Now; var json = new List(); foreach (var entry in _entries) json.Add(entry.JsonData()); @@ -233,8 +221,7 @@ public class Syncer : ISyncer where T : class, IDataModel, new() fh.Close(); } _watcher.Locked = false; - // TODO: Replace with Logger - Console.WriteLine($"Updated {_dbPath}."); + Log.Information("Updated {DbName}, completed in {TimeSpan}", _dbName, DateTime.Now - start); _isDirty = false; _syncing = false; } From d8e650deec293e40d8465fa52690e24db19529db Mon Sep 17 00:00:00 2001 From: Mario Steele Date: Thu, 31 Jul 2025 16:15:02 -0500 Subject: [PATCH 3/4] Updated CSProj Updated project from Dotnet 8, to Dotent 9. Ensure compiler is using latest major release. Added Serilog assemblies. --- FreeTubeSyncer/FreeTubeSyncer.csproj | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/FreeTubeSyncer/FreeTubeSyncer.csproj b/FreeTubeSyncer/FreeTubeSyncer.csproj index a7ed908..44fce9e 100644 --- a/FreeTubeSyncer/FreeTubeSyncer.csproj +++ b/FreeTubeSyncer/FreeTubeSyncer.csproj @@ -1,11 +1,12 @@  WinExe - net8.0 + net9.0 enable true app.manifest true + latestmajor @@ -21,6 +22,10 @@ + + + + From 145c863fbeb95c6e7b79a85b27ebbc12b4d79547 Mon Sep 17 00:00:00 2001 From: Mario Steele Date: Thu, 31 Jul 2025 16:17:06 -0500 Subject: [PATCH 4/4] Updated App Updated app to implement Serilog logging functionality. Added GetAppFolder() Changed GetSettingsPath() to use GetAppFolder() Removed all Console.WriteLine() and replaed with Logger functions. Created SetupLogger(), to log to both Console, and a Log file. --- FreeTubeSyncer/App.axaml.cs | 92 +++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/FreeTubeSyncer/App.axaml.cs b/FreeTubeSyncer/App.axaml.cs index 207aee9..4f96b59 100644 --- a/FreeTubeSyncer/App.axaml.cs +++ b/FreeTubeSyncer/App.axaml.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Linq; using System.Text.Json; using System.Threading; @@ -13,6 +14,10 @@ using FreeTubeSyncer.Library; using FreeTubeSyncer.Models; using FreeTubeSyncer.Models.DatabaseModels; using FreeTubeSyncer.REST; +using Serilog; +using Serilog.Sinks.File.GzArchive; +using Serilog.Sinks.FileEx; +using Serilog.Sinks.SystemConsole.Themes; namespace FreeTubeSyncer; @@ -45,6 +50,7 @@ public partial class App : Application public override void OnFrameworkInitializationCompleted() { + SetupLogger(); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { var path = ""; @@ -59,7 +65,7 @@ public partial class App : Application path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".var", "app", "io.freetubeapp.FreeTube", "config", "FreeTube"); if (!Path.Exists(path)) - Console.WriteLine("Failed to find Path for FreeTube!"); + Log.Error("Failed to find Path for FreeTube!"); } } @@ -112,9 +118,8 @@ public partial class App : Application var path = GetSettingsPath(); if (!File.Exists(path)) { - var dir = Path.GetDirectoryName(path); - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); + if (!Directory.Exists(GetAppFolder())) + Directory.CreateDirectory(GetAppFolder()); } File.WriteAllText(path, JsonSerializer.Serialize(_settings)); _settings!.SettingsDirty = false; @@ -129,20 +134,35 @@ public partial class App : Application _settings = new AppSettings(); return; } + var data = File.ReadAllText(path); _settings = JsonSerializer.Deserialize(data); } - - private string GetSettingsPath() => OperatingSystem.IsLinux() - ? Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreeTubeSyncer", "settings.json") - : Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "FreeTubeSyncer", "settings.json"); + + private string GetAppFolder() => Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreeTubeSyncer"); + private string GetSettingsPath() => Path.Join(GetAppFolder(), "settings.json"); + + private void SetupLogger() + { + var path = Path.Join(GetAppFolder(), "logs"); + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + var log = new LoggerConfiguration() + .WriteTo.Console(theme: AnsiConsoleTheme.Code, + outputTemplate: "[{Timestamp:hh:mm:ss t} {Level:u4}] {Message:lj}{NewLine}{Exception}") + .WriteTo.FileEx(Path.Join(path, "activity.log"), "MM-dd-yy", + rollingInterval: RollingInterval.Day, + outputTemplate: "[{Timestamp:MM/dd/yy - hh:mm:ss t}] [{Level:u4}] {Message:lj}{NewLine}{Exception}", + hooks: new FileArchiveRollingHooks(CompressionLevel.SmallestSize, path)) + .CreateLogger(); + Log.Logger = log; + Log.Information("Log Started."); + } private async Task SyncMonitor() { - // TODO: Replace with Logger - Console.WriteLine("Sync Monitor Starting Up."); - // TODO: Replace with Logger - Console.WriteLine("Starting API Validation Check."); + Log.Information("Sync Monitor Starting Up."); + Log.Information("Starting API Validation Check."); while (_isRunning) { await _semaphoreSlim.WaitAsync(); @@ -154,8 +174,7 @@ public partial class App : Application continue; } - // TODO: Replace with Logger - Console.WriteLine("Fetching initial data from Database."); + Log.Information("Fetching initial data from REST API."); foreach (var syncer in _syncers!) { await syncer.FetchDatabase(); @@ -167,8 +186,7 @@ public partial class App : Application break; } - // TODO: Replace with Logger - Console.WriteLine("Starting Filesystem Sync Monitoring."); + Log.Information("Starting Filesystem Sync Monitoring."); var lastCheck = DateTime.Now; while (_isRunning) { @@ -179,13 +197,11 @@ public partial class App : Application { await _semaphoreSlim.WaitAsync(); var start = DateTime.Now; - // TODO: Replace with Logger - Console.WriteLine("Checking for updates..."); + Log.Information("Checking for updates..."); var updateCheck = await _syncers[0].GetLastUpdated(); if (_lastUpdated < updateCheck) { - // TODO: Replace with Logger - Console.WriteLine($"Update Found, fetching updates..."); + Log.Information("Update Found, fetching updates..."); _lastUpdated = updateCheck; foreach (var syncer in _syncers) await syncer.FetchDatabase(); @@ -193,8 +209,7 @@ public partial class App : Application lastCheck = DateTime.Now; var end = DateTime.Now - start; _semaphoreSlim.Release(); - // TODO: Replace with Logger - Console.WriteLine($"Check Completed. Total Time: {end}"); + Log.Information("Check Completed. Total Time: {EndTime}", end); continue; } @@ -202,8 +217,7 @@ public partial class App : Application var procs = Process.GetProcessesByName("FreeTube"); if (procs.Length > 0) continue; - // TODO: Replace with Logger - Console.WriteLine("FreeTube closed, and we have writes to make..."); + Log.Information("FreeTube instance closed, and we have writes to make..."); await Task.Delay(1500); await _semaphoreSlim.WaitAsync(); @@ -212,71 +226,61 @@ public partial class App : Application syncer.Sync(); var syncEnd = DateTime.Now - syncStart; _semaphoreSlim.Release(); - // TODO: Replace with Logger - Console.WriteLine($"Sync completed in {syncEnd}."); + Log.Information("Sync Completed. Total Time: {EndTime}", syncEnd); } - // TODO: Replace with Logger - Console.WriteLine($"Filesystem Sync Monitor Shutdown."); + Log.Information("Filesystem Sync Monitor shutdown."); } private async void HandleSettingsChanged(object? sender, EventArgs e) { - // TODO: Replace with Logger - Console.WriteLine("Settings have changed. Updating Settings..."); + Log.Information("Settings have changed. Updating Settings..."); await _semaphoreSlim.WaitAsync(); var old = JsonSerializer.Deserialize(_oldSettings!); if (_settings!.RestBaseUrl != old!.RestBaseUrl) { - // TODO: Replace with Logger - Console.WriteLine($"Updating syncers with new URL: {_settings.RestBaseUrl}."); + Log.Information("Updating syncers with new URL: {SettingsRestBaseUrl}.", _settings.RestBaseUrl); foreach (var syncer in _syncers!) syncer.UpdateBaseUrl(_settings.RestBaseUrl); } if (old.CheckInterval != _settings.CheckInterval) { - // TODO: Replace with Logger - Console.WriteLine($"Updating Check Interval to {_settings.CheckInterval}."); + Log.Information("Updating Check Interval to {SettingsCheckInterval}.", _settings.CheckInterval); _checkInterval = TimeSpan.FromSeconds(_settings.CheckInterval); } if (old.SyncHistory != _settings.SyncHistory) { - // TODO: Replace with Logger - Console.WriteLine("History Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled")); + Log.Information("History Syncer: {Status}",(_settings.SyncHistory ? "Enabled" : "Disabled")); _historySyncer!.SetEnabled(_settings.SyncHistory); await _historySyncer.FetchDatabase(); } if (old.SyncPlaylist != _settings.SyncPlaylist) { - // TODO: Replace with Logger - Console.WriteLine("Playlist Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled")); + Log.Information("Playlist Syncer: {Status}",(_settings.SyncHistory ? "Enabled" : "Disabled")); _playlistSyncer!.SetEnabled(_settings.SyncPlaylist); await _playlistSyncer.FetchDatabase(); } if (old.SyncProfile != _settings.SyncProfile) { - // TODO: Replace with Logger - Console.WriteLine("Profile Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled")); + Log.Information("Profile Syncer: {Status}", (_settings.SyncHistory ? "Enabled" : "Disabled")); _profileSyncer!.SetEnabled(_settings.SyncProfile); await _profileSyncer.FetchDatabase(); } if (old.SyncSearchHistory != _settings.SyncSearchHistory) { - // TODO: Replace with Logger - Console.WriteLine("Search History Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled")); + Log.Information("Search History Syncer: {Status}", (_settings.SyncHistory ? "Enabled" : "Disabled")); _searchHistorySyncer!.SetEnabled(_settings.SyncSearchHistory); await _searchHistorySyncer.FetchDatabase(); } if (old.SyncSettings != _settings.SyncSettings) { - // TODO: Replace with Logger - Console.WriteLine("Settings Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled")); + Log.Information("Settings Syncer: {Status}",(_settings.SyncHistory ? "Enabled" : "Disabled")); _settingSyncer!.SetEnabled(_settings.SyncSettings); await _settingSyncer.FetchDatabase(); }