forked from sim0n00ps/OF-DL
Update GUI so that the stop button works quicker and more reliably
This commit is contained in:
parent
35f7d98112
commit
85e299db41
@ -159,7 +159,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The user endpoint.</param>
|
||||
/// <returns>The user entity when available.</returns>
|
||||
public async Task<UserEntities.User?> GetUserInfo(string endpoint)
|
||||
public async Task<UserEntities.User?> GetUserInfo(string endpoint, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetUserInfo: {endpoint}");
|
||||
|
||||
@ -170,6 +170,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
UserEntities.User user = new();
|
||||
Dictionary<string, string> getParams = new()
|
||||
{
|
||||
@ -179,7 +180,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
HttpClient client = new();
|
||||
HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint);
|
||||
|
||||
using HttpResponseMessage response = await client.SendAsync(request);
|
||||
using HttpResponseMessage response = await client.SendAsync(request, cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@ -415,7 +416,8 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
public async Task<Dictionary<long, string>?> GetMedia(MediaType mediatype,
|
||||
string endpoint,
|
||||
string? username,
|
||||
string folder)
|
||||
string folder,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetMedia - {username}");
|
||||
|
||||
@ -426,6 +428,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Dictionary<long, string> returnUrls = new();
|
||||
const int limit = 5;
|
||||
int offset = 0;
|
||||
@ -649,7 +652,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <returns>A paid post collection.</returns>
|
||||
public async Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder,
|
||||
string username,
|
||||
List<long> paidPostIds, IStatusReporter statusReporter)
|
||||
List<long> paidPostIds, IStatusReporter statusReporter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetPaidPosts - {username}");
|
||||
|
||||
@ -830,7 +833,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <param name="statusReporter">Status reporter.</param>
|
||||
/// <returns>A post collection.</returns>
|
||||
public async Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paidPostIds,
|
||||
IStatusReporter statusReporter)
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetPosts - {endpoint}");
|
||||
|
||||
@ -1018,7 +1021,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <param name="endpoint">The post endpoint.</param>
|
||||
/// <param name="folder">The creator folder path.</param>
|
||||
/// <returns>A single post collection.</returns>
|
||||
public async Task<SinglePostCollection> GetPost(string endpoint, string folder)
|
||||
public async Task<SinglePostCollection> GetPost(string endpoint, string folder, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetPost - {endpoint}");
|
||||
|
||||
@ -1168,7 +1171,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <returns>A streams collection.</returns>
|
||||
public async Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder,
|
||||
List<long> paidPostIds,
|
||||
IStatusReporter statusReporter)
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetStreams - {endpoint}");
|
||||
|
||||
@ -1319,7 +1322,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <param name="statusReporter">Status reporter.</param>
|
||||
/// <returns>An archived collection.</returns>
|
||||
public async Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder,
|
||||
IStatusReporter statusReporter)
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetArchived - {endpoint}");
|
||||
|
||||
@ -1473,7 +1476,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <param name="statusReporter">Status reporter.</param>
|
||||
/// <returns>A message collection.</returns>
|
||||
public async Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder,
|
||||
IStatusReporter statusReporter)
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetMessages - {endpoint}");
|
||||
|
||||
@ -1661,7 +1664,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <param name="endpoint">The paid message endpoint.</param>
|
||||
/// <param name="folder">The creator folder path.</param>
|
||||
/// <returns>A single paid message collection.</returns>
|
||||
public async Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder)
|
||||
public async Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetPaidMessage - {endpoint}");
|
||||
|
||||
@ -1809,7 +1812,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <returns>A paid message collection.</returns>
|
||||
public async Task<PurchasedEntities.PaidMessageCollection> GetPaidMessages(string endpoint, string folder,
|
||||
string username,
|
||||
IStatusReporter statusReporter)
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetPaidMessages - {username}");
|
||||
|
||||
@ -2031,7 +2034,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <param name="endpoint">The purchased tab endpoint.</param>
|
||||
/// <param name="users">Known users map.</param>
|
||||
/// <returns>A username-to-userId map.</returns>
|
||||
public async Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users)
|
||||
public async Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetPurchasedTabUsers - {endpoint}");
|
||||
|
||||
@ -2046,6 +2049,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
{ "skip_users", "all" }
|
||||
};
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient());
|
||||
if (body == null)
|
||||
{
|
||||
@ -2060,6 +2064,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
getParams["offset"] = purchased.List.Count.ToString();
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
PurchasedEntities.Purchased newPurchased;
|
||||
Dictionary<string, string> loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams);
|
||||
@ -2073,7 +2078,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
looprequest.Headers.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
using (HttpResponseMessage loopresponse = await loopclient.SendAsync(looprequest))
|
||||
using (HttpResponseMessage loopresponse = await loopclient.SendAsync(looprequest, cancellationToken))
|
||||
{
|
||||
loopresponse.EnsureSuccessStatusCode();
|
||||
string loopbody = await loopresponse.Content.ReadAsStringAsync();
|
||||
@ -2098,6 +2103,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
foreach (PurchasedEntities.ListItem purchase in
|
||||
purchased.List.OrderByDescending(p => p.PostedAt ?? p.CreatedAt))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
long fromUserId = purchase.FromUser?.Id ?? 0;
|
||||
long authorId = purchase.Author?.Id ?? 0;
|
||||
|
||||
@ -2197,7 +2203,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
/// <param name="users">Known users map.</param>
|
||||
/// <returns>A list of purchased tab collections.</returns>
|
||||
public async Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder,
|
||||
Dictionary<string, long> users)
|
||||
Dictionary<string, long> users, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Debug($"Calling GetPurchasedTab - {endpoint}");
|
||||
|
||||
@ -2213,6 +2219,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
{ "skip_users", "all" }
|
||||
};
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient());
|
||||
PurchasedDtos.PurchasedDto? purchasedDto =
|
||||
DeserializeJson<PurchasedDtos.PurchasedDto>(body, s_mJsonSerializerSettings);
|
||||
@ -2222,6 +2229,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
getParams["offset"] = purchased.List.Count.ToString();
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string loopqueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
PurchasedEntities.Purchased newPurchased;
|
||||
Dictionary<string, string> loopheaders = GetDynamicHeaders("/api2/v2" + endpoint, loopqueryParams);
|
||||
@ -2235,7 +2243,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
looprequest.Headers.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
using (HttpResponseMessage loopresponse = await loopclient.SendAsync(looprequest))
|
||||
using (HttpResponseMessage loopresponse = await loopclient.SendAsync(looprequest, cancellationToken))
|
||||
{
|
||||
loopresponse.EnsureSuccessStatusCode();
|
||||
string loopbody = await loopresponse.Content.ReadAsStringAsync();
|
||||
@ -2260,6 +2268,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
foreach (PurchasedEntities.ListItem purchase in
|
||||
purchased.List.OrderByDescending(p => p.PostedAt ?? p.CreatedAt))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (purchase.FromUser != null)
|
||||
{
|
||||
if (!userPurchases.ContainsKey(purchase.FromUser.Id))
|
||||
@ -2283,6 +2292,7 @@ public class ApiService(IAuthService authService, IConfigService configService,
|
||||
|
||||
foreach (KeyValuePair<long, List<PurchasedEntities.ListItem>> user in userPurchases)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
PurchasedEntities.PurchasedTabCollection purchasedTabCollection = new();
|
||||
JObject? userObject = await GetUserInfoById($"/users/list?x[]={user.Key}");
|
||||
purchasedTabCollection.UserId = user.Key;
|
||||
|
||||
@ -171,11 +171,13 @@ public class DownloadOrchestrationService(
|
||||
eventHandler.OnUserStarting(username);
|
||||
Log.Debug($"Scraping Data for {username}");
|
||||
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
await PrepareUserFolderAsync(username, userId, path);
|
||||
|
||||
if (config.DownloadAvatarHeaderPhoto)
|
||||
{
|
||||
UserEntities.User? userInfo = await apiService.GetUserInfo($"/users/{username}");
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
UserEntities.User? userInfo = await apiService.GetUserInfo($"/users/{username}", eventHandler.CancellationToken);
|
||||
if (userInfo != null)
|
||||
{
|
||||
await downloadService.DownloadAvatarHeader(userInfo.Avatar, userInfo.Header, path, username);
|
||||
@ -240,9 +242,10 @@ public class DownloadOrchestrationService(
|
||||
|
||||
if (config.DownloadStories)
|
||||
{
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
eventHandler.OnMessage("Getting Stories");
|
||||
Dictionary<long, string>? tempStories = await apiService.GetMedia(MediaType.Stories,
|
||||
$"/users/{userId}/stories", null, path);
|
||||
$"/users/{userId}/stories", null, path, eventHandler.CancellationToken);
|
||||
|
||||
if (tempStories is { Count: > 0 })
|
||||
{
|
||||
@ -268,9 +271,10 @@ public class DownloadOrchestrationService(
|
||||
|
||||
if (config.DownloadHighlights)
|
||||
{
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
eventHandler.OnMessage("Getting Highlights");
|
||||
Dictionary<long, string>? tempHighlights = await apiService.GetMedia(MediaType.Highlights,
|
||||
$"/users/{userId}/stories/highlights", null, path);
|
||||
$"/users/{userId}/stories/highlights", null, path, eventHandler.CancellationToken);
|
||||
|
||||
if (tempHighlights is { Count: > 0 })
|
||||
{
|
||||
@ -387,13 +391,19 @@ public class DownloadOrchestrationService(
|
||||
{
|
||||
Config config = configService.CurrentConfig;
|
||||
|
||||
eventHandler.OnMessage("Fetching purchased tab users...");
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Dictionary<string, long> purchasedTabUsers =
|
||||
await apiService.GetPurchasedTabUsers("/posts/paid/all", users);
|
||||
await apiService.GetPurchasedTabUsers("/posts/paid/all", users, eventHandler.CancellationToken);
|
||||
|
||||
eventHandler.OnMessage("Checking folders for users in Purchased Tab");
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (KeyValuePair<string, long> user in purchasedTabUsers)
|
||||
{
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string path = ResolveDownloadPath(user.Key);
|
||||
Log.Debug($"Download path: {path}");
|
||||
|
||||
@ -405,7 +415,7 @@ public class DownloadOrchestrationService(
|
||||
Log.Debug($"Created folder for {user.Key}");
|
||||
}
|
||||
|
||||
await apiService.GetUserInfo($"/users/{user.Key}");
|
||||
await apiService.GetUserInfo($"/users/{user.Key}", eventHandler.CancellationToken);
|
||||
await dbService.CreateDb(path);
|
||||
}
|
||||
|
||||
@ -415,11 +425,16 @@ public class DownloadOrchestrationService(
|
||||
|
||||
Log.Debug($"Download path: {basePath}");
|
||||
|
||||
eventHandler.OnMessage("Fetching purchased tab content...");
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
List<PurchasedEntities.PurchasedTabCollection> purchasedTabCollections =
|
||||
await apiService.GetPurchasedTab("/posts/paid/all", basePath, users);
|
||||
await apiService.GetPurchasedTab("/posts/paid/all", basePath, users, eventHandler.CancellationToken);
|
||||
|
||||
foreach (PurchasedEntities.PurchasedTabCollection purchasedTabCollection in purchasedTabCollections)
|
||||
{
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
eventHandler.OnUserStarting(purchasedTabCollection.Username);
|
||||
string path = ResolveDownloadPath(purchasedTabCollection.Username);
|
||||
Log.Debug($"Download path: {path}");
|
||||
|
||||
@ -191,7 +191,7 @@ public class DownloadService(
|
||||
await OnFFMPEGDownloadComplete(tempFilename, lastModified, folder, path, customFileName, filename,
|
||||
mediaId, apiType, progressReporter);
|
||||
};
|
||||
await ffmpeg.ExecuteAsync(parameters, CancellationToken.None);
|
||||
await ffmpeg.ExecuteAsync(parameters, progressReporter.CancellationToken);
|
||||
|
||||
return await _completionSource.Task;
|
||||
}
|
||||
@ -747,9 +747,9 @@ public class DownloadService(
|
||||
using HttpClient client = new();
|
||||
HttpRequestMessage request = new() { Method = HttpMethod.Get, RequestUri = new Uri(url) };
|
||||
|
||||
using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, progressReporter.CancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
Stream body = await response.Content.ReadAsStreamAsync();
|
||||
Stream body = await response.Content.ReadAsStreamAsync(progressReporter.CancellationToken);
|
||||
|
||||
// Wrap the body stream with the ThrottledStream to limit read rate.
|
||||
await using (ThrottledStream throttledStream = new(body,
|
||||
@ -761,14 +761,14 @@ public class DownloadService(
|
||||
true);
|
||||
byte[] buffer = new byte[16384];
|
||||
int read;
|
||||
while ((read = await throttledStream.ReadAsync(buffer, CancellationToken.None)) > 0)
|
||||
while ((read = await throttledStream.ReadAsync(buffer, progressReporter.CancellationToken)) > 0)
|
||||
{
|
||||
if (configService.CurrentConfig.ShowScrapeSize)
|
||||
{
|
||||
progressReporter.ReportProgress(read);
|
||||
}
|
||||
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, read), CancellationToken.None);
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, read), progressReporter.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,69 +39,69 @@ public interface IApiService
|
||||
/// <summary>
|
||||
/// Retrieves media URLs for stories or highlights.
|
||||
/// </summary>
|
||||
Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username, string folder);
|
||||
Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username, string folder, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves paid posts and their media.
|
||||
/// </summary>
|
||||
Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username,
|
||||
List<long> paidPostIds,
|
||||
IStatusReporter statusReporter);
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves posts and their media.
|
||||
/// </summary>
|
||||
Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paidPostIds,
|
||||
IStatusReporter statusReporter);
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a single post and its media.
|
||||
/// </summary>
|
||||
Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder);
|
||||
Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves streams and their media.
|
||||
/// </summary>
|
||||
Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder, List<long> paidPostIds,
|
||||
IStatusReporter statusReporter);
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves archived posts and their media.
|
||||
/// </summary>
|
||||
Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder,
|
||||
IStatusReporter statusReporter);
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves messages and their media.
|
||||
/// </summary>
|
||||
Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder, IStatusReporter statusReporter);
|
||||
Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder, IStatusReporter statusReporter, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves paid messages and their media.
|
||||
/// </summary>
|
||||
Task<PurchasedEntities.PaidMessageCollection> GetPaidMessages(string endpoint, string folder, string username,
|
||||
IStatusReporter statusReporter);
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a single paid message and its media.
|
||||
/// </summary>
|
||||
Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder);
|
||||
Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves users that appear in the Purchased tab.
|
||||
/// </summary>
|
||||
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users);
|
||||
Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves Purchased tab content grouped by user.
|
||||
/// </summary>
|
||||
Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder,
|
||||
Dictionary<string, long> users);
|
||||
Dictionary<string, long> users, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves user information.
|
||||
/// </summary>
|
||||
Task<UserEntities.User?> GetUserInfo(string endpoint);
|
||||
Task<UserEntities.User?> GetUserInfo(string endpoint, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves user information by ID.
|
||||
|
||||
@ -8,6 +8,11 @@ namespace OF_DL.Services;
|
||||
/// </summary>
|
||||
public interface IDownloadEventHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the cancellation token for the operation.
|
||||
/// </summary>
|
||||
CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Wraps work in a status indicator (spinner) during API fetching.
|
||||
/// The implementation controls how the status is displayed.
|
||||
|
||||
@ -11,4 +11,9 @@ public interface IProgressReporter
|
||||
/// </summary>
|
||||
/// <param name="increment">The amount to increment progress by</param>
|
||||
void ReportProgress(long increment);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cancellation token for canceling the operation.
|
||||
/// </summary>
|
||||
CancellationToken CancellationToken { get; }
|
||||
}
|
||||
|
||||
@ -9,8 +9,11 @@ internal sealed class AvaloniaDownloadEventHandler(
|
||||
Action<string, long, bool> progressStart,
|
||||
Action<long> progressIncrement,
|
||||
Action progressStop,
|
||||
Func<bool> isCancellationRequested) : IDownloadEventHandler
|
||||
Func<bool> isCancellationRequested,
|
||||
CancellationToken cancellationToken) : IDownloadEventHandler
|
||||
{
|
||||
public CancellationToken CancellationToken { get; } = cancellationToken;
|
||||
|
||||
public async Task<T> WithStatusAsync<T>(string statusMessage, Func<IStatusReporter, Task<T>> work)
|
||||
{
|
||||
ThrowIfCancellationRequested();
|
||||
@ -33,7 +36,7 @@ internal sealed class AvaloniaDownloadEventHandler(
|
||||
progressStart(description, maxValue, showSize);
|
||||
try
|
||||
{
|
||||
AvaloniaProgressReporter reporter = new(progressIncrement, isCancellationRequested);
|
||||
AvaloniaProgressReporter reporter = new(progressIncrement, isCancellationRequested, cancellationToken);
|
||||
return await work(reporter);
|
||||
}
|
||||
finally
|
||||
|
||||
@ -4,8 +4,11 @@ namespace OF_DL.Gui.Services;
|
||||
|
||||
internal sealed class AvaloniaProgressReporter(
|
||||
Action<long> reportAction,
|
||||
Func<bool> isCancellationRequested) : IProgressReporter
|
||||
Func<bool> isCancellationRequested,
|
||||
CancellationToken cancellationToken) : IProgressReporter
|
||||
{
|
||||
public CancellationToken CancellationToken { get; } = cancellationToken;
|
||||
|
||||
public void ReportProgress(long increment)
|
||||
{
|
||||
if (isCancellationRequested())
|
||||
|
||||
@ -598,6 +598,17 @@ public partial class MainWindowViewModel(
|
||||
AppendLog(downloadPurchasedTabOnly
|
||||
? "Starting Purchased Tab download."
|
||||
: $"Starting download for {selectedUsers.Count} users.");
|
||||
StatusMessage = downloadPurchasedTabOnly
|
||||
? "Starting Purchased Tab download..."
|
||||
: $"Starting download for {selectedUsers.Count} users...";
|
||||
|
||||
// Show progress bar immediately with indeterminate state
|
||||
StartDownloadProgress(
|
||||
downloadPurchasedTabOnly
|
||||
? "Initializing Purchased Tab download..."
|
||||
: $"Initializing download for {selectedUsers.Count} users...",
|
||||
0,
|
||||
false);
|
||||
|
||||
AvaloniaDownloadEventHandler eventHandler = new(
|
||||
AppendLog,
|
||||
@ -605,7 +616,8 @@ public partial class MainWindowViewModel(
|
||||
StartDownloadProgress,
|
||||
IncrementDownloadProgress,
|
||||
StopDownloadProgress,
|
||||
() => _workCancellationSource?.IsCancellationRequested == true);
|
||||
() => _workCancellationSource?.IsCancellationRequested == true,
|
||||
_workCancellationSource.Token);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@ -773,16 +773,18 @@
|
||||
<TextBlock FontWeight="SemiBold" Foreground="#1F2A44" Text="Users" />
|
||||
<CheckBox IsThreeState="True"
|
||||
IsChecked="{Binding AllUsersSelected}"
|
||||
Content="{Binding SelectedUsersSummary}" />
|
||||
Content="{Binding SelectedUsersSummary}"
|
||||
IsEnabled="{Binding !IsDownloading}" />
|
||||
</StackPanel>
|
||||
<ComboBox Grid.Column="1"
|
||||
Width="280"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderText="Select a list of users"
|
||||
ItemsSource="{Binding UserLists}"
|
||||
SelectedItem="{Binding SelectedListName}" />
|
||||
SelectedItem="{Binding SelectedListName}"
|
||||
IsEnabled="{Binding !IsDownloading}" />
|
||||
</Grid>
|
||||
<ListBox Grid.Row="1" Margin="0,8,0,0" ItemsSource="{Binding AvailableUsers}">
|
||||
<ListBox Grid.Row="1" Margin="0,8,0,0" ItemsSource="{Binding AvailableUsers}" IsEnabled="{Binding !IsDownloading}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
|
||||
@ -42,6 +42,8 @@ internal sealed class ProgressRecorder : IProgressReporter
|
||||
{
|
||||
public long Total { get; private set; }
|
||||
|
||||
public CancellationToken CancellationToken { get; } = CancellationToken.None;
|
||||
|
||||
public void ReportProgress(long increment) => Total += increment;
|
||||
}
|
||||
|
||||
@ -138,44 +140,44 @@ internal sealed class StaticApiService : IApiService
|
||||
new() { { "X-Test", "value" } };
|
||||
|
||||
public Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username,
|
||||
string folder) => Task.FromResult(MediaToReturn);
|
||||
string folder, CancellationToken cancellationToken = default) => Task.FromResult(MediaToReturn);
|
||||
|
||||
public Task<Dictionary<string, long>?> GetLists(string endpoint) => throw new NotImplementedException();
|
||||
|
||||
public Task<List<string>?> GetListUsers(string endpoint) => throw new NotImplementedException();
|
||||
|
||||
public Task<OF_DL.Models.Entities.Purchased.PaidPostCollection> GetPaidPosts(string endpoint, string folder,
|
||||
string username, List<long> paidPostIds, IStatusReporter statusReporter) =>
|
||||
string username, List<long> paidPostIds, IStatusReporter statusReporter, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<OF_DL.Models.Entities.Posts.PostCollection> GetPosts(string endpoint, string folder,
|
||||
List<long> paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException();
|
||||
List<long> paidPostIds, IStatusReporter statusReporter, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
public Task<OF_DL.Models.Entities.Posts.SinglePostCollection> GetPost(string endpoint, string folder) =>
|
||||
public Task<OF_DL.Models.Entities.Posts.SinglePostCollection> GetPost(string endpoint, string folder, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<OF_DL.Models.Entities.Streams.StreamsCollection> GetStreams(string endpoint, string folder,
|
||||
List<long> paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException();
|
||||
List<long> paidPostIds, IStatusReporter statusReporter, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
public Task<OF_DL.Models.Entities.Archived.ArchivedCollection> GetArchived(string endpoint, string folder,
|
||||
IStatusReporter statusReporter) => throw new NotImplementedException();
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
public Task<OF_DL.Models.Entities.Messages.MessageCollection> GetMessages(string endpoint, string folder,
|
||||
IStatusReporter statusReporter) => throw new NotImplementedException();
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
public Task<OF_DL.Models.Entities.Purchased.PaidMessageCollection> GetPaidMessages(string endpoint,
|
||||
string folder, string username, IStatusReporter statusReporter) => throw new NotImplementedException();
|
||||
string folder, string username, IStatusReporter statusReporter, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
public Task<OF_DL.Models.Entities.Purchased.SinglePaidMessageCollection> GetPaidMessage(string endpoint,
|
||||
string folder) => throw new NotImplementedException();
|
||||
string folder, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
public Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users) =>
|
||||
public Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<List<OF_DL.Models.Entities.Purchased.PurchasedTabCollection>> GetPurchasedTab(string endpoint,
|
||||
string folder, Dictionary<string, long> users) => throw new NotImplementedException();
|
||||
string folder, Dictionary<string, long> users, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
|
||||
public Task<UserEntities.User?> GetUserInfo(string endpoint) =>
|
||||
public Task<UserEntities.User?> GetUserInfo(string endpoint, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<JObject?> GetUserInfoById(string endpoint) =>
|
||||
@ -217,52 +219,52 @@ internal sealed class ConfigurableApiService : IApiService
|
||||
ListUsersHandler?.Invoke(endpoint) ?? Task.FromResult<List<string>?>(null);
|
||||
|
||||
public Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username,
|
||||
string folder) =>
|
||||
string folder, CancellationToken cancellationToken = default) =>
|
||||
MediaHandler?.Invoke(mediaType, endpoint, username, folder) ??
|
||||
Task.FromResult<Dictionary<long, string>?>(null);
|
||||
|
||||
public Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder) =>
|
||||
public Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder, CancellationToken cancellationToken = default) =>
|
||||
PostHandler?.Invoke(endpoint, folder) ?? Task.FromResult(new PostEntities.SinglePostCollection());
|
||||
|
||||
public Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder) =>
|
||||
public Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder, CancellationToken cancellationToken = default) =>
|
||||
PaidMessageHandler?.Invoke(endpoint, folder) ??
|
||||
Task.FromResult(new PurchasedEntities.SinglePaidMessageCollection());
|
||||
|
||||
public Task<UserEntities.User?> GetUserInfo(string endpoint) =>
|
||||
public Task<UserEntities.User?> GetUserInfo(string endpoint, CancellationToken cancellationToken = default) =>
|
||||
UserInfoHandler?.Invoke(endpoint) ?? Task.FromResult<UserEntities.User?>(null);
|
||||
|
||||
public Task<JObject?> GetUserInfoById(string endpoint) =>
|
||||
UserInfoByIdHandler?.Invoke(endpoint) ?? Task.FromResult<JObject?>(null);
|
||||
|
||||
public Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username,
|
||||
List<long> paidPostIds, IStatusReporter statusReporter) =>
|
||||
List<long> paidPostIds, IStatusReporter statusReporter, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paidPostIds,
|
||||
IStatusReporter statusReporter) =>
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder, List<long> paidPostIds,
|
||||
IStatusReporter statusReporter) =>
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder,
|
||||
IStatusReporter statusReporter) =>
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder,
|
||||
IStatusReporter statusReporter) =>
|
||||
IStatusReporter statusReporter, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<PurchasedEntities.PaidMessageCollection> GetPaidMessages(string endpoint, string folder,
|
||||
string username, IStatusReporter statusReporter) =>
|
||||
string username, IStatusReporter statusReporter, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users) =>
|
||||
public Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder,
|
||||
Dictionary<string, long> users) =>
|
||||
Dictionary<string, long> users, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Dictionary<string, string> GetDynamicHeaders(string path, string queryParam) =>
|
||||
@ -427,6 +429,8 @@ internal sealed class RecordingDownloadEventHandler : IDownloadEventHandler
|
||||
public List<(string contentType, DownloadResult result)> DownloadCompletes { get; } = [];
|
||||
public List<(string description, long maxValue, bool showSize)> ProgressCalls { get; } = [];
|
||||
|
||||
public CancellationToken CancellationToken { get; } = CancellationToken.None;
|
||||
|
||||
public Task<T> WithStatusAsync<T>(string statusMessage, Func<IStatusReporter, Task<T>> work) =>
|
||||
work(new RecordingStatusReporter(statusMessage));
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@ namespace OF_DL.CLI;
|
||||
/// </summary>
|
||||
public class SpectreDownloadEventHandler : IDownloadEventHandler
|
||||
{
|
||||
public CancellationToken CancellationToken { get; } = CancellationToken.None;
|
||||
|
||||
public async Task<T> WithStatusAsync<T>(string statusMessage, Func<IStatusReporter, Task<T>> work)
|
||||
{
|
||||
TaskCompletionSource<T> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
@ -6,9 +6,11 @@ namespace OF_DL.CLI;
|
||||
/// <summary>
|
||||
/// Implementation of IProgressReporter that uses Spectre.Console's ProgressTask for CLI output.
|
||||
/// </summary>
|
||||
public class SpectreProgressReporter(ProgressTask task) : IProgressReporter
|
||||
public class SpectreProgressReporter(ProgressTask task, CancellationToken cancellationToken = default) : IProgressReporter
|
||||
{
|
||||
private readonly ProgressTask _task = task ?? throw new ArgumentNullException(nameof(task));
|
||||
|
||||
public CancellationToken CancellationToken { get; } = cancellationToken;
|
||||
|
||||
public void ReportProgress(long increment) => _task.Increment(increment);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user