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.
This commit is contained in:
Mario Steele 2025-07-31 03:32:30 -05:00
parent e6236b8a5a
commit 7099e31ff5
2 changed files with 212 additions and 16 deletions

View file

@ -1,22 +1,24 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI"
x:Class="FreeTubeSyncer.App"
RequestedThemeVariant="Default">
RequestedThemeVariant="Dark">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
<sukiUi:SukiTheme ThemeColor="Blue" />
</Application.Styles>
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="/Assets/freetube.png"
ToolTipText="FreeTube Syncer">
ToolTipText="FreeTube Syncer"
Clicked="TrayIcon_OnClicked">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Header="Show Settings"/>
<NativeMenuItem Header="Show Settings" Click="ShowSettings_OnClick"/>
<NativeMenuItemSeparator/>
<NativeMenuItem Header="Quit"/>
<NativeMenuItem Header="Quit" Click="Quit_OnClick"/>
</NativeMenu>
</TrayIcon.Menu>
</TrayIcon>

View file

@ -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<ISyncer>? _syncers;
private Syncer<History>? _historySyncer;
private Syncer<Playlist>? _playlistSyncer;
private Syncer<Profile>? _profileSyncer;
private Syncer<SearchHistory>? _searchHistorySyncer;
private Syncer<Setting>? _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<History>(_watcher, Path.Join(path, "history.db"), "history.db", _settings.RestBaseUrl, "/history");
_playlistSyncer = new Syncer<Playlist>(_watcher, Path.Join(path, "playlists.db"), "playlists.db", _settings.RestBaseUrl, "/playlist");
_profileSyncer = new Syncer<Profile>(_watcher, Path.Join(path, "profiles.db"), "profiles.db", _settings.RestBaseUrl, "/profile");
_searchHistorySyncer = new Syncer<SearchHistory>(_watcher, Path.Join(path, "search-history.db"), "search-history.db", _settings.RestBaseUrl, "/searchHistory");
_settingSyncer = new Syncer<Setting>(_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<AppSettings>(_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<AppSettings>(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<AppSettings>(_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);
}
}
}