From 6fc7506eb7abe7641575a9900f6145f8eaf2cd70 Mon Sep 17 00:00:00 2001 From: netquick Date: Tue, 25 Aug 2026 01:49:50 +0200 Subject: [PATCH] Add queued FFmpeg conversion and finish splitting the window from the host. Co-authored-by: Cursor --- Backlog.md | 12 +- Explorer.slnx | 1 + docs/ARCHITECTURE.md | 25 +- docs/Documentation.md | 46 ++- src/Explorer.App/App.xaml.cs | 131 +++++-- src/Explorer.App/AppServices.cs | 8 - src/Explorer.App/ConvertWindow.xaml | 47 +++ src/Explorer.App/ConvertWindow.xaml.cs | 41 +++ src/Explorer.App/Explorer.App.csproj | 22 +- src/Explorer.App/MainWindow.xaml | 8 +- src/Explorer.App/MainWindow.xaml.cs | 42 +++ src/Explorer.App/OperationProfilesWindow.xaml | 6 +- src/Explorer.App/SettingsWindow.xaml | 10 +- src/Explorer.App/SettingsWindow.xaml.cs | 18 +- src/Explorer.Application/BrowseService.cs | 6 +- src/Explorer.Application/ConversionPlanner.cs | 148 ++++++++ src/Explorer.Application/FfmpegLocator.cs | 41 +++ .../FileOperationProfilePlanner.cs | 48 ++- src/Explorer.Application/HydrationGuard.cs | 6 +- src/Explorer.Application/ICloudOverlay.cs | 20 ++ .../IMediaConversionProvider.cs | 18 + .../IndexStoreLifetime.cs | 55 +++ src/Explorer.Application/NullCloudOverlay.cs | 32 ++ .../StorageProviderRegistry.cs | 4 +- .../UiPreferencesStore.cs | 121 ++++++- src/Explorer.Contracts/IHostConnection.cs | 8 + src/Explorer.Contracts/IWorkbenchHost.cs | 2 + src/Explorer.Domain/AppConstants.cs | 2 +- src/Explorer.Domain/ConversionFormats.cs | 99 ++++++ src/Explorer.Domain/Enums.cs | 10 +- src/Explorer.Domain/OperationProfile.cs | 4 +- .../ArchiveCatalog.cs | 2 +- .../Explorer.FileOperations.csproj | 2 + .../FileOperationErrors.cs | 2 +- .../FileOperationRegistration.cs | 21 ++ .../FileOperationService.cs | 14 + .../NativeFileOperationExecutor.cs | 78 ++++- .../OperationAvailability.cs | 1 + .../OperationProfileService.cs | 29 +- src/Explorer.FileOperations/TransferQueue.cs | 11 + src/Explorer.Host/Explorer.Host.csproj | 5 +- src/Explorer.Host/HostEntry.cs | 87 +++++ src/Explorer.Host/HostTray.cs | 159 +++++++++ src/Explorer.Host/Program.cs | 29 +- src/Explorer.Host/app.manifest | 14 + .../Explorer.Hosting.Client.csproj | 26 ++ .../ExplorerHostClientServices.cs | 74 ++++ .../HostLogonAutostart.cs | 99 ++++++ .../Ipc/WorkbenchIpc.cs | 15 +- .../Ipc/WorkbenchPipeClient.cs | 270 +++++++++++--- .../WorkbenchHostConnector.cs | 130 +++++++ .../DeferredServiceProvider.cs | 17 + src/Explorer.Hosting/Explorer.Hosting.csproj | 1 + src/Explorer.Hosting/ExplorerHostServices.cs | 38 +- src/Explorer.Hosting/HostLogonAutostart.cs | 109 ------ src/Explorer.Hosting/IndexStoreLifetime.cs | 25 -- .../Ipc/WorkbenchPipeServer.cs | 329 ++++++++++++++---- .../WorkbenchHostConnector.cs | 58 --- .../Explorer.Indexing.csproj | 1 - .../ViewModels/ConvertViewModel.cs | 108 ++++++ .../ViewModels/ExplorerTabViewModel.cs | 29 +- .../ViewModels/MainViewModel.cs | 136 +++++++- .../ViewModels/NavigationTreeViewModel.cs | 4 +- .../ViewModels/OperationProfilesViewModel.cs | 24 +- .../ViewModels/TransferQueueViewModel.cs | 8 + src/Explorer.Storage.Sqlite/IndexStoreLock.cs | 21 ++ .../OperationProfileStore.cs | 23 +- src/Explorer.Storage.Sqlite/SchemaScript.cs | 2 + .../SqliteIndexStore.cs | 11 + .../FfmpegConversionExecutor.cs | 247 +++++++++++++ .../ConversionPlannerTests.cs | 109 ++++++ .../FfmpegLocatorTests.cs | 30 ++ .../FileOperationProfilePlannerTests.cs | 43 ++- .../UiPreferencesStoreTests.cs | 66 +++- tests/Explorer.Domain.Tests/DomainTests.cs | 26 ++ .../OperationProfileServiceTests.cs | 18 +- .../TransferQueueTests.cs | 65 +++- .../CoreRegistrationTests.cs | 24 +- .../Explorer.Hosting.Tests.csproj | 1 + .../HostLogonAutostartTests.cs | 15 +- .../ProjectGraphTests.cs | 67 ++++ .../WorkbenchPipeTests.cs | 124 ++++++- .../Explorer.Indexing.Tests.csproj | 1 + tests/Explorer.Indexing.Tests/ScannerTests.cs | 1 + tests/Explorer.Storage.Tests/StorageTests.cs | 16 + 85 files changed, 3394 insertions(+), 512 deletions(-) create mode 100644 src/Explorer.App/ConvertWindow.xaml create mode 100644 src/Explorer.App/ConvertWindow.xaml.cs create mode 100644 src/Explorer.Application/ConversionPlanner.cs create mode 100644 src/Explorer.Application/FfmpegLocator.cs create mode 100644 src/Explorer.Application/ICloudOverlay.cs create mode 100644 src/Explorer.Application/IMediaConversionProvider.cs create mode 100644 src/Explorer.Application/IndexStoreLifetime.cs create mode 100644 src/Explorer.Application/NullCloudOverlay.cs create mode 100644 src/Explorer.Contracts/IHostConnection.cs create mode 100644 src/Explorer.Domain/ConversionFormats.cs rename src/{Explorer.Indexing => Explorer.FileOperations}/ArchiveCatalog.cs (98%) create mode 100644 src/Explorer.FileOperations/FileOperationRegistration.cs create mode 100644 src/Explorer.Host/HostEntry.cs create mode 100644 src/Explorer.Host/HostTray.cs create mode 100644 src/Explorer.Host/app.manifest create mode 100644 src/Explorer.Hosting.Client/Explorer.Hosting.Client.csproj create mode 100644 src/Explorer.Hosting.Client/ExplorerHostClientServices.cs create mode 100644 src/Explorer.Hosting.Client/HostLogonAutostart.cs rename src/{Explorer.Hosting => Explorer.Hosting.Client}/Ipc/WorkbenchIpc.cs (71%) rename src/{Explorer.Hosting => Explorer.Hosting.Client}/Ipc/WorkbenchPipeClient.cs (61%) create mode 100644 src/Explorer.Hosting.Client/WorkbenchHostConnector.cs create mode 100644 src/Explorer.Hosting/DeferredServiceProvider.cs delete mode 100644 src/Explorer.Hosting/HostLogonAutostart.cs delete mode 100644 src/Explorer.Hosting/IndexStoreLifetime.cs delete mode 100644 src/Explorer.Hosting/WorkbenchHostConnector.cs create mode 100644 src/Explorer.Presentation/ViewModels/ConvertViewModel.cs create mode 100644 src/Explorer.Windows/FfmpegConversionExecutor.cs create mode 100644 tests/Explorer.Application.Tests/ConversionPlannerTests.cs create mode 100644 tests/Explorer.Application.Tests/FfmpegLocatorTests.cs create mode 100644 tests/Explorer.Hosting.Tests/ProjectGraphTests.cs diff --git a/Backlog.md b/Backlog.md index 0edba29..66089f2 100644 --- a/Backlog.md +++ b/Backlog.md @@ -543,16 +543,16 @@ Potential provider: Possible operations: -- [ ] Video conversion +- [x] Video conversion - [ ] Audio conversion - [ ] Codec conversion - [ ] Resolution conversion -- [ ] Extract audio +- [x] Extract audio - [ ] Generate thumbnails ## Images -- [ ] HEIC -> JPEG +- [x] HEIC -> JPEG - [ ] PNG -> JPEG - [ ] Resize - [ ] Rotate @@ -566,6 +566,8 @@ Conversions should support: and be usable inside File Operation Profiles. +V1: Convert… dialog and an operation-profile Convert toggle. Jobs run on the host through the File Operations Queue. FFmpeg is discovered, not bundled. + --- # Git Integration @@ -693,9 +695,9 @@ Discovery: Settings path, then Program Files, then PATH. Missing 7-Zip fails the ## FFmpeg -Potential: +`FfmpegConversionExecutor` (`IMediaConversionProvider`) -`MediaConversionProvider` +Discovery: Settings path, then Program Files, then PATH. Missing FFmpeg fails the queued job with an install hint. FFmpeg is not bundled. V1: H.264 MP4, extract AAC/M4A, HEIC→JPEG. Preview first. Online-only cloud files are not hydrated. ## Git diff --git a/Explorer.slnx b/Explorer.slnx index 3f7859b..42b849b 100644 --- a/Explorer.slnx +++ b/Explorer.slnx @@ -7,6 +7,7 @@ + diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cce8051..6e44bae 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -837,17 +837,24 @@ Bitte diese Punkte klären. Empfohlene Defaults in Klammern: --- -## Hintergrunddienst: A vs. B +## Hintergrundprozess: Explorer.Host.exe -| | A In-Process (V1) | B Windows Service (später) | +Indexing, USN/watchers, transfer queue, hash worker, and history rollup run in **`Explorer.Host.exe`**, not in the WPF window. + +| | Explorer.Host.exe (current) | Windows Service | |---|---|---| -| Komplexität | niedrig | Session 0, ACL, IPC, Updates | -| Index wenn UI zu | stoppt | läuft weiter | -| USN-Rechte | oft unzureichend | SYSTEM kann Journal lesen | -| Crash-Isolation | UI-Crash stoppt Index | getrennt | -| Empfohlen | **V1 = A** | **V2 = B**, wenn Identity+Schema stabil sind | +| Complexity | per-user process, named pipe | Session 0, ACL, service updates | +| Index when the window is closed | continues | continues | +| Rights | same user as the window | typically SYSTEM | +| Crash isolation | window crash does not stop the index | isolated | +| Autostart | optional HKCU Run (current user, no elevation) | service start | +| Used | **yes** | **not used** | -V1 so schneiden, dass der Indexer ein `IHostedService` ist. Im Dienst-Host später derselbe Service, UI spricht über Named Pipe / gRPC (`Explorer.Contracts`). Nicht in V1 bauen, Interfaces nicht an WPF kleben. +The window (`Explorer.App.exe`) opens the SQLite index **read-only** (WAL). Only the host opens it for write. `Explorer.Contracts` (`IWorkbenchHost`, `ICloudOverlay`, `IHostConnection`) is the IPC surface over a current-user named pipe. Plugin implementations load in the host; the window talks overlay through the pipe. + +If the host is not running, the window starts `Explorer.Host.exe` beside itself. Closing the window does not stop the host. Quit it from the host tray (**Quit background host**) or **File → Stop background host…**. Settings can add the host to the current user’s Windows sign-in programs (`HKCU\...\Run`) so it starts at logon without administrator rights. + +The original V1 sketch was in-process `IHostedService` inside the UI. That path is gone. A Windows Service is still out of scope. --- @@ -886,7 +893,7 @@ Deviations from the design above, with reasons: 3. **WPF-UI (lepoco)** — not used. Light/Dark Fluent-style brushes live in `Themes/Dark.xaml` and `Themes/Light.xaml` so the UI toolkit stays replaceable. 4. **`PRAGMA mmap_size` / large `cache_size`** — not applied at runtime. They made SQLite native startup unreliable under concurrent test hosts; WAL + `synchronous=NORMAL` remain. 5. **App data folder** — `%LocalAppData%\ExplorerWorkbench` (not `Explorer`) so the working name does not collide with Windows Explorer. -6. **Background work** — in-process `IHostedService` instances (indexer, transfer queue, hash worker, history rollup, watchers). No Windows Service in this run. +6. **Background work** — indexer, transfer queue, hash worker, history rollup, and watchers run as `IHostedService` instances inside **`Explorer.Host.exe`**. The WPF window is a named-pipe client (`Explorer.Hosting.Client`) with a read-only SQLite store. No Windows Service; optional current-user sign-in (`HKCU\Software\Microsoft\Windows\CurrentVersion\Run`). 7. **Search syntax** — structured `SearchQuery` exists; Everything-like lexer is not shipped (Phase 7). 8. **UNIQUE identity** — `UNIQUE (source_id, ifnull(parent_id,-1), name_norm)` because SQLite UNIQUE treats NULLs as distinct. 9. **INSERT ids** — `Microsoft.Data.Sqlite` + Dapper `ExecuteScalarAsync` on `INSERT … RETURNING` leaves the write connection busy and hangs the next command. Writer-connection SQL uses `SqliteCommand` (`SqliteExec`) and `last_insert_rowid()`. diff --git a/docs/Documentation.md b/docs/Documentation.md index f95bbda..71fe24f 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -8,7 +8,7 @@ Mental model: Windows remains the source of truth. Removing a location from Workbench removes Workbench’s index data for it. It does **not** disconnect a network drive, unlink OneDrive, or change Explorer settings. -Version documented here: **0.1** (schema 8). This file is the user guide. Edit it in any text editor; Explorer Workbench reloads it when you open **Help → Documentation**. +Version documented here: **0.1** (schema 9). This file is the user guide. Edit it in any text editor; Explorer Workbench reloads it when you open **Help → Documentation**. --- @@ -18,27 +18,27 @@ Workbench **does**: - Browse live folders (local, removable, network, cloud mounts) - Index locations you choose, then search and analyze them -- Queue copy, move, recycle, rename, archive, sync, and organize work +- Queue copy, move, recycle, rename, archive, convert, sync, and organize work - Overlay Git and cloud status without becoming a Git client or a sync engine Workbench **does not**: - Two-way sync -- Convert media (no FFmpeg in this build) - Hydrate online-only cloud files just to look at them - Change Windows drive mappings or cloud client folders - Replace Git (no stash, branch UI, mergetool, or credential dialog) -Specialized tools still do specialized jobs. 7-Zip compresses. Git reports status, shows diffs, commits selected files, resolves conflicts, and runs fetch, pull, and push. Workbench orchestrates. +Specialized tools still do specialized jobs. 7-Zip compresses. FFmpeg converts a few media kinds. Git reports status, shows diffs, commits selected files, resolves conflicts, and runs fetch, pull, and push. Workbench orchestrates. --- ## First launch -The window opens immediately. Locations fill in a moment later — Workbench does not wait for slow network shares or a second instance locking the index. +1. The window starts `Explorer.Host.exe` if it is not already running, then connects over a named pipe. Locations fill in a moment later — Workbench does not wait for slow network shares. 2. Browsing works with an empty index. 3. Folder sizes, search, duplicates, and storage analysis need an index. Use the banner **Build index**, **Tools → Locations → Index this location**, or the toolbar **Index** control. 4. Data lives under `%LocalAppData%\ExplorerWorkbench\` — never beside the executable. +5. Closing the window leaves the host running (indexing and the queue). A tray icon **Explorer Workbench host** can open the window again or **Quit background host**. **File → Stop background host…** does the same from the window. | Path | Contents | | --- | --- | @@ -51,7 +51,7 @@ The window opens immediately. Locations fill in a moment later — Workbench doe ## Window layout - **Title bar** — Explorer Workbench; minimize / maximize / close. -- **Menu** — File, View, Tools, Settings, Help. Tools is grouped: Storage, Locations, File Operations (including Archives), Automation, Development, Recycle Bin. +- **Menu** — File (including Stop background host), View, Tools, Settings, Help. Tools is grouped: Storage, Locations, File Operations (including Archives and Convert), Automation, Development, Recycle Bin. - **Toolbar** — path, navigation, view mode, search, storage, queue summary. - **Tree** — This PC, Network, Cloud (grouping is optional in Settings). - **Folder pane** — details, list, or preview. Split pane is optional. @@ -67,6 +67,8 @@ The window opens immediately. Locations fill in a moment later — Workbench doe | Details / List / Preview | View menu or toolbar | | Refresh | View → Refresh, or `F5` | +Tabs, split panes, and the folder shown in each pane are restored the next time you open Workbench. + --- ## Locations @@ -172,7 +174,7 @@ Intentional and sync copies are hidden by default. Hardlinks are not wasted spac Almost every change goes through the queue instead of happening silently. -Supported operations today: copy, move, recycle, permanent delete, rename, empty Recycle Bin, extract, compress, add to archive, verify archive. +Supported operations today: copy, move, recycle, permanent delete, rename, empty Recycle Bin, extract, compress, add to archive, verify archive, convert. The queue: @@ -217,6 +219,20 @@ Jobs go through the queue. Online-only cloud archives are refused so Workbench w --- +## Convert + +Needs **`ffmpeg.exe`** on the machine. FFmpeg is usually a zip, not an installer: unpack a Windows build and either put `ffmpeg.exe` on PATH, under `Program Files\ffmpeg\bin\`, or point Settings at the file. `ffprobe` / `ffplay` are not required. FFmpeg is not bundled. This is not HandBrake — a few conversions only. + +| Kind | Output | +| --- | --- | +| Video to H.264 MP4 | `.mp4` next to the source, or in a folder you pick | +| Extract audio | AAC in `.m4a` | +| HEIC to JPEG | `.jpg` (depends on the FFmpeg build having a HEIC decoder) | + +**Tools → File Operations → Convert…** or **Convert…** on the item context menu. Select files or a folder, pick a kind and destination, preview names, then Queue. Each file is one queue job on the background host. Online-only cloud files are skipped. Existing names get a unique suffix so nothing is overwritten. Source last-write time is copied onto the output when that is possible. + +--- + ## Folder sync **Tools → Automation → Folder sync…** — **one-way** only. @@ -236,18 +252,19 @@ Files copied by sync are tagged as synchronized duplicates. **Tools → Automation → Operation profiles…** — named recipes that **plan** work, then enqueue it. Steps never touch the filesystem themselves. -Toggles (not a free-form graph): Copy, Rename, Compress, require a clean Git working tree. Excludes are globs, one per line. +Toggles (not a free-form graph): Copy, Rename, Compress, Convert, require a clean Git working tree. Excludes are globs, one per line. Built-in recipes (seeded when the list is empty): 1. **Archive folder** — require clean Git, compress 7z, exclude `.git` / `bin` / `obj` / `.vs` 2. **Copy to destination** — copy; AutoRun when the destination volume connects +3. **Convert videos to MP4** — FFmpeg H.264 MP4 into the destination folder -Run from the window, from **Run profile** on the context menu (always previews first), or by dropping files onto a profile. AutoRun is Copy-only (no rename, no compress) and fires on unreachable → reachable, not on a timer while already online. +Run from the window, from **Run profile** on the context menu (always previews first), or by dropping files onto a profile. AutoRun is Copy-only (no rename, no compress, no convert) and fires on unreachable → reachable, not on a timer while already online. -Dirty or missing Git, missing 7-Zip, or an unreachable destination stops the plan. Nothing is queued. +Dirty or missing Git, missing 7-Zip, missing FFmpeg, or an unreachable destination stops the plan. Nothing is queued. -Not in this build: SHA-256, recycle source after success, FFmpeg, scheduled or folder-watcher triggers. +Not in this build: SHA-256, recycle source after success, GPU tuner, trim editor, scheduled or folder-watcher triggers. --- @@ -295,7 +312,7 @@ When a cloud folder is added: - **Always keep on this device** / **Free up space** when the provider supports pin/dehydrate - Quota in capacity/free space where the provider reports it -Workbench never starts a cloud vendor’s own two-way sync. It never hydrates a file as a side effect of browse, size, search, hash, or archive. +Workbench never starts a cloud vendor’s own two-way sync. It never hydrates a file as a side effect of browse, size, search, hash, archive, or convert. --- @@ -309,8 +326,11 @@ Workbench never starts a cloud vendor’s own two-way sync. It never hydrates a - Show protected system locations - Auto-clear queue when done - Include archive contents in the index +- Automatically index removable drives when they appear +- Start Explorer.Host.exe at Windows sign-in (current-user Startup, no administrator rights) - Path to 7-Zip - Path to git.exe +- Path to ffmpeg.exe These options change what Workbench shows and indexes. They do not change Windows Explorer settings. @@ -336,7 +356,7 @@ Left open on purpose: - Two-way sync and conflict resolution UI - Undo for copy/move - Concurrent copies across different disks -- FFmpeg / media conversion +- GPU / trim / filter conversion - Multi-PC search, sharing, encrypted vaults - Robocopy as a second transfer engine - Scheduled profiles and folder-watcher triggers diff --git a/src/Explorer.App/App.xaml.cs b/src/Explorer.App/App.xaml.cs index 5c0a0ef..1b403cc 100644 --- a/src/Explorer.App/App.xaml.cs +++ b/src/Explorer.App/App.xaml.cs @@ -1,11 +1,14 @@ using System.IO; using System.Windows; +using System.Windows.Controls; using Explorer.Hosting; using Explorer.Hosting.Ipc; using Explorer.Presentation.ViewModels; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Serilog; +using Serilog.Extensions.Logging; namespace Explorer.App; @@ -14,8 +17,10 @@ public partial class App : System.Windows.Application private IHost? _host; private WorkbenchPipeClient? _workbenchClient; - protected override async void OnStartup(StartupEventArgs e) + protected override void OnStartup(StartupEventArgs e) { + // Closing the splash must not quit the process before the main window exists. + ShutdownMode = ShutdownMode.OnExplicitShutdown; base.OnStartup(e); DispatcherUnhandledException += (_, args) => { @@ -32,53 +37,80 @@ public partial class App : System.Windows.Application retainedFileCountLimit: 14) .CreateLogger(); - _workbenchClient = await WorkbenchHostConnector.ConnectOrStartAsync( - TimeSpan.FromSeconds(12), - logger: null).ConfigureAwait(true); - var hostExe = HostLogonAutostart.FindHostExecutable(); - if (_workbenchClient is null && hostExe is not null) - { - MessageBox.Show( - "Explorer.Host.exe is present but the window could not connect to it. See logs.", - "Explorer Workbench", - MessageBoxButton.OK, - MessageBoxImage.Error); - Shutdown(-1); - return; - } + var splash = ShowStartupSplash(); + _ = StartWorkbenchAsync(splash); + } - _host = Host.CreateDefaultBuilder() - .UseSerilog() - .ConfigureServices((_, services) => + private async Task StartWorkbenchAsync(Window splash) + { + using var loggerFactory = new SerilogLoggerFactory(Log.Logger); + try + { + try { - if (_workbenchClient is not null) + var logger = loggerFactory.CreateLogger("HostConnector"); + _workbenchClient = await WorkbenchHostConnector.ConnectOrStartAsync( + TimeSpan.FromSeconds(60), + logger) + .ConfigureAwait(true); + } + catch (Exception ex) + { + Log.Error(ex, "Could not connect to Explorer.Host.exe"); + } + + if (_workbenchClient is null) + { + var hostExe = HostLogonAutostart.FindHostExecutable(); + MessageBox.Show( + hostExe is null + ? "Explorer.Host.exe was not found beside Explorer.App.exe. Copy the host executable next to the window, then start again." + : "Explorer.Host.exe did not accept a connection in time. The host process you just started is still initializing; wait until its CPU usage drops, then start Explorer.App.exe again. You do not need to close Explorer.Host.", + "Explorer Workbench", + MessageBoxButton.OK, + MessageBoxImage.Error); + Shutdown(-1); + return; + } + + _host = Host.CreateDefaultBuilder() + .UseSerilog() + .ConfigureServices((_, services) => { services.AddExplorerClient(_workbenchClient); services.AddExplorerUi(); - } - else - { - services.AddExplorer(); - } - }) - .Build(); + }) + .Build(); - var vm = _host.Services.GetRequiredService(); - var window = _host.Services.GetRequiredService(); - vm.PrepareUi(); - window.DataContext = vm; - window.Show(); - try - { - await vm.InitializeAsync().ConfigureAwait(true); + var vm = _host.Services.GetRequiredService(); + var window = _host.Services.GetRequiredService(); + vm.PrepareUi(); + window.DataContext = vm; + MainWindow = window; + window.Show(); + ShutdownMode = ShutdownMode.OnMainWindowClose; + + try + { + await vm.InitializeAsync().ConfigureAwait(true); + } + catch (Exception ex) + { + Log.Error(ex, "Startup initialization failed"); + vm.Footer = "Started with errors. See logs."; + } + + await _host.StartAsync().ConfigureAwait(true); } catch (Exception ex) { - Log.Error(ex, "Startup initialization failed"); - vm.Footer = "Started with errors. See logs."; + Log.Error(ex, "Could not start Explorer Workbench"); + Shutdown(-1); + } + finally + { + splash.Close(); } - - await _host.StartAsync().ConfigureAwait(true); } protected override async void OnExit(ExitEventArgs e) @@ -97,4 +129,27 @@ public partial class App : System.Windows.Application Log.CloseAndFlush(); base.OnExit(e); } + + private static Window ShowStartupSplash() + { + var splash = new Window + { + Title = "Explorer Workbench", + Width = 420, + Height = 120, + WindowStartupLocation = WindowStartupLocation.CenterScreen, + ResizeMode = ResizeMode.NoResize, + WindowStyle = WindowStyle.ToolWindow, + ShowInTaskbar = true, + Content = new TextBlock + { + Text = "Starting Explorer Workbench…", + Margin = new Thickness(20), + TextWrapping = TextWrapping.Wrap, + VerticalAlignment = VerticalAlignment.Center + } + }; + splash.Show(); + return splash; + } } diff --git a/src/Explorer.App/AppServices.cs b/src/Explorer.App/AppServices.cs index 28a211e..1c6275f 100644 --- a/src/Explorer.App/AppServices.cs +++ b/src/Explorer.App/AppServices.cs @@ -1,6 +1,5 @@ using Explorer.Application; using Explorer.Domain.Abstractions; -using Explorer.Hosting; using Explorer.Presentation; using Explorer.Presentation.ViewModels; using Explorer.Windows; @@ -11,13 +10,6 @@ namespace Explorer.App; public static class AppServices { - public static IServiceCollection AddExplorer(this IServiceCollection services) - { - services.AddExplorerCore(); - services.AddExplorerUi(); - return services; - } - public static IServiceCollection AddExplorerUi(this IServiceCollection services) { services.AddSingleton(); diff --git a/src/Explorer.App/ConvertWindow.xaml b/src/Explorer.App/ConvertWindow.xaml new file mode 100644 index 0000000..c96b560 --- /dev/null +++ b/src/Explorer.App/ConvertWindow.xaml @@ -0,0 +1,47 @@ + + + +