forked from sim0n00ps/OF-DL
Add old Avalonia WIP files
This commit is contained in:
parent
43fb74067c
commit
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>
|
<ItemGroup>
|
||||||
<PackageReference Include="Akka" Version="1.5.39" />
|
<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="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="HtmlAgilityPack" Version="1.12.0" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.46" />
|
<PackageReference Include="protobuf-net" Version="3.2.46" />
|
||||||
<PackageReference Include="PuppeteerSharp" Version="20.2.5" />
|
<PackageReference Include="PuppeteerSharp" Version="20.2.5" />
|
||||||
|
<PackageReference Include="ReactiveUI" Version="22.3.1" />
|
||||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" 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" />
|
<PackageReference Include="xFFmpeg.NET" Version="7.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ using System.Text.RegularExpressions;
|
|||||||
using static OF_DL.Entities.Messages.Messages;
|
using static OF_DL.Entities.Messages.Messages;
|
||||||
using Akka.Configuration;
|
using Akka.Configuration;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Avalonia;
|
||||||
using static Akka.Actor.ProviderSelection;
|
using static Akka.Actor.ProviderSelection;
|
||||||
|
|
||||||
namespace OF_DL;
|
namespace OF_DL;
|
||||||
@ -35,6 +36,32 @@ public class Program
|
|||||||
private static Auth? auth = null;
|
private static Auth? auth = null;
|
||||||
private static LoggingLevelSwitch levelSwitch = new LoggingLevelSwitch();
|
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()
|
private static async Task LoadAuthFromBrowser()
|
||||||
{
|
{
|
||||||
bool runningInDocker = Environment.GetEnvironmentVariable("OFDL_DOCKER") != null;
|
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