Compare commits
1 Commits
master
...
avalonia-g
| Author | SHA1 | Date | |
|---|---|---|---|
| 41adc604de |
293
OF DL/AppCommon.cs
Normal file
293
OF DL/AppCommon.cs
Normal file
@ -0,0 +1,293 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using OF_DL.Entities;
|
||||
using OF_DL.Exceptions;
|
||||
using OF_DL.Helpers;
|
||||
using Serilog;
|
||||
|
||||
namespace OF_DL;
|
||||
|
||||
public class AppCommon
|
||||
{
|
||||
private readonly Auth _auth;
|
||||
private readonly Config _config;
|
||||
private readonly bool _useCdrmProject;
|
||||
|
||||
private readonly IAPIHelper _apiHelper;
|
||||
private readonly IDBHelper _dbHelper;
|
||||
private readonly IDownloadHelper _downloadHelper;
|
||||
|
||||
private Dictionary<string, int> _activeSubscriptions = new();
|
||||
private Dictionary<string, int> _expiredSubscriptions = new();
|
||||
|
||||
public AppCommon()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.File("logs/OFDL.txt", rollingInterval: RollingInterval.Day)
|
||||
.WriteTo.Console()
|
||||
.CreateLogger();
|
||||
|
||||
VerifyOperatingSystemCompatibility();
|
||||
_auth = GetAuth();
|
||||
_config = GetConfig();
|
||||
_useCdrmProject = !DetectDrmKeysPresence();
|
||||
LoadFfmpeg();
|
||||
|
||||
_apiHelper = new APIHelper();
|
||||
_dbHelper = new DBHelper();
|
||||
_downloadHelper = new DownloadHelper();
|
||||
}
|
||||
|
||||
private static void VerifyOperatingSystemCompatibility()
|
||||
{
|
||||
var os = Environment.OSVersion;
|
||||
if (os.Platform != PlatformID.Win32NT || os.Version.Major >= 10) return;
|
||||
|
||||
var platform =
|
||||
os.Platform switch
|
||||
{
|
||||
PlatformID.Win32NT => "Windows",
|
||||
PlatformID.Unix => "Unix",
|
||||
PlatformID.MacOSX => "macOS",
|
||||
_ => "Unknown"
|
||||
};
|
||||
|
||||
Log.Error($"Unsupported operating system: {platform} version {os.VersionString}");
|
||||
throw new UnsupportedOperatingSystem(platform, os.VersionString);
|
||||
}
|
||||
|
||||
private static Auth GetAuth()
|
||||
{
|
||||
if (File.Exists("auth.json"))
|
||||
{
|
||||
Log.Debug("auth.json located successfully");
|
||||
var authJson = JsonConvert.DeserializeObject<Auth>(File.ReadAllText("auth.json"));
|
||||
if (authJson != null)
|
||||
{
|
||||
return authJson;
|
||||
}
|
||||
|
||||
Log.Error("auth.json is invalid");
|
||||
throw new MalformedFileException("auth.json");
|
||||
}
|
||||
|
||||
Log.Error("auth.json does not exist");
|
||||
throw new MissingFileException("auth.json");
|
||||
}
|
||||
|
||||
private static Config GetConfig()
|
||||
{
|
||||
if (File.Exists("config.json"))
|
||||
{
|
||||
Log.Debug("config.json located successfully");
|
||||
var configJson = JsonConvert.DeserializeObject<Config>(File.ReadAllText("config.json"));
|
||||
if (configJson != null)
|
||||
{
|
||||
return configJson;
|
||||
}
|
||||
|
||||
Log.Error("config.json is invalid");
|
||||
throw new MalformedFileException("config.json");
|
||||
}
|
||||
|
||||
Log.Error("config.json does not exist");
|
||||
throw new MissingFileException("config.json");
|
||||
}
|
||||
|
||||
private void LoadFfmpeg()
|
||||
{
|
||||
var ffmpegFound = false;
|
||||
var pathAutoDetected = false;
|
||||
if (!string.IsNullOrEmpty(_config!.FFmpegPath) && ValidateFilePath(_config.FFmpegPath))
|
||||
{
|
||||
// FFmpeg path is set in config.json and is valid
|
||||
ffmpegFound = true;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(_auth!.FFMPEG_PATH) && ValidateFilePath(_auth.FFMPEG_PATH))
|
||||
{
|
||||
// FFmpeg path is set in auth.json and is valid (config.json takes precedence and auth.json is only available for backward compatibility)
|
||||
ffmpegFound = true;
|
||||
_config.FFmpegPath = _auth.FFMPEG_PATH;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(_config.FFmpegPath))
|
||||
{
|
||||
// FFmpeg path is not set in config.json, so we will try to locate it in the PATH or current directory
|
||||
var ffmpegPath = GetFullPath("ffmpeg");
|
||||
if (ffmpegPath != null)
|
||||
{
|
||||
// FFmpeg is found in the PATH or current directory
|
||||
ffmpegFound = true;
|
||||
pathAutoDetected = true;
|
||||
_config.FFmpegPath = ffmpegPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// FFmpeg is not found in the PATH or current directory, so we will try to locate the windows executable
|
||||
ffmpegPath = GetFullPath("ffmpeg.exe");
|
||||
if (ffmpegPath != null)
|
||||
{
|
||||
// FFmpeg windows executable is found in the PATH or current directory
|
||||
ffmpegFound = true;
|
||||
pathAutoDetected = true;
|
||||
_config.FFmpegPath = ffmpegPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ffmpegFound)
|
||||
{
|
||||
Log.Debug(
|
||||
pathAutoDetected
|
||||
? $"FFmpeg located successfully. Path auto-detected: {_config.FFmpegPath}"
|
||||
: $"FFmpeg located successfully"
|
||||
);
|
||||
|
||||
// Escape backslashes in the path for Windows
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.FFmpegPath!.Contains(@":\") && !_config.FFmpegPath.Contains(@":\\"))
|
||||
{
|
||||
_config.FFmpegPath = _config.FFmpegPath.Replace(@"\", @"\\");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Cannot locate FFmpeg with path: {_config.FFmpegPath}");
|
||||
throw new Exception("Cannot locate FFmpeg");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DetectDrmKeysPresence()
|
||||
{
|
||||
var clientIdBlobMissing = false;
|
||||
var devicePrivateKeyMissing = false;
|
||||
|
||||
if (!File.Exists("cdm/devices/chrome_1610/device_client_id_blob"))
|
||||
{
|
||||
clientIdBlobMissing = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"device_client_id_blob located successfully");
|
||||
}
|
||||
|
||||
if (!File.Exists("cdm/devices/chrome_1610/device_private_key"))
|
||||
{
|
||||
devicePrivateKeyMissing = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"device_private_key located successfully");
|
||||
}
|
||||
|
||||
if (!clientIdBlobMissing && !devicePrivateKeyMissing)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Information("device_client_id_blob and/or device_private_key missing, https://cdrm-project.com/ will be used instead for DRM protected videos");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ValidateFilePath(string path)
|
||||
{
|
||||
var invalidChars = System.IO.Path.GetInvalidPathChars();
|
||||
var foundInvalidChars = path.Where(c => invalidChars.Contains(c)).ToArray();
|
||||
|
||||
if (foundInvalidChars.Length != 0)
|
||||
{
|
||||
Log.Information($"Invalid characters found in path {path}:[/] {string.Join(", ", foundInvalidChars)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (File.Exists(path)) return true;
|
||||
|
||||
Log.Information(
|
||||
Directory.Exists(path)
|
||||
? $"The provided path {path} improperly points to a directory and not a file."
|
||||
: $"The provided path {path} does not exist or is not accessible."
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string? GetFullPath(string filename)
|
||||
{
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
return Path.GetFullPath(filename);
|
||||
}
|
||||
|
||||
var pathEnv = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
|
||||
return pathEnv
|
||||
.Split(Path.PathSeparator)
|
||||
.Select(path => Path.Combine(path, filename))
|
||||
.FirstOrDefault(File.Exists);
|
||||
}
|
||||
|
||||
public async Task<User> GetUser()
|
||||
{
|
||||
var user = await _apiHelper.GetUserInfo("/users/me", _auth);
|
||||
|
||||
if (user is not { id: not null })
|
||||
{
|
||||
Log.Error("Authentication failed. Please check your credentials in auth.json");
|
||||
throw new AuthenticationFailureException();
|
||||
}
|
||||
|
||||
Log.Debug($"Logged in successfully as {user.name} {user.username}");
|
||||
return user;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, int>> GetActiveSubscriptions()
|
||||
{
|
||||
if (_activeSubscriptions.Count > 0)
|
||||
{
|
||||
return _activeSubscriptions;
|
||||
}
|
||||
|
||||
_activeSubscriptions = await _apiHelper.GetActiveSubscriptions("/subscriptions/subscribes", _auth, _config.IncludeRestrictedSubscriptions);
|
||||
return _activeSubscriptions;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, int>> GetExpiredSubscriptions()
|
||||
{
|
||||
if (_expiredSubscriptions.Count > 0)
|
||||
{
|
||||
return _expiredSubscriptions;
|
||||
}
|
||||
|
||||
_expiredSubscriptions = await _apiHelper.GetExpiredSubscriptions("/subscriptions/subscribes", _auth, _config.IncludeRestrictedSubscriptions);
|
||||
return _expiredSubscriptions;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, int>> GetSubscriptions()
|
||||
{
|
||||
var subscriptions = new Dictionary<string, int>();
|
||||
|
||||
foreach (var (key, value) in await GetActiveSubscriptions())
|
||||
{
|
||||
subscriptions.Add(key, value);
|
||||
}
|
||||
|
||||
if (_config.IncludeExpiredSubscriptions)
|
||||
{
|
||||
foreach (var (key, value) in await GetExpiredSubscriptions())
|
||||
{
|
||||
subscriptions.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, int>> GetLists()
|
||||
{
|
||||
return await _apiHelper.GetLists("/lists", _auth);
|
||||
}
|
||||
|
||||
public async Task CreateOrUpdateUsersDatabase()
|
||||
{
|
||||
var users = await GetSubscriptions();
|
||||
await _dbHelper.CreateUsersDB(users);
|
||||
}
|
||||
}
|
||||
26
OF DL/ConsoleApp.cs
Normal file
26
OF DL/ConsoleApp.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Serilog;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace OF_DL;
|
||||
|
||||
public static class ConsoleApp
|
||||
{
|
||||
public static async Task Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
var common = new AppCommon();
|
||||
await common.GetUser();
|
||||
await common.CreateOrUpdateUsersDatabase();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
7
OF DL/Entities/Subscription.cs
Normal file
7
OF DL/Entities/Subscription.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace OF_DL.Entities;
|
||||
|
||||
public class Subscription(string username, int id)
|
||||
{
|
||||
public string Username { get; set; } = username;
|
||||
public int Id { get; set; } = id;
|
||||
}
|
||||
3
OF DL/Exceptions/AuthenticationFailureException.cs
Normal file
3
OF DL/Exceptions/AuthenticationFailureException.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace OF_DL.Exceptions;
|
||||
|
||||
public class AuthenticationFailureException() : Exception("Authentication failed");
|
||||
6
OF DL/Exceptions/MalformedFileException.cs
Normal file
6
OF DL/Exceptions/MalformedFileException.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace OF_DL.Exceptions;
|
||||
|
||||
public class MalformedFileException(string filename) : Exception("File malformed: " + filename)
|
||||
{
|
||||
public string Filename { get; } = filename;
|
||||
}
|
||||
6
OF DL/Exceptions/MissingFileException.cs
Normal file
6
OF DL/Exceptions/MissingFileException.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace OF_DL.Exceptions;
|
||||
|
||||
public class MissingFileException(string filename) : Exception("File missing: " + filename)
|
||||
{
|
||||
public string Filename { get; } = filename;
|
||||
}
|
||||
7
OF DL/Exceptions/UnsupportedOperatingSystem.cs
Normal file
7
OF DL/Exceptions/UnsupportedOperatingSystem.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace OF_DL.Exceptions;
|
||||
|
||||
public class UnsupportedOperatingSystem(string platform, string version) : Exception($"{platform} version {version} is not supported")
|
||||
{
|
||||
public string Platform { get; } = platform;
|
||||
public string Version { get; } = version;
|
||||
}
|
||||
11
OF DL/GuiApp.axaml
Normal file
11
OF DL/GuiApp.axaml
Normal file
@ -0,0 +1,11 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="OF_DL.GuiApp"
|
||||
RequestedThemeVariant="Light">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://AvaloniaProgressRing/Styles/ProgressRing.xaml"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
24
OF DL/GuiApp.axaml.cs
Normal file
24
OF DL/GuiApp.axaml.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace OF_DL;
|
||||
|
||||
public partial class GuiApp : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
88
OF DL/MainWindow.axaml
Normal file
88
OF DL/MainWindow.axaml
Normal file
@ -0,0 +1,88 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:OF_DL.ViewModels"
|
||||
xmlns:avaloniaProgressRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing"
|
||||
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="720"
|
||||
Width="1280" Height="720"
|
||||
x:Class="OF_DL.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Title="OF-DL">
|
||||
|
||||
<Grid ColumnDefinitions="*, *">
|
||||
<Border Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="White" ZIndex="2" Padding="0" IsVisible="{Binding IsLoading}">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<avaloniaProgressRing:ProgressRing Width="80"
|
||||
Height="80"
|
||||
Name="LoadingRing"
|
||||
IsActive="{Binding IsLoading}"
|
||||
Foreground="Green"
|
||||
ZIndex="3"
|
||||
Margin="10,24,0,32"/>
|
||||
<Label FontSize="32" Content="{Binding LoadingText}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Vertical">
|
||||
<StackPanel Margin="20">
|
||||
<TextBlock FontSize="24">Media Sources</TextBlock>
|
||||
<StackPanel Margin="20">
|
||||
<CheckBox FontSize="24">Purchased Tab</CheckBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="20 20 300 20">
|
||||
<CheckBox FontSize="24">Users</CheckBox>
|
||||
<ListBox SelectionMode="Multiple,Toggle" Margin="30 0 0 0" ItemsSource="{Binding SubscriptionsList}" Height="300">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Username}" Margin="0, 0, 10, 0" />
|
||||
<TextBlock Text="("></TextBlock>
|
||||
<TextBlock Text="{Binding Id}" />
|
||||
<TextBlock Text=")"></TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Vertical">
|
||||
<StackPanel Margin="20">
|
||||
<TextBlock FontSize="24">Media Types</TextBlock>
|
||||
<StackPanel Orientation="Vertical" Margin="30 0 0 0">
|
||||
<CheckBox>Images</CheckBox>
|
||||
<CheckBox>Videos</CheckBox>
|
||||
<CheckBox>Audios</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="20">
|
||||
<TextBlock FontSize="24">Options</TextBlock>
|
||||
<StackPanel Margin="20">
|
||||
<TextBlock>Start Date</TextBlock>
|
||||
<DatePicker DayFormat="ddd dd"/>
|
||||
<Button>Clear</Button>
|
||||
|
||||
<TextBlock Margin="0 30 0 0">End Date</TextBlock>
|
||||
<DatePicker DayFormat="ddd dd"/>
|
||||
<Button>Clear</Button>
|
||||
|
||||
<Button Margin="0 30 0 0">Folder options</Button>
|
||||
<Button Margin="0 30 0 0">Additional settings</Button>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="20">
|
||||
<Button Width="250" Height="100" FontSize="50">START</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
|
||||
|
||||
</Window>
|
||||
|
||||
15
OF DL/MainWindow.axaml.cs
Normal file
15
OF DL/MainWindow.axaml.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Avalonia.Controls;
|
||||
using OF_DL.ViewModels;
|
||||
|
||||
namespace OF_DL;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new MainWindowViewModel();
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,16 +15,24 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Akka" Version="1.5.39" />
|
||||
<PackageReference Include="Avalonia" Version="11.3.11" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.11" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.11" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.11" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Deadpikle.AvaloniaProgressRing" Version="0.10.11-preview20251127001" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.46" />
|
||||
<PackageReference Include="PuppeteerSharp" Version="20.2.5" />
|
||||
<PackageReference Include="ReactiveUI" Version="22.3.1" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageReference Include="System.Reactive" Version="6.1.0" />
|
||||
<PackageReference Include="xFFmpeg.NET" Version="7.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ using System.Text.RegularExpressions;
|
||||
using static OF_DL.Entities.Messages.Messages;
|
||||
using Akka.Configuration;
|
||||
using System.Text;
|
||||
using Avalonia;
|
||||
using static Akka.Actor.ProviderSelection;
|
||||
|
||||
namespace OF_DL;
|
||||
@ -35,6 +36,32 @@ public class Program
|
||||
private static Auth? auth = null;
|
||||
private static LoggingLevelSwitch levelSwitch = new LoggingLevelSwitch();
|
||||
|
||||
[STAThread]
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ConsoleApp.Run();
|
||||
}
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp() =>
|
||||
AppBuilder.Configure<GuiApp>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
|
||||
private async static Task ConsoleMain()
|
||||
{
|
||||
var apiHelper = new APIHelper(auth, config);
|
||||
await DownloadAllData(apiHelper, auth, config);
|
||||
}
|
||||
|
||||
private static async Task LoadAuthFromBrowser()
|
||||
{
|
||||
bool runningInDocker = Environment.GetEnvironmentVariable("OFDL_DOCKER") != null;
|
||||
|
||||
109
OF DL/ViewModels/MainWindowViewModel.cs
Normal file
109
OF DL/ViewModels/MainWindowViewModel.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System.Reactive.Concurrency;
|
||||
using OF_DL.Entities;
|
||||
using OF_DL.Exceptions;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
|
||||
namespace OF_DL.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel : ObservableObject
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
[ObservableProperty] private bool _isLoading = true;
|
||||
[ObservableProperty] private string _loadingText = "";
|
||||
[ObservableProperty] private bool _hasSubscriptionsLoaded = false;
|
||||
[ObservableProperty] private bool _hasAuthenticationFailed = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Subscription> _subscriptionsList = [];
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly AppCommon? _appCommon;
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
try
|
||||
{
|
||||
_appCommon = new AppCommon();
|
||||
RxApp.MainThreadScheduler.Schedule(LoadSubscriptions);
|
||||
}
|
||||
catch (MissingFileException ex)
|
||||
{
|
||||
Log.Error(ex, ex.ToString());
|
||||
if (ex.Filename == "auth.json")
|
||||
{
|
||||
// Missing auth.json
|
||||
HasAuthenticationFailed = true;
|
||||
}
|
||||
else if (ex.Filename == "config.json")
|
||||
{
|
||||
// Missing config.json
|
||||
// TODO: Show a dialog to create a new config.json (OK to create new config, cancel to exit)
|
||||
}
|
||||
}
|
||||
catch (MalformedFileException ex)
|
||||
{
|
||||
Log.Error(ex, ex.ToString());
|
||||
if (ex.Filename == "auth.json")
|
||||
{
|
||||
// Malformed auth.json
|
||||
HasAuthenticationFailed = true;
|
||||
}
|
||||
else if (ex.Filename == "config.json")
|
||||
{
|
||||
// Malformed config.json
|
||||
// TODO: Show a dialog to create a new config.json (OK to create new config, cancel to exit)
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to initialize");
|
||||
}
|
||||
}
|
||||
|
||||
private async void LoadSubscriptions()
|
||||
{
|
||||
if (_appCommon == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
LoadingText = "Getting account";
|
||||
await _appCommon.GetUser();
|
||||
|
||||
LoadingText = "Getting subscriptions";
|
||||
await _appCommon.CreateOrUpdateUsersDatabase();
|
||||
var subscriptions = await _appCommon.GetSubscriptions();
|
||||
|
||||
Log.Information($"Found {subscriptions.Count} subscriptions");
|
||||
|
||||
var subscriptionsList = new ObservableCollection<Subscription>();
|
||||
foreach (var (key, value) in subscriptions)
|
||||
{
|
||||
subscriptionsList.Add(new Subscription(key, value));
|
||||
}
|
||||
|
||||
SubscriptionsList = subscriptionsList;
|
||||
HasSubscriptionsLoaded = true;
|
||||
}
|
||||
catch (UnsupportedOperatingSystem ex)
|
||||
{
|
||||
Log.Error(ex, ex.ToString());
|
||||
// TODO: Show error dialog (exit on confirmation)
|
||||
}
|
||||
catch (AuthenticationFailureException ex)
|
||||
{
|
||||
Log.Error(ex, ex.ToString());
|
||||
HasAuthenticationFailed = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to load subscriptions");
|
||||
}
|
||||
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
18
OF DL/app.manifest
Normal file
18
OF DL/app.manifest
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="OF-DL.Desktop"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
Loading…
x
Reference in New Issue
Block a user