Compare commits

...

4 commits

Author SHA1 Message Date
145c863fbe 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.
2025-07-31 16:17:06 -05:00
d8e650deec Updated CSProj
Updated project from Dotnet 8, to Dotent 9.
Ensure compiler is using latest major release.
Added Serilog assemblies.
2025-07-31 16:15:02 -05:00
c94b709145 Updated Syncer
Added Logging functionality to write to log files.
2025-07-31 16:14:21 -05:00
725a37d9cf 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.
2025-07-31 16:13:49 -05:00
4 changed files with 103 additions and 83 deletions

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
@ -13,6 +14,10 @@ using FreeTubeSyncer.Library;
using FreeTubeSyncer.Models; using FreeTubeSyncer.Models;
using FreeTubeSyncer.Models.DatabaseModels; using FreeTubeSyncer.Models.DatabaseModels;
using FreeTubeSyncer.REST; using FreeTubeSyncer.REST;
using Serilog;
using Serilog.Sinks.File.GzArchive;
using Serilog.Sinks.FileEx;
using Serilog.Sinks.SystemConsole.Themes;
namespace FreeTubeSyncer; namespace FreeTubeSyncer;
@ -45,6 +50,7 @@ public partial class App : Application
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
SetupLogger();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
var path = ""; 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", path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".var", "app", "io.freetubeapp.FreeTube", "config",
"FreeTube"); "FreeTube");
if (!Path.Exists(path)) 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(); var path = GetSettingsPath();
if (!File.Exists(path)) if (!File.Exists(path))
{ {
var dir = Path.GetDirectoryName(path); if (!Directory.Exists(GetAppFolder()))
if (!Directory.Exists(dir)) Directory.CreateDirectory(GetAppFolder());
Directory.CreateDirectory(dir);
} }
File.WriteAllText(path, JsonSerializer.Serialize(_settings)); File.WriteAllText(path, JsonSerializer.Serialize(_settings));
_settings!.SettingsDirty = false; _settings!.SettingsDirty = false;
@ -129,20 +134,35 @@ public partial class App : Application
_settings = new AppSettings(); _settings = new AppSettings();
return; return;
} }
var data = File.ReadAllText(path); var data = File.ReadAllText(path);
_settings = JsonSerializer.Deserialize<AppSettings>(data); _settings = JsonSerializer.Deserialize<AppSettings>(data);
} }
private string GetSettingsPath() => OperatingSystem.IsLinux() private string GetAppFolder() => Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreeTubeSyncer");
? Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreeTubeSyncer", "settings.json") private string GetSettingsPath() => Path.Join(GetAppFolder(), "settings.json");
: Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "FreeTubeSyncer", "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() private async Task SyncMonitor()
{ {
// TODO: Replace with Logger Log.Information("Sync Monitor Starting Up.");
Console.WriteLine("Sync Monitor Starting Up."); Log.Information("Starting API Validation Check.");
// TODO: Replace with Logger
Console.WriteLine("Starting API Validation Check.");
while (_isRunning) while (_isRunning)
{ {
await _semaphoreSlim.WaitAsync(); await _semaphoreSlim.WaitAsync();
@ -154,8 +174,7 @@ public partial class App : Application
continue; continue;
} }
// TODO: Replace with Logger Log.Information("Fetching initial data from REST API.");
Console.WriteLine("Fetching initial data from Database.");
foreach (var syncer in _syncers!) foreach (var syncer in _syncers!)
{ {
await syncer.FetchDatabase(); await syncer.FetchDatabase();
@ -167,8 +186,7 @@ public partial class App : Application
break; break;
} }
// TODO: Replace with Logger Log.Information("Starting Filesystem Sync Monitoring.");
Console.WriteLine("Starting Filesystem Sync Monitoring.");
var lastCheck = DateTime.Now; var lastCheck = DateTime.Now;
while (_isRunning) while (_isRunning)
{ {
@ -179,13 +197,11 @@ public partial class App : Application
{ {
await _semaphoreSlim.WaitAsync(); await _semaphoreSlim.WaitAsync();
var start = DateTime.Now; var start = DateTime.Now;
// TODO: Replace with Logger Log.Information("Checking for updates...");
Console.WriteLine("Checking for updates...");
var updateCheck = await _syncers[0].GetLastUpdated(); var updateCheck = await _syncers[0].GetLastUpdated();
if (_lastUpdated < updateCheck) if (_lastUpdated < updateCheck)
{ {
// TODO: Replace with Logger Log.Information("Update Found, fetching updates...");
Console.WriteLine($"Update Found, fetching updates...");
_lastUpdated = updateCheck; _lastUpdated = updateCheck;
foreach (var syncer in _syncers) foreach (var syncer in _syncers)
await syncer.FetchDatabase(); await syncer.FetchDatabase();
@ -193,8 +209,7 @@ public partial class App : Application
lastCheck = DateTime.Now; lastCheck = DateTime.Now;
var end = DateTime.Now - start; var end = DateTime.Now - start;
_semaphoreSlim.Release(); _semaphoreSlim.Release();
// TODO: Replace with Logger Log.Information("Check Completed. Total Time: {EndTime}", end);
Console.WriteLine($"Check Completed. Total Time: {end}");
continue; continue;
} }
@ -202,8 +217,7 @@ public partial class App : Application
var procs = Process.GetProcessesByName("FreeTube"); var procs = Process.GetProcessesByName("FreeTube");
if (procs.Length > 0) continue; if (procs.Length > 0) continue;
// TODO: Replace with Logger Log.Information("FreeTube instance closed, and we have writes to make...");
Console.WriteLine("FreeTube closed, and we have writes to make...");
await Task.Delay(1500); await Task.Delay(1500);
await _semaphoreSlim.WaitAsync(); await _semaphoreSlim.WaitAsync();
@ -212,71 +226,61 @@ public partial class App : Application
syncer.Sync(); syncer.Sync();
var syncEnd = DateTime.Now - syncStart; var syncEnd = DateTime.Now - syncStart;
_semaphoreSlim.Release(); _semaphoreSlim.Release();
// TODO: Replace with Logger Log.Information("Sync Completed. Total Time: {EndTime}", syncEnd);
Console.WriteLine($"Sync completed in {syncEnd}.");
} }
// TODO: Replace with Logger Log.Information("Filesystem Sync Monitor shutdown.");
Console.WriteLine($"Filesystem Sync Monitor Shutdown.");
} }
private async void HandleSettingsChanged(object? sender, EventArgs e) private async void HandleSettingsChanged(object? sender, EventArgs e)
{ {
// TODO: Replace with Logger Log.Information("Settings have changed. Updating Settings...");
Console.WriteLine("Settings have changed. Updating Settings...");
await _semaphoreSlim.WaitAsync(); await _semaphoreSlim.WaitAsync();
var old = JsonSerializer.Deserialize<AppSettings>(_oldSettings!); var old = JsonSerializer.Deserialize<AppSettings>(_oldSettings!);
if (_settings!.RestBaseUrl != old!.RestBaseUrl) if (_settings!.RestBaseUrl != old!.RestBaseUrl)
{ {
// TODO: Replace with Logger Log.Information("Updating syncers with new URL: {SettingsRestBaseUrl}.", _settings.RestBaseUrl);
Console.WriteLine($"Updating syncers with new URL: {_settings.RestBaseUrl}.");
foreach (var syncer in _syncers!) foreach (var syncer in _syncers!)
syncer.UpdateBaseUrl(_settings.RestBaseUrl); syncer.UpdateBaseUrl(_settings.RestBaseUrl);
} }
if (old.CheckInterval != _settings.CheckInterval) if (old.CheckInterval != _settings.CheckInterval)
{ {
// TODO: Replace with Logger Log.Information("Updating Check Interval to {SettingsCheckInterval}.", _settings.CheckInterval);
Console.WriteLine($"Updating Check Interval to {_settings.CheckInterval}.");
_checkInterval = TimeSpan.FromSeconds(_settings.CheckInterval); _checkInterval = TimeSpan.FromSeconds(_settings.CheckInterval);
} }
if (old.SyncHistory != _settings.SyncHistory) if (old.SyncHistory != _settings.SyncHistory)
{ {
// TODO: Replace with Logger Log.Information("History Syncer: {Status}",(_settings.SyncHistory ? "Enabled" : "Disabled"));
Console.WriteLine("History Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled"));
_historySyncer!.SetEnabled(_settings.SyncHistory); _historySyncer!.SetEnabled(_settings.SyncHistory);
await _historySyncer.FetchDatabase(); await _historySyncer.FetchDatabase();
} }
if (old.SyncPlaylist != _settings.SyncPlaylist) if (old.SyncPlaylist != _settings.SyncPlaylist)
{ {
// TODO: Replace with Logger Log.Information("Playlist Syncer: {Status}",(_settings.SyncHistory ? "Enabled" : "Disabled"));
Console.WriteLine("Playlist Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled"));
_playlistSyncer!.SetEnabled(_settings.SyncPlaylist); _playlistSyncer!.SetEnabled(_settings.SyncPlaylist);
await _playlistSyncer.FetchDatabase(); await _playlistSyncer.FetchDatabase();
} }
if (old.SyncProfile != _settings.SyncProfile) if (old.SyncProfile != _settings.SyncProfile)
{ {
// TODO: Replace with Logger Log.Information("Profile Syncer: {Status}", (_settings.SyncHistory ? "Enabled" : "Disabled"));
Console.WriteLine("Profile Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled"));
_profileSyncer!.SetEnabled(_settings.SyncProfile); _profileSyncer!.SetEnabled(_settings.SyncProfile);
await _profileSyncer.FetchDatabase(); await _profileSyncer.FetchDatabase();
} }
if (old.SyncSearchHistory != _settings.SyncSearchHistory) if (old.SyncSearchHistory != _settings.SyncSearchHistory)
{ {
// TODO: Replace with Logger Log.Information("Search History Syncer: {Status}", (_settings.SyncHistory ? "Enabled" : "Disabled"));
Console.WriteLine("Search History Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled"));
_searchHistorySyncer!.SetEnabled(_settings.SyncSearchHistory); _searchHistorySyncer!.SetEnabled(_settings.SyncSearchHistory);
await _searchHistorySyncer.FetchDatabase(); await _searchHistorySyncer.FetchDatabase();
} }
if (old.SyncSettings != _settings.SyncSettings) if (old.SyncSettings != _settings.SyncSettings)
{ {
// TODO: Replace with Logger Log.Information("Settings Syncer: {Status}",(_settings.SyncHistory ? "Enabled" : "Disabled"));
Console.WriteLine("Settings Syncer: " + (_settings.SyncHistory ? "Enabled" : "Disabled"));
_settingSyncer!.SetEnabled(_settings.SyncSettings); _settingSyncer!.SetEnabled(_settings.SyncSettings);
await _settingSyncer.FetchDatabase(); await _settingSyncer.FetchDatabase();
} }

