mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-07-16 16:11:02 +03:00
Compare commits
6 Commits
58259c0bb4
...
8f5f6065d8
Author | SHA1 | Date | |
---|---|---|---|
|
8f5f6065d8 | ||
|
ad299bf762 | ||
|
f70bbd53d9 | ||
|
ae821f738e | ||
|
ab7f5d0a41 | ||
|
1bddde7ccd |
@ -14,7 +14,7 @@ using DHT.Server.Database;
|
|||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls {
|
||||||
sealed class FilterPanelModel : BaseModel {
|
sealed class FilterPanelModel : BaseModel, IDisposable {
|
||||||
private static readonly HashSet<string> FilterProperties = new () {
|
private static readonly HashSet<string> FilterProperties = new () {
|
||||||
nameof(FilterByDate),
|
nameof(FilterByDate),
|
||||||
nameof(StartDate),
|
nameof(StartDate),
|
||||||
@ -103,6 +103,10 @@ namespace DHT.Desktop.Main.Controls {
|
|||||||
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
if (e.PropertyName != null && FilterProperties.Contains(e.PropertyName)) {
|
if (e.PropertyName != null && FilterProperties.Contains(e.PropertyName)) {
|
||||||
FilterPropertyChanged?.Invoke(sender, e);
|
FilterPropertyChanged?.Invoke(sender, e);
|
||||||
|
@ -56,6 +56,7 @@ namespace DHT.Desktop.Main {
|
|||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
TrackingPageModel.Dispose();
|
TrackingPageModel.Dispose();
|
||||||
|
ViewerPageModel.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<Panel>
|
<Panel>
|
||||||
<ContentPresenter Content="{Binding WelcomeScreen}" IsVisible="{Binding ShowWelcomeScreen}" />
|
<ContentPresenter Content="{Binding CurrentScreen}" />
|
||||||
<ContentPresenter Content="{Binding MainContentScreen}" IsVisible="{Binding ShowMainContentScreen}" />
|
|
||||||
</Panel>
|
</Panel>
|
||||||
</Window>
|
</Window>
|
||||||
|
@ -15,14 +15,13 @@ namespace DHT.Desktop.Main {
|
|||||||
|
|
||||||
public string Title { get; private set; } = DefaultTitle;
|
public string Title { get; private set; } = DefaultTitle;
|
||||||
|
|
||||||
public WelcomeScreen WelcomeScreen { get; }
|
public UserControl CurrentScreen { get; private set; }
|
||||||
private WelcomeScreenModel WelcomeScreenModel { get; }
|
|
||||||
|
|
||||||
public MainContentScreen? MainContentScreen { get; private set; }
|
private readonly WelcomeScreen welcomeScreen;
|
||||||
private MainContentScreenModel? MainContentScreenModel { get; set; }
|
private readonly WelcomeScreenModel welcomeScreenModel;
|
||||||
|
|
||||||
public bool ShowWelcomeScreen => db == null;
|
private MainContentScreen? mainContentScreen;
|
||||||
public bool ShowMainContentScreen => db != null;
|
private MainContentScreenModel? mainContentScreenModel;
|
||||||
|
|
||||||
private readonly Window window;
|
private readonly Window window;
|
||||||
|
|
||||||
@ -34,10 +33,11 @@ namespace DHT.Desktop.Main {
|
|||||||
public MainWindowModel(Window window, Arguments args) {
|
public MainWindowModel(Window window, Arguments args) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
|
||||||
WelcomeScreenModel = new WelcomeScreenModel(window);
|
welcomeScreenModel = new WelcomeScreenModel(window);
|
||||||
WelcomeScreen = new WelcomeScreen { DataContext = WelcomeScreenModel };
|
welcomeScreen = new WelcomeScreen { DataContext = welcomeScreenModel };
|
||||||
|
CurrentScreen = welcomeScreen;
|
||||||
|
|
||||||
WelcomeScreenModel.PropertyChanged += WelcomeScreenModelOnPropertyChanged;
|
welcomeScreenModel.PropertyChanged += WelcomeScreenModelOnPropertyChanged;
|
||||||
|
|
||||||
var dbFile = args.DatabaseFile;
|
var dbFile = args.DatabaseFile;
|
||||||
if (!string.IsNullOrWhiteSpace(dbFile)) {
|
if (!string.IsNullOrWhiteSpace(dbFile)) {
|
||||||
@ -50,7 +50,7 @@ namespace DHT.Desktop.Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(dbFile)) {
|
if (File.Exists(dbFile)) {
|
||||||
await WelcomeScreenModel.OpenOrCreateDatabaseFromPath(dbFile);
|
await welcomeScreenModel.OpenOrCreateDatabaseFromPath(dbFile);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await Dialog.ShowOk(window, "Database Error", "Database file not found:\n" + dbFile);
|
await Dialog.ShowOk(window, "Database Error", "Database file not found:\n" + dbFile);
|
||||||
@ -70,31 +70,31 @@ namespace DHT.Desktop.Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async void WelcomeScreenModelOnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
private async void WelcomeScreenModelOnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
if (e.PropertyName == nameof(WelcomeScreenModel.Db)) {
|
if (e.PropertyName == nameof(welcomeScreenModel.Db)) {
|
||||||
if (MainContentScreenModel != null) {
|
if (mainContentScreenModel != null) {
|
||||||
MainContentScreenModel.DatabaseClosed -= MainContentScreenModelOnDatabaseClosed;
|
mainContentScreenModel.DatabaseClosed -= MainContentScreenModelOnDatabaseClosed;
|
||||||
MainContentScreenModel.Dispose();
|
mainContentScreenModel.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
db?.Dispose();
|
db?.Dispose();
|
||||||
db = WelcomeScreenModel.Db;
|
db = welcomeScreenModel.Db;
|
||||||
|
|
||||||
if (db == null) {
|
if (db == null) {
|
||||||
Title = DefaultTitle;
|
Title = DefaultTitle;
|
||||||
MainContentScreenModel = null;
|
mainContentScreenModel = null;
|
||||||
MainContentScreen = null;
|
mainContentScreen = null;
|
||||||
|
CurrentScreen = welcomeScreen;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Title = Path.GetFileName(db.Path) + " - " + DefaultTitle;
|
Title = Path.GetFileName(db.Path) + " - " + DefaultTitle;
|
||||||
MainContentScreenModel = new MainContentScreenModel(window, db);
|
mainContentScreenModel = new MainContentScreenModel(window, db);
|
||||||
await MainContentScreenModel.Initialize();
|
await mainContentScreenModel.Initialize();
|
||||||
MainContentScreenModel.DatabaseClosed += MainContentScreenModelOnDatabaseClosed;
|
mainContentScreenModel.DatabaseClosed += MainContentScreenModelOnDatabaseClosed;
|
||||||
MainContentScreen = new MainContentScreen { DataContext = MainContentScreenModel };
|
mainContentScreen = new MainContentScreen { DataContext = mainContentScreenModel };
|
||||||
OnPropertyChanged(nameof(MainContentScreen));
|
CurrentScreen = mainContentScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPropertyChanged(nameof(ShowWelcomeScreen));
|
OnPropertyChanged(nameof(CurrentScreen));
|
||||||
OnPropertyChanged(nameof(ShowMainContentScreen));
|
|
||||||
OnPropertyChanged(nameof(Title));
|
OnPropertyChanged(nameof(Title));
|
||||||
|
|
||||||
window.Focus();
|
window.Focus();
|
||||||
@ -102,12 +102,12 @@ namespace DHT.Desktop.Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void MainContentScreenModelOnDatabaseClosed(object? sender, EventArgs e) {
|
private void MainContentScreenModelOnDatabaseClosed(object? sender, EventArgs e) {
|
||||||
WelcomeScreenModel.CloseDatabase();
|
welcomeScreenModel.CloseDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
WelcomeScreenModel.Dispose();
|
welcomeScreenModel.Dispose();
|
||||||
MainContentScreenModel?.Dispose();
|
mainContentScreenModel?.Dispose();
|
||||||
db?.Dispose();
|
db?.Dispose();
|
||||||
db = null;
|
db = null;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ using DHT.Utils.Models;
|
|||||||
using static DHT.Desktop.Program;
|
using static DHT.Desktop.Program;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages {
|
||||||
sealed class ViewerPageModel : BaseModel {
|
sealed class ViewerPageModel : BaseModel, IDisposable {
|
||||||
public string ExportedMessageText { get; private set; } = "";
|
public string ExportedMessageText { get; private set; } = "";
|
||||||
|
|
||||||
public bool DatabaseToolFilterModeKeep { get; set; } = true;
|
public bool DatabaseToolFilterModeKeep { get; set; } = true;
|
||||||
@ -41,12 +41,17 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
this.window = window;
|
this.window = window;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
|
||||||
this.FilterModel = new FilterPanelModel(window, db);
|
FilterModel = new FilterPanelModel(window, db);
|
||||||
this.FilterModel.FilterPropertyChanged += OnFilterPropertyChanged;
|
FilterModel.FilterPropertyChanged += OnFilterPropertyChanged;
|
||||||
this.db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||||
UpdateStatistics();
|
UpdateStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
|
||||||
|
FilterModel.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
private void OnFilterPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
private void OnFilterPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
UpdateStatistics();
|
UpdateStatistics();
|
||||||
HasFilters = FilterModel.HasAnyFilters;
|
HasFilters = FilterModel.HasAnyFilters;
|
||||||
|
16
app/Resources/Tracker/bootstrap.js
vendored
16
app/Resources/Tracker/bootstrap.js
vendored
@ -42,6 +42,10 @@
|
|||||||
stopTrackingDelayed(() => isSending = false);
|
stopTrackingDelayed(() => isSending = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isNoAction = function(action) {
|
||||||
|
return action === null || action === CONSTANTS.AUTOSCROLL_ACTION_NOTHING;
|
||||||
|
};
|
||||||
|
|
||||||
const onTrackingContinued = function(anyNewMessages) {
|
const onTrackingContinued = function(anyNewMessages) {
|
||||||
if (!STATE.isTracking()) {
|
if (!STATE.isTracking()) {
|
||||||
return;
|
return;
|
||||||
@ -59,14 +63,14 @@
|
|||||||
if (SETTINGS.autoscroll) {
|
if (SETTINGS.autoscroll) {
|
||||||
let action = null;
|
let action = null;
|
||||||
|
|
||||||
if (!anyNewMessages) {
|
if (!DISCORD.hasMoreMessages()) {
|
||||||
action = SETTINGS.afterSavedMsg;
|
|
||||||
}
|
|
||||||
else if (!DISCORD.hasMoreMessages()) {
|
|
||||||
action = SETTINGS.afterFirstMsg;
|
action = SETTINGS.afterFirstMsg;
|
||||||
}
|
}
|
||||||
|
if (isNoAction(action) && !anyNewMessages) {
|
||||||
|
action = SETTINGS.afterSavedMsg;
|
||||||
|
}
|
||||||
|
|
||||||
if (action === null || action === CONSTANTS.AUTOSCROLL_ACTION_NOTHING) {
|
if (isNoAction(action)) {
|
||||||
DISCORD.loadOlderMessages();
|
DISCORD.loadOlderMessages();
|
||||||
}
|
}
|
||||||
else if (action === CONSTANTS.AUTOSCROLL_ACTION_PAUSE || (action === CONSTANTS.AUTOSCROLL_ACTION_SWITCH && !DISCORD.selectNextTextChannel())) {
|
else if (action === CONSTANTS.AUTOSCROLL_ACTION_PAUSE || (action === CONSTANTS.AUTOSCROLL_ACTION_SWITCH && !DISCORD.selectNextTextChannel())) {
|
||||||
@ -113,8 +117,8 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!messages.length) {
|
if (!messages.length) {
|
||||||
DISCORD.loadOlderMessages();
|
|
||||||
isSending = false;
|
isSending = false;
|
||||||
|
onTrackingContinued(false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const anyNewMessages = await STATE.addDiscordMessages(info.id, messages);
|
const anyNewMessages = await STATE.addDiscordMessages(info.id, messages);
|
||||||
|
@ -67,14 +67,7 @@ class DISCORD {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const messages = this.getMessages();
|
const messages = this.getMessages();
|
||||||
let hasChanged = false;
|
const hasChanged = messages.some(message => !previousMessages.has(message.id)) || !this.hasMoreMessages();
|
||||||
|
|
||||||
for (const message of messages) {
|
|
||||||
if (!previousMessages.has(message.id)) {
|
|
||||||
hasChanged = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasChanged) {
|
if (!hasChanged) {
|
||||||
return;
|
return;
|
||||||
@ -140,6 +133,16 @@ class DISCORD {
|
|||||||
try {
|
try {
|
||||||
let obj;
|
let obj;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const child of DOM.getReactProps(DOM.queryReactClass("chatContent")).children) {
|
||||||
|
if (child && child.props && child.props.channel) {
|
||||||
|
obj = child.props.channel;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[DHT] Error retrieving selected channel from 'chatContent' element.", e);
|
||||||
|
|
||||||
for (const ele of this.getMessageElements()) {
|
for (const ele of this.getMessageElements()) {
|
||||||
const props = this.getMessageElementProps(ele);
|
const props = this.getMessageElementProps(ele);
|
||||||
|
|
||||||
@ -148,8 +151,9 @@ class DISCORD {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!obj) {
|
if (!obj || typeof obj.id !== "string") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +219,7 @@ class DISCORD {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error("[DHT] Error retrieving selected channel.", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,30 +244,21 @@ class DISCORD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const channelIcons = [
|
const channelListEle = document.getElementById("channels");
|
||||||
/* normal */ "M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41045 9L8.35045 15H14.3504L15.4104 9H9.41045Z",
|
|
||||||
/* normal + thread */ "M5.43309 21C5.35842 21 5.30189 20.9325 5.31494 20.859L5.99991 17H2.14274C2.06819 17 2.01168 16.9327 2.02453 16.8593L2.33253 15.0993C2.34258 15.0419 2.39244 15 2.45074 15H6.34991L7.40991 9H3.55274C3.47819 9 3.42168 8.93274 3.43453 8.85931L3.74253 7.09931C3.75258 7.04189 3.80244 7 3.86074 7H7.75991L8.45234 3.09903C8.46251 3.04174 8.51231 3 8.57049 3H10.3267C10.4014 3 10.4579 3.06746 10.4449 3.14097L9.75991 7H15.7599L16.4523 3.09903C16.4625 3.04174 16.5123 3 16.5705 3H18.3267C18.4014 3 18.4579 3.06746 18.4449 3.14097L17.7599 7H21.6171C21.6916 7 21.7481 7.06725 21.7353 7.14069L21.4273 8.90069C21.4172 8.95811 21.3674 9 21.3091 9H17.4099L17.0495 11.04H15.05L15.4104 9H9.41035L8.35035 15H10.5599V17H7.99991L7.30749 20.901C7.29732 20.9583 7.24752 21 7.18934 21H5.43309Z",
|
|
||||||
/* nsfw or private */ "M14 8C14 7.44772 13.5523 7 13 7H9.76001L10.3657 3.58738C10.4201 3.28107 10.1845 3 9.87344 3H8.88907C8.64664 3 8.43914 3.17391 8.39677 3.41262L7.76001 7H4.18011C3.93722 7 3.72946 7.17456 3.68759 7.41381L3.51259 8.41381C3.45905 8.71977 3.69449 9 4.00511 9H7.41001L6.35001 15H2.77011C2.52722 15 2.31946 15.1746 2.27759 15.4138L2.10259 16.4138C2.04905 16.7198 2.28449 17 2.59511 17H6.00001L5.39427 20.4126C5.3399 20.7189 5.57547 21 5.88657 21H6.87094C7.11337 21 7.32088 20.8261 7.36325 20.5874L8.00001 17H14L13.3943 20.4126C13.3399 20.7189 13.5755 21 13.8866 21H14.8709C15.1134 21 15.3209 20.8261 15.3632 20.5874L16 17H19.5799C19.8228 17 20.0306 16.8254 20.0724 16.5862L20.2474 15.5862C20.301 15.2802 20.0655 15 19.7549 15H16.35L16.6758 13.1558C16.7823 12.5529 16.3186 12 15.7063 12C15.2286 12 14.8199 12.3429 14.7368 12.8133L14.3504 15H8.35045L9.41045 9H13C13.5523 9 14 8.55228 14 8Z",
|
|
||||||
/* nsfw + thread */ "M14.4 7C14.5326 7 14.64 7.10745 14.64 7.24V8.76C14.64 8.89255 14.5326 9 14.4 9H9.41045L8.35045 15H10.56V17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H14.4Z",
|
|
||||||
/* private + thread */ "M15.44 6.99992C15.5725 6.99992 15.68 7.10737 15.68 7.23992V8.75992C15.68 8.89247 15.5725 8.99992 15.44 8.99992H9.41045L8.35045 14.9999H10.56V16.9999H8.00001L7.36325 20.5873C7.32088 20.826 7.11337 20.9999 6.87094 20.9999H5.88657C5.57547 20.9999 5.3399 20.7189 5.39427 20.4125L6.00001 16.9999H2.59511C2.28449 16.9999 2.04905 16.7197 2.10259 16.4137L2.27759 15.4137C2.31946 15.1745 2.52722 14.9999 2.77011 14.9999H6.35001L7.41001 8.99992H4.00511C3.69449 8.99992 3.45905 8.71969 3.51259 8.41373L3.68759 7.41373C3.72946 7.17448 3.93722 6.99992 4.18011 6.99992H7.76001L8.39677 3.41254C8.43914 3.17384 8.64664 2.99992 8.88907 2.99992H9.87344C10.1845 2.99992 10.4201 3.28099 10.3657 3.58731L9.76001 6.99992H15.44Z"
|
|
||||||
];
|
|
||||||
|
|
||||||
const isValidChannelClass = cls => cls.includes("wrapper-") && !cls.includes("clickable-");
|
|
||||||
const isValidChannelType = ele => channelIcons.some(icon => !!ele.querySelector("path[d=\"" + icon + "\"]"));
|
|
||||||
const isValidChannel = ele => ele.childElementCount > 0 && isValidChannelClass(ele.children[0].className) && isValidChannelType(ele);
|
|
||||||
|
|
||||||
const channelListEle = document.querySelector("div[class*='sidebar'] > nav[class*='container'] > div[class*='scroller']");
|
|
||||||
|
|
||||||
if (!channelListEle) {
|
if (!channelListEle) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allChannels = Array.prototype.filter.call(channelListEle.querySelectorAll("[class*='containerDefault']"), isValidChannel);
|
function getLinkElement(channel) {
|
||||||
|
return channel.querySelector("a[href^='/channels/'][role='link']");
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTextChannels = Array.prototype.filter.call(channelListEle.querySelectorAll("[class*='containerDefault']"), ele => getLinkElement(ele) !== null);
|
||||||
let nextChannel = null;
|
let nextChannel = null;
|
||||||
|
|
||||||
for (let index = 0; index < allChannels.length - 1; index++) {
|
for (let index = 0; index < allTextChannels.length - 1; index++) {
|
||||||
if (allChannels[index].children[0].className.includes("modeSelected")) {
|
if (allTextChannels[index].className.includes("selected-")) {
|
||||||
nextChannel = allChannels[index + 1];
|
nextChannel = allTextChannels[index + 1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,7 +267,7 @@ class DISCORD {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextChannelLink = nextChannel.querySelector("a[href^='/channels/']");
|
const nextChannelLink = getLinkElement(nextChannel);
|
||||||
if (!nextChannelLink) {
|
if (!nextChannelLink) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
using System.ComponentModel;
|
using DHT.Utils.Models;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace DHT.Server.Database {
|
namespace DHT.Server.Database {
|
||||||
public sealed class DatabaseStatistics : INotifyPropertyChanged {
|
public sealed class DatabaseStatistics : BaseModel {
|
||||||
private long totalServers;
|
private long totalServers;
|
||||||
private long totalChannels;
|
private long totalChannels;
|
||||||
private long totalUsers;
|
private long totalUsers;
|
||||||
@ -10,29 +9,22 @@ namespace DHT.Server.Database {
|
|||||||
|
|
||||||
public long TotalServers {
|
public long TotalServers {
|
||||||
get => totalServers;
|
get => totalServers;
|
||||||
internal set => Change(out totalServers, value);
|
internal set => Change(ref totalServers, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long TotalChannels {
|
public long TotalChannels {
|
||||||
get => totalChannels;
|
get => totalChannels;
|
||||||
internal set => Change(out totalChannels, value);
|
internal set => Change(ref totalChannels, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long TotalUsers {
|
public long TotalUsers {
|
||||||
get => totalUsers;
|
get => totalUsers;
|
||||||
internal set => Change(out totalUsers, value);
|
internal set => Change(ref totalUsers, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long TotalMessages {
|
public long TotalMessages {
|
||||||
get => totalMessages;
|
get => totalMessages;
|
||||||
internal set => Change(out totalMessages, value);
|
internal set => Change(ref totalMessages, value);
|
||||||
}
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
|
|
||||||
private void Change<T>(out T field, T value, [CallerMemberName] string? propertyName = null) {
|
|
||||||
field = value;
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DatabaseStatistics Clone() {
|
public DatabaseStatistics Clone() {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DHT.Server.Database.Exceptions;
|
using DHT.Server.Database.Exceptions;
|
||||||
|
using DHT.Server.Database.Sqlite.Utils;
|
||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
|
|
||||||
namespace DHT.Server.Database.Sqlite {
|
namespace DHT.Server.Database.Sqlite {
|
||||||
sealed class Schema {
|
sealed class Schema {
|
||||||
@ -10,20 +10,14 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
|
|
||||||
private static readonly Log Log = Log.ForType<Schema>();
|
private static readonly Log Log = Log.ForType<Schema>();
|
||||||
|
|
||||||
private readonly SqliteConnection conn;
|
private readonly ISqliteConnection conn;
|
||||||
|
|
||||||
public Schema(SqliteConnection conn) {
|
public Schema(ISqliteConnection conn) {
|
||||||
this.conn = conn;
|
this.conn = conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqliteCommand Sql(string sql) {
|
|
||||||
var cmd = conn.CreateCommand();
|
|
||||||
cmd.CommandText = sql;
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Execute(string sql) {
|
private void Execute(string sql) {
|
||||||
Sql(sql).ExecuteNonQuery();
|
conn.Command(sql).ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> Setup(Func<Task<bool>> checkCanUpgradeSchemas) {
|
public async Task<bool> Setup(Func<Task<bool>> checkCanUpgradeSchemas) {
|
||||||
|
@ -5,46 +5,57 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DHT.Server.Data;
|
using DHT.Server.Data;
|
||||||
using DHT.Server.Data.Filters;
|
using DHT.Server.Data.Filters;
|
||||||
|
using DHT.Server.Database.Sqlite.Utils;
|
||||||
using DHT.Utils.Collections;
|
using DHT.Utils.Collections;
|
||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Sqlite {
|
namespace DHT.Server.Database.Sqlite {
|
||||||
public sealed class SqliteDatabaseFile : IDatabaseFile {
|
public sealed class SqliteDatabaseFile : IDatabaseFile {
|
||||||
|
private const int DefaultPoolSize = 5;
|
||||||
|
|
||||||
public static async Task<SqliteDatabaseFile?> OpenOrCreate(string path, Func<Task<bool>> checkCanUpgradeSchemas) {
|
public static async Task<SqliteDatabaseFile?> OpenOrCreate(string path, Func<Task<bool>> checkCanUpgradeSchemas) {
|
||||||
string connectionString = new SqliteConnectionStringBuilder {
|
var connectionString = new SqliteConnectionStringBuilder {
|
||||||
DataSource = path,
|
DataSource = path,
|
||||||
Mode = SqliteOpenMode.ReadWriteCreate
|
Mode = SqliteOpenMode.ReadWriteCreate,
|
||||||
}.ToString();
|
};
|
||||||
|
|
||||||
var conn = new SqliteConnection(connectionString);
|
var pool = new SqliteConnectionPool(connectionString, DefaultPoolSize);
|
||||||
conn.Open();
|
|
||||||
|
|
||||||
return await new Schema(conn).Setup(checkCanUpgradeSchemas) ? new SqliteDatabaseFile(path, conn) : null;
|
using (var conn = pool.Take()) {
|
||||||
|
if (!await new Schema(conn).Setup(checkCanUpgradeSchemas)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SqliteDatabaseFile(path, pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
public DatabaseStatistics Statistics { get; }
|
public DatabaseStatistics Statistics { get; }
|
||||||
|
|
||||||
private readonly Log log;
|
private readonly Log log;
|
||||||
private readonly SqliteConnection conn;
|
private readonly SqliteConnectionPool pool;
|
||||||
|
|
||||||
private SqliteDatabaseFile(string path, SqliteConnection conn) {
|
private SqliteDatabaseFile(string path, SqliteConnectionPool pool) {
|
||||||
this.log = Log.ForType(typeof(SqliteDatabaseFile), System.IO.Path.GetFileName(path));
|
this.log = Log.ForType(typeof(SqliteDatabaseFile), System.IO.Path.GetFileName(path));
|
||||||
this.conn = conn;
|
this.pool = pool;
|
||||||
this.Path = path;
|
this.Path = path;
|
||||||
this.Statistics = new DatabaseStatistics();
|
this.Statistics = new DatabaseStatistics();
|
||||||
UpdateServerStatistics();
|
|
||||||
UpdateChannelStatistics();
|
using var conn = pool.Take();
|
||||||
UpdateUserStatistics();
|
UpdateServerStatistics(conn);
|
||||||
UpdateMessageStatistics();
|
UpdateChannelStatistics(conn);
|
||||||
|
UpdateUserStatistics(conn);
|
||||||
|
UpdateMessageStatistics(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
conn.Dispose();
|
pool.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddServer(Data.Server server) {
|
public void AddServer(Data.Server server) {
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Upsert("servers", new[] {
|
using var cmd = conn.Upsert("servers", new[] {
|
||||||
("id", SqliteType.Integer),
|
("id", SqliteType.Integer),
|
||||||
("name", SqliteType.Text),
|
("name", SqliteType.Text),
|
||||||
@ -55,13 +66,14 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
cmd.Set(":name", server.Name);
|
cmd.Set(":name", server.Name);
|
||||||
cmd.Set(":type", ServerTypes.ToString(server.Type));
|
cmd.Set(":type", ServerTypes.ToString(server.Type));
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
UpdateServerStatistics();
|
UpdateServerStatistics(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Data.Server> GetAllServers() {
|
public List<Data.Server> GetAllServers() {
|
||||||
var perf = log.Start();
|
var perf = log.Start();
|
||||||
var list = new List<Data.Server>();
|
var list = new List<Data.Server>();
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command("SELECT id, name, type FROM servers");
|
using var cmd = conn.Command("SELECT id, name, type FROM servers");
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
@ -78,6 +90,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void AddChannel(Channel channel) {
|
public void AddChannel(Channel channel) {
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Upsert("channels", new[] {
|
using var cmd = conn.Upsert("channels", new[] {
|
||||||
("id", SqliteType.Integer),
|
("id", SqliteType.Integer),
|
||||||
("server", SqliteType.Integer),
|
("server", SqliteType.Integer),
|
||||||
@ -96,12 +109,13 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
cmd.Set(":topic", channel.Topic);
|
cmd.Set(":topic", channel.Topic);
|
||||||
cmd.Set(":nsfw", channel.Nsfw);
|
cmd.Set(":nsfw", channel.Nsfw);
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
UpdateChannelStatistics();
|
UpdateChannelStatistics(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Channel> GetAllChannels() {
|
public List<Channel> GetAllChannels() {
|
||||||
var list = new List<Channel>();
|
var list = new List<Channel>();
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command("SELECT id, server, name, parent_id, position, topic, nsfw FROM channels");
|
using var cmd = conn.Command("SELECT id, server, name, parent_id, position, topic, nsfw FROM channels");
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
@ -121,6 +135,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void AddUsers(User[] users) {
|
public void AddUsers(User[] users) {
|
||||||
|
using var conn = pool.Take();
|
||||||
using var tx = conn.BeginTransaction();
|
using var tx = conn.BeginTransaction();
|
||||||
using var cmd = conn.Upsert("users", new[] {
|
using var cmd = conn.Upsert("users", new[] {
|
||||||
("id", SqliteType.Integer),
|
("id", SqliteType.Integer),
|
||||||
@ -138,13 +153,14 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx.Commit();
|
tx.Commit();
|
||||||
UpdateUserStatistics();
|
UpdateUserStatistics(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<User> GetAllUsers() {
|
public List<User> GetAllUsers() {
|
||||||
var perf = log.Start();
|
var perf = log.Start();
|
||||||
var list = new List<User>();
|
var list = new List<User>();
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command("SELECT id, name, avatar_url, discriminator FROM users");
|
using var cmd = conn.Command("SELECT id, name, avatar_url, discriminator FROM users");
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
@ -162,7 +178,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void AddMessages(Message[] messages) {
|
public void AddMessages(Message[] messages) {
|
||||||
static SqliteCommand DeleteByMessageId(SqliteConnection conn, string tableName) {
|
static SqliteCommand DeleteByMessageId(ISqliteConnection conn, string tableName) {
|
||||||
return conn.Delete(tableName, ("message_id", SqliteType.Integer));
|
return conn.Delete(tableName, ("message_id", SqliteType.Integer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +187,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var tx = conn.BeginTransaction();
|
using var tx = conn.BeginTransaction();
|
||||||
|
|
||||||
using var messageCmd = conn.Upsert("messages", new[] {
|
using var messageCmd = conn.Upsert("messages", new[] {
|
||||||
@ -282,10 +299,11 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx.Commit();
|
tx.Commit();
|
||||||
UpdateMessageStatistics();
|
UpdateMessageStatistics(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CountMessages(MessageFilter? filter = null) {
|
public int CountMessages(MessageFilter? filter = null) {
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command("SELECT COUNT(*) FROM messages" + filter.GenerateWhereClause());
|
using var cmd = conn.Command("SELECT COUNT(*) FROM messages" + filter.GenerateWhereClause());
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
@ -300,6 +318,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
var embeds = GetAllEmbeds();
|
var embeds = GetAllEmbeds();
|
||||||
var reactions = GetAllReactions();
|
var reactions = GetAllReactions();
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command(@"
|
using var cmd = conn.Command(@"
|
||||||
SELECT m.message_id, m.sender_id, m.channel_id, m.text, m.timestamp, et.edit_timestamp, rt.replied_to_id
|
SELECT m.message_id, m.sender_id, m.channel_id, m.text, m.timestamp, et.edit_timestamp, rt.replied_to_id
|
||||||
FROM messages m
|
FROM messages m
|
||||||
@ -342,16 +361,18 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
|
|||||||
.Append("FROM messages")
|
.Append("FROM messages")
|
||||||
.Append(whereClause);
|
.Append(whereClause);
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command(build.ToString());
|
using var cmd = conn.Command(build.ToString());
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
UpdateMessageStatistics();
|
UpdateMessageStatistics(conn);
|
||||||
perf.End();
|
perf.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MultiDictionary<ulong, Attachment> GetAllAttachments() {
|
private MultiDictionary<ulong, Attachment> GetAllAttachments() {
|
||||||
var dict = new MultiDictionary<ulong, Attachment>();
|
var dict = new MultiDictionary<ulong, Attachment>();
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command("SELECT message_id, attachment_id, name, type, url, size FROM attachments");
|
using var cmd = conn.Command("SELECT message_id, attachment_id, name, type, url, size FROM attachments");
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
@ -373,6 +394,7 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
|
|||||||
private MultiDictionary<ulong, Embed> GetAllEmbeds() {
|
private MultiDictionary<ulong, Embed> GetAllEmbeds() {
|
||||||
var dict = new MultiDictionary<ulong, Embed>();
|
var dict = new MultiDictionary<ulong, Embed>();
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command("SELECT message_id, json FROM embeds");
|
using var cmd = conn.Command("SELECT message_id, json FROM embeds");
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
@ -390,6 +412,7 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
|
|||||||
private MultiDictionary<ulong, Reaction> GetAllReactions() {
|
private MultiDictionary<ulong, Reaction> GetAllReactions() {
|
||||||
var dict = new MultiDictionary<ulong, Reaction>();
|
var dict = new MultiDictionary<ulong, Reaction>();
|
||||||
|
|
||||||
|
using var conn = pool.Take();
|
||||||
using var cmd = conn.Command("SELECT message_id, emoji_id, emoji_name, emoji_flags, count FROM reactions");
|
using var cmd = conn.Command("SELECT message_id, emoji_id, emoji_name, emoji_flags, count FROM reactions");
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
@ -407,19 +430,19 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
|
|||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateServerStatistics() {
|
private void UpdateServerStatistics(ISqliteConnection conn) {
|
||||||
Statistics.TotalServers = conn.SelectScalar("SELECT COUNT(*) FROM servers") as long? ?? 0;
|
Statistics.TotalServers = conn.SelectScalar("SELECT COUNT(*) FROM servers") as long? ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateChannelStatistics() {
|
private void UpdateChannelStatistics(ISqliteConnection conn) {
|
||||||
Statistics.TotalChannels = conn.SelectScalar("SELECT COUNT(*) FROM channels") as long? ?? 0;
|
Statistics.TotalChannels = conn.SelectScalar("SELECT COUNT(*) FROM channels") as long? ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateUserStatistics() {
|
private void UpdateUserStatistics(ISqliteConnection conn) {
|
||||||
Statistics.TotalUsers = conn.SelectScalar("SELECT COUNT(*) FROM users") as long? ?? 0;
|
Statistics.TotalUsers = conn.SelectScalar("SELECT COUNT(*) FROM users") as long? ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMessageStatistics() {
|
private void UpdateMessageStatistics(ISqliteConnection conn) {
|
||||||
Statistics.TotalMessages = conn.SelectScalar("SELECT COUNT(*) FROM messages") as long? ?? 0L;
|
Statistics.TotalMessages = conn.SelectScalar("SELECT COUNT(*) FROM messages") as long? ?? 0L;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
app/Server/Database/Sqlite/Utils/ISqliteConnection.cs
Normal file
8
app/Server/Database/Sqlite/Utils/ISqliteConnection.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Sqlite.Utils {
|
||||||
|
interface ISqliteConnection : IDisposable {
|
||||||
|
SqliteConnection InnerConnection { get; }
|
||||||
|
}
|
||||||
|
}
|
109
app/Server/Database/Sqlite/Utils/SqliteConnectionPool.cs
Normal file
109
app/Server/Database/Sqlite/Utils/SqliteConnectionPool.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using DHT.Utils.Logging;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Sqlite.Utils {
|
||||||
|
sealed class SqliteConnectionPool : IDisposable {
|
||||||
|
private static string GetConnectionString(SqliteConnectionStringBuilder connectionStringBuilder) {
|
||||||
|
connectionStringBuilder.Pooling = false;
|
||||||
|
return connectionStringBuilder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly object monitor = new ();
|
||||||
|
private volatile bool isDisposed;
|
||||||
|
|
||||||
|
private readonly BlockingCollection<PooledConnection> free = new (new ConcurrentStack<PooledConnection>());
|
||||||
|
private readonly List<PooledConnection> used;
|
||||||
|
|
||||||
|
public SqliteConnectionPool(SqliteConnectionStringBuilder connectionStringBuilder, int poolSize) {
|
||||||
|
var connectionString = GetConnectionString(connectionStringBuilder);
|
||||||
|
|
||||||
|
for (int i = 0; i < poolSize; i++) {
|
||||||
|
var conn = new SqliteConnection(connectionString);
|
||||||
|
conn.Open();
|
||||||
|
free.Add(new PooledConnection(this, conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
used = new List<PooledConnection>(poolSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThrowIfDisposed() {
|
||||||
|
if (isDisposed) {
|
||||||
|
throw new ObjectDisposedException(nameof(SqliteConnectionPool));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISqliteConnection Take() {
|
||||||
|
PooledConnection? conn = null;
|
||||||
|
|
||||||
|
while (conn == null) {
|
||||||
|
ThrowIfDisposed();
|
||||||
|
lock (monitor) {
|
||||||
|
if (free.TryTake(out conn, TimeSpan.FromMilliseconds(100))) {
|
||||||
|
used.Add(conn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.ForType<SqliteConnectionPool>().Warn("Thread " + Thread.CurrentThread.ManagedThreadId + " is starving for connections.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Return(PooledConnection conn) {
|
||||||
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
lock (monitor) {
|
||||||
|
if (used.Remove(conn)) {
|
||||||
|
free.Add(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
if (isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisposed = true;
|
||||||
|
|
||||||
|
lock (monitor) {
|
||||||
|
while (free.TryTake(out var conn)) {
|
||||||
|
Close(conn.InnerConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var conn in used) {
|
||||||
|
Close(conn.InnerConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
free.Dispose();
|
||||||
|
used.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Close(SqliteConnection conn) {
|
||||||
|
conn.Close();
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class PooledConnection : ISqliteConnection {
|
||||||
|
public SqliteConnection InnerConnection { get; }
|
||||||
|
|
||||||
|
private readonly SqliteConnectionPool pool;
|
||||||
|
|
||||||
|
public PooledConnection(SqliteConnectionPool pool, SqliteConnection conn) {
|
||||||
|
this.pool = pool;
|
||||||
|
this.InnerConnection = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose() {
|
||||||
|
pool.Return(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,20 +2,24 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Sqlite {
|
namespace DHT.Server.Database.Sqlite.Utils {
|
||||||
static class SqliteUtils {
|
static class SqliteExtensions {
|
||||||
public static SqliteCommand Command(this SqliteConnection conn, string sql) {
|
public static SqliteCommand Command(this ISqliteConnection conn, string sql) {
|
||||||
var cmd = conn.CreateCommand();
|
var cmd = conn.InnerConnection.CreateCommand();
|
||||||
cmd.CommandText = sql;
|
cmd.CommandText = sql;
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static object? SelectScalar(this SqliteConnection conn, string sql) {
|
public static SqliteTransaction BeginTransaction(this ISqliteConnection conn) {
|
||||||
|
return conn.InnerConnection.BeginTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object? SelectScalar(this ISqliteConnection conn, string sql) {
|
||||||
using var cmd = conn.Command(sql);
|
using var cmd = conn.Command(sql);
|
||||||
return cmd.ExecuteScalar();
|
return cmd.ExecuteScalar();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqliteCommand Insert(this SqliteConnection conn, string tableName, (string Name, SqliteType Type)[] columns) {
|
public static SqliteCommand Insert(this ISqliteConnection conn, string tableName, (string Name, SqliteType Type)[] columns) {
|
||||||
string columnNames = string.Join(',', columns.Select(static c => c.Name));
|
string columnNames = string.Join(',', columns.Select(static c => c.Name));
|
||||||
string columnParams = string.Join(',', columns.Select(static c => ':' + c.Name));
|
string columnParams = string.Join(',', columns.Select(static c => ':' + c.Name));
|
||||||
|
|
||||||
@ -26,7 +30,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqliteCommand Upsert(this SqliteConnection conn, string tableName, (string Name, SqliteType Type)[] columns) {
|
public static SqliteCommand Upsert(this ISqliteConnection conn, string tableName, (string Name, SqliteType Type)[] columns) {
|
||||||
string columnNames = string.Join(',', columns.Select(static c => c.Name));
|
string columnNames = string.Join(',', columns.Select(static c => c.Name));
|
||||||
string columnParams = string.Join(',', columns.Select(static c => ':' + c.Name));
|
string columnParams = string.Join(',', columns.Select(static c => ':' + c.Name));
|
||||||
string columnUpdates = string.Join(',', columns.Skip(1).Select(static c => c.Name + " = excluded." + c.Name));
|
string columnUpdates = string.Join(',', columns.Skip(1).Select(static c => c.Name + " = excluded." + c.Name));
|
||||||
@ -40,7 +44,7 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqliteCommand Delete(this SqliteConnection conn, string tableName, (string Name, SqliteType Type) column) {
|
public static SqliteCommand Delete(this ISqliteConnection conn, string tableName, (string Name, SqliteType Type) column) {
|
||||||
var cmd = conn.Command("DELETE FROM " + tableName + " WHERE " + column.Name + " = :" + column.Name);
|
var cmd = conn.Command("DELETE FROM " + tableName + " WHERE " + column.Name + " = :" + column.Name);
|
||||||
CreateParameters(cmd, new [] { column });
|
CreateParameters(cmd, new [] { column });
|
||||||
return cmd;
|
return cmd;
|
@ -93,11 +93,12 @@ namespace DHT.Server.Service {
|
|||||||
if (Server != null) {
|
if (Server != null) {
|
||||||
Log.Info("Stopping server...");
|
Log.Info("Stopping server...");
|
||||||
Server.StopAsync().Wait();
|
Server.StopAsync().Wait();
|
||||||
|
Server.Dispose();
|
||||||
|
Server = null;
|
||||||
|
|
||||||
Log.Info("Server stopped");
|
Log.Info("Server stopped");
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
ServerStatusChanged?.Invoke(null, EventArgs.Empty);
|
ServerStatusChanged?.Invoke(null, EventArgs.Empty);
|
||||||
Server = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user