mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-05-31 23:59:27 +03:00
Add channel & user filters to the app
This commit is contained in:
parent
c0123b9f91
commit
96e125b812
@ -8,6 +8,7 @@
|
||||
<entry key="Desktop/Dialogs/MessageDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Dialogs/ProgressDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/AboutWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Controls/FilterPanel.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Controls/StatusBar.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/MainContentScreen.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/MainWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||
|
60
app/Desktop/Main/Controls/FilterPanel.axaml
Normal file
60
app/Desktop/Main/Controls/FilterPanel.axaml
Normal file
@ -0,0 +1,60 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d"
|
||||
x:Class="DHT.Desktop.Main.Controls.FilterPanel">
|
||||
|
||||
<Design.DataContext>
|
||||
<controls:FilterPanelModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="WrapPanel">
|
||||
<Setter Property="Background" Value="#FFFFFF" />
|
||||
</Style>
|
||||
<Style Selector="WrapPanel > StackPanel">
|
||||
<Setter Property="Margin" Value="0 20 40 0" />
|
||||
<Setter Property="Spacing" Value="4" />
|
||||
</Style>
|
||||
<Style Selector="Grid > Label">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="Grid > CalendarDatePicker">
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="IsTodayHighlighted" Value="True" />
|
||||
<Setter Property="SelectedDateFormat" Value="Short" />
|
||||
</Style>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Margin" Value="0 0 0 8" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<WrapPanel>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding FilterByDate}">Filter by Date</CheckBox>
|
||||
<Grid ColumnDefinitions="Auto, 4, 140" RowDefinitions="Auto, 4, Auto" Margin="4 0">
|
||||
<Label Grid.Row="0" Grid.Column="0">From:</Label>
|
||||
<CalendarDatePicker Grid.Row="0" Grid.Column="2" x:Name="StartDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||
<Label Grid.Row="2" Grid.Column="0">To:</Label>
|
||||
<CalendarDatePicker Grid.Row="2" Grid.Column="2" x:Name="EndDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding FilterByChannel}">Filter by Channel</CheckBox>
|
||||
<Button Command="{Binding OpenChannelFilterDialog}" IsEnabled="{Binding FilterByChannel}">Select Channels...</Button>
|
||||
<TextBlock Text="{Binding ChannelFilterLabel}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding FilterByUser}">Filter by User</CheckBox>
|
||||
<Button Command="{Binding OpenUserFilterDialog}" IsEnabled="{Binding FilterByUser}">Select Users...</Button>
|
||||
<TextBlock Text="{Binding UserFilterLabel}" />
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
|
||||
</UserControl>
|
21
app/Desktop/Main/Controls/FilterPanel.axaml.cs
Normal file
21
app/Desktop/Main/Controls/FilterPanel.axaml.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls {
|
||||
public class FilterPanel : UserControl {
|
||||
public FilterPanel() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void CalendarDatePicker_OnSelectedDateChanged(object? sender, SelectionChangedEventArgs e) {
|
||||
if (DataContext is FilterPanelModel model) {
|
||||
model.StartDate = this.FindControl<CalendarDatePicker>("StartDatePicker").SelectedDate;
|
||||
model.EndDate = this.FindControl<CalendarDatePicker>("EndDatePicker").SelectedDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
234
app/Desktop/Main/Controls/FilterPanelModel.cs
Normal file
234
app/Desktop/Main/Controls/FilterPanelModel.cs
Normal file
@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs;
|
||||
using DHT.Desktop.Models;
|
||||
using DHT.Server.Data;
|
||||
using DHT.Server.Data.Filters;
|
||||
using DHT.Server.Database;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls {
|
||||
public class FilterPanelModel : BaseModel {
|
||||
private static readonly HashSet<string> FilterProperties = new () {
|
||||
nameof(FilterByDate),
|
||||
nameof(StartDate),
|
||||
nameof(EndDate),
|
||||
nameof(FilterByChannel),
|
||||
nameof(IncludedChannels),
|
||||
nameof(FilterByUser),
|
||||
nameof(IncludedUsers)
|
||||
};
|
||||
|
||||
public event PropertyChangedEventHandler? FilterPropertyChanged;
|
||||
|
||||
private bool filterByDate = false;
|
||||
private DateTime? startDate = null;
|
||||
private DateTime? endDate = null;
|
||||
private bool filterByChannel = false;
|
||||
private HashSet<ulong>? includedChannels = null;
|
||||
private bool filterByUser = false;
|
||||
private HashSet<ulong>? includedUsers = null;
|
||||
|
||||
public bool FilterByDate {
|
||||
get => filterByDate;
|
||||
set => Change(ref filterByDate, value);
|
||||
}
|
||||
|
||||
public DateTime? StartDate {
|
||||
get => startDate;
|
||||
set => Change(ref startDate, value);
|
||||
}
|
||||
|
||||
public DateTime? EndDate {
|
||||
get => endDate;
|
||||
set => Change(ref endDate, value);
|
||||
}
|
||||
|
||||
public bool FilterByChannel {
|
||||
get => filterByChannel;
|
||||
set => Change(ref filterByChannel, value);
|
||||
}
|
||||
|
||||
public HashSet<ulong> IncludedChannels {
|
||||
get => includedChannels ?? db.GetAllChannels().Select(channel => channel.Id).ToHashSet();
|
||||
set => Change(ref includedChannels, value);
|
||||
}
|
||||
|
||||
public bool FilterByUser {
|
||||
get => filterByUser;
|
||||
set => Change(ref filterByUser, value);
|
||||
}
|
||||
|
||||
|
||||
public HashSet<ulong> IncludedUsers {
|
||||
get => includedUsers ?? db.GetAllUsers().Select(user => user.Id).ToHashSet();
|
||||
set => Change(ref includedUsers, value);
|
||||
}
|
||||
|
||||
private string channelFilterLabel = "";
|
||||
|
||||
public string ChannelFilterLabel {
|
||||
get => channelFilterLabel;
|
||||
set => Change(ref channelFilterLabel, value);
|
||||
}
|
||||
|
||||
private string userFilterLabel = "";
|
||||
|
||||
public string UserFilterLabel {
|
||||
get => userFilterLabel;
|
||||
set => Change(ref userFilterLabel, value);
|
||||
}
|
||||
|
||||
private readonly Window window;
|
||||
private readonly IDatabaseFile db;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public FilterPanelModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||
|
||||
public FilterPanelModel(Window window, IDatabaseFile db) {
|
||||
this.window = window;
|
||||
this.db = db;
|
||||
|
||||
UpdateChannelFilterLabel();
|
||||
UpdateUserFilterLabel();
|
||||
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName != null && FilterProperties.Contains(e.PropertyName)) {
|
||||
FilterPropertyChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
if (e.PropertyName is nameof(FilterByChannel) or nameof(IncludedChannels)) {
|
||||
UpdateChannelFilterLabel();
|
||||
}
|
||||
else if (e.PropertyName is nameof(FilterByUser) or nameof(IncludedUsers)) {
|
||||
UpdateUserFilterLabel();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(DatabaseStatistics.TotalChannels)) {
|
||||
UpdateChannelFilterLabel();
|
||||
}
|
||||
else if (e.PropertyName == nameof(DatabaseStatistics.TotalUsers)) {
|
||||
UpdateUserFilterLabel();
|
||||
}
|
||||
}
|
||||
|
||||
public async void OpenChannelFilterDialog() {
|
||||
var servers = db.GetAllServers().ToDictionary(server => server.Id);
|
||||
var items = new List<CheckBoxItem<ulong>>();
|
||||
var included = IncludedChannels;
|
||||
|
||||
foreach (var channel in db.GetAllChannels()) {
|
||||
var channelId = channel.Id;
|
||||
var channelName = channel.Name;
|
||||
|
||||
string title;
|
||||
if (servers.TryGetValue(channel.Server, out var server)) {
|
||||
var titleBuilder = new StringBuilder();
|
||||
var serverType = server.Type;
|
||||
|
||||
titleBuilder.Append('[')
|
||||
.Append(ServerTypes.ToString(serverType))
|
||||
.Append("] ");
|
||||
|
||||
if (serverType == ServerType.DirectMessage) {
|
||||
titleBuilder.Append(channelName);
|
||||
}
|
||||
else {
|
||||
titleBuilder.Append(server.Name)
|
||||
.Append(" - ")
|
||||
.Append(channelName);
|
||||
}
|
||||
|
||||
title = titleBuilder.ToString();
|
||||
}
|
||||
else {
|
||||
title = channelName;
|
||||
}
|
||||
|
||||
items.Add(new CheckBoxItem<ulong>(channelId) {
|
||||
Title = title,
|
||||
Checked = included.Contains(channelId)
|
||||
});
|
||||
}
|
||||
|
||||
var result = await OpenIdFilterDialog(window, "Included Channels", items);
|
||||
if (result != null) {
|
||||
IncludedChannels = result;
|
||||
}
|
||||
}
|
||||
|
||||
public async void OpenUserFilterDialog() {
|
||||
var items = new List<CheckBoxItem<ulong>>();
|
||||
var included = IncludedUsers;
|
||||
|
||||
foreach (var user in db.GetAllUsers()) {
|
||||
var name = user.Name;
|
||||
var discriminator = user.Discriminator;
|
||||
|
||||
items.Add(new CheckBoxItem<ulong>(user.Id) {
|
||||
Title = discriminator == null ? name : name + " #" + discriminator,
|
||||
Checked = included.Contains(user.Id)
|
||||
});
|
||||
}
|
||||
|
||||
var result = await OpenIdFilterDialog(window, "Included Users", items);
|
||||
if (result != null) {
|
||||
IncludedUsers = result;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChannelFilterLabel() {
|
||||
long total = db.Statistics.TotalChannels;
|
||||
long included = FilterByChannel ? IncludedChannels.Count : total;
|
||||
ChannelFilterLabel = "Selected " + included + " / " + total + (total == 1 ? " channel." : " channels.");
|
||||
}
|
||||
|
||||
private void UpdateUserFilterLabel() {
|
||||
long total = db.Statistics.TotalUsers;
|
||||
long included = FilterByUser ? IncludedUsers.Count : total;
|
||||
UserFilterLabel = "Selected " + included + " / " + total + (total == 1 ? " user." : " users.");
|
||||
}
|
||||
|
||||
public MessageFilter CreateFilter() {
|
||||
MessageFilter filter = new();
|
||||
|
||||
if (FilterByDate) {
|
||||
filter.StartDate = StartDate;
|
||||
filter.EndDate = EndDate;
|
||||
}
|
||||
|
||||
if (FilterByChannel) {
|
||||
filter.ChannelIds = new HashSet<ulong>(IncludedChannels);
|
||||
}
|
||||
|
||||
if (FilterByUser) {
|
||||
filter.UserIds = new HashSet<ulong>(IncludedUsers);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static async Task<HashSet<ulong>?> OpenIdFilterDialog(Window window, string title, List<CheckBoxItem<ulong>> items) {
|
||||
items.Sort((item1, item2) => item1.Title.CompareTo(item2.Title));
|
||||
|
||||
var model = new CheckBoxDialogModel<ulong>(items) {
|
||||
Title = title
|
||||
};
|
||||
|
||||
var dialog = new CheckBoxDialog { DataContext = model };
|
||||
var result = await dialog.ShowDialog<DialogResult.OkCancel>(window);
|
||||
|
||||
return result == DialogResult.OkCancel.Ok ? model.SelectedItems.Select(item => item.Item).ToHashSet() : null;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Pages.ViewerPage">
|
||||
|
||||
@ -10,36 +11,13 @@
|
||||
<pages:ViewerPageModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Grid > Label">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="Grid > CalendarDatePicker">
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="IsTodayHighlighted" Value="True" />
|
||||
<Setter Property="SelectedDateFormat" Value="Short" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel Orientation="Vertical" Spacing="20">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
|
||||
<Button Command="{Binding OnClickOpenViewer}" Margin="0 0 5 0">Open Viewer</Button>
|
||||
<Button Command="{Binding OnClickSaveViewer}" Margin="5 0 0 0">Save Viewer</Button>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding ExportedMessageText}" />
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding FilterByDate}">Filter by Date</CheckBox>
|
||||
<Grid ColumnDefinitions="Auto, 4, 140" RowDefinitions="Auto, 4, Auto" Margin="4">
|
||||
<Label Grid.Row="0" Grid.Column="0">From:</Label>
|
||||
<CalendarDatePicker Grid.Row="0" Grid.Column="2" x:Name="StartDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||
<Label Grid.Row="2" Grid.Column="0">To:</Label>
|
||||
<CalendarDatePicker Grid.Row="2" Grid.Column="2" x:Name="EndDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding ExportedMessageText}" Margin="0 20 0 0" />
|
||||
<controls:FilterPanel DataContext="{Binding FilterModel}" />
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
||||
|
@ -10,12 +10,5 @@ namespace DHT.Desktop.Main.Pages {
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void CalendarDatePicker_OnSelectedDateChanged(object? sender, SelectionChangedEventArgs e) {
|
||||
if (DataContext is ViewerPageModel model) {
|
||||
model.StartDate = this.FindControl<CalendarDatePicker>("StartDatePicker").SelectedDate;
|
||||
model.EndDate = this.FindControl<CalendarDatePicker>("EndDatePicker").SelectedDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Main.Controls;
|
||||
using DHT.Desktop.Models;
|
||||
using DHT.Desktop.Resources;
|
||||
using DHT.Server.Data.Filters;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Database.Export;
|
||||
|
||||
@ -16,26 +16,7 @@ namespace DHT.Desktop.Main.Pages {
|
||||
public class ViewerPageModel : BaseModel {
|
||||
public string ExportedMessageText { get; private set; } = "";
|
||||
|
||||
private bool filterByDate = false;
|
||||
|
||||
public bool FilterByDate {
|
||||
get => filterByDate;
|
||||
set => Change(ref filterByDate, value);
|
||||
}
|
||||
|
||||
private DateTime? startDate = null;
|
||||
|
||||
public DateTime? StartDate {
|
||||
get => startDate;
|
||||
set => Change(ref startDate, value);
|
||||
}
|
||||
|
||||
private DateTime? endDate = null;
|
||||
|
||||
public DateTime? EndDate {
|
||||
get => endDate;
|
||||
set => Change(ref endDate, value);
|
||||
}
|
||||
private FilterPanelModel FilterModel { get; }
|
||||
|
||||
private readonly Window window;
|
||||
private readonly IDatabaseFile db;
|
||||
@ -47,15 +28,14 @@ namespace DHT.Desktop.Main.Pages {
|
||||
this.window = window;
|
||||
this.db = db;
|
||||
|
||||
this.PropertyChanged += OnPropertyChanged;
|
||||
this.FilterModel = new FilterPanelModel(window, db);
|
||||
this.FilterModel.FilterPropertyChanged += OnFilterPropertyChanged;
|
||||
this.db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||
UpdateStatistics();
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName is nameof(FilterByDate) or nameof(StartDate) or nameof(EndDate)) {
|
||||
UpdateStatistics();
|
||||
}
|
||||
private void OnFilterPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
UpdateStatistics();
|
||||
}
|
||||
|
||||
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
@ -64,24 +44,13 @@ namespace DHT.Desktop.Main.Pages {
|
||||
}
|
||||
}
|
||||
|
||||
private MessageFilter CreateFilter() {
|
||||
MessageFilter filter = new();
|
||||
|
||||
if (FilterByDate) {
|
||||
filter.StartDate = StartDate;
|
||||
filter.EndDate = EndDate;
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private void UpdateStatistics() {
|
||||
ExportedMessageText = "Will export " + db.CountMessages(CreateFilter()) + " out of " + db.Statistics.TotalMessages + " message(s).";
|
||||
ExportedMessageText = "Will export " + db.CountMessages(FilterModel.CreateFilter()) + " out of " + db.Statistics.TotalMessages + " message(s).";
|
||||
OnPropertyChanged(nameof(ExportedMessageText));
|
||||
}
|
||||
|
||||
private async Task<string> GenerateViewerContents() {
|
||||
string json = ViewerJsonExport.Generate(db, CreateFilter());
|
||||
string json = ViewerJsonExport.Generate(db, FilterModel.CreateFilter());
|
||||
|
||||
string index = await ResourceLoader.ReadTextAsync("Viewer/index.html");
|
||||
string viewer = index.Replace("/*[JS]*/", await ResourceLoader.ReadJoinedAsync("Viewer/scripts/", '\n'))
|
||||
|
@ -6,6 +6,8 @@ namespace DHT.Server.Data.Filters {
|
||||
public DateTime? StartDate { get; set; }
|
||||
public DateTime? EndDate { get; set; }
|
||||
|
||||
public HashSet<ulong> MessageIds { get; } = new();
|
||||
public HashSet<ulong>? ChannelIds { get; set; } = null;
|
||||
public HashSet<ulong>? UserIds { get; set; } = null;
|
||||
public HashSet<ulong>? MessageIds { get; set; } = null;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace DHT.Server.Database {
|
||||
public class DatabaseStatistics : INotifyPropertyChanged {
|
||||
private long totalServers;
|
||||
private long totalChannels;
|
||||
private long totalUsers;
|
||||
private long totalMessages;
|
||||
|
||||
public long TotalServers {
|
||||
@ -17,6 +18,11 @@ namespace DHT.Server.Database {
|
||||
internal set => Change(out totalChannels, value);
|
||||
}
|
||||
|
||||
public long TotalUsers {
|
||||
get => totalUsers;
|
||||
internal set => Change(out totalUsers, value);
|
||||
}
|
||||
|
||||
public long TotalMessages {
|
||||
get => totalMessages;
|
||||
internal set => Change(out totalMessages, value);
|
||||
@ -33,6 +39,7 @@ namespace DHT.Server.Database {
|
||||
return new DatabaseStatistics {
|
||||
totalServers = totalServers,
|
||||
totalChannels = totalChannels,
|
||||
totalUsers = TotalUsers,
|
||||
totalMessages = totalMessages
|
||||
};
|
||||
}
|
||||
|
@ -8,26 +8,48 @@ using DHT.Server.Data.Filters;
|
||||
namespace DHT.Server.Database.Export {
|
||||
public static class ViewerJsonExport {
|
||||
public static string Generate(IDatabaseFile db, MessageFilter? filter = null) {
|
||||
JsonSerializerOptions opts = new();
|
||||
var includedUserIds = new HashSet<ulong>();
|
||||
var includedChannelIds = new HashSet<ulong>();
|
||||
var includedServerIds = new HashSet<ulong>();
|
||||
|
||||
var includedMessages = db.GetMessages(filter);
|
||||
var includedChannels = new List<Channel>();
|
||||
|
||||
foreach (var message in includedMessages) {
|
||||
includedUserIds.Add(message.Sender);
|
||||
includedChannelIds.Add(message.Channel);
|
||||
}
|
||||
|
||||
foreach (var channel in db.GetAllChannels()) {
|
||||
if (includedChannelIds.Contains(channel.Id)) {
|
||||
includedChannels.Add(channel);
|
||||
includedServerIds.Add(channel.Server);
|
||||
}
|
||||
}
|
||||
|
||||
var opts = new JsonSerializerOptions();
|
||||
opts.Converters.Add(new ViewerJsonSnowflakeSerializer());
|
||||
|
||||
var users = GenerateUserList(db, out var userindex, out var userIndices);
|
||||
var servers = GenerateServerList(db, out var serverindex);
|
||||
var channels = GenerateChannelList(db, serverindex);
|
||||
var users = GenerateUserList(db, includedUserIds, out var userindex, out var userIndices);
|
||||
var servers = GenerateServerList(db, includedServerIds, out var serverindex);
|
||||
var channels = GenerateChannelList(includedChannels, serverindex);
|
||||
|
||||
return JsonSerializer.Serialize(new {
|
||||
meta = new { users, userindex, servers, channels },
|
||||
data = GenerateMessageList(db, filter, userIndices)
|
||||
data = GenerateMessageList(includedMessages, userIndices)
|
||||
}, opts);
|
||||
}
|
||||
|
||||
private static dynamic GenerateUserList(IDatabaseFile db, out List<string> userindex, out Dictionary<ulong, int> userIndices) {
|
||||
private static dynamic GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<string> userindex, out Dictionary<ulong, int> userIndices) {
|
||||
var users = new Dictionary<string, dynamic>();
|
||||
userindex = new List<string>();
|
||||
userIndices = new Dictionary<ulong, int>();
|
||||
|
||||
foreach (var user in db.GetAllUsers()) {
|
||||
var id = user.Id.ToString();
|
||||
var id = user.Id;
|
||||
if (!userIds.Contains(id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dynamic obj = new ExpandoObject();
|
||||
obj.name = user.Name;
|
||||
@ -40,20 +62,26 @@ namespace DHT.Server.Database.Export {
|
||||
obj.tag = user.Discriminator;
|
||||
}
|
||||
|
||||
userIndices[user.Id] = users.Count;
|
||||
userindex.Add(id);
|
||||
users[id] = obj;
|
||||
var idStr = id.ToString();
|
||||
userIndices[id] = users.Count;
|
||||
userindex.Add(idStr);
|
||||
users[idStr] = obj;
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
private static dynamic GenerateServerList(IDatabaseFile db, out Dictionary<ulong, int> serverIndices) {
|
||||
private static dynamic GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, int> serverIndices) {
|
||||
var servers = new List<dynamic>();
|
||||
serverIndices = new Dictionary<ulong, int>();
|
||||
|
||||
foreach (var server in db.GetAllServers()) {
|
||||
serverIndices[server.Id] = servers.Count;
|
||||
var id = server.Id;
|
||||
if (!serverIds.Contains(id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
serverIndices[id] = servers.Count;
|
||||
servers.Add(new {
|
||||
name = server.Name,
|
||||
type = ServerTypes.ToJsonViewerString(server.Type)
|
||||
@ -63,10 +91,10 @@ namespace DHT.Server.Database.Export {
|
||||
return servers;
|
||||
}
|
||||
|
||||
private static dynamic GenerateChannelList(IDatabaseFile db, Dictionary<ulong, int> serverIndices) {
|
||||
private static dynamic GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, int> serverIndices) {
|
||||
var channels = new Dictionary<string, dynamic>();
|
||||
|
||||
foreach (var channel in db.GetAllChannels()) {
|
||||
foreach (var channel in includedChannels) {
|
||||
dynamic obj = new ExpandoObject();
|
||||
obj.server = serverIndices[channel.Server];
|
||||
obj.name = channel.Name;
|
||||
@ -93,10 +121,10 @@ namespace DHT.Server.Database.Export {
|
||||
return channels;
|
||||
}
|
||||
|
||||
private static dynamic GenerateMessageList(IDatabaseFile db, MessageFilter? filter, Dictionary<ulong, int> userIndices) {
|
||||
private static dynamic GenerateMessageList(List<Message> includedMessages, Dictionary<ulong, int> userIndices) {
|
||||
var data = new Dictionary<string, Dictionary<string, dynamic>>();
|
||||
|
||||
foreach (var grouping in db.GetMessages(filter).GroupBy(message => message.Channel)) {
|
||||
foreach (var grouping in includedMessages.GroupBy(message => message.Channel)) {
|
||||
var channel = grouping.Key.ToString();
|
||||
var channelData = new Dictionary<string, dynamic>();
|
||||
|
||||
|
@ -32,6 +32,7 @@ namespace DHT.Server.Database.Sqlite {
|
||||
this.Statistics = new DatabaseStatistics();
|
||||
UpdateServerStatistics();
|
||||
UpdateChannelStatistics();
|
||||
UpdateUserStatistics();
|
||||
UpdateMessageStatistics();
|
||||
}
|
||||
|
||||
@ -129,6 +130,7 @@ namespace DHT.Server.Database.Sqlite {
|
||||
}
|
||||
|
||||
tx.Commit();
|
||||
UpdateUserStatistics();
|
||||
}
|
||||
|
||||
public List<User> GetAllUsers() {
|
||||
@ -369,6 +371,11 @@ namespace DHT.Server.Database.Sqlite {
|
||||
Statistics.TotalChannels = cmd.ExecuteScalar() as long? ?? 0;
|
||||
}
|
||||
|
||||
private void UpdateUserStatistics() {
|
||||
using var cmd = conn.Command("SELECT COUNT(*) FROM users");
|
||||
Statistics.TotalUsers = cmd.ExecuteScalar() as long? ?? 0;
|
||||
}
|
||||
|
||||
private void UpdateMessageStatistics() {
|
||||
using var cmd = conn.Command("SELECT COUNT(*) FROM messages");
|
||||
Statistics.TotalMessages = cmd.ExecuteScalar() as long? ?? 0L;
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DHT.Server.Data.Filters;
|
||||
|
||||
namespace DHT.Server.Database.Sqlite {
|
||||
@ -20,8 +19,16 @@ namespace DHT.Server.Database.Sqlite {
|
||||
conditions.Add("timestamp <= " + new DateTimeOffset(filter.EndDate.Value).ToUnixTimeMilliseconds());
|
||||
}
|
||||
|
||||
if (filter.MessageIds.Count > 0) {
|
||||
conditions.Add("(" + string.Join(" OR ", filter.MessageIds.Select(id => "message_id = " + id)) + ")");
|
||||
if (filter.ChannelIds != null) {
|
||||
conditions.Add("channel_id IN (" + string.Join(",", filter.ChannelIds) + ")");
|
||||
}
|
||||
|
||||
if (filter.UserIds != null) {
|
||||
conditions.Add("sender_id IN (" + string.Join(",", filter.UserIds) + ")");
|
||||
}
|
||||
|
||||
if (filter.MessageIds != null) {
|
||||
conditions.Add("message_id IN (" + string.Join(",", filter.MessageIds) + ")");
|
||||
}
|
||||
|
||||
return conditions.Count == 0 ? "" : " WHERE " + string.Join(" AND ", conditions);
|
||||
|
@ -22,17 +22,19 @@ namespace DHT.Server.Endpoints {
|
||||
throw new HttpException(HttpStatusCode.BadRequest, "Expected root element to be an array.");
|
||||
}
|
||||
|
||||
var addedMessageIdFilter = new MessageFilter();
|
||||
var addedMessageIds = new HashSet<ulong>();
|
||||
var messages = new Message[root.GetArrayLength()];
|
||||
|
||||
int i = 0;
|
||||
foreach (JsonElement ele in root.EnumerateArray()) {
|
||||
var message = ReadMessage(ele, "message");
|
||||
messages[i++] = message;
|
||||
addedMessageIdFilter.MessageIds.Add(message.Id);
|
||||
addedMessageIds.Add(message.Id);
|
||||
}
|
||||
|
||||
bool anyNewMessages = Db.CountMessages(addedMessageIdFilter) < messages.Length;
|
||||
var addedMessageFilter = new MessageFilter { MessageIds = addedMessageIds };
|
||||
bool anyNewMessages = Db.CountMessages(addedMessageFilter) < messages.Length;
|
||||
|
||||
Db.AddMessages(messages);
|
||||
|
||||
return (HttpStatusCode.OK, anyNewMessages ? 1 : 0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user