Skip to content

Commit 45cd8d0

Browse files
committed
Fix: CI. Add Close to Tray dialog and improve shutdown handling
Introduced CloseToTrayDialog for window close actions, allowing users to choose hide-to-tray or exit and remember their preference. Moved core manager shutdown logic to App for proper cleanup. Improved DownloadManager and RSSManager thread responsiveness. Updated MainViewModel port check to prevent shutdown hangs. Enhanced build pipeline for multi-platform packaging and artifact handling. Includes minor fixes and project file updates.
1 parent 55e4324 commit 45cd8d0

19 files changed

Lines changed: 361 additions & 74 deletions

.github/workflows/alpha.yml

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ jobs:
1212
alpha:
1313
runs-on: windows-2025-vs2026
1414

15+
strategy:
16+
matrix:
17+
configuration: [Release]
18+
platform: [x64, ARM64]
19+
20+
permissions:
21+
contents: write
22+
packages: write
23+
1524
steps:
1625
- uses: actions/checkout@v5
1726
with:
@@ -20,34 +29,72 @@ jobs:
2029
- uses: microsoft/setup-msbuild@v2
2130
- uses: nuget/setup-nuget@v2
2231

23-
- name: Restore Cert
32+
- name: Set up vcpkg
33+
working-directory: ${{env.VCPKG_INSTALLATION_ROOT}}
34+
run: vcpkg integrate install
35+
36+
- name: Restore NuGet
37+
run: nuget restore OpenNet.slnx
38+
shell: pwsh
39+
40+
- name: cache nuget packages
41+
uses: actions/cache@v5
42+
with:
43+
path: ~/.nuget/packages
44+
45+
- name: Restore Certificate
2446
run: |
2547
$bytes = [Convert]::FromBase64String("${{ secrets.PFX_CERT_BASE64 }}")
2648
[IO.File]::WriteAllBytes("cert.pfx", $bytes)
2749
shell: pwsh
28-
29-
- run: nuget restore OpenNet.slnx
30-
shell: pwsh
31-
32-
- name: Build Bundle
50+
51+
- name: Build MSIX
3352
run: |
3453
msbuild OpenNet.slnx `
35-
/p:Configuration=Release `
36-
/p:Platform=x64 `
37-
/p:AppxBundle=Always `
38-
/p:AppxBundlePlatforms="x64|ARM64" `
39-
/p:GenerateAppxPackageOnBuild=true `
40-
/p:PackageCertificateKeyFile=cert.pfx `
41-
/p:PackageCertificatePassword="${{ secrets.PFX_PASSWORD }}" `
54+
/p:Configuration=${{ matrix.configuration }} `
55+
/p:Platform=${{ matrix.platform }} `
56+
/p:UapAppxPackageBuildMode=SideloadOnly `
4257
/p:AppxPackageSigningEnabled=true `
43-
/p:VcpkgEnableManifest=true `
58+
/p:PackageCertificateKeyFile=../cert.pfx `
59+
/p:PackageCertificatePassword="${{ secrets.PFX_PASSWORD }}" `
4460
/m
4561
shell: pwsh
62+
63+
- name: Generate Date
64+
run: |
65+
$date = Get-Date -Format yyyyMMdd
66+
Add-Content -Path $Env:GITHUB_ENV -Value "DATE=$date"
67+
shell: pwsh
68+
69+
- name: Zip Package
70+
run: |
71+
Compress-Archive `
72+
-Path "Packages/*${{ matrix.platform }}*" `
73+
-DestinationPath "OpenNet.Canary_${{ env.DATE }}_${{ matrix.platform }}.zip"
74+
shell: pwsh
75+
76+
- name: 7z Package
77+
run: |
78+
if (-not (Get-Command 7z -ErrorAction SilentlyContinue)) {
79+
choco install 7zip -y
80+
}
4681
47-
- uses: softprops/action-gh-release@v2
82+
7z a -t7z -mx=9 `
83+
"OpenNet.Canary_${{ env.DATE }}_${{ matrix.platform }}.7z" `
84+
"Packages\*${{ matrix.platform }}*"
85+
shell: pwsh
86+
87+
- name: Upload Canary Artifact
88+
uses: actions/upload-artifact@v6
89+
with:
90+
name: OpenNet.Canary_${{ env.DATE }}_${{ matrix.platform }}
91+
path: |
92+
OpenNet.Canary_${{ env.DATE }}_${{ matrix.platform }}.zip
93+
OpenNet.Canary_${{ env.DATE }}_${{ matrix.platform }}.7z
94+
95+
- name: Create Pre-release
96+
uses: softprops/action-gh-release@v2
4897
with:
4998
tag_name: ${{ github.ref_name }}
5099
prerelease: true
51-
files: "**/*.msixbundle"
52-
env:
53-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
100+
files: OpenNet.Canary_${{ env.DATE }}_${{ matrix.platform }}.zip, OpenNet.Canary_${{ env.DATE }}_${{ matrix.platform }}.7z

.github/workflows/canary.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,19 @@ jobs:
4141
- uses: microsoft/setup-msbuild@v2
4242
- uses: nuget/setup-nuget@v2
4343

44+
- name: Set up vcpkg
45+
working-directory: ${{env.VCPKG_INSTALLATION_ROOT}}
46+
run: vcpkg integrate install
47+
4448
- name: Restore NuGet
4549
run: nuget restore OpenNet.slnx
4650
shell: pwsh
47-
51+
52+
- name: cache nuget packages
53+
uses: actions/cache@v5
54+
with:
55+
path: ~/.nuget/packages
56+
4857
- name: Restore Certificate
4958
run: |
5059
$bytes = [Convert]::FromBase64String("${{ secrets.PFX_CERT_BASE64 }}")

OpenNet/App.xaml.cpp

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
#include "App.xaml.h"
33
#include "MainWindow.xaml.h"
44
#include "UI/Shell/NotifyIconContextMenu.xaml.h"
5+
#include "UI/Xaml/View/Dialog/CloseToTrayDialog.h"
56
#include "Helpers/WindowHelper.h"
67
#include "Helpers/ThemeHelper.h"
78
#include "Core/P2PManager.h"
9+
#include "Core/DownloadManager.h"
810
#include "Core/RSS/RSSManager.h"
911
#include "Core/GeoIP/GeoIPManager.h"
1012

1113
#include <winrt/Windows.ApplicationModel.Activation.h>
14+
#include <winrt/Microsoft.Windows.Storage.h>
15+
#include <winrt/Microsoft.UI.Xaml.Controls.h>
1216
#include <sentry.h>
1317

1418
using namespace winrt;
@@ -60,23 +64,26 @@ namespace winrt::OpenNet::implementation
6064
trayIcon = OpenNet::UI::Shell::NotifyIconContextMenu();
6165
trayIcon.Show();
6266

63-
// Register window closing event - hide to tray instead of closing
67+
// Register window closing event - close strategy (hide to tray / ask / exit)
6468
window.AppWindow().Closing([](auto const&, winrt::Microsoft::UI::Windowing::AppWindowClosingEventArgs const& args)
6569
{
6670
// If we are in an intentional exit, allow the window to close
6771
if (App::s_isExiting)
6872
return;
6973

70-
// Cancel the close and hide to tray instead
74+
// If no tray icon was created, allow direct close
75+
if (!App::trayIcon)
76+
return;
77+
78+
// Cancel the close; the async strategy decides what happens next
7179
args.Cancel(true);
7280

73-
// Hide the window
74-
if (App::window)
75-
{
76-
App::window.AppWindow().Hide();
77-
}
81+
// Prevent re-entrance
82+
if (App::s_isHandlingClose)
83+
return;
84+
App::s_isHandlingClose = true;
7885

79-
OutputDebugStringA("App: MainWindow hidden to tray\n");
86+
HandleCloseStrategyAsync();
8087
});
8188

8289
// Initialize RSS Manager early so feeds update in the background
@@ -203,11 +210,14 @@ namespace winrt::OpenNet::implementation
203210
trayIcon = nullptr;
204211
}
205212

213+
// Stop RSS background updates
214+
::OpenNet::Core::RSS::RSSManager::Instance().Stop();
215+
206216
// Shutdown P2PManager
207217
::OpenNet::Core::P2PManager::Instance().Shutdown();
208218

209-
// Stop RSS background updates
210-
::OpenNet::Core::RSS::RSSManager::Instance().Stop();
219+
// Shutdown DownloadManager (Aria2 + refresh thread)
220+
::OpenNet::Core::DownloadManager::Instance().Shutdown();
211221

212222
// make sure everything flushes
213223
sentry_close();
@@ -223,6 +233,102 @@ namespace winrt::OpenNet::implementation
223233
}
224234
}
225235

236+
winrt::fire_and_forget App::HandleCloseStrategyAsync()
237+
{
238+
try
239+
{
240+
auto localSettings = winrt::Microsoft::Windows::Storage::ApplicationData::GetDefault().LocalSettings();
241+
auto values = localSettings.Values();
242+
243+
// Strategy A: user already chose, don't ask again
244+
if (values.HasKey(L"Hide2TrayWhenCloseAsked"))
245+
{
246+
bool asked = unbox_value<bool>(values.Lookup(L"Hide2TrayWhenCloseAsked"));
247+
if (asked)
248+
{
249+
bool hide = false;
250+
if (values.HasKey(L"Hide2TrayWhenClose"))
251+
hide = unbox_value<bool>(values.Lookup(L"Hide2TrayWhenClose"));
252+
253+
if (hide)
254+
HideToTray();
255+
else
256+
ReallyClose();
257+
258+
s_isHandlingClose = false;
259+
co_return;
260+
}
261+
}
262+
263+
// Strategy B: first time — show dialog
264+
if (!window)
265+
{
266+
ReallyClose();
267+
s_isHandlingClose = false;
268+
co_return;
269+
}
270+
271+
auto content = window.Content();
272+
if (!content)
273+
{
274+
ReallyClose();
275+
s_isHandlingClose = false;
276+
co_return;
277+
}
278+
279+
auto xamlRoot = content.XamlRoot();
280+
if (!xamlRoot)
281+
{
282+
ReallyClose();
283+
s_isHandlingClose = false;
284+
co_return;
285+
}
286+
287+
auto dlg = winrt::OpenNet::UI::Xaml::View::Dialog::CloseToTrayDialog();
288+
dlg.XamlRoot(xamlRoot);
289+
290+
auto result = co_await dlg.ShowAsync();
291+
292+
// Save preference if "remember" was checked
293+
if (dlg.RememberChoice())
294+
{
295+
values.Insert(L"Hide2TrayWhenCloseAsked", box_value(true));
296+
values.Insert(L"Hide2TrayWhenClose", box_value(result == winrt::Microsoft::UI::Xaml::Controls::ContentDialogResult::Primary));
297+
}
298+
299+
if (result == winrt::Microsoft::UI::Xaml::Controls::ContentDialogResult::Primary)
300+
{
301+
HideToTray();
302+
}
303+
else
304+
{
305+
ReallyClose();
306+
}
307+
}
308+
catch (...)
309+
{
310+
OutputDebugStringA("App: HandleCloseStrategyAsync error, falling back to exit\n");
311+
ReallyClose();
312+
}
313+
314+
s_isHandlingClose = false;
315+
}
316+
317+
void App::HideToTray()
318+
{
319+
if (window)
320+
{
321+
window.AppWindow().Hide();
322+
}
323+
OutputDebugStringA("App: MainWindow hidden to tray\n");
324+
}
325+
326+
void App::ReallyClose()
327+
{
328+
s_isExiting = true;
329+
Microsoft::UI::Xaml::Application::Current().Exit();
330+
}
331+
226332
winrt::fire_and_forget App::InitializeRSSManagerAsync()
227333
{
228334
try

OpenNet/App.xaml.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ namespace winrt::OpenNet::implementation
2323
static inline bool s_isExiting{ false };
2424

2525
private:
26-
void OnClosing(winrt::Microsoft::UI::Xaml::Window const& sender,
27-
winrt::Microsoft::UI::Xaml::WindowEventArgs const& args);
26+
static winrt::fire_and_forget HandleCloseStrategyAsync();
27+
static void HideToTray();
28+
static void ReallyClose();
2829
static winrt::fire_and_forget InitializeRSSManagerAsync();
30+
31+
static inline bool s_isHandlingClose{ false };
2932
};
3033
}

OpenNet/Core/DownloadManager.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ namespace OpenNet::Core
7777
}
7878

7979
// Stop refresh thread
80-
m_stopRefresh.store(true);
80+
{
81+
std::lock_guard stopLock(m_stopMutex);
82+
m_stopRefresh.store(true);
83+
}
84+
m_stopCv.notify_all();
8185
if (m_refreshThread.joinable())
8286
m_refreshThread.join();
8387

@@ -311,11 +315,10 @@ namespace OpenNet::Core
311315
ProcessAria2Tasks();
312316
}
313317

314-
// Sleep remainder of interval
315-
auto elapsed = clock::now() - start;
316-
if (elapsed < kInterval)
318+
// Sleep remainder of interval, wake immediately if stopped
317319
{
318-
std::this_thread::sleep_for(kInterval - elapsed);
320+
std::unique_lock<std::mutex> lock(m_stopMutex);
321+
m_stopCv.wait_for(lock, kInterval, [this] { return m_stopRefresh.load(); });
319322
}
320323
}
321324
}

OpenNet/Core/DownloadManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#pragma once
1010

1111
#include <cstdint>
12+
#include <condition_variable>
1213
#include <functional>
1314
#include <memory>
1415
#include <mutex>
@@ -110,6 +111,8 @@ namespace OpenNet::Core
110111

111112
std::thread m_refreshThread;
112113
std::atomic<bool> m_stopRefresh{false};
114+
std::condition_variable m_stopCv;
115+
std::mutex m_stopMutex;
113116
mutable std::mutex m_mutex;
114117

115118
// Cached task GIDs for change detection

OpenNet/Core/IO/FileSystem.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "pch.h"
22
#include "FileSystem.h"
3-
#include "..\AppEnvironment.h"
3+
#include "../AppEnvironment.h"
44
#include <winrt/Microsoft.Windows.Storage.h>
55
#include <shlobj_core.h>
66
#include <windows.h>

OpenNet/Core/IO/FileSystem.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace winrt::OpenNet::Core::IO
1010
// Get AppData folder path (where SQLite database is stored)
1111
// Returns path to %APPDATA%\OpenNet
1212
static std::string GetAppDataPath();
13+
static std::string AppDataPath;
1314

1415
// Get application temporary folder
1516
static std::string GetTempPath();

OpenNet/Core/RSS/RSSManager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ namespace OpenNet::Core::RSS
131131
// Fetch outside the lock to avoid deadlock
132132
for (const auto& id : feedsToUpdate)
133133
{
134+
if (!m_running.load()) break;
134135
FetchFeed(id);
135136
}
136137

0 commit comments

Comments
 (0)