mirror of
				https://github.com/chylex/Discord-History-Tracker.git
				synced 2025-11-04 03:40:12 +01:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			0fff3e8eaf
			...
			d0955b6853
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						d0955b6853
	
				 | 
					
					
						|||
| 
						
						
							
						
						712f17b684
	
				 | 
					
					
						|||
| 
						
						
							
						
						ae64747ce4
	
				 | 
					
					
						
@@ -2,15 +2,11 @@ using System;
 | 
				
			|||||||
using System.Diagnostics.CodeAnalysis;
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Avalonia.Controls;
 | 
					using Avalonia.Controls;
 | 
				
			||||||
using DHT.Desktop.Dialogs.Message;
 | 
					 | 
				
			||||||
using DHT.Utils.Logging;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DHT.Desktop.Dialogs.Progress;
 | 
					namespace DHT.Desktop.Dialogs.Progress;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
 | 
					[SuppressMessage("ReSharper", "MemberCanBeInternal")]
 | 
				
			||||||
public sealed partial class ProgressDialog : Window {
 | 
					public sealed partial class ProgressDialog : Window {
 | 
				
			||||||
	private static readonly Log Log = Log.ForType<ProgressDialog>();
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	internal static async Task Show(Window owner, string title, Func<ProgressDialog, IProgressCallback, Task> action) {
 | 
						internal static async Task Show(Window owner, string title, Func<ProgressDialog, IProgressCallback, Task> action) {
 | 
				
			||||||
		var taskCompletionSource = new TaskCompletionSource();
 | 
							var taskCompletionSource = new TaskCompletionSource();
 | 
				
			||||||
		var dialog = new ProgressDialog();
 | 
							var dialog = new ProgressDialog();
 | 
				
			||||||
@@ -88,11 +84,6 @@ public sealed partial class ProgressDialog : Window {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	public async Task ShowProgressDialog(Window owner) {
 | 
						public async Task ShowProgressDialog(Window owner) {
 | 
				
			||||||
		await ShowDialog(owner);
 | 
							await ShowDialog(owner);
 | 
				
			||||||
		try {
 | 
					 | 
				
			||||||
		await progressTask;
 | 
							await progressTask;
 | 
				
			||||||
		} catch (Exception e) {
 | 
					 | 
				
			||||||
			Log.Error(e);
 | 
					 | 
				
			||||||
			await Dialog.ShowOk(owner, "Unexpected Error", e.Message);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,8 +13,8 @@
 | 
				
			|||||||
    </Design.DataContext>
 | 
					    </Design.DataContext>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <UserControl.Styles>
 | 
					    <UserControl.Styles>
 | 
				
			||||||
        <Style Selector="WrapPanel > Button">
 | 
					        <Style Selector="Expander">
 | 
				
			||||||
            <Setter Property="Margin" Value="0 0 10 10" />
 | 
					            <Setter Property="Margin" Value="0 5 0 0" />
 | 
				
			||||||
        </Style>
 | 
					        </Style>
 | 
				
			||||||
        <Style Selector="DataGridColumnHeader">
 | 
					        <Style Selector="DataGridColumnHeader">
 | 
				
			||||||
            <Setter Property="FontWeight" Value="Medium" />
 | 
					            <Setter Property="FontWeight" Value="Medium" />
 | 
				
			||||||
@@ -30,17 +30,16 @@
 | 
				
			|||||||
        </Style>
 | 
					        </Style>
 | 
				
			||||||
    </UserControl.Styles>
 | 
					    </UserControl.Styles>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <StackPanel Orientation="Vertical">
 | 
					    <StackPanel Orientation="Vertical" Spacing="20">
 | 
				
			||||||
        <WrapPanel Orientation="Horizontal">
 | 
					        <StackPanel Orientation="Horizontal" Spacing="10">
 | 
				
			||||||
            <Button Command="{Binding OnClickToggleDownload}" Content="{Binding ToggleDownloadButtonText}" IsEnabled="{Binding IsToggleDownloadButtonEnabled}" />
 | 
					            <Button Command="{Binding OnClickToggleDownload}" Content="{Binding ToggleDownloadButtonText}" IsEnabled="{Binding IsToggleDownloadButtonEnabled}" />
 | 
				
			||||||
            <Button Command="{Binding OnClickRetryFailedDownloads}" IsEnabled="{Binding IsRetryFailedOnDownloadsButtonEnabled}">Retry Failed Downloads</Button>
 | 
					            <Button Command="{Binding OnClickRetryFailedDownloads}" IsEnabled="{Binding IsRetryFailedOnDownloadsButtonEnabled}">Retry Failed Downloads</Button>
 | 
				
			||||||
            <Button Command="{Binding OnClickDeleteOrphanedDownloads}">Delete Orphaned Downloads</Button>
 | 
					        </StackPanel>
 | 
				
			||||||
        </WrapPanel>
 | 
					 | 
				
			||||||
        <StackPanel Orientation="Vertical" Spacing="20" Margin="0 10 0 0">
 | 
					 | 
				
			||||||
        <controls:DownloadItemFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !$parent[UserControl].((pages:DownloadsPageModel)DataContext).IsDownloading}" />
 | 
					        <controls:DownloadItemFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !$parent[UserControl].((pages:DownloadsPageModel)DataContext).IsDownloading}" />
 | 
				
			||||||
        <TextBlock TextWrapping="Wrap">
 | 
					        <TextBlock TextWrapping="Wrap">
 | 
				
			||||||
            Downloading state and filter settings are remembered per-database.
 | 
					            Downloading state and filter settings are remembered per-database.
 | 
				
			||||||
        </TextBlock>
 | 
					        </TextBlock>
 | 
				
			||||||
 | 
					        <StackPanel Orientation="Vertical" Spacing="12">
 | 
				
			||||||
            <Expander Header="Download Status" IsExpanded="True">
 | 
					            <Expander Header="Download Status" IsExpanded="True">
 | 
				
			||||||
                <DataGrid ItemsSource="{Binding StatisticsRows}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" IsReadOnly="True">
 | 
					                <DataGrid ItemsSource="{Binding StatisticsRows}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" IsReadOnly="True">
 | 
				
			||||||
                    <DataGrid.Columns>
 | 
					                    <DataGrid.Columns>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,12 @@
 | 
				
			|||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Collections.ObjectModel;
 | 
					using System.Collections.ObjectModel;
 | 
				
			||||||
using System.Reactive.Linq;
 | 
					using System.Reactive.Linq;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Avalonia.Controls;
 | 
					 | 
				
			||||||
using Avalonia.ReactiveUI;
 | 
					using Avalonia.ReactiveUI;
 | 
				
			||||||
using CommunityToolkit.Mvvm.ComponentModel;
 | 
					using CommunityToolkit.Mvvm.ComponentModel;
 | 
				
			||||||
using DHT.Desktop.Common;
 | 
					using DHT.Desktop.Common;
 | 
				
			||||||
using DHT.Desktop.Dialogs.Message;
 | 
					 | 
				
			||||||
using DHT.Desktop.Dialogs.Progress;
 | 
					 | 
				
			||||||
using DHT.Desktop.Main.Controls;
 | 
					using DHT.Desktop.Main.Controls;
 | 
				
			||||||
using DHT.Server;
 | 
					using DHT.Server;
 | 
				
			||||||
using DHT.Server.Data;
 | 
					 | 
				
			||||||
using DHT.Server.Data.Aggregations;
 | 
					using DHT.Server.Data.Aggregations;
 | 
				
			||||||
using DHT.Server.Data.Filters;
 | 
					using DHT.Server.Data.Filters;
 | 
				
			||||||
using DHT.Server.Data.Settings;
 | 
					using DHT.Server.Data.Settings;
 | 
				
			||||||
@@ -53,7 +48,6 @@ sealed partial class DownloadsPageModel : ObservableObject, IAsyncDisposable {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	public bool IsDownloading => state.Downloader.IsDownloading;
 | 
						public bool IsDownloading => state.Downloader.IsDownloading;
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private readonly Window window;
 | 
					 | 
				
			||||||
	private readonly State state;
 | 
						private readonly State state;
 | 
				
			||||||
	private readonly ThrottledTask<DownloadStatusStatistics> downloadStatisticsTask;
 | 
						private readonly ThrottledTask<DownloadStatusStatistics> downloadStatisticsTask;
 | 
				
			||||||
	private readonly IDisposable downloadItemCountSubscription;
 | 
						private readonly IDisposable downloadItemCountSubscription;
 | 
				
			||||||
@@ -61,10 +55,9 @@ sealed partial class DownloadsPageModel : ObservableObject, IAsyncDisposable {
 | 
				
			|||||||
	private IDisposable? finishedItemsSubscription;
 | 
						private IDisposable? finishedItemsSubscription;
 | 
				
			||||||
	private DownloadItemFilter? currentDownloadFilter;
 | 
						private DownloadItemFilter? currentDownloadFilter;
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public DownloadsPageModel() : this(null!, State.Dummy) {}
 | 
						public DownloadsPageModel() : this(State.Dummy) {}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public DownloadsPageModel(Window window, State state) {
 | 
						public DownloadsPageModel(State state) {
 | 
				
			||||||
		this.window = window;
 | 
					 | 
				
			||||||
		this.state = state;
 | 
							this.state = state;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		FilterModel = new DownloadItemFilterPanelModel(state);
 | 
							FilterModel = new DownloadItemFilterPanelModel(state);
 | 
				
			||||||
@@ -165,50 +158,6 @@ sealed partial class DownloadsPageModel : ObservableObject, IAsyncDisposable {
 | 
				
			|||||||
		downloadStatisticsTask.Post(cancellationToken => state.Db.Downloads.GetStatistics(currentDownloadFilter ?? new DownloadItemFilter(), cancellationToken));
 | 
							downloadStatisticsTask.Post(cancellationToken => state.Db.Downloads.GetStatistics(currentDownloadFilter ?? new DownloadItemFilter(), cancellationToken));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private const string DeleteOrphanedDownloadsTitle = "Delete Orphaned Downloads";
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	public async Task OnClickDeleteOrphanedDownloads() {
 | 
					 | 
				
			||||||
		await ProgressDialog.Show(window, DeleteOrphanedDownloadsTitle, DeleteOrphanedDownloads);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	private async Task DeleteOrphanedDownloads(ProgressDialog dialog, IProgressCallback callback) {
 | 
					 | 
				
			||||||
		await callback.UpdateIndeterminate("Searching for orphaned downloads...");
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		HashSet<string> reachableNormalizedUrls = [];
 | 
					 | 
				
			||||||
		HashSet<string> orphanedNormalizedUrls = [];
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		await foreach (Download download in state.Db.Downloads.FindAllDownloadableUrls()) {
 | 
					 | 
				
			||||||
			reachableNormalizedUrls.Add(download.NormalizedUrl);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		await foreach (Download download in state.Db.Downloads.Get()) {
 | 
					 | 
				
			||||||
			string normalizedUrl = download.NormalizedUrl;
 | 
					 | 
				
			||||||
			if (!reachableNormalizedUrls.Contains(normalizedUrl)) {
 | 
					 | 
				
			||||||
				orphanedNormalizedUrls.Add(normalizedUrl);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		if (orphanedNormalizedUrls.Count == 0) {
 | 
					 | 
				
			||||||
			await Dialog.ShowOk(window, DeleteOrphanedDownloadsTitle, "No orphaned downloads found.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		if (await Dialog.ShowYesNo(window, DeleteOrphanedDownloadsTitle, orphanedNormalizedUrls.Count + " orphaned download(s) will be removed from this database. This action cannot be undone. Proceed?") != DialogResult.YesNo.Yes) {
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		await callback.UpdateIndeterminate("Deleting orphaned downloads...");
 | 
					 | 
				
			||||||
		await state.Db.Downloads.Remove(orphanedNormalizedUrls);
 | 
					 | 
				
			||||||
		RecomputeDownloadStatistics();
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		if (await Dialog.ShowYesNo(window, DeleteOrphanedDownloadsTitle, "Orphaned downloads deleted. Vacuum database now to reclaim space?") != DialogResult.YesNo.Yes) {
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		await callback.UpdateIndeterminate("Vacuuming database...");
 | 
					 | 
				
			||||||
		await state.Db.Vacuum();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	private void UpdateStatistics(DownloadStatusStatistics statusStatistics) {
 | 
						private void UpdateStatistics(DownloadStatusStatistics statusStatistics) {
 | 
				
			||||||
		statisticsPending.Items = statusStatistics.PendingCount;
 | 
							statisticsPending.Items = statusStatistics.PendingCount;
 | 
				
			||||||
		statisticsPending.Size = statusStatistics.PendingTotalSize;
 | 
							statisticsPending.Size = statusStatistics.PendingTotalSize;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@ sealed class MainContentScreenModel : IAsyncDisposable {
 | 
				
			|||||||
		TrackingPageModel = new TrackingPageModel(window);
 | 
							TrackingPageModel = new TrackingPageModel(window);
 | 
				
			||||||
		TrackingPage = new TrackingPage { DataContext = TrackingPageModel };
 | 
							TrackingPage = new TrackingPage { DataContext = TrackingPageModel };
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		DownloadsPageModel = new DownloadsPageModel(window, state);
 | 
							DownloadsPageModel = new DownloadsPageModel(state);
 | 
				
			||||||
		DownloadsPage = new DownloadsPage { DataContext = DownloadsPageModel };
 | 
							DownloadsPage = new DownloadsPage { DataContext = DownloadsPageModel };
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		ViewerPageModel = new ViewerPageModel(window, state);
 | 
							ViewerPageModel = new ViewerPageModel(window, state);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,10 +32,6 @@ public interface IDownloadRepository {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	Task<int> RetryFailed(CancellationToken cancellationToken = default);
 | 
						Task<int> RetryFailed(CancellationToken cancellationToken = default);
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	Task Remove(ICollection<string> normalizedUrls);
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	IAsyncEnumerable<Data.Download> FindAllDownloadableUrls(CancellationToken cancellationToken = default);
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	internal sealed class Dummy : IDownloadRepository {
 | 
						internal sealed class Dummy : IDownloadRepository {
 | 
				
			||||||
		public IObservable<long> TotalCount { get; } = Observable.Return(0L);
 | 
							public IObservable<long> TotalCount { get; } = Observable.Return(0L);
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
@@ -74,13 +70,5 @@ public interface IDownloadRepository {
 | 
				
			|||||||
		public Task<int> RetryFailed(CancellationToken cancellationToken) {
 | 
							public Task<int> RetryFailed(CancellationToken cancellationToken) {
 | 
				
			||||||
			return Task.FromResult(0);
 | 
								return Task.FromResult(0);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		public Task Remove(ICollection<string> normalizedUrls) {
 | 
					 | 
				
			||||||
			return Task.CompletedTask;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		public IAsyncEnumerable<Data.Download> FindAllDownloadableUrls(CancellationToken cancellationToken) {
 | 
					 | 
				
			||||||
			return AsyncEnumerable.Empty<Data.Download>();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -321,62 +321,4 @@ sealed class SqliteDownloadRepository(SqliteConnectionPool pool) : BaseSqliteRep
 | 
				
			|||||||
		cmd.AddAndSet(":success", SqliteType.Integer, (int) DownloadStatus.Success);
 | 
							cmd.AddAndSet(":success", SqliteType.Integer, (int) DownloadStatus.Success);
 | 
				
			||||||
		return await cmd.ExecuteNonQueryAsync(cancellationToken);
 | 
							return await cmd.ExecuteNonQueryAsync(cancellationToken);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	public async Task Remove(ICollection<string> normalizedUrls) {
 | 
					 | 
				
			||||||
		await using (var conn = await pool.Take()) {
 | 
					 | 
				
			||||||
			await conn.BeginTransactionAsync();
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			await using (var cmd = conn.Command("DELETE FROM download_metadata WHERE normalized_url = :normalized_url")) {
 | 
					 | 
				
			||||||
				cmd.Add(":normalized_url", SqliteType.Text);
 | 
					 | 
				
			||||||
				
 | 
					 | 
				
			||||||
				foreach (string normalizedUrl in normalizedUrls) {
 | 
					 | 
				
			||||||
					cmd.Set(":normalized_url", normalizedUrl);
 | 
					 | 
				
			||||||
					await cmd.ExecuteNonQueryAsync();
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			await conn.CommitTransactionAsync();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		UpdateTotalCount();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	public async IAsyncEnumerable<Data.Download> FindAllDownloadableUrls([EnumeratorCancellation] CancellationToken cancellationToken = default) {
 | 
					 | 
				
			||||||
		await using var conn = await pool.Take();
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		await using (var cmd = conn.Command("SELECT normalized_url, download_url, type, size FROM attachments")) {
 | 
					 | 
				
			||||||
			await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			while (await reader.ReadAsync(cancellationToken)) {
 | 
					 | 
				
			||||||
				yield return DownloadLinkExtractor.FromAttachment(reader.GetString(0), reader.GetString(1), reader.IsDBNull(2) ? null : reader.GetString(2), reader.GetUint64(3));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		await using (var cmd = conn.Command("SELECT json FROM message_embeds")) {
 | 
					 | 
				
			||||||
			await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			while (await reader.ReadAsync(cancellationToken)) {
 | 
					 | 
				
			||||||
				var result = await DownloadLinkExtractor.TryFromEmbedJson(reader.GetStream(0));
 | 
					 | 
				
			||||||
				if (result is not null) {
 | 
					 | 
				
			||||||
					yield return result;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		await using (var cmd = conn.Command("SELECT id, avatar_url FROM users WHERE avatar_url IS NOT NULL")) {
 | 
					 | 
				
			||||||
			await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			while (await reader.ReadAsync(cancellationToken)) {
 | 
					 | 
				
			||||||
				yield return DownloadLinkExtractor.FromUserAvatar(reader.GetUint64(0), reader.GetString(1));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		await using (var cmd = conn.Command("SELECT DISTINCT emoji_id, emoji_flags FROM message_reactions WHERE emoji_id IS NOT NULL")) {
 | 
					 | 
				
			||||||
			await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			while (await reader.ReadAsync(cancellationToken)) {
 | 
					 | 
				
			||||||
				yield return DownloadLinkExtractor.FromEmoji(reader.GetUint64(0), (EmojiFlags) reader.GetInt16(1));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,11 +28,7 @@ static class DownloadLinkExtractor {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public static Data.Download FromAttachment(Attachment attachment) {
 | 
						public static Data.Download FromAttachment(Attachment attachment) {
 | 
				
			||||||
		return FromAttachment(attachment.NormalizedUrl, attachment.DownloadUrl, attachment.Type, attachment.Size);
 | 
							return new Data.Download(attachment.NormalizedUrl, attachment.DownloadUrl, DownloadStatus.Pending, attachment.Type, attachment.Size);
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	public static Data.Download FromAttachment(string normalizedUrl, string downloadUrl, string? type, ulong size) {
 | 
					 | 
				
			||||||
		return new Data.Download(normalizedUrl, downloadUrl, DownloadStatus.Pending, type, size);
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public static async Task<Data.Download?> TryFromEmbedJson(Stream jsonStream) {
 | 
						public static async Task<Data.Download?> TryFromEmbedJson(Stream jsonStream) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								app/empty.dht
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/empty.dht
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user