Skip to content

Commit b951d5b

Browse files
ResoDrive 0.3.1
1 parent 1ed9da8 commit b951d5b

8 files changed

Lines changed: 166 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [0.3.1] - 2026-08-28
4+
5+
- Prevent a crash when restoring a maximized window after background startup.
6+
- Contain notification-area callback failures so a single UI action cannot
7+
terminate the application.
8+
- Log fatal early-startup failures with an error ID and show a useful error
9+
message instead of exiting silently.
10+
311
## [0.3.0] - 2026-08-28
412

513
First public preview of the cleaned ResoDrive codebase.
@@ -14,4 +22,5 @@ First public preview of the cleaned ResoDrive codebase.
1422
- Install through a small setup bundle that downloads the .NET Desktop Runtime
1523
only when it is missing.
1624

25+
[0.3.1]: https://github.com/alphasixtyfive/resodrive/releases/tag/v0.3.1
1726
[0.3.0]: https://github.com/alphasixtyfive/resodrive/releases/tag/v0.3.0

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
1010
<Deterministic>true</Deterministic>
1111
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
12-
<VersionPrefix>0.3.0</VersionPrefix>
12+
<VersionPrefix>0.3.1</VersionPrefix>
1313
<ProductDisplayName>ResoDrive</ProductDisplayName>
1414
<ExecutableBaseName>resodrive</ExecutableBaseName>
1515
<ProductDescription>Mount Nextcloud, other WebDAV storage, and SFTP servers as drives in Windows</ProductDescription>

RELEASE_NOTES.md

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
ResoDrive 0.3 is a focused public preview.
1+
ResoDrive 0.3.1 fixes a crash that could occur when reopening a maximized window
2+
after ResoDrive started in the background.
23

3-
It mounts Nextcloud, WebDAV, and SFTP storage as Windows drives, runs copy and
4-
mirror jobs, and keeps active work running when the management window is closed.
5-
Optional sign-in startup now uses Windows Task Scheduler without an artificial
6-
delay.
4+
Notification-area actions are now isolated so an unexpected failure in one UI
5+
callback cannot terminate the application. Fatal failures during very early
6+
startup are also written to the diagnostic log and displayed with an error ID
7+
instead of looking like a silent exit.
78

8-
Mount recovery uses bounded retry intervals for slow or intermittent links.
9-
Application and rclone downloads can continue after a dropped connection and are
10-
verified before use.
11-
12-
The normal download is `ResoDrive-Setup.exe`. It keeps updates small by using the
13-
shared .NET 10 Desktop Runtime and downloads that prerequisite only when required.
14-
WinFsp remains necessary only for drive mounting.
9+
The normal download is `ResoDrive-Setup.exe`. Existing settings, credentials,
10+
mounts, and sync jobs are preserved during the upgrade.

src/ResoDrive.App/Infrastructure/TrayController.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,18 @@ private void Dispatch(Action action)
116116
if (_disposed || _dispatcher.HasShutdownStarted) return;
117117
try
118118
{
119-
_dispatcher.BeginInvoke(() => { if (!_disposed) action(); });
119+
_dispatcher.BeginInvoke(() =>
120+
{
121+
if (_disposed) return;
122+
try
123+
{
124+
action();
125+
}
126+
catch (Exception exception)
127+
{
128+
ReportError(exception);
129+
}
130+
});
120131
}
121132
catch (InvalidOperationException)
122133
{
@@ -174,12 +185,25 @@ private async Task RunActionAsync(Func<Task<TrayActionResult>> action, Action<Tr
174185
catch (OperationCanceledException) { }
175186
catch (Exception exception)
176187
{
177-
_reportError?.Invoke(exception);
188+
ReportError(exception);
178189
if (!_disposed)
179190
ShowResult(TrayActionResult.Failure("Action failed", $"The operation could not be completed. Open {ProductInfo.Name} to see details."));
180191
}
181192
}
182193

194+
private void ReportError(Exception exception)
195+
{
196+
try
197+
{
198+
_reportError?.Invoke(exception);
199+
}
200+
catch (Exception reportingException)
201+
{
202+
UiDiagnosticLog.Current.Exception("tray.error_reporting_failed", reportingException);
203+
}
204+
UiDiagnosticLog.Current.Exception("tray.action_failed", exception);
205+
}
206+
183207
private static string Truncate(string value, int maximumLength) => value.Length <= maximumLength
184208
? value : string.Concat(value.AsSpan(0, maximumLength - 1), "…");
185209
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Windows;
2+
3+
namespace ResoDrive.App;
4+
5+
internal static class WindowRestoration
6+
{
7+
internal static void PrepareForShow(Window window)
8+
{
9+
ArgumentNullException.ThrowIfNull(window);
10+
11+
// WPF rejects showing a maximized window when ShowActivated is false. A
12+
// background launch deliberately sets that flag to false, so reset it
13+
// before making the window visible again. Normalize a hidden maximized
14+
// window as an additional guard; the caller reapplies the saved state
15+
// after Show has created/restored the native window.
16+
window.ShowActivated = true;
17+
window.ShowInTaskbar = true;
18+
if (!window.IsVisible && window.WindowState == WindowState.Maximized)
19+
window.WindowState = WindowState.Normal;
20+
}
21+
}

src/ResoDrive.App/MainWindow.xaml.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,13 +2385,14 @@ private void HideToTray()
23852385

23862386
private void RestoreWindow()
23872387
{
2388-
ShowInTaskbar = true;
2388+
var restoredState = _windowStateBeforeMinimize == WindowState.Maximized
2389+
? WindowState.Maximized
2390+
: WindowState.Normal;
2391+
WindowRestoration.PrepareForShow(this);
23892392
Show();
23902393
var handle = new System.Windows.Interop.WindowInteropHelper(this).Handle;
23912394
WindowAppearance.Restore(handle);
2392-
WindowState = _windowStateBeforeMinimize == WindowState.Maximized
2393-
? WindowState.Maximized
2394-
: WindowState.Normal;
2395+
WindowState = restoredState;
23952396
Activate();
23962397
WindowAppearance.BringToForeground(handle);
23972398
Focus();

src/ResoDrive.App/Program.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,29 @@ public static int Main(string[] args)
7373
return 0;
7474
}
7575

76-
var application = new App();
77-
application.InitializeComponent();
78-
return application.Run();
76+
try
77+
{
78+
var application = new App();
79+
application.InitializeComponent();
80+
return application.Run();
81+
}
82+
catch (Exception exception)
83+
{
84+
var errorId = UiDiagnosticLog.Current.Exception("startup.fatal", exception);
85+
try
86+
{
87+
System.Windows.MessageBox.Show(
88+
$"{ProductInfo.Name} could not start.\n\n{exception.Message}\n\nError ID: {errorId}",
89+
$"{ProductInfo.Name} startup error",
90+
System.Windows.MessageBoxButton.OK,
91+
System.Windows.MessageBoxImage.Error);
92+
}
93+
catch (Exception dialogException)
94+
{
95+
UiDiagnosticLog.Current.Exception("startup.error_dialog_failed", dialogException);
96+
}
97+
return 1;
98+
}
7999
}
80100

81101
internal static void TryStartHostEarly()
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.Windows;
2+
3+
namespace ResoDrive.App.Tests;
4+
5+
public sealed class WindowRestorationTests
6+
{
7+
[Fact]
8+
public void PrepareForShow_MakesHiddenMaximizedBackgroundWindowSafeToShow()
9+
{
10+
RunInSta(() =>
11+
{
12+
var window = new Window
13+
{
14+
ShowActivated = false,
15+
ShowInTaskbar = false,
16+
WindowState = WindowState.Maximized,
17+
};
18+
19+
WindowRestoration.PrepareForShow(window);
20+
21+
Assert.True(window.ShowActivated);
22+
Assert.True(window.ShowInTaskbar);
23+
Assert.Equal(WindowState.Normal, window.WindowState);
24+
var exception = Record.Exception(window.Show);
25+
Assert.Null(exception);
26+
window.Close();
27+
});
28+
}
29+
30+
[Theory]
31+
[InlineData(WindowState.Normal)]
32+
[InlineData(WindowState.Minimized)]
33+
public void PrepareForShow_PreservesNonMaximizedState(WindowState state)
34+
{
35+
RunInSta(() =>
36+
{
37+
var window = new Window
38+
{
39+
ShowActivated = false,
40+
ShowInTaskbar = false,
41+
WindowState = state,
42+
};
43+
44+
WindowRestoration.PrepareForShow(window);
45+
46+
Assert.True(window.ShowActivated);
47+
Assert.True(window.ShowInTaskbar);
48+
Assert.Equal(state, window.WindowState);
49+
window.Close();
50+
});
51+
}
52+
53+
private static void RunInSta(Action action)
54+
{
55+
Exception? failure = null;
56+
var thread = new Thread(() =>
57+
{
58+
try
59+
{
60+
action();
61+
}
62+
catch (Exception exception)
63+
{
64+
failure = exception;
65+
}
66+
});
67+
thread.SetApartmentState(ApartmentState.STA);
68+
thread.Start();
69+
Assert.True(thread.Join(TimeSpan.FromSeconds(10)));
70+
if (failure is not null)
71+
throw failure;
72+
}
73+
}

0 commit comments

Comments
 (0)