View file

@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<LangVersion>latestmajor</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -21,6 +22,10 @@
</PackageReference> </PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="RestSharp" Version="112.1.0" /> <PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File.GzArchive" Version="1.1.10" />
<PackageReference Include="Serilog.Sinks.FileEx" Version="5.1.8" />
<PackageReference Include="SukiUI" Version="6.0.2" /> <PackageReference Include="SukiUI" Version="6.0.2" />
</ItemGroup> </ItemGroup>

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using Serilog;
namespace FreeTubeSyncer.Library; namespace FreeTubeSyncer.Library;
@ -44,8 +45,15 @@ public class DBSyncWatcher
if (!WatchFiles.Keys.Contains(dbName)) return; if (!WatchFiles.Keys.Contains(dbName)) return;
var data = File.ReadAllText(e.FullPath); Log.Information("Database File Changed: {DbName}", dbName);
foreach (var line in data.Split('\n'))
var data = new List<string>();
using var fh = File.OpenText(e.FullPath);
while (!fh.EndOfStream)
data.Add(fh.ReadLine() ?? string.Empty);
foreach (var line in data)
{ {
if (line == "") continue; if (line == "") continue;
var type = WatchFiles[dbName]; var type = WatchFiles[dbName];
@ -56,11 +64,27 @@ public class DBSyncWatcher
private void HandleCreated(object sender, FileSystemEventArgs e) private void HandleCreated(object sender, FileSystemEventArgs e)
{ {
if (e.ChangeType != WatcherChangeTypes.Created) return; if (e.ChangeType != WatcherChangeTypes.Created) return;
var dbName = Path.GetFileName(e.FullPath);
if (!WatchFiles.Keys.Contains(dbName)) return; while (Locked)
var data = File.ReadAllText(e.FullPath);
foreach (var line in data.Split('\n'))
{ {
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<string>();
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]; var type = WatchFiles[dbName];
OnDatabaseChange?.Invoke(dbName, line); OnDatabaseChange?.Invoke(dbName, line);
} }
@ -68,6 +92,6 @@ public class DBSyncWatcher
private void HandleError(object sender, ErrorEventArgs e) 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);
} }
} }

View file

@ -10,6 +10,8 @@ using System.Threading.Tasks;
using FreeTubeSyncer.Library; using FreeTubeSyncer.Library;
using FreeTubeSyncer.Models.DatabaseModels; using FreeTubeSyncer.Models.DatabaseModels;
using RestSharp; using RestSharp;
using Serilog;
using Serilog.Core;
namespace FreeTubeSyncer.REST; namespace FreeTubeSyncer.REST;
@ -68,34 +70,29 @@ public class Syncer<T> : ISyncer where T : class, IDataModel, new()
public async Task<bool> PingApi() public async Task<bool> PingApi()
{ {
// TODO: Replace with Logger Log.Information("Pinging API at {BuildUri}...", _client.BuildUri(new RestRequest("/ping")));
Console.WriteLine($"Pinging API at {_client.BuildUri(new RestRequest("/ping"))}...");
try try
{ {
var res = await _client.GetAsync<Ping>(new RestRequest("/ping")); var res = await _client.GetAsync<Ping>(new RestRequest("/ping"));
if (res == null) if (res == null)
{ {
// TODO: Replace with Logger Log.Information("Ping returned null, not the server we are looking for!");
Console.WriteLine($"Ping returned null, not the server we are looking for!");
return false; return false;
} }
if (res.AppVersion == "0.1.3") if (res.AppVersion == "0.1.3")
{ {
// TODO: Replace with Logger Log.Information("Server Online! {AppVersion}", res.AppVersion);
Console.WriteLine($"Server Online! {res.AppVersion}");
return true; return true;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
// TODO: Replace with Logger Log.Error("API not alive. Network Error: {Message}", ex.Message);
Console.WriteLine($"Network Error: {ex.Message}, API not alive.");
return false; return false;
} }
// TODO: Replace with Logger Log.Error("Responded with something other then Ping. This is not the server we are looking for...");
Console.WriteLine("Responded with something other then 404, API not what we expected.");
return false; return false;
} }
@ -128,8 +125,7 @@ public class Syncer<T> : ISyncer where T : class, IDataModel, new()
if (_entries.Any(x => x.EqualId(item.Id()))) if (_entries.Any(x => x.EqualId(item.Id())))
_entries.RemoveAll(x => x.EqualId(item.Id())); _entries.RemoveAll(x => x.EqualId(item.Id()));
_entries.Add(item); _entries.Add(item);
// TODO: Replace with Logger Log.Information("Posting to REST: {ItemId}", item.Id());
Console.WriteLine($"Posting to REST: {item.Id()}");
await _client.PostJsonAsync<T>(_restEndpoint, item); await _client.PostJsonAsync<T>(_restEndpoint, item);
} }
} }
@ -147,15 +143,11 @@ public class Syncer<T> : ISyncer where T : class, IDataModel, new()
if (data.Equals(entry)) continue; if (data.Equals(entry)) continue;
// TODO: Replace with Logger Log.Information("Updated Entry from REST for {DbName} - {EntryId}", _dbName, entry.Id());
Console.WriteLine($"Updated Entry from REST for {_dbName} - {entry.Id()}");
_entries.RemoveAll(x => x.EqualId(entry.Id())); _entries.RemoveAll(x => x.EqualId(entry.Id()));
} }
else else
{ Log.Information("New Entry from REST for {DbName} - {EntryId}", _dbName, entry.Id());
// TODO: Replace with Logger
Console.WriteLine($"New Entry from REST for {_dbName} - {entry.Id()}");
}
_entries.Add(entry); _entries.Add(entry);
_isDirty = true; _isDirty = true;
@ -186,8 +178,8 @@ public class Syncer<T> : ISyncer where T : class, IDataModel, new()
} }
catch (Exception iex) catch (Exception iex)
{ {
// TODO: Replace with Logger Log.Error("Failed to parse line: {EntryLine}", entryObject);
Console.WriteLine($"Failed to parse line: {entryObject}\nMessage: {iex.Message}"); Log.Error("Error Message: {Messsage}", iex.Message);
entry = null; entry = null;
} }
} }
@ -199,15 +191,11 @@ public class Syncer<T> : ISyncer where T : class, IDataModel, new()
{ {
var data = _entries.First(x => x.EqualId(entry.Id())); var data = _entries.First(x => x.EqualId(entry.Id()));
if (data.Equals(entry)) return; if (data.Equals(entry)) return;
// TODO: Replace with Logger Log.Information("Updated File Entry {EntryId} updated for {DbName}", entry.Id(), _dbName);
Console.WriteLine($"File Entry {entry.Id()} updated for {_dbName}");
_entries.RemoveAll(x => x.EqualId(entry.Id())); _entries.RemoveAll(x => x.EqualId(entry.Id()));
} }
else else
{ Log.Information("New File Entry {EntryId} for {DbName}", entry.Id(), _dbName);
// TODO: Replace with Logger
Console.WriteLine($"New File Entry {entry.Id()} for {_dbName}");
}
_entries.Add(entry); _entries.Add(entry);
await _client.PostJsonAsync<T>(_restEndpoint, entry); await _client.PostJsonAsync<T>(_restEndpoint, entry);
@ -219,8 +207,8 @@ public class Syncer<T> : ISyncer where T : class, IDataModel, new()
if (!_isDirty) if (!_isDirty)
return; return;
_syncing = true; _syncing = true;
// TODO: Replace with Logger Log.Information("Syncing {DbName}...", _dbPath);
Console.WriteLine($"Syncing {_dbPath}..."); var start = DateTime.Now;
var json = new List<string>(); var json = new List<string>();
foreach (var entry in _entries) foreach (var entry in _entries)
json.Add(entry.JsonData()); json.Add(entry.JsonData());
@ -233,8 +221,7 @@ public class Syncer<T> : ISyncer where T : class, IDataModel, new()
fh.Close(); fh.Close();
} }
_watcher.Locked = false; _watcher.Locked = false;
// TODO: Replace with Logger Log.Information("Updated {DbName}, completed in {TimeSpan}", _dbName, DateTime.Now - start);
Console.WriteLine($"Updated {_dbPath}.");
_isDirty = false; _isDirty = false;
_syncing = false; _syncing = false;
} }