1
0
mirror of https://github.com/chylex/Discord-History-Tracker.git synced 2025-10-18 13:39:35 +02:00

5 Commits

7 changed files with 110 additions and 51 deletions

View File

@@ -55,11 +55,11 @@ sealed class DatabasePageModel {
break; break;
case PlatformID.Unix: case PlatformID.Unix:
Process.Start("xdg-open", [ folder ]); Process.Start("xdg-open", [folder]);
break; break;
case PlatformID.MacOSX: case PlatformID.MacOSX:
Process.Start("open", [ folder ]); Process.Start("open", [folder]);
break; break;
default: default:
@@ -80,22 +80,25 @@ sealed class DatabasePageModel {
const string Title = "Database Merge"; const string Title = "Database Merge";
ImportResult? result; var result = new TaskCompletionSource<ImportResult?>();
try { try {
result = await ProgressDialog.Show(window, Title, async (dialog, callback) => await MergeWithDatabaseFromPaths(Db, paths, dialog, callback)); var dialog = new ProgressDialog();
dialog.DataContext = new ProgressDialogModel(Title, async callbacks => result.SetResult(await MergeWithDatabaseFromPaths(Db, paths, dialog, callbacks)), progressItems: 2);
await dialog.ShowProgressDialog(window);
} catch (Exception e) { } catch (Exception e) {
Log.Error("Could not merge databases.", e); Log.Error("Could not merge databases.", e);
await Dialog.ShowOk(window, Title, "Could not merge databases: " + e.Message); await Dialog.ShowOk(window, Title, "Could not merge databases: " + e.Message);
return; return;
} }
await Dialog.ShowOk(window, Title, GetImportDialogMessage(result, "database file")); await Dialog.ShowOk(window, Title, GetImportDialogMessage(result.Task.Result, "database file"));
} }
private static async Task<ImportResult?> MergeWithDatabaseFromPaths(IDatabaseFile target, string[] paths, ProgressDialog dialog, IProgressCallback callback) { private static async Task<ImportResult?> MergeWithDatabaseFromPaths(IDatabaseFile target, string[] paths, ProgressDialog dialog, IReadOnlyList<IProgressCallback> callbacks) {
var schemaUpgradeCallbacks = new SchemaUpgradeCallbacks(dialog, paths.Length); var schemaUpgradeCallbacks = new SchemaUpgradeCallbacks(dialog, paths.Length);
var databaseMergeProgressCallback = new DatabaseMergeProgressCallback(callbacks[1]);
return await PerformImport(target, paths, dialog, callback, "Database Merge", async path => { return await PerformImport(target, paths, dialog, callbacks[0], "Database Merge", async path => {
IDatabaseFile? db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, dialog, schemaUpgradeCallbacks); IDatabaseFile? db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, dialog, schemaUpgradeCallbacks);
if (db == null) { if (db == null) {
@@ -103,7 +106,7 @@ sealed class DatabasePageModel {
} }
try { try {
await target.AddFrom(db); await target.Merge(db, databaseMergeProgressCallback);
return true; return true;
} finally { } finally {
await db.DisposeAsync(); await db.DisposeAsync();
@@ -143,6 +146,20 @@ sealed class DatabasePageModel {
} }
} }
private sealed class DatabaseMergeProgressCallback(IProgressCallback callback) : DatabaseMerging.IProgressCallback {
public void OnImportingMetadata() {
callback.UpdateIndeterminate("Importing metadata...");
}
public void OnMessagesImported(long finished, long total) {
callback.Update("Importing messages...", finished, total);
}
public void OnDownloadsImported(long finished, long total) {
callback.Update("Importing downloaded files...", finished, total);
}
}
public async Task ImportLegacyArchive() { public async Task ImportLegacyArchive() {
string[] paths = await window.StorageProvider.OpenFiles(new FilePickerOpenOptions { string[] paths = await window.StorageProvider.OpenFiles(new FilePickerOpenOptions {
Title = "Open Legacy DHT Archive", Title = "Open Legacy DHT Archive",
@@ -223,7 +240,7 @@ sealed class DatabasePageModel {
int finished = 0; int finished = 0;
foreach (string path in paths) { foreach (string path in paths) {
await callback.Update(Path.GetFileName(path), finished, total); await callback.Update("File: " + Path.GetFileName(path), finished, total);
++finished; ++finished;
if (!File.Exists(path)) { if (!File.Exists(path)) {

View File

@@ -107,7 +107,7 @@ export default (function() {
const isImageUrl = function(url) { const isImageUrl = function(url) {
const dot = url.pathname.lastIndexOf("."); const dot = url.pathname.lastIndexOf(".");
const ext = dot === -1 ? "" : url.pathname.substring(dot).toLowerCase(); const ext = dot === -1 ? "" : url.pathname.substring(dot).toLowerCase();
return ext === ".png" || ext === ".gif" || ext === ".jpg" || ext === ".jpeg"; return ext === ".png" || ext === ".gif" || ext === ".jpg" || ext === ".jpeg" || ext === ".webp" || ext === ".avif";
}; };
return { return {

View File

@@ -1,34 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DHT.Server.Data;
namespace DHT.Server.Database;
public static class DatabaseExtensions {
public static async Task AddFrom(this IDatabaseFile target, IDatabaseFile source) {
await target.Users.Add(await source.Users.Get().ToListAsync());
await target.Servers.Add(await source.Servers.Get().ToListAsync());
await target.Channels.Add(await source.Channels.Get().ToListAsync());
const int MessageBatchSize = 100;
List<Message> batchedMessages = new (MessageBatchSize);
await foreach (Message message in source.Messages.Get()) {
batchedMessages.Add(message);
if (batchedMessages.Count >= MessageBatchSize) {
await target.Messages.Add(batchedMessages);
batchedMessages.Clear();
}
}
await target.Messages.Add(batchedMessages);
await foreach (Data.Download download in source.Downloads.Get()) {
if (download.Status != DownloadStatus.Success || !await source.Downloads.GetDownloadData(download.NormalizedUrl, stream => target.Downloads.AddDownload(download, stream))) {
await target.Downloads.AddDownload(download, stream: null);
}
}
}
}

View File

@@ -0,0 +1,77 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DHT.Server.Data;
namespace DHT.Server.Database.Import;
public static class DatabaseMerging {
public static async Task Merge(this IDatabaseFile target, IDatabaseFile source, IProgressCallback callback) {
// Import downloads first, otherwise automatic downloads would try to re-download files from other imported data.
await MergeDownloads(target, source, callback);
callback.OnImportingMetadata();
await target.Users.Add(await source.Users.Get().ToListAsync());
await target.Servers.Add(await source.Servers.Get().ToListAsync());
await target.Channels.Add(await source.Channels.Get().ToListAsync());
await MergeMessages(target, source, callback);
}
private static async Task MergeDownloads(IDatabaseFile target, IDatabaseFile source, IProgressCallback callback) {
const int ReportBatchSize = 100;
long totalDownloads = await source.Downloads.Count();
long importedDownloads = 0;
callback.OnDownloadsImported(importedDownloads, totalDownloads);
await foreach (Data.Download download in source.Downloads.Get()) {
if (download.Status != DownloadStatus.Success || !await source.Downloads.GetDownloadData(download.NormalizedUrl, stream => target.Downloads.AddDownload(download, stream))) {
await target.Downloads.AddDownload(download, stream: null);
}
if (++importedDownloads % ReportBatchSize == 0) {
callback.OnDownloadsImported(importedDownloads, totalDownloads);
}
}
callback.OnDownloadsImported(totalDownloads, totalDownloads);
}
private static async Task MergeMessages(IDatabaseFile target, IDatabaseFile source, IProgressCallback callback) {
const int MessageBatchSize = 100;
const int ReportEveryBatches = 10;
List<Message> batchedMessages = new (MessageBatchSize);
long totalMessages = await source.Messages.Count();
long importedMessages = 0;
callback.OnMessagesImported(importedMessages, totalMessages);
await foreach (Message message in source.Messages.Get()) {
batchedMessages.Add(message);
if (batchedMessages.Count >= MessageBatchSize) {
await target.Messages.Add(batchedMessages);
importedMessages += batchedMessages.Count;
if (importedMessages % (MessageBatchSize * ReportEveryBatches) == 0) {
callback.OnMessagesImported(importedMessages, totalMessages);
}
batchedMessages.Clear();
}
}
await target.Messages.Add(batchedMessages);
callback.OnMessagesImported(totalMessages, totalMessages);
}
public interface IProgressCallback {
void OnImportingMetadata();
void OnMessagesImported(long finished, long total);
void OnDownloadsImported(long finished, long total);
}
}

View File

@@ -17,7 +17,7 @@ public interface IDownloadRepository {
Task AddDownload(Data.Download item, Stream? stream); Task AddDownload(Data.Download item, Stream? stream);
Task<long> Count(DownloadItemFilter filter, CancellationToken cancellationToken = default); Task<long> Count(DownloadItemFilter? filter = null, CancellationToken cancellationToken = default);
Task<DownloadStatusStatistics> GetStatistics(DownloadItemFilter nonSkippedFilter, CancellationToken cancellationToken = default); Task<DownloadStatusStatistics> GetStatistics(DownloadItemFilter nonSkippedFilter, CancellationToken cancellationToken = default);
@@ -44,7 +44,7 @@ public interface IDownloadRepository {
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task<long> Count(DownloadItemFilter filter, CancellationToken cancellationToken) { public Task<long> Count(DownloadItemFilter? filter, CancellationToken cancellationToken) {
return Task.FromResult(0L); return Task.FromResult(0L);
} }

View File

@@ -22,7 +22,6 @@ define('LATEST_VERSION', $version === false ? '_' : $version);
<h1>Discord History Tracker&nbsp;<span class="bar">|</span>&nbsp;<span class="notes"><a href="https://github.com/chylex/Discord-History-Tracker/releases">Release&nbsp;Notes</a></span></h1> <h1>Discord History Tracker&nbsp;<span class="bar">|</span>&nbsp;<span class="notes"><a href="https://github.com/chylex/Discord-History-Tracker/releases">Release&nbsp;Notes</a></span></h1>
<p>Discord History Tracker lets you save chat history in your servers, groups, and private conversations, and view it offline.</p> <p>Discord History Tracker lets you save chat history in your servers, groups, and private conversations, and view it offline.</p>
<img src="img/tracker.png" width="851" class="dht bordered"> <img src="img/tracker.png" width="851" class="dht bordered">
<p>This page explains how to use the desktop app. If you are looking for the older version of Discord History Tracker which only needs a browser or the Discord app, visit the page for the <a href="https://dht.chylex.com/browser-only">browser-only version</a>, however keep in mind that this version has <strong>significant limitations and fewer features</strong>.</p>
<h2>How to Use</h2> <h2>How to Use</h2>
<p>Download the latest version of the desktop app here, or visit <a href="https://github.com/chylex/Discord-History-Tracker/releases">All Releases</a> for older versions and release notes.</p> <p>Download the latest version of the desktop app here, or visit <a href="https://github.com/chylex/Discord-History-Tracker/releases">All Releases</a> for older versions and release notes.</p>
@@ -71,9 +70,9 @@ define('LATEST_VERSION', $version === false ? '_' : $version);
<h2>External Links</h2> <h2>External Links</h2>
<p class="links"> <p class="links">
<a href="https://github.com/chylex/Discord-History-Tracker/issues">Issues&nbsp;&amp;&nbsp;Suggestions</a>&nbsp;&nbsp;&mdash;&nbsp; <a href="https://github.com/chylex/Discord-History-Tracker/issues">Issues&nbsp;&amp;&nbsp;Suggestions</a>&nbsp;&nbsp;&bullet;&nbsp;
<a href="https://github.com/chylex/Discord-History-Tracker">Source&nbsp;Code</a>&nbsp;&nbsp;&mdash;&nbsp; <a href="https://github.com/chylex/Discord-History-Tracker">Source&nbsp;Code</a>&nbsp;&nbsp;&bullet;&nbsp;
<a href="https://twitter.com/chylexmc">Follow&nbsp;Dev&nbsp;on&nbsp;Twitter</a>&nbsp;&nbsp;&mdash;&nbsp; <a href="https://chylex.com">Developer's&nbsp;Website</a>&nbsp;&nbsp;&bullet;&nbsp;
<a href="https://ko-fi.com/chylex">Support&nbsp;via&nbsp;Ko-fi</a> <a href="https://ko-fi.com/chylex">Support&nbsp;via&nbsp;Ko-fi</a>
</p> </p>

View File

@@ -165,7 +165,7 @@ code {
.downloads svg { .downloads svg {
margin: 1px 4px; margin: 1px 4px;
vertical-align: -25%; vertical-align: -30%;
} }
.downloads svg.icon-large { .downloads svg.icon-large {