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 diff --git a/FreeTubeSyncer/FreeTubeSyncer.csproj b/FreeTubeSyncer/FreeTubeSyncer.csproj index b76aa99..bf74326 100644 --- a/FreeTubeSyncer/FreeTubeSyncer.csproj +++ b/FreeTubeSyncer/FreeTubeSyncer.csproj @@ -19,7 +19,9 @@ None All + + diff --git a/FreeTubeSyncer/MainWindow.axaml b/FreeTubeSyncer/MainWindow.axaml index 797d249..d98322d 100644 --- a/FreeTubeSyncer/MainWindow.axaml +++ b/FreeTubeSyncer/MainWindow.axaml @@ -1,9 +1,51 @@ - - Welcome to Avalonia! - + + +