1
0
mirror of https://github.com/chylex/Discord-History-Tracker.git synced 2024-10-17 17:42:51 +02:00

Compare commits

..

No commits in common. "daa2feb445851e80d0e39a6ab9969584ba65eb36" and "3d435d016503d0f750e92baa92073b77d2ff9826" have entirely different histories.

10 changed files with 57 additions and 167 deletions

View File

@ -87,9 +87,6 @@ namespace DHT.Desktop.Main.Pages {
downloadStatisticsComputer.Recompute();
}
}
else if (e.PropertyName == nameof(DatabaseStatistics.TotalDownloads)) {
downloadStatisticsComputer.Recompute();
}
}
private void EnqueueDownloadItems() {

View File

@ -2,10 +2,8 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Threading;
using DHT.Desktop.Common;
using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Dialogs.Progress;
@ -58,10 +56,6 @@ namespace DHT.Desktop.Main.Pages {
}
}
public void CloseDatabase() {
DatabaseClosed?.Invoke(this, EventArgs.Empty);
}
public async void MergeWithDatabase() {
var fileDialog = DatabaseGui.NewOpenDatabaseFileDialog();
fileDialog.Directory = Path.GetDirectoryName(Db.Path);
@ -80,6 +74,10 @@ namespace DHT.Desktop.Main.Pages {
await progressDialog.ShowDialog(window);
}
public void CloseDatabase() {
DatabaseClosed?.Invoke(this, EventArgs.Empty);
}
private static async Task MergeWithDatabaseFromPaths(IDatabaseFile target, string[] paths, ProgressDialog dialog, IProgressCallback callback) {
int total = paths.Length;
@ -92,29 +90,9 @@ namespace DHT.Desktop.Main.Pages {
return DialogResult.YesNo.Yes == upgradeResult;
}
await PerformImport(target, paths, dialog, callback, "Database Merge", "Database Error", "database file", async path => {
SynchronizationContext? prevSyncContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(new AvaloniaSynchronizationContext());
IDatabaseFile? db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, dialog, CheckCanUpgradeDatabase);
SynchronizationContext.SetSynchronizationContext(prevSyncContext);
if (db == null) {
return false;
}
try {
target.AddFrom(db);
return true;
} finally {
db.Dispose();
}
});
}
private static async Task PerformImport(IDatabaseFile target, string[] paths, ProgressDialog dialog, IProgressCallback callback, string neutralDialogTitle, string errorDialogTitle, string itemName, Func<string, Task<bool>> performImport) {
int total = paths.Length;
var oldStatistics = target.SnapshotStatistics();
var oldStatistics = target.Statistics.Clone();
var oldMessageCount = target.CountMessages();
int successful = 0;
int finished = 0;
@ -124,53 +102,56 @@ namespace DHT.Desktop.Main.Pages {
++finished;
if (!File.Exists(path)) {
await Dialog.ShowOk(dialog, errorDialogTitle, "File '" + Path.GetFileName(path) + "' no longer exists.");
await Dialog.ShowOk(dialog, "Database Error", "Database '" + Path.GetFileName(path) + "' no longer exists.");
continue;
}
IDatabaseFile? db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, dialog, CheckCanUpgradeDatabase);
if (db == null) {
continue;
}
try {
if (await performImport(path)) {
++successful;
}
target.AddFrom(db);
} catch (Exception ex) {
Log.Error(ex);
await Dialog.ShowOk(dialog, errorDialogTitle, "File '" + Path.GetFileName(path) + "' could not be imported: " + ex.Message);
await Dialog.ShowOk(dialog, "Database Error", "Database '" + Path.GetFileName(path) + "' could not be merged: " + ex.Message);
continue;
} finally {
db.Dispose();
}
++successful;
}
await callback.Update("Done", finished, total);
if (successful == 0) {
await Dialog.ShowOk(dialog, neutralDialogTitle, "Nothing was imported.");
await Dialog.ShowOk(dialog, "Database Merge", "Nothing was merged.");
return;
}
await Dialog.ShowOk(dialog, neutralDialogTitle, GetImportDialogMessage(oldStatistics, target.SnapshotStatistics(), successful, total, itemName));
}
private static string GetImportDialogMessage(DatabaseStatisticsSnapshot oldStatistics, DatabaseStatisticsSnapshot newStatistics, int successfulItems, int totalItems, string itemName) {
var newStatistics = target.Statistics;
long newServers = newStatistics.TotalServers - oldStatistics.TotalServers;
long newChannels = newStatistics.TotalChannels - oldStatistics.TotalChannels;
long newUsers = newStatistics.TotalUsers - oldStatistics.TotalUsers;
long newMessages = newStatistics.TotalMessages - oldStatistics.TotalMessages;
long newMessages = target.CountMessages() - oldMessageCount;
StringBuilder message = new StringBuilder();
message.Append("Processed ");
if (successfulItems == totalItems) {
message.Append(successfulItems.Pluralize(itemName));
if (successful == total) {
message.Append(successful.Pluralize("database file"));
}
else {
message.Append(successfulItems.Format()).Append(" out of ").Append(totalItems.Pluralize(itemName));
message.Append(successful.Format()).Append(" out of ").Append(total.Pluralize("database file"));
}
message.Append(" and added:\n\n \u2022 ");
message.Append(newServers.Pluralize("server")).Append("\n \u2022 ");
message.Append(newChannels.Pluralize("channel")).Append("\n \u2022 ");
message.Append(newUsers.Pluralize("user")).Append("\n \u2022 ");
message.Append(newMessages.Pluralize("message"));
return message.ToString();
await Dialog.ShowOk(dialog, "Database Merge", message.ToString());
}
}
}

View File

@ -3,6 +3,10 @@ using System.Net;
namespace DHT.Server.Data {
public readonly struct Download {
internal static Download NewEnqueued(string url, ulong size) {
return new Download(url, DownloadStatus.Enqueued, size);
}
internal static Download NewSuccess(string url, byte[] data) {
return new Download(url, DownloadStatus.Success, (ulong) Math.Max(data.LongLength, 0), data);
}
@ -16,15 +20,11 @@ namespace DHT.Server.Data {
public ulong Size { get; }
public byte[]? Data { get; }
internal Download(string url, DownloadStatus status, ulong size, byte[]? data = null) {
private Download(string url, DownloadStatus status, ulong size, byte[]? data = null) {
Url = url;
Status = status;
Size = size;
Data = data;
}
internal Download WithData(byte[] data) {
return new Download(Url, Status, Size, data);
}
}
}

View File

@ -1,5 +1,3 @@
using DHT.Server.Data;
namespace DHT.Server.Database {
public static class DatabaseExtensions {
public static void AddFrom(this IDatabaseFile target, IDatabaseFile source) {
@ -13,10 +11,6 @@ namespace DHT.Server.Database {
target.AddUsers(source.GetAllUsers().ToArray());
target.AddMessages(source.GetMessages().ToArray());
foreach (var download in source.GetDownloadsWithoutData()) {
target.AddDownload(download.Status == DownloadStatus.Success ? source.GetDownloadWithData(download) : download);
}
}
}
}

View File

@ -1,17 +1,12 @@
using DHT.Utils.Models;
namespace DHT.Server.Database {
/// <summary>
/// A live view of database statistics.
/// Some of the totals are computed asynchronously and may not reflect the most recent version of the database, or may not be available at all until computed for the first time.
/// </summary>
public sealed class DatabaseStatistics : BaseModel {
private long totalServers;
private long totalChannels;
private long totalUsers;
private long? totalMessages;
private long? totalAttachments;
private long? totalDownloads;
public long TotalServers {
get => totalServers;
@ -38,9 +33,14 @@ namespace DHT.Server.Database {
internal set => Change(ref totalAttachments, value);
}
public long? TotalDownloads {
get => totalDownloads;
internal set => Change(ref totalDownloads, value);
public DatabaseStatistics Clone() {
return new DatabaseStatistics {
totalServers = totalServers,
totalChannels = totalChannels,
totalUsers = TotalUsers,
totalMessages = totalMessages,
totalAttachments = totalAttachments
};
}
}
}

View File

@ -1,11 +0,0 @@
namespace DHT.Server.Database {
/// <summary>
/// A complete snapshot of database statistics at a particular point in time.
/// </summary>
public readonly struct DatabaseStatisticsSnapshot {
public long TotalServers { get; internal init; }
public long TotalChannels { get; internal init; }
public long TotalUsers { get; internal init; }
public long TotalMessages { get; internal init; }
}
}

View File

@ -13,10 +13,6 @@ namespace DHT.Server.Database {
private DummyDatabaseFile() {}
public DatabaseStatisticsSnapshot SnapshotStatistics() {
return new();
}
public void AddServer(Data.Server server) {}
public List<Data.Server> GetAllServers() {
@ -51,15 +47,7 @@ namespace DHT.Server.Database {
return new();
}
public List<Data.Download> GetDownloadsWithoutData() {
return new();
}
public Data.Download GetDownloadWithData(Data.Download download) {
return download;
}
public void AddDownload(Data.Download download) {}
public void AddDownloads(IEnumerable<Data.Download> downloads) {}
public void EnqueueDownloadItems(AttachmentFilter? filter = null) {}

View File

@ -9,7 +9,6 @@ namespace DHT.Server.Database {
public interface IDatabaseFile : IDisposable {
string Path { get; }
DatabaseStatistics Statistics { get; }
DatabaseStatisticsSnapshot SnapshotStatistics();
void AddServer(Data.Server server);
List<Data.Server> GetAllServers();
@ -27,10 +26,7 @@ namespace DHT.Server.Database {
int CountAttachments(AttachmentFilter? filter = null);
void AddDownload(Data.Download download);
List<Data.Download> GetDownloadsWithoutData();
Data.Download GetDownloadWithData(Data.Download download);
void AddDownloads(IEnumerable<Data.Download> downloads);
void EnqueueDownloadItems(AttachmentFilter? filter = null);
List<DownloadItem> GetEnqueuedDownloadItems(int count);
void RemoveDownloadItems(DownloadItemFilter? filter, FilterRemovalMode mode);

View File

@ -24,19 +24,14 @@ namespace DHT.Server.Database.Sqlite {
};
var pool = new SqliteConnectionPool(connectionString, DefaultPoolSize);
bool wasOpened;
using (var conn = pool.Take()) {
wasOpened = await new Schema(conn).Setup(checkCanUpgradeSchemas);
if (!await new Schema(conn).Setup(checkCanUpgradeSchemas)) {
return null;
}
}
if (wasOpened) {
return new SqliteDatabaseFile(path, pool);
}
else {
pool.Dispose();
return null;
}
return new SqliteDatabaseFile(path, pool);
}
public string Path { get; }
@ -46,7 +41,6 @@ namespace DHT.Server.Database.Sqlite {
private readonly SqliteConnectionPool pool;
private readonly AsyncValueComputer<long>.Single totalMessagesComputer;
private readonly AsyncValueComputer<long>.Single totalAttachmentsComputer;
private readonly AsyncValueComputer<long>.Single totalDownloadsComputer;
private SqliteDatabaseFile(string path, SqliteConnectionPool pool) {
this.log = Log.ForType(typeof(SqliteDatabaseFile), System.IO.Path.GetFileName(path));
@ -54,7 +48,6 @@ namespace DHT.Server.Database.Sqlite {
this.totalMessagesComputer = AsyncValueComputer<long>.WithResultProcessor(UpdateMessageStatistics).WithOutdatedResults().BuildWithComputer(ComputeMessageStatistics);
this.totalAttachmentsComputer = AsyncValueComputer<long>.WithResultProcessor(UpdateAttachmentStatistics).WithOutdatedResults().BuildWithComputer(ComputeAttachmentStatistics);
this.totalDownloadsComputer = AsyncValueComputer<long>.WithResultProcessor(UpdateDownloadStatistics).WithOutdatedResults().BuildWithComputer(ComputeDownloadStatistics);
this.Path = path;
this.Statistics = new DatabaseStatistics();
@ -67,22 +60,12 @@ namespace DHT.Server.Database.Sqlite {
totalMessagesComputer.Recompute();
totalAttachmentsComputer.Recompute();
totalDownloadsComputer.Recompute();
}
public void Dispose() {
pool.Dispose();
}
public DatabaseStatisticsSnapshot SnapshotStatistics() {
return new DatabaseStatisticsSnapshot {
TotalServers = Statistics.TotalServers,
TotalChannels = Statistics.TotalChannels,
TotalUsers = Statistics.TotalUsers,
TotalMessages = ComputeMessageStatistics()
};
}
public void AddServer(Data.Server server) {
using var conn = pool.Take();
using var cmd = conn.Upsert("servers", new[] {
@ -407,8 +390,10 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
return reader.Read() ? reader.GetInt32(0) : 0;
}
public void AddDownload(Data.Download download) {
public void AddDownloads(IEnumerable<Data.Download> downloads) {
using var conn = pool.Take();
using var tx = conn.BeginTransaction();
using var cmd = conn.Upsert("downloads", new[] {
("url", SqliteType.Text),
("status", SqliteType.Integer),
@ -416,46 +401,15 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
("blob", SqliteType.Blob)
});
cmd.Set(":url", download.Url);
cmd.Set(":status", (int) download.Status);
cmd.Set(":size", download.Size);
cmd.Set(":blob", download.Data);
cmd.ExecuteNonQuery();
totalDownloadsComputer.Recompute();
}
public List<Data.Download> GetDownloadsWithoutData() {
var list = new List<Data.Download>();
using var conn = pool.Take();
using var cmd = conn.Command("SELECT url, status, size FROM downloads");
using var reader = cmd.ExecuteReader();
while (reader.Read()) {
string url = reader.GetString(0);
var status = (DownloadStatus) reader.GetInt32(1);
ulong size = reader.GetUint64(2);
list.Add(new Data.Download(url, status, size));
foreach (var download in downloads) {
cmd.Set(":url", download.Url);
cmd.Set(":status", (int) download.Status);
cmd.Set(":size", download.Size);
cmd.Set(":blob", download.Data);
cmd.ExecuteNonQuery();
}
return list;
}
public Data.Download GetDownloadWithData(Data.Download download) {
using var conn = pool.Take();
using var cmd = conn.Command("SELECT blob FROM downloads WHERE url = :url");
cmd.AddAndSet(":url", SqliteType.Text, download.Url);
using var reader = cmd.ExecuteReader();
if (reader.Read() && !reader.IsDBNull(0)) {
return download.WithData((byte[]) reader["blob"]);
}
else {
return download;
}
tx.Commit();
}
public void EnqueueDownloadItems(AttachmentFilter? filter = null) {
@ -645,14 +599,5 @@ FROM downloads");
private void UpdateAttachmentStatistics(long totalAttachments) {
Statistics.TotalAttachments = totalAttachments;
}
private long ComputeDownloadStatistics() {
using var conn = pool.Take();
return conn.SelectScalar("SELECT COUNT(*) FROM downloads") as long? ?? 0L;
}
private void UpdateDownloadStatistics(long totalDownloads) {
Statistics.TotalDownloads = totalDownloads;
}
}
}

View File

@ -92,9 +92,9 @@ namespace DHT.Server.Download {
Log.Debug("Downloading " + url + "...");
try {
db.AddDownload(Data.Download.NewSuccess(url, client.DownloadData(url)));
db.AddDownloads(new [] { Data.Download.NewSuccess(url, client.DownloadData(url)) });
} catch (WebException e) {
db.AddDownload(Data.Download.NewFailure(url, e.Response is HttpWebResponse response ? response.StatusCode : null, item.Size));
db.AddDownloads(new [] { Data.Download.NewFailure(url, e.Response is HttpWebResponse response ? response.StatusCode : null, item.Size) });
Log.Error(e);
} finally {
parameters.FireOnItemFinished(item);