Skip to content

Commit 98c4226

Browse files
committed
Add subscription update all
1 parent 4670091 commit 98c4226

12 files changed

Lines changed: 386 additions & 88 deletions

YtFlowApp/App.idl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,26 @@ namespace YtFlowApp
293293
{
294294
get;
295295
};
296+
String SubscriptionUrl
297+
{
298+
get;
299+
};
300+
String SubscriptionUploadUsed
301+
{
302+
get;
303+
};
304+
String SubscriptionDownloadUsed
305+
{
306+
get;
307+
};
308+
String SubscriptionBytesTotal
309+
{
310+
get;
311+
};
312+
String SubscriptionRetrievedAt
313+
{
314+
get;
315+
};
296316
Windows.Foundation.Collections.IObservableVector<ProxyModel> Proxies
297317
{
298318
get;

YtFlowApp/CoreSubscription.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,28 @@ namespace winrt::YtFlowApp::implementation
9393
return ret;
9494
}
9595

96+
char const *ConvertSubscriptionFormatToStatic(char const *input)
97+
{
98+
if (input == nullptr)
99+
{
100+
throw std::invalid_argument("Empty input for subscription format");
101+
}
102+
if (strcmp(input, "sip008") == 0)
103+
{
104+
return SIP008_LITERAL;
105+
}
106+
throw std::invalid_argument(std::string("Unknown subscription format: ") + std::string(input));
107+
}
96108
std::optional<std::vector<uint8_t>> DecodeSubscriptionProxies(std::string_view data, char const *&decodedFormat)
97109
{
98-
auto sip008Res = DecodeSip008(data);
99-
if (!sip008Res.empty())
110+
if (decodedFormat == nullptr || strcmp(decodedFormat, SIP008_LITERAL) == 0)
100111
{
101-
decodedFormat = "sip008";
102-
return {nlohmann::json::to_cbor(std::move(sip008Res))};
112+
auto sip008Res = DecodeSip008(data);
113+
if (!sip008Res.empty())
114+
{
115+
decodedFormat = SIP008_LITERAL;
116+
return {nlohmann::json::to_cbor(std::move(sip008Res))};
117+
}
103118
}
104119
decodedFormat = nullptr;
105120
return std::nullopt;

YtFlowApp/CoreSubscription.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace winrt::YtFlowApp::implementation
66
{
7+
constexpr static char SIP008_LITERAL[7] = "sip008";
78
struct DecodedSubscriptionUserInfo
89
{
910
std::optional<uint64_t> upload_bytes_used;
@@ -12,6 +13,7 @@ namespace winrt::YtFlowApp::implementation
1213
std::optional<std::string> expires_at;
1314
};
1415

16+
char const *ConvertSubscriptionFormatToStatic(char const *input);
1517
DecodedSubscriptionUserInfo DecodeSubscriptionUserInfoFromResponseHeaderValue(std::string_view resValue);
1618
std::optional<std::vector<uint8_t>> DecodeSubscriptionProxies(std::string_view data, char const *&decodedFormat);
1719
}

YtFlowApp/ForwardHomeWidget.cpp

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,6 @@ using namespace Windows::UI::Xaml;
1313

1414
namespace winrt::YtFlowApp::implementation
1515
{
16-
hstring ForwardHomeWidget::HumanizeByteSpeed(uint64_t num)
17-
{
18-
if (num == 0)
19-
{
20-
return L"0 B/s";
21-
}
22-
if (num < 1024)
23-
{
24-
return to_hstring(num) + L" B/s";
25-
}
26-
if (num < 1024ULL * 1000)
27-
{
28-
return to_hstring(double(num * 10 / 1024) / 10) + L" KB/s";
29-
}
30-
if (num < 1024ULL * 1024 * 1000)
31-
{
32-
return to_hstring(double(num * 10 / 1024 / 1024) / 10) + L" MB/s";
33-
}
34-
if (num < 1024ULL * 1024 * 1024 * 1000)
35-
{
36-
return to_hstring(double(num * 10 / 1024 / 1024 / 1024) / 10) + L" GB/s";
37-
}
38-
return L"";
39-
}
40-
4116
ForwardHomeWidget::ForwardHomeWidget(hstring pluginName, std::shared_ptr<std::vector<uint8_t>> sharedInfo)
4217
: m_sharedInfo(std::move(sharedInfo))
4318
{

YtFlowApp/ForwardHomeWidget.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ namespace winrt::YtFlowApp::implementation
1313
};
1414
struct ForwardHomeWidget : ForwardHomeWidgetT<ForwardHomeWidget>
1515
{
16-
static hstring HumanizeByteSpeed(uint64_t num);
17-
1816
ForwardHomeWidget()
1917
{
2018
// Xaml objects should not call InitializeComponent during construction.

YtFlowApp/LibraryPage.cpp

Lines changed: 164 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
#include "winrt\Windows.Web.Http.Headers.h"
88
#include <ranges>
9-
#include <winrt\Windows.Web.Http.h>
109

1110
#include "CoreFfi.h"
1211
#include "CoreProxy.h"
@@ -41,10 +40,25 @@ namespace winrt::YtFlowApp::implementation
4140
auto conn = FfiDbInstance.Connect();
4241
auto const proxyGroups = conn.GetProxyGroups();
4342
std::vector<YtFlowApp::ProxyGroupModel> proxyGroupModels;
43+
std::vector<std::pair<ProxyGroupModel *, FfiProxyGroupSubscription>> subscriptionInfoToAttach;
4444
proxyGroupModels.reserve(proxyGroups.size());
45+
subscriptionInfoToAttach.reserve(proxyGroups.size());
4546
std::transform(proxyGroups.begin(), proxyGroups.end(), std::back_inserter(proxyGroupModels),
46-
[](auto const &group) { return make<ProxyGroupModel>(group); });
47+
[&conn, &subscriptionInfoToAttach](auto const &group) {
48+
auto ret = make<ProxyGroupModel>(group);
49+
if (group.type == "subscription")
50+
{
51+
subscriptionInfoToAttach.emplace_back(
52+
std::make_pair(get_self<ProxyGroupModel>(ret),
53+
conn.GetProxySubscriptionByProxyGroup(group.id)));
54+
}
55+
return ret;
56+
});
4757
co_await resume_foreground(Dispatcher());
58+
for (auto &&[model, subscriptionInfo] : subscriptionInfoToAttach)
59+
{
60+
model->AttachSubscriptionInfo(subscriptionInfo);
61+
}
4862
m_model->ProxyGroups(single_threaded_observable_vector(std::move(proxyGroupModels)));
4963
}
5064
catch (...)
@@ -72,6 +86,27 @@ namespace winrt::YtFlowApp::implementation
7286
}
7387
}
7488

89+
void LibraryPage::Page_Loaded(IInspectable const &, RoutedEventArgs const &)
90+
{
91+
ProxySubscriptionUpdatesRunning$.get_observable()
92+
.scan(std::make_pair(0, 0),
93+
[](auto prev, int curr) { return std::make_pair(prev.second, prev.second + curr); })
94+
.filter([](auto change) { return change.first == 0 || change.second == 0; })
95+
.subscribe([weak{get_weak()}](auto change) {
96+
if (auto const self = weak.get())
97+
{
98+
if (change.first == 0)
99+
{
100+
self->SyncSubscriptionButtonRunStoryboard().Begin();
101+
}
102+
else
103+
{
104+
self->SyncSubscriptionButtonRunStoryboard().Stop();
105+
}
106+
}
107+
});
108+
}
109+
75110
fire_and_forget LibraryPage::ProxyGroupItemDelete_Click(IInspectable const &sender, RoutedEventArgs const &e)
76111
{
77112
try
@@ -178,66 +213,84 @@ namespace winrt::YtFlowApp::implementation
178213
}
179214
}
180215

216+
Windows::Web::Http::HttpClient LibraryPage::GetHttpClientForSubscription()
217+
{
218+
static Windows::Web::Http::HttpClient client{nullptr};
219+
if (client == nullptr)
220+
{
221+
client = Windows::Web::Http::HttpClient();
222+
client.DefaultRequestHeaders().UserAgent().Clear();
223+
client.DefaultRequestHeaders().UserAgent().ParseAdd(L"YtFlowApp/0.0 SubscriptionUpdater/0.0");
224+
}
225+
return client;
226+
}
227+
IAsyncAction LibraryPage::DownloadSubscriptionProxies(Windows::Web::Http::HttpClient client, Uri uri,
228+
char const *format,
229+
std::shared_ptr<SubscriptionDownloadDecodeResult> result)
230+
{
231+
auto const res = (co_await client.GetAsync(uri)).EnsureSuccessStatusCode();
232+
auto const userinfoHeader = res.Headers().TryLookup(L"subscription-userinfo");
233+
DecodedSubscriptionUserInfo userinfo{};
234+
if (userinfoHeader.has_value())
235+
{
236+
userinfo = DecodeSubscriptionUserInfoFromResponseHeaderValue(to_string(*userinfoHeader));
237+
}
238+
239+
auto const resStr = to_string(co_await res.Content().ReadAsStringAsync());
240+
auto proxies = DecodeSubscriptionProxies(resStr, format);
241+
if (!proxies.has_value() || format == nullptr)
242+
{
243+
throw hresult_invalid_argument(L"The subscription data contains no valid proxy.");
244+
}
245+
*result = SubscriptionDownloadDecodeResult{.proxies = std::move(proxies).value(),
246+
.format = format,
247+
.userinfo = std::move(userinfo),
248+
.expiresAt = nullptr};
249+
if (result->userinfo.expires_at.has_value())
250+
{
251+
result->expiresAt = userinfo.expires_at->c_str();
252+
}
253+
}
181254
fire_and_forget LibraryPage::CreateSubscriptionButton_Click(IInspectable const &, RoutedEventArgs const &)
182255
{
183256
try
184257
{
185258
auto const lifetime = get_strong();
186259
ProxyGroupAddSubscriptionError().Text(L"");
187260
ProxyGroupAddSubscriptionError().Visibility(Visibility::Collapsed);
261+
auto const client = GetHttpClientForSubscription();
188262
while (co_await ProxyGroupAddSubscriptionDialog().ShowAsync() == ContentDialogResult::Primary)
189263
{
190264
auto const url = ProxyGroupAddSubscriptionUrlText().Text();
191265
std::optional<hstring> errMsg = std::nullopt;
266+
lifetime->ProxySubscriptionUpdatesRunning$.get_subscriber().on_next(1);
192267

193268
try
194269
{
195270
Uri const uri{url};
196-
// TODO:
197-
198-
static Windows::Web::Http::HttpClient client{};
199-
client.DefaultRequestHeaders().UserAgent().Clear();
200-
client.DefaultRequestHeaders().UserAgent().ParseAdd(L"YtFlowApp/0.0 SubscriptionUpdater/0.0");
201-
auto const res = (co_await client.GetAsync(uri)).EnsureSuccessStatusCode();
202-
auto const userinfoHeader = res.Headers().TryLookup(L"subscription-userinfo");
203-
DecodedSubscriptionUserInfo userinfo{};
204-
if (userinfoHeader.has_value())
205-
{
206-
userinfo = DecodeSubscriptionUserInfoFromResponseHeaderValue(to_string(*userinfoHeader));
207-
}
208-
// TODO: total, not remaining
209-
210-
auto const resStr = to_string(co_await res.Content().ReadAsStringAsync());
211-
char const *format{nullptr};
212-
auto const proxies = DecodeSubscriptionProxies(resStr, format);
213-
if (!proxies.has_value() || format == nullptr)
214-
{
215-
throw hresult_invalid_argument(L"The subscription data contains no valid proxy.");
216-
}
271+
auto const res = std::make_shared<SubscriptionDownloadDecodeResult>();
272+
co_await DownloadSubscriptionProxies(client, uri, nullptr, res);
217273

218274
co_await resume_background();
219275
auto conn = FfiDbInstance.Connect();
220-
auto const newGroupId = conn.CreateProxySubscriptionGroup(to_string(uri.Domain()).c_str(), format,
221-
to_string(url).c_str());
222-
conn.BatchUpdateProxyInGroup(newGroupId, proxies->data(), proxies->size());
223-
char const *expiresAt{nullptr};
224-
if (userinfo.expires_at.has_value())
225-
{
226-
expiresAt = userinfo.expires_at->c_str();
227-
}
228-
conn.UpdateProxySubscriptionRetrievedByProxyGroup(newGroupId, userinfo.upload_bytes_used,
229-
userinfo.download_bytes_used,
230-
userinfo.bytes_total, expiresAt);
231-
auto const newGroupModel = make<ProxyGroupModel>(conn.GetProxyGroupById(newGroupId),
232-
conn.GetProxySubscriptionByProxyGroup(newGroupId));
276+
auto const newGroupId = conn.CreateProxySubscriptionGroup(to_string(uri.Domain()).c_str(),
277+
res->format, to_string(url).c_str());
278+
conn.BatchUpdateProxyInGroup(newGroupId, res->proxies.data(), res->proxies.size());
279+
conn.UpdateProxySubscriptionRetrievedByProxyGroup(newGroupId, res->userinfo.upload_bytes_used,
280+
res->userinfo.download_bytes_used,
281+
res->userinfo.bytes_total, res->expiresAt);
282+
auto const newGroupModel = make<ProxyGroupModel>(conn.GetProxyGroupById(newGroupId));
233283
co_await resume_foreground(lifetime->Dispatcher());
284+
get_self<ProxyGroupModel>(newGroupModel)
285+
->AttachSubscriptionInfo(conn.GetProxySubscriptionByProxyGroup(newGroupId));
234286
m_model->ProxyGroups().Append(newGroupModel);
235287
}
236288
catch (hresult_error const &hr)
237289
{
238290
errMsg = {hr.message()};
239291
}
240292
co_await resume_foreground(lifetime->Dispatcher());
293+
lifetime->ProxySubscriptionUpdatesRunning$.get_subscriber().on_next(-1);
241294
if (errMsg.has_value())
242295
{
243296
ProxyGroupAddSubscriptionError().Text(*errMsg);
@@ -255,6 +308,81 @@ namespace winrt::YtFlowApp::implementation
255308
}
256309
}
257310

311+
void LibraryPage::SyncSubscriptionButton_Click(IInspectable const &, RoutedEventArgs const &)
312+
{
313+
UpdateSubscription(std::nullopt);
314+
}
315+
316+
fire_and_forget LibraryPage::UpdateSubscription(std::optional<uint32_t> id)
317+
{
318+
auto const lifetime = get_strong();
319+
auto proxyGroupIt =
320+
std::ranges::views::transform(lifetime->m_model->ProxyGroups(),
321+
[](auto const &model) { return model.as<ProxyGroupModel>(); }) |
322+
std::ranges::views::filter([&id](auto const &model) {
323+
return !model->IsManualGroup() && !model->IsUpdating && (!id.has_value() || model->Id() == *id);
324+
});
325+
std::vector const proxyGroups(proxyGroupIt.begin(), proxyGroupIt.end());
326+
try
327+
{
328+
for (auto &&model : proxyGroups)
329+
{
330+
model->IsUpdating = true;
331+
}
332+
lifetime->ProxySubscriptionUpdatesRunning$.get_subscriber().on_next(1);
333+
std::vector<std::pair<com_ptr<ProxyGroupModel>, FfiProxyGroupSubscription>> subscriptionInfoToAttach;
334+
subscriptionInfoToAttach.reserve(proxyGroups.size());
335+
auto const client = GetHttpClientForSubscription();
336+
337+
co_await resume_background();
338+
339+
hstring errors;
340+
auto res = std::make_shared<SubscriptionDownloadDecodeResult>();
341+
auto conn = FfiDbInstance.Connect();
342+
for (auto &&model : proxyGroups)
343+
{
344+
try
345+
{
346+
auto const groupId = model->Id();
347+
auto const subscription = conn.GetProxySubscriptionByProxyGroup(groupId);
348+
co_await DownloadSubscriptionProxies(client, Uri{to_hstring(subscription.url)},
349+
ConvertSubscriptionFormatToStatic(subscription.format.data()),
350+
res);
351+
conn.BatchUpdateProxyInGroup(groupId, res->proxies.data(), res->proxies.size());
352+
conn.UpdateProxySubscriptionRetrievedByProxyGroup(groupId, res->userinfo.upload_bytes_used,
353+
res->userinfo.download_bytes_used,
354+
res->userinfo.bytes_total, res->expiresAt);
355+
subscriptionInfoToAttach.emplace_back(
356+
std::make_pair(model, conn.GetProxySubscriptionByProxyGroup(groupId)));
357+
}
358+
catch (hresult_error const &hr)
359+
{
360+
errors = errors + hstring{L"\r\n"} + model->Name() + L": " + hr.message();
361+
}
362+
}
363+
364+
co_await resume_foreground(lifetime->Dispatcher());
365+
for (auto &&[model, subscriptionInfo] : subscriptionInfoToAttach)
366+
{
367+
model->AttachSubscriptionInfo(subscriptionInfo);
368+
}
369+
if (!errors.empty())
370+
{
371+
NotifyUser(errors, L"Update errors");
372+
}
373+
}
374+
catch (...)
375+
{
376+
NotifyException(L"Updating Subscription");
377+
}
378+
co_await resume_foreground(lifetime->Dispatcher());
379+
for (auto &&model : proxyGroups)
380+
{
381+
model->IsUpdating = false;
382+
}
383+
lifetime->ProxySubscriptionUpdatesRunning$.get_subscriber().on_next(-1);
384+
}
385+
258386
void LibraryPage::ProxyGroupItem_Click(IInspectable const &sender, RoutedEventArgs const &)
259387
{
260388
auto const source = sender.as<FrameworkElement>();
@@ -505,5 +633,4 @@ namespace winrt::YtFlowApp::implementation
505633
ProxyGroupProxyExportText().Text(std::move(text));
506634
auto const _ = ProxyGroupProxyExportDialog().ShowAsync();
507635
}
508-
509636
}

0 commit comments

Comments
 (0)