This commit is contained in:
Tyrrrz
2021-12-08 23:50:21 +02:00
parent 8e7baee8a5
commit 880f400e2c
148 changed files with 14241 additions and 14396 deletions

View File

@@ -10,129 +10,128 @@ using DiscordChatExporter.Core.Utils.Extensions;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.ViewModels.Framework;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
public class ExportSetupViewModel : DialogScreen
{
public class ExportSetupViewModel : DialogScreen
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
public Guild? Guild { get; set; }
public IReadOnlyList<Channel>? Channels { get; set; }
public bool IsSingleChannel => Channels is null || Channels.Count == 1;
public string? OutputPath { get; set; }
public IReadOnlyList<ExportFormat> AvailableFormats =>
Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
public ExportFormat SelectedFormat { get; set; }
// This date/time abomination is required because we use separate controls to set these
public DateTimeOffset? AfterDate { get; set; }
public bool IsAfterDateSet => AfterDate is not null;
public TimeSpan? AfterTime { get; set; }
public DateTimeOffset? After => AfterDate?.Add(AfterTime ?? TimeSpan.Zero);
public DateTimeOffset? BeforeDate { get; set; }
public bool IsBeforeDateSet => BeforeDate is not null;
public TimeSpan? BeforeTime { get; set; }
public DateTimeOffset? Before => BeforeDate?.Add(BeforeTime ?? TimeSpan.Zero);
public string? PartitionLimitValue { get; set; }
public PartitionLimit PartitionLimit => !string.IsNullOrWhiteSpace(PartitionLimitValue)
? PartitionLimit.Parse(PartitionLimitValue)
: PartitionLimit.Null;
public string? MessageFilterValue { get; set; }
public MessageFilter MessageFilter => !string.IsNullOrWhiteSpace(MessageFilterValue)
? MessageFilter.Parse(MessageFilterValue)
: MessageFilter.Null;
public bool ShouldDownloadMedia { get; set; }
// Whether to show the "advanced options" by default when the dialog opens.
// This is active if any of the advanced options are set to non-default values.
public bool IsAdvancedSectionDisplayedByDefault =>
After != default ||
Before != default ||
!string.IsNullOrWhiteSpace(PartitionLimitValue) ||
!string.IsNullOrWhiteSpace(MessageFilterValue) ||
ShouldDownloadMedia != default;
public ExportSetupViewModel(DialogManager dialogManager, SettingsService settingsService)
{
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
_dialogManager = dialogManager;
_settingsService = settingsService;
public Guild? Guild { get; set; }
public IReadOnlyList<Channel>? Channels { get; set; }
public bool IsSingleChannel => Channels is null || Channels.Count == 1;
public string? OutputPath { get; set; }
public IReadOnlyList<ExportFormat> AvailableFormats =>
Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
public ExportFormat SelectedFormat { get; set; }
// This date/time abomination is required because we use separate controls to set these
public DateTimeOffset? AfterDate { get; set; }
public bool IsAfterDateSet => AfterDate is not null;
public TimeSpan? AfterTime { get; set; }
public DateTimeOffset? After => AfterDate?.Add(AfterTime ?? TimeSpan.Zero);
public DateTimeOffset? BeforeDate { get; set; }
public bool IsBeforeDateSet => BeforeDate is not null;
public TimeSpan? BeforeTime { get; set; }
public DateTimeOffset? Before => BeforeDate?.Add(BeforeTime ?? TimeSpan.Zero);
public string? PartitionLimitValue { get; set; }
public PartitionLimit PartitionLimit => !string.IsNullOrWhiteSpace(PartitionLimitValue)
? PartitionLimit.Parse(PartitionLimitValue)
: PartitionLimit.Null;
public string? MessageFilterValue { get; set; }
public MessageFilter MessageFilter => !string.IsNullOrWhiteSpace(MessageFilterValue)
? MessageFilter.Parse(MessageFilterValue)
: MessageFilter.Null;
public bool ShouldDownloadMedia { get; set; }
// Whether to show the "advanced options" by default when the dialog opens.
// This is active if any of the advanced options are set to non-default values.
public bool IsAdvancedSectionDisplayedByDefault =>
After != default ||
Before != default ||
!string.IsNullOrWhiteSpace(PartitionLimitValue) ||
!string.IsNullOrWhiteSpace(MessageFilterValue) ||
ShouldDownloadMedia != default;
public ExportSetupViewModel(DialogManager dialogManager, SettingsService settingsService)
{
_dialogManager = dialogManager;
_settingsService = settingsService;
// Persist preferences
SelectedFormat = _settingsService.LastExportFormat;
PartitionLimitValue = _settingsService.LastPartitionLimitValue;
MessageFilterValue = _settingsService.LastMessageFilterValue;
ShouldDownloadMedia = _settingsService.LastShouldDownloadMedia;
}
public void Confirm()
{
// Persist preferences
_settingsService.LastExportFormat = SelectedFormat;
_settingsService.LastPartitionLimitValue = PartitionLimitValue;
_settingsService.LastMessageFilterValue = MessageFilterValue;
_settingsService.LastShouldDownloadMedia = ShouldDownloadMedia;
// If single channel - prompt file path
if (Channels is not null && IsSingleChannel)
{
var channel = Channels.Single();
var defaultFileName = ExportRequest.GetDefaultOutputFileName(
Guild!,
channel,
SelectedFormat,
After?.Pipe(Snowflake.FromDate),
Before?.Pipe(Snowflake.FromDate)
);
// Filter
var ext = SelectedFormat.GetFileExtension();
var filter = $"{ext.ToUpperInvariant()} files|*.{ext}";
OutputPath = _dialogManager.PromptSaveFilePath(filter, defaultFileName);
}
// If multiple channels - prompt dir path
else
{
OutputPath = _dialogManager.PromptDirectoryPath();
}
if (string.IsNullOrWhiteSpace(OutputPath))
return;
Close(true);
}
// Persist preferences
SelectedFormat = _settingsService.LastExportFormat;
PartitionLimitValue = _settingsService.LastPartitionLimitValue;
MessageFilterValue = _settingsService.LastMessageFilterValue;
ShouldDownloadMedia = _settingsService.LastShouldDownloadMedia;
}
public static class ExportSetupViewModelExtensions
public void Confirm()
{
public static ExportSetupViewModel CreateExportSetupViewModel(this IViewModelFactory factory,
Guild guild, IReadOnlyList<Channel> channels)
// Persist preferences
_settingsService.LastExportFormat = SelectedFormat;
_settingsService.LastPartitionLimitValue = PartitionLimitValue;
_settingsService.LastMessageFilterValue = MessageFilterValue;
_settingsService.LastShouldDownloadMedia = ShouldDownloadMedia;
// If single channel - prompt file path
if (Channels is not null && IsSingleChannel)
{
var viewModel = factory.CreateExportSetupViewModel();
var channel = Channels.Single();
var defaultFileName = ExportRequest.GetDefaultOutputFileName(
Guild!,
channel,
SelectedFormat,
After?.Pipe(Snowflake.FromDate),
Before?.Pipe(Snowflake.FromDate)
);
viewModel.Guild = guild;
viewModel.Channels = channels;
// Filter
var ext = SelectedFormat.GetFileExtension();
var filter = $"{ext.ToUpperInvariant()} files|*.{ext}";
return viewModel;
OutputPath = _dialogManager.PromptSaveFilePath(filter, defaultFileName);
}
// If multiple channels - prompt dir path
else
{
OutputPath = _dialogManager.PromptDirectoryPath();
}
if (string.IsNullOrWhiteSpace(OutputPath))
return;
Close(true);
}
}
public static class ExportSetupViewModelExtensions
{
public static ExportSetupViewModel CreateExportSetupViewModel(this IViewModelFactory factory,
Guild guild, IReadOnlyList<Channel> channels)
{
var viewModel = factory.CreateExportSetupViewModel();
viewModel.Guild = guild;
viewModel.Channels = channels;
return viewModel;
}
}

View File

@@ -1,27 +1,26 @@
using DiscordChatExporter.Gui.ViewModels.Framework;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
public class MessageBoxViewModel : DialogScreen
{
public class MessageBoxViewModel : DialogScreen
public string? Title { get; set; }
public string? Message { get; set; }
}
public static class MessageBoxViewModelExtensions
{
public static MessageBoxViewModel CreateMessageBoxViewModel(
this IViewModelFactory factory,
string title,
string message)
{
public string? Title { get; set; }
var viewModel = factory.CreateMessageBoxViewModel();
public string? Message { get; set; }
}
viewModel.Title = title;
viewModel.Message = message;
public static class MessageBoxViewModelExtensions
{
public static MessageBoxViewModel CreateMessageBoxViewModel(
this IViewModelFactory factory,
string title,
string message)
{
var viewModel = factory.CreateMessageBoxViewModel();
viewModel.Title = title;
viewModel.Message = message;
return viewModel;
}
return viewModel;
}
}

View File

@@ -2,49 +2,48 @@
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.ViewModels.Framework;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
public class SettingsViewModel : DialogScreen
{
public class SettingsViewModel : DialogScreen
private readonly SettingsService _settingsService;
public bool IsAutoUpdateEnabled
{
private readonly SettingsService _settingsService;
public bool IsAutoUpdateEnabled
{
get => _settingsService.IsAutoUpdateEnabled;
set => _settingsService.IsAutoUpdateEnabled = value;
}
public bool IsDarkModeEnabled
{
get => _settingsService.IsDarkModeEnabled;
set => _settingsService.IsDarkModeEnabled = value;
}
public bool IsTokenPersisted
{
get => _settingsService.IsTokenPersisted;
set => _settingsService.IsTokenPersisted = value;
}
public string DateFormat
{
get => _settingsService.DateFormat;
set => _settingsService.DateFormat = value;
}
public int ParallelLimit
{
get => _settingsService.ParallelLimit;
set => _settingsService.ParallelLimit = Math.Clamp(value, 1, 10);
}
public bool ShouldReuseMedia
{
get => _settingsService.ShouldReuseMedia;
set => _settingsService.ShouldReuseMedia = value;
}
public SettingsViewModel(SettingsService settingsService) =>
_settingsService = settingsService;
get => _settingsService.IsAutoUpdateEnabled;
set => _settingsService.IsAutoUpdateEnabled = value;
}
public bool IsDarkModeEnabled
{
get => _settingsService.IsDarkModeEnabled;
set => _settingsService.IsDarkModeEnabled = value;
}
public bool IsTokenPersisted
{
get => _settingsService.IsTokenPersisted;
set => _settingsService.IsTokenPersisted = value;
}
public string DateFormat
{
get => _settingsService.DateFormat;
set => _settingsService.DateFormat = value;
}
public int ParallelLimit
{
get => _settingsService.ParallelLimit;
set => _settingsService.ParallelLimit = Math.Clamp(value, 1, 10);
}
public bool ShouldReuseMedia
{
get => _settingsService.ShouldReuseMedia;
set => _settingsService.ShouldReuseMedia = value;
}
public SettingsViewModel(SettingsService settingsService) =>
_settingsService = settingsService;
}

View File

@@ -6,58 +6,57 @@ using Microsoft.Win32;
using Ookii.Dialogs.Wpf;
using Stylet;
namespace DiscordChatExporter.Gui.ViewModels.Framework
namespace DiscordChatExporter.Gui.ViewModels.Framework;
public class DialogManager
{
public class DialogManager
private readonly IViewManager _viewManager;
public DialogManager(IViewManager viewManager)
{
private readonly IViewManager _viewManager;
_viewManager = viewManager;
}
public DialogManager(IViewManager viewManager)
public async ValueTask<T?> ShowDialogAsync<T>(DialogScreen<T> dialogScreen)
{
var view = _viewManager.CreateAndBindViewForModelIfNecessary(dialogScreen);
void OnDialogOpened(object? sender, DialogOpenedEventArgs openArgs)
{
_viewManager = viewManager;
}
public async ValueTask<T?> ShowDialogAsync<T>(DialogScreen<T> dialogScreen)
{
var view = _viewManager.CreateAndBindViewForModelIfNecessary(dialogScreen);
void OnDialogOpened(object? sender, DialogOpenedEventArgs openArgs)
void OnScreenClosed(object? o, EventArgs closeArgs)
{
void OnScreenClosed(object? o, EventArgs closeArgs)
{
openArgs.Session.Close();
dialogScreen.Closed -= OnScreenClosed;
}
dialogScreen.Closed += OnScreenClosed;
openArgs.Session.Close();
dialogScreen.Closed -= OnScreenClosed;
}
await DialogHost.Show(view, OnDialogOpened);
return dialogScreen.DialogResult;
dialogScreen.Closed += OnScreenClosed;
}
public string? PromptSaveFilePath(string filter = "All files|*.*", string defaultFilePath = "")
await DialogHost.Show(view, OnDialogOpened);
return dialogScreen.DialogResult;
}
public string? PromptSaveFilePath(string filter = "All files|*.*", string defaultFilePath = "")
{
var dialog = new SaveFileDialog
{
var dialog = new SaveFileDialog
{
Filter = filter,
AddExtension = true,
FileName = defaultFilePath,
DefaultExt = Path.GetExtension(defaultFilePath)
};
Filter = filter,
AddExtension = true,
FileName = defaultFilePath,
DefaultExt = Path.GetExtension(defaultFilePath)
};
return dialog.ShowDialog() == true ? dialog.FileName : null;
}
return dialog.ShowDialog() == true ? dialog.FileName : null;
}
public string? PromptDirectoryPath(string defaultDirPath = "")
public string? PromptDirectoryPath(string defaultDirPath = "")
{
var dialog = new VistaFolderBrowserDialog
{
var dialog = new VistaFolderBrowserDialog
{
SelectedPath = defaultDirPath
};
SelectedPath = defaultDirPath
};
return dialog.ShowDialog() == true ? dialog.SelectedPath : null;
}
return dialog.ShowDialog() == true ? dialog.SelectedPath : null;
}
}

View File

@@ -1,22 +1,21 @@
using System;
using Stylet;
namespace DiscordChatExporter.Gui.ViewModels.Framework
namespace DiscordChatExporter.Gui.ViewModels.Framework;
public abstract class DialogScreen<T> : PropertyChangedBase
{
public abstract class DialogScreen<T> : PropertyChangedBase
public T? DialogResult { get; private set; }
public event EventHandler? Closed;
public void Close(T dialogResult)
{
public T? DialogResult { get; private set; }
public event EventHandler? Closed;
public void Close(T dialogResult)
{
DialogResult = dialogResult;
Closed?.Invoke(this, EventArgs.Empty);
}
DialogResult = dialogResult;
Closed?.Invoke(this, EventArgs.Empty);
}
}
public abstract class DialogScreen : DialogScreen<bool?>
{
}
public abstract class DialogScreen : DialogScreen<bool?>
{
}

View File

@@ -1,14 +1,13 @@
using DiscordChatExporter.Gui.ViewModels.Dialogs;
namespace DiscordChatExporter.Gui.ViewModels.Framework
namespace DiscordChatExporter.Gui.ViewModels.Framework;
// Used to instantiate new view models while making use of dependency injection
public interface IViewModelFactory
{
// Used to instantiate new view models while making use of dependency injection
public interface IViewModelFactory
{
ExportSetupViewModel CreateExportSetupViewModel();
ExportSetupViewModel CreateExportSetupViewModel();
MessageBoxViewModel CreateMessageBoxViewModel();
MessageBoxViewModel CreateMessageBoxViewModel();
SettingsViewModel CreateSettingsViewModel();
}
SettingsViewModel CreateSettingsViewModel();
}

View File

@@ -16,247 +16,246 @@ using Gress;
using MaterialDesignThemes.Wpf;
using Stylet;
namespace DiscordChatExporter.Gui.ViewModels
namespace DiscordChatExporter.Gui.ViewModels;
public class RootViewModel : Screen
{
public class RootViewModel : Screen
private readonly IViewModelFactory _viewModelFactory;
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
private readonly UpdateService _updateService;
public ISnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
public IProgressManager ProgressManager { get; } = new ProgressManager();
public bool IsBusy { get; private set; }
public bool IsProgressIndeterminate { get; private set; }
public bool IsBotToken { get; set; }
public string? TokenValue { get; set; }
private IReadOnlyDictionary<Guild, IReadOnlyList<Channel>>? GuildChannelMap { get; set; }
public IReadOnlyList<Guild>? AvailableGuilds => GuildChannelMap?.Keys.ToArray();
public Guild? SelectedGuild { get; set; }
public IReadOnlyList<Channel>? AvailableChannels => SelectedGuild is not null
? GuildChannelMap?[SelectedGuild]
: null;
public IReadOnlyList<Channel>? SelectedChannels { get; set; }
public RootViewModel(
IViewModelFactory viewModelFactory,
DialogManager dialogManager,
SettingsService settingsService,
UpdateService updateService)
{
private readonly IViewModelFactory _viewModelFactory;
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
private readonly UpdateService _updateService;
_viewModelFactory = viewModelFactory;
_dialogManager = dialogManager;
_settingsService = settingsService;
_updateService = updateService;
public ISnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
DisplayName = $"{App.Name} v{App.VersionString}";
public IProgressManager ProgressManager { get; } = new ProgressManager();
// Update busy state when progress manager changes
ProgressManager.Bind(o => o.IsActive, (_, _) =>
IsBusy = ProgressManager.IsActive
);
public bool IsBusy { get; private set; }
ProgressManager.Bind(o => o.IsActive, (_, _) =>
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress is <= 0 or >= 1
);
public bool IsProgressIndeterminate { get; private set; }
ProgressManager.Bind(o => o.Progress, (_, _) =>
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress is <= 0 or >= 1
);
}
public bool IsBotToken { get; set; }
public string? TokenValue { get; set; }
private IReadOnlyDictionary<Guild, IReadOnlyList<Channel>>? GuildChannelMap { get; set; }
public IReadOnlyList<Guild>? AvailableGuilds => GuildChannelMap?.Keys.ToArray();
public Guild? SelectedGuild { get; set; }
public IReadOnlyList<Channel>? AvailableChannels => SelectedGuild is not null
? GuildChannelMap?[SelectedGuild]
: null;
public IReadOnlyList<Channel>? SelectedChannels { get; set; }
public RootViewModel(
IViewModelFactory viewModelFactory,
DialogManager dialogManager,
SettingsService settingsService,
UpdateService updateService)
private async ValueTask CheckForUpdatesAsync()
{
try
{
_viewModelFactory = viewModelFactory;
_dialogManager = dialogManager;
_settingsService = settingsService;
_updateService = updateService;
var updateVersion = await _updateService.CheckForUpdatesAsync();
if (updateVersion is null)
return;
DisplayName = $"{App.Name} v{App.VersionString}";
Notifications.Enqueue($"Downloading update to {App.Name} v{updateVersion}...");
await _updateService.PrepareUpdateAsync(updateVersion);
// Update busy state when progress manager changes
ProgressManager.Bind(o => o.IsActive, (_, _) =>
IsBusy = ProgressManager.IsActive
);
ProgressManager.Bind(o => o.IsActive, (_, _) =>
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress is <= 0 or >= 1
);
ProgressManager.Bind(o => o.Progress, (_, _) =>
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress is <= 0 or >= 1
Notifications.Enqueue(
"Update has been downloaded and will be installed when you exit",
"INSTALL NOW", () =>
{
_updateService.FinalizeUpdate(true);
RequestClose();
}
);
}
private async ValueTask CheckForUpdatesAsync()
catch
{
try
{
var updateVersion = await _updateService.CheckForUpdatesAsync();
if (updateVersion is null)
return;
// Failure to update shouldn't crash the application
Notifications.Enqueue("Failed to perform application update");
}
}
Notifications.Enqueue($"Downloading update to {App.Name} v{updateVersion}...");
await _updateService.PrepareUpdateAsync(updateVersion);
protected override async void OnViewLoaded()
{
base.OnViewLoaded();
Notifications.Enqueue(
"Update has been downloaded and will be installed when you exit",
"INSTALL NOW", () =>
{
_updateService.FinalizeUpdate(true);
RequestClose();
}
);
}
catch
{
// Failure to update shouldn't crash the application
Notifications.Enqueue("Failed to perform application update");
}
_settingsService.Load();
if (_settingsService.LastToken is not null)
{
IsBotToken = _settingsService.LastToken.Kind == AuthTokenKind.Bot;
TokenValue = _settingsService.LastToken.Value;
}
protected override async void OnViewLoaded()
if (_settingsService.IsDarkModeEnabled)
{
base.OnViewLoaded();
_settingsService.Load();
if (_settingsService.LastToken is not null)
{
IsBotToken = _settingsService.LastToken.Kind == AuthTokenKind.Bot;
TokenValue = _settingsService.LastToken.Value;
}
if (_settingsService.IsDarkModeEnabled)
{
App.SetDarkTheme();
}
else
{
App.SetLightTheme();
}
await CheckForUpdatesAsync();
App.SetDarkTheme();
}
else
{
App.SetLightTheme();
}
protected override void OnClose()
{
base.OnClose();
await CheckForUpdatesAsync();
}
_settingsService.Save();
_updateService.FinalizeUpdate(false);
protected override void OnClose()
{
base.OnClose();
_settingsService.Save();
_updateService.FinalizeUpdate(false);
}
public async void ShowSettings()
{
var dialog = _viewModelFactory.CreateSettingsViewModel();
await _dialogManager.ShowDialogAsync(dialog);
}
public void ShowHelp() => ProcessEx.StartShellExecute(App.GitHubProjectWikiUrl);
public bool CanPopulateGuildsAndChannels =>
!IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
public async void PopulateGuildsAndChannels()
{
using var operation = ProgressManager.CreateOperation();
try
{
var tokenValue = TokenValue?.Trim('"', ' ');
if (string.IsNullOrWhiteSpace(tokenValue))
return;
var token = new AuthToken(
IsBotToken ? AuthTokenKind.Bot : AuthTokenKind.User,
tokenValue
);
_settingsService.LastToken = token;
var discord = new DiscordClient(token);
var guildChannelMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
await foreach (var guild in discord.GetUserGuildsAsync())
{
var channels = await discord.GetGuildChannelsAsync(guild.Id);
guildChannelMap[guild] = channels.Where(c => c.IsTextChannel).ToArray();
}
GuildChannelMap = guildChannelMap;
SelectedGuild = guildChannelMap.Keys.FirstOrDefault();
}
public async void ShowSettings()
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
var dialog = _viewModelFactory.CreateSettingsViewModel();
Notifications.Enqueue(ex.Message.TrimEnd('.'));
}
catch (Exception ex)
{
var dialog = _viewModelFactory.CreateMessageBoxViewModel(
"Error pulling guilds and channels",
ex.ToString()
);
await _dialogManager.ShowDialogAsync(dialog);
}
}
public void ShowHelp() => ProcessEx.StartShellExecute(App.GitHubProjectWikiUrl);
public bool CanExportChannels =>
!IsBusy && SelectedGuild is not null && SelectedChannels is not null && SelectedChannels.Any();
public bool CanPopulateGuildsAndChannels =>
!IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
public async void PopulateGuildsAndChannels()
public async void ExportChannels()
{
try
{
using var operation = ProgressManager.CreateOperation();
var token = _settingsService.LastToken;
if (token is null || SelectedGuild is null || SelectedChannels is null || !SelectedChannels.Any())
return;
try
var dialog = _viewModelFactory.CreateExportSetupViewModel(SelectedGuild, SelectedChannels);
if (await _dialogManager.ShowDialogAsync(dialog) != true)
return;
var exporter = new ChannelExporter(token);
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
var successfulExportCount = 0;
await dialog.Channels.Zip(operations).ParallelForEachAsync(async tuple =>
{
var tokenValue = TokenValue?.Trim('"', ' ');
if (string.IsNullOrWhiteSpace(tokenValue))
return;
var (channel, operation) = tuple;
var token = new AuthToken(
IsBotToken ? AuthTokenKind.Bot : AuthTokenKind.User,
tokenValue
);
_settingsService.LastToken = token;
var discord = new DiscordClient(token);
var guildChannelMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
await foreach (var guild in discord.GetUserGuildsAsync())
try
{
var channels = await discord.GetGuildChannelsAsync(guild.Id);
guildChannelMap[guild] = channels.Where(c => c.IsTextChannel).ToArray();
var request = new ExportRequest(
dialog.Guild!,
channel!,
dialog.OutputPath!,
dialog.SelectedFormat,
dialog.After?.Pipe(Snowflake.FromDate),
dialog.Before?.Pipe(Snowflake.FromDate),
dialog.PartitionLimit,
dialog.MessageFilter,
dialog.ShouldDownloadMedia,
_settingsService.ShouldReuseMedia,
_settingsService.DateFormat
);
await exporter.ExportChannelAsync(request, operation);
Interlocked.Increment(ref successfulExportCount);
}
GuildChannelMap = guildChannelMap;
SelectedGuild = guildChannelMap.Keys.FirstOrDefault();
}
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
Notifications.Enqueue(ex.Message.TrimEnd('.'));
}
catch (Exception ex)
{
var dialog = _viewModelFactory.CreateMessageBoxViewModel(
"Error pulling guilds and channels",
ex.ToString()
);
await _dialogManager.ShowDialogAsync(dialog);
}
}
public bool CanExportChannels =>
!IsBusy && SelectedGuild is not null && SelectedChannels is not null && SelectedChannels.Any();
public async void ExportChannels()
{
try
{
var token = _settingsService.LastToken;
if (token is null || SelectedGuild is null || SelectedChannels is null || !SelectedChannels.Any())
return;
var dialog = _viewModelFactory.CreateExportSetupViewModel(SelectedGuild, SelectedChannels);
if (await _dialogManager.ShowDialogAsync(dialog) != true)
return;
var exporter = new ChannelExporter(token);
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
var successfulExportCount = 0;
await dialog.Channels.Zip(operations).ParallelForEachAsync(async tuple =>
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
var (channel, operation) = tuple;
Notifications.Enqueue(ex.Message.TrimEnd('.'));
}
finally
{
operation.Dispose();
}
}, Math.Max(1, _settingsService.ParallelLimit));
try
{
var request = new ExportRequest(
dialog.Guild!,
channel!,
dialog.OutputPath!,
dialog.SelectedFormat,
dialog.After?.Pipe(Snowflake.FromDate),
dialog.Before?.Pipe(Snowflake.FromDate),
dialog.PartitionLimit,
dialog.MessageFilter,
dialog.ShouldDownloadMedia,
_settingsService.ShouldReuseMedia,
_settingsService.DateFormat
);
// Notify of overall completion
if (successfulExportCount > 0)
Notifications.Enqueue($"Successfully exported {successfulExportCount} channel(s)");
}
catch (Exception ex)
{
var dialog = _viewModelFactory.CreateMessageBoxViewModel(
"Error exporting channel(s)",
ex.ToString()
);
await exporter.ExportChannelAsync(request, operation);
Interlocked.Increment(ref successfulExportCount);
}
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
Notifications.Enqueue(ex.Message.TrimEnd('.'));
}
finally
{
operation.Dispose();
}
}, Math.Max(1, _settingsService.ParallelLimit));
// Notify of overall completion
if (successfulExportCount > 0)
Notifications.Enqueue($"Successfully exported {successfulExportCount} channel(s)");
}
catch (Exception ex)
{
var dialog = _viewModelFactory.CreateMessageBoxViewModel(
"Error exporting channel(s)",
ex.ToString()
);
await _dialogManager.ShowDialogAsync(dialog);
}
await _dialogManager.ShowDialogAsync(dialog);
}
}
}