From 7099e31ff5c8d5b73039ed2040e5bb7e1d8d6ef2 Mon Sep 17 00:00:00 2001 From: Mario Steele Date: Thu, 31 Jul 2025 03:32:30 -0500 Subject: [PATCH] Updated App Updated App.xaml to use SukiUI. Added Event Handlers for System Tray. Updated App.xaml.cs code behind file to implement Logic that was in Program.cs, into the core of the UI App. Implemented Async Tasks, to allow for UI to remain responsive, while doing long running tasks. Added Loading, Saving and Updating settings from UI updates. --- FreeTubeSyncer/App.axaml | 12 +- FreeTubeSyncer/App.axaml.cs | 216 ++++++++++++++++++++++++++++++++++-- 2 files changed, 212 insertions(+), 16 deletions(-) diff --git a/FreeTubeSyncer/App.axaml b/FreeTubeSyncer/App.axaml index bf82d18..8c9d9af 100644 --- a/FreeTubeSyncer/App.axaml +++ b/FreeTubeSyncer/App.axaml @@ -1,22 +1,24 @@ + RequestedThemeVariant="Dark"> - + + ToolTipText="FreeTube Syncer" + Clicked="TrayIcon_OnClicked"> - + - + diff --git a/FreeTubeSyncer/App.axaml.cs b/FreeTubeSyncer/App.axaml.cs index dded1f1..0026e01 100644 --- a/FreeTubeSyncer/App.axaml.cs +++ b/FreeTubeSyncer/App.axaml.cs @@ -1,17 +1,42 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; using Avalonia; -using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using FreeTubeSyncer.Library; +using FreeTubeSyncer.Models; using FreeTubeSyncer.Models.DatabaseModels; +using FreeTubeSyncer.REST; namespace FreeTubeSyncer; public partial class App : Application { - private DBSyncWatcher _watcher; + private DBSyncWatcher? _watcher; + private List? _syncers; + private Syncer? _historySyncer; + private Syncer? _playlistSyncer; + private Syncer? _profileSyncer; + private Syncer? _searchHistorySyncer; + private Syncer? _settingSyncer; + private bool _isRunning = true; + private Task? _watcherTask; + private DateTime _lastClick = DateTime.MinValue; + private AppSettings? _settings; + private string? _oldSettings; + private static readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); + private TimeSpan _checkInterval; + + public event EventHandler? SettingsChanged; + + public static App GetApp() => (App)App.Current!; + public static ClassicDesktopStyleApplicationLifetime GetDesktop() => (ClassicDesktopStyleApplicationLifetime)App.Current!.ApplicationLifetime!; public override void Initialize() { AvaloniaXamlLoader.Load(this); @@ -21,9 +46,8 @@ public partial class App : Application { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - //desktop.MainWindow = new MainWindow(); var path = ""; - if (OperatingSystem.IsWindows()) + if (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS()) path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreeTube"); else if (OperatingSystem.IsLinux()) { @@ -37,17 +61,187 @@ public partial class App : Application Console.WriteLine("Failed to find Path for FreeTube!"); } } - else if (OperatingSystem.IsMacOS()) - path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreeTube"); + + LoadSettings(); + _checkInterval = TimeSpan.FromSeconds(_settings!.CheckInterval); _watcher = new DBSyncWatcher(path); - _watcher.WatchFiles.Add("history.db", typeof(History)); - _watcher.WatchFiles.Add("playlist.db", typeof(Playlist)); - _watcher.WatchFiles.Add("profiles.db", typeof(Profile)); - _watcher.WatchFiles.Add("search-history.db", typeof(SearchHistory)); - _watcher.WatchFiles.Add("settings.db", typeof(Setting)); + _historySyncer = new Syncer(_watcher, Path.Join(path, "history.db"), "history.db", _settings.RestBaseUrl, "/history"); + _playlistSyncer = new Syncer(_watcher, Path.Join(path, "playlists.db"), "playlists.db", _settings.RestBaseUrl, "/playlist"); + _profileSyncer = new Syncer(_watcher, Path.Join(path, "profiles.db"), "profiles.db", _settings.RestBaseUrl, "/profile"); + _searchHistorySyncer = new Syncer(_watcher, Path.Join(path, "search-history.db"), "search-history.db", _settings.RestBaseUrl, "/searchHistory"); + _settingSyncer = new Syncer(_watcher, Path.Join(path, "settings.db"), "settings.db", _settings.RestBaseUrl, "/settings"); + _syncers = + [ + _historySyncer, + _playlistSyncer, + _profileSyncer, + _searchHistorySyncer, + _settingSyncer + ]; + SettingsChanged += HandleSettingsChanged; + _watcherTask = Task.Run(SyncMonitor); } base.OnFrameworkInitializationCompleted(); } + + public void StampSettings() => _oldSettings = JsonSerializer.Serialize(_settings); + + public void ResetSettings() + { + var @new = JsonSerializer.Deserialize(_oldSettings!); + _settings!.RestBaseUrl = @new!.RestBaseUrl; + _settings.CheckInterval = @new.CheckInterval; + _settings.SyncHistory = @new.SyncHistory; + _settings.SyncPlaylist = @new.SyncPlaylist; + _settings.SyncProfile = @new.SyncProfile; + _settings.SyncSearchHistory = @new.SyncSearchHistory; + _settings.SyncSettings = @new.SyncSettings; + } + + public void SaveSettings() + { + var path = GetSettingsPath(); + if (!File.Exists(path)) + { + var dir = Path.GetDirectoryName(path); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + } + File.WriteAllText(path, JsonSerializer.Serialize(_settings)); + SettingsChanged?.Invoke(this, EventArgs.Empty); + } + + public void LoadSettings() + { + var path = GetSettingsPath(); + if (!File.Exists(path)) + { + _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 async Task SyncMonitor() + { + while (_isRunning) + { + await _semaphoreSlim.WaitAsync(); + if (!await _syncers![0].PingApi()) + { + _semaphoreSlim.Release(); + await Task.Delay(5000); + continue; + } + foreach (var syncer in _syncers!) + { + await syncer.FetchDatabase(); + } + + _semaphoreSlim.Release(); + break; + } + + var lastCheck = DateTime.Now; + while (_isRunning) + { + await Task.Delay(100); + + var sinceLastCheck = DateTime.Now - lastCheck; + if (sinceLastCheck.TotalSeconds > _checkInterval.TotalSeconds) + { + await _semaphoreSlim.WaitAsync(); + var start = DateTime.Now; + // TODO: Replace with Logger + Console.WriteLine("Checking for updates..."); + foreach (var syncer in _syncers) + await syncer.FetchDatabase(); + lastCheck = DateTime.Now; + var end = DateTime.Now - start; + _semaphoreSlim.Release(); + // TODO: Replace with Logger + Console.WriteLine($"Check Completed. Total Time: {end}"); + continue; + } + + if (!_syncers.Any(x => x.IsDirty())) continue; + + var procs = Process.GetProcessesByName("FreeTube"); + if (procs.Length > 0) continue; + // TODO: Replace with Logger + Console.WriteLine("FreeTube closed, and we have writes to make..."); + await Task.Delay(1500); + + await _semaphoreSlim.WaitAsync(); + var syncStart = DateTime.Now; + foreach (var syncer in _syncers) + syncer.Sync(); + var syncEnd = DateTime.Now - syncStart; + _semaphoreSlim.Release(); + // TODO: Replace with Logger + Console.WriteLine($"Sync completed in {syncEnd}."); + } + } + + private async void HandleSettingsChanged(object? sender, EventArgs e) + { + await _semaphoreSlim.WaitAsync(); + var old = JsonSerializer.Deserialize(_oldSettings!); + if (_settings!.RestBaseUrl != old!.RestBaseUrl) + { + foreach (var syncer in _syncers!) + syncer.UpdateBaseUrl(_settings.RestBaseUrl); + } + + if (old.CheckInterval != _settings.CheckInterval) _checkInterval = TimeSpan.FromSeconds(_settings.CheckInterval); + if (old.SyncHistory != _settings.SyncHistory) _historySyncer!.SetEnabled(_settings.SyncHistory); + if (old.SyncPlaylist != _settings.SyncPlaylist) _playlistSyncer!.SetEnabled(_settings.SyncPlaylist); + if (old.SyncProfile != _settings.SyncProfile) _profileSyncer!.SetEnabled(_settings.SyncProfile); + if (old.SyncSearchHistory != _settings.SyncSearchHistory) _searchHistorySyncer!.SetEnabled(_settings.SyncSearchHistory); + if (old.SyncSettings != _settings.SyncSettings) _settingSyncer!.SetEnabled(_settings.SyncSettings); + _semaphoreSlim.Release(); + } + + private void ShowSettings_OnClick(object? sender, EventArgs e) + { + if (App.Current!.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) return; + if (desktop.MainWindow is null) + { + desktop.MainWindow = new MainWindow() + { + DataContext = _settings + }; + } + StampSettings(); + desktop.MainWindow!.Show(); + } + + private void Quit_OnClick(object? sender, EventArgs e) + { + if (App.Current!.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) return; + _semaphoreSlim.Wait(); + _isRunning = false; + _semaphoreSlim.Release(); + _watcherTask?.Wait(); + desktop.Shutdown(); + } + + private void TrayIcon_OnClicked(object? sender, EventArgs e) + { + var clicked = DateTime.Now; + if (_lastClick == DateTime.MinValue || (clicked - _lastClick).TotalMilliseconds > TimeSpan.FromMilliseconds(500).TotalMilliseconds) + _lastClick = clicked; + else + { + _lastClick = DateTime.MinValue; + ShowSettings_OnClick(sender, e); + } + } } \ No newline at end of file