diff --git a/OF DL.Core/Services/AuthService.cs b/OF DL.Core/Services/AuthService.cs index a277a34..5851fe1 100644 --- a/OF DL.Core/Services/AuthService.cs +++ b/OF DL.Core/Services/AuthService.cs @@ -73,19 +73,33 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService /// Launches a browser session and extracts auth data after login. /// /// True when auth data is captured successfully. - public async Task LoadFromBrowserAsync() + public async Task LoadFromBrowserAsync(Action? statusCallback = null) { + statusCallback?.Invoke("Preparing browser dependencies ..."); + try { bool runningInDocker = Environment.GetEnvironmentVariable("OFDL_DOCKER") != null; - await SetupBrowser(runningInDocker); + } + catch (Exception ex) + { + statusCallback?.Invoke("Failed to prepare browser dependencies."); + Log.Error(ex, "Failed to download browser dependencies"); + return false; + } + + statusCallback?.Invoke("Please login using the opened Chromium window."); + + try + { CurrentAuth = await GetAuthFromBrowser(); return CurrentAuth != null; } catch (Exception ex) { + statusCallback?.Invoke("Failed to get auth from browser."); Log.Error(ex, "Failed to load auth from browser"); return false; } @@ -107,7 +121,7 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService { string json = JsonConvert.SerializeObject(CurrentAuth, Formatting.Indented); await File.WriteAllTextAsync(filePath, json); - Log.Debug($"Auth saved to file: {filePath}"); + Log.Debug("Auth saved to file: {FilePath}", filePath); } catch (Exception ex) { @@ -115,7 +129,7 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService } } - private Task SetupBrowser(bool runningInDocker) + private Task SetupBrowser(bool runningInDocker) => Task.Run(() => { if (runningInDocker) { @@ -134,15 +148,16 @@ public class AuthService(IServiceProvider serviceProvider) : IAuthService if (folders.Any()) { Log.Information("chromium already downloaded. Skipping install step."); - return Task.CompletedTask; + return; } } int exitCode = Program.Main(["install", "--with-deps", "chromium"]); - return exitCode != 0 - ? throw new Exception($"Playwright chromium download failed. Exited with code {exitCode}") - : Task.CompletedTask; - } + if (exitCode != 0) + { + throw new Exception($"Playwright chromium download failed. Exited with code {exitCode}"); + } + }); private static async Task GetBcToken(IPage page) => await page.EvaluateAsync("window.localStorage.getItem('bcTokenSha') || ''"); diff --git a/OF DL.Core/Services/DownloadService.cs b/OF DL.Core/Services/DownloadService.cs index 173d376..c47f6b1 100644 --- a/OF DL.Core/Services/DownloadService.cs +++ b/OF DL.Core/Services/DownloadService.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using System.Globalization; -using System.Diagnostics; using System.Security.Cryptography; using System.Text.RegularExpressions; using FFmpeg.NET; diff --git a/OF DL.Core/Services/IAuthService.cs b/OF DL.Core/Services/IAuthService.cs index 5078615..d882872 100644 --- a/OF DL.Core/Services/IAuthService.cs +++ b/OF DL.Core/Services/IAuthService.cs @@ -18,7 +18,10 @@ public interface IAuthService /// /// Launches a browser session and extracts auth data after login. /// - Task LoadFromBrowserAsync(); + /// + /// Optional callback for reporting status messages to be displayed in the UI. + /// + Task LoadFromBrowserAsync(Action? statusCallback = null); /// /// Persists the current auth data to disk. diff --git a/OF DL.Gui/ViewModels/MainWindowViewModel.cs b/OF DL.Gui/ViewModels/MainWindowViewModel.cs index f879f55..f702c79 100644 --- a/OF DL.Gui/ViewModels/MainWindowViewModel.cs +++ b/OF DL.Gui/ViewModels/MainWindowViewModel.cs @@ -258,6 +258,9 @@ public partial class MainWindowViewModel( [ObservableProperty] private string _authScreenMessage = string.Empty; + [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(StartBrowserLoginCommand))] + private bool _isBrowserLoginInProgress; + [ObservableProperty] private string _errorMessage = string.Empty; private string _actualFfmpegPath = string.Empty; @@ -676,7 +679,7 @@ public partial class MainWindowViewModel( CreatorConfigEditor.AddCreatorCommand.Execute(null); } - [RelayCommand] + [RelayCommand(CanExecute = nameof(CanStartBrowserLogin))] private async Task StartBrowserLoginAsync() { if (configService.CurrentConfig.DisableBrowserAuth) @@ -685,10 +688,29 @@ public partial class MainWindowViewModel( return; } - SetLoading("Opening browser for authentication..."); + IsBrowserLoginInProgress = true; AppendLog("Starting browser authentication flow."); - bool success = await authService.LoadFromBrowserAsync(); + StartBrowserLoginCommand.NotifyCanExecuteChanged(); + bool success; + try + { + success = await authService.LoadFromBrowserAsync(message => + { + if (string.IsNullOrWhiteSpace(message)) + { + return; + } + + AuthScreenMessage = message; + AppendLog(message); + }); + } + finally + { + IsBrowserLoginInProgress = false; + } + if (!success || authService.CurrentAuth == null) { AuthScreenMessage = @@ -963,6 +985,11 @@ public partial class MainWindowViewModel( CurrentScreen != AppScreen.Loading && !IsDownloading; + private bool CanStartBrowserLogin() => + CurrentScreen == AppScreen.Auth && + !IsDownloading && + !IsBrowserLoginInProgress; + partial void OnCurrentScreenChanged(AppScreen value) { OnPropertyChanged(nameof(IsLoadingScreen)); @@ -979,6 +1006,7 @@ public partial class MainWindowViewModel( RefreshIgnoredUsersListsCommand.NotifyCanExecuteChanged(); LogoutCommand.NotifyCanExecuteChanged(); EditConfigCommand.NotifyCanExecuteChanged(); + StartBrowserLoginCommand.NotifyCanExecuteChanged(); OpenSinglePostOrMessageModalCommand.NotifyCanExecuteChanged(); SubmitSinglePostOrMessageCommand.NotifyCanExecuteChanged(); } @@ -992,6 +1020,7 @@ public partial class MainWindowViewModel( RefreshUsersCommand.NotifyCanExecuteChanged(); RefreshIgnoredUsersListsCommand.NotifyCanExecuteChanged(); LogoutCommand.NotifyCanExecuteChanged(); + StartBrowserLoginCommand.NotifyCanExecuteChanged(); OpenSinglePostOrMessageModalCommand.NotifyCanExecuteChanged(); SubmitSinglePostOrMessageCommand.NotifyCanExecuteChanged(); } @@ -1425,7 +1454,8 @@ public partial class MainWindowViewModel( private bool ValidateConfiguredToolPathsOnStartup() { - IReadOnlyDictionary validationErrors = ConfigValidationService.Validate(configService.CurrentConfig); + IReadOnlyDictionary validationErrors = + ConfigValidationService.Validate(configService.CurrentConfig); bool hasToolPathErrors = false; FfmpegPathError = string.Empty; FfprobePathError = string.Empty; diff --git a/OF DL.Gui/Views/AboutWindow.axaml.cs b/OF DL.Gui/Views/AboutWindow.axaml.cs index 3a8b19a..9d4a604 100644 --- a/OF DL.Gui/Views/AboutWindow.axaml.cs +++ b/OF DL.Gui/Views/AboutWindow.axaml.cs @@ -46,8 +46,6 @@ public partial class AboutWindow : Window private async void OnOpenFfprobeLicenseClick(object? sender, RoutedEventArgs e) => await OpenExternalUrlAsync(FfprobeLicenseUrl); - private void OnCloseClick(object? sender, RoutedEventArgs e) => Close(); - private async Task OpenExternalUrlAsync(string url) { try diff --git a/OF DL.Gui/Views/FaqWindow.axaml.cs b/OF DL.Gui/Views/FaqWindow.axaml.cs index 966b92d..221a1d4 100644 --- a/OF DL.Gui/Views/FaqWindow.axaml.cs +++ b/OF DL.Gui/Views/FaqWindow.axaml.cs @@ -61,10 +61,6 @@ public partial class FaqWindow : Window public ObservableCollection Entries { get; } = []; - public string RuntimeSummary { get; private set; } = string.Empty; - - private void OnCloseClick(object? sender, RoutedEventArgs e) => Close(); - private void BuildEntries() { Entries.Clear(); diff --git a/OF DL.Gui/Views/MainWindow.axaml b/OF DL.Gui/Views/MainWindow.axaml index 748b009..1a4e212 100644 --- a/OF DL.Gui/Views/MainWindow.axaml +++ b/OF DL.Gui/Views/MainWindow.axaml @@ -994,6 +994,7 @@