mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-02-14 15:53:30 -07:00
Refactor
This commit is contained in:
parent
2fea455c64
commit
511af1e35c
|
|
@ -12,6 +12,7 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Exceptions;
|
using DiscordChatExporter.Core.Exceptions;
|
||||||
using DiscordChatExporter.Core.Exporting;
|
using DiscordChatExporter.Core.Exporting;
|
||||||
|
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using Tyrrrz.Extensions;
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
|
|
@ -31,9 +32,8 @@ namespace DiscordChatExporter.Cli.Commands.Base
|
||||||
[CommandOption("before", Description = "Only include messages sent before this date or message ID.")]
|
[CommandOption("before", Description = "Only include messages sent before this date or message ID.")]
|
||||||
public Snowflake? Before { get; init; }
|
public Snowflake? Before { get; init; }
|
||||||
|
|
||||||
[CommandOption("partition", 'p', Converter = typeof(PartitionConverter),
|
[CommandOption("partition", 'p', Description = "Split output into partitions, each limited to this number of message (e.g. 100) or file size (e.g. 10mb).")]
|
||||||
Description = "Split output into partitions limited to this number of messages or a maximum file size (e.g. \"25mb\").")]
|
public PartitionLimit PartitionLimit { get; init; } = NullPartitionLimit.Instance;
|
||||||
public IPartitioner Partitoner { get; init; } = new NullPartitioner();
|
|
||||||
|
|
||||||
[CommandOption("parallel", Description = "Limits how many channels can be exported in parallel.")]
|
[CommandOption("parallel", Description = "Limits how many channels can be exported in parallel.")]
|
||||||
public int ParallelLimit { get; init; } = 1;
|
public int ParallelLimit { get; init; } = 1;
|
||||||
|
|
@ -75,7 +75,7 @@ namespace DiscordChatExporter.Cli.Commands.Base
|
||||||
ExportFormat,
|
ExportFormat,
|
||||||
After,
|
After,
|
||||||
Before,
|
Before,
|
||||||
Partitoner,
|
PartitionLimit,
|
||||||
ShouldDownloadMedia,
|
ShouldDownloadMedia,
|
||||||
ShouldReuseMedia,
|
ShouldReuseMedia,
|
||||||
DateFormat
|
DateFormat
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using ByteSizeLib;
|
|
||||||
using CliFx.Extensibility;
|
|
||||||
using DiscordChatExporter.Core;
|
|
||||||
using DiscordChatExporter.Core.Exporting;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands.Base
|
|
||||||
{
|
|
||||||
public class PartitionConverter : BindingConverter<IPartitioner>
|
|
||||||
{
|
|
||||||
public override IPartitioner Convert(string? rawValue)
|
|
||||||
{
|
|
||||||
if (rawValue == null) return new NullPartitioner();
|
|
||||||
|
|
||||||
if (ByteSize.TryParse(rawValue, out ByteSize filesize))
|
|
||||||
{
|
|
||||||
return new FileSizePartitioner((long)filesize.Bytes);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int messageLimit = int.Parse(rawValue);
|
|
||||||
return new MessageCountPartitioner(messageLimit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace DiscordChatExporter.Cli.Commands
|
||||||
[Command("export", Description = "Export one or multiple channels.")]
|
[Command("export", Description = "Export one or multiple channels.")]
|
||||||
public class ExportChannelsCommand : ExportCommandBase
|
public class ExportChannelsCommand : ExportCommandBase
|
||||||
{
|
{
|
||||||
// TODO: change this to plural with a breaking change
|
// TODO: change this to plural (breaking change)
|
||||||
[CommandOption("channel", 'c', IsRequired = true, Description = "Channel ID(s).")]
|
[CommandOption("channel", 'c', IsRequired = true, Description = "Channel ID(s).")]
|
||||||
public IReadOnlyList<Snowflake> ChannelIds { get; init; } = Array.Empty<Snowflake>();
|
public IReadOnlyList<Snowflake> ChannelIds { get; init; } = Array.Empty<Snowflake>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
<PackageReference Include="CliFx" Version="2.0.1" />
|
<PackageReference Include="CliFx" Version="2.0.1" />
|
||||||
<PackageReference Include="Spectre.Console" Version="0.38.0" />
|
<PackageReference Include="Spectre.Console" Version="0.38.0" />
|
||||||
<PackageReference Include="Gress" Version="1.2.0" />
|
<PackageReference Include="Gress" Version="1.2.0" />
|
||||||
<PackageReference Include="OneOf" Version="3.0.174" />
|
|
||||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.5" />
|
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Discord.Data.Common;
|
using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
using FileSize = DiscordChatExporter.Core.Discord.Data.Common.FileSize;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Common
|
namespace DiscordChatExporter.Core.Discord.Data.Common
|
||||||
{
|
{
|
||||||
|
|
@ -61,5 +63,37 @@ namespace DiscordChatExporter.Core.Discord.Data.Common
|
||||||
public partial struct FileSize
|
public partial struct FileSize
|
||||||
{
|
{
|
||||||
public static FileSize FromBytes(long bytes) => new(bytes);
|
public static FileSize FromBytes(long bytes) => new(bytes);
|
||||||
|
|
||||||
|
public static FileSize? TryParse(string value)
|
||||||
|
{
|
||||||
|
var match = Regex.Match(value, @"^(\d+[\.,]?\d*)\s*(\w)?b$", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
// Number part
|
||||||
|
if (!double.TryParse(
|
||||||
|
match.Groups[1].Value,
|
||||||
|
NumberStyles.Float,
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
out var number))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Magnitude part
|
||||||
|
var magnitude = match.Groups[2].Value.ToUpperInvariant() switch
|
||||||
|
{
|
||||||
|
"G" => 1_000_000_000,
|
||||||
|
"M" => 1_000_000,
|
||||||
|
"K" => 1_000,
|
||||||
|
"" => 1,
|
||||||
|
_ => -1
|
||||||
|
};
|
||||||
|
|
||||||
|
if (magnitude < 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FromBytes((long) (number * magnitude));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ namespace DiscordChatExporter.Core.Discord
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Snowflake Parse(string str, IFormatProvider? formatProvider) =>
|
public static Snowflake Parse(string str, IFormatProvider? formatProvider) =>
|
||||||
TryParse(str, formatProvider) ?? throw new FormatException($"Invalid snowflake: {str}.");
|
TryParse(str, formatProvider) ?? throw new FormatException($"Invalid snowflake '{str}'.");
|
||||||
|
|
||||||
public static Snowflake Parse(string str) => Parse(str, null);
|
public static Snowflake Parse(string str) => Parse(str, null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ByteSize" Version="2.0.0" />
|
|
||||||
<PackageReference Include="JsonExtensions" Version="1.0.1" />
|
<PackageReference Include="JsonExtensions" Version="1.0.1" />
|
||||||
<PackageReference Include="MiniRazor.CodeGen" Version="2.1.2" />
|
<PackageReference Include="MiniRazor.CodeGen" Version="2.1.2" />
|
||||||
<PackageReference Include="Polly" Version="7.2.1" />
|
<PackageReference Include="Polly" Version="7.2.1" />
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
using DiscordChatExporter.Core.Utils;
|
using DiscordChatExporter.Core.Utils;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
namespace DiscordChatExporter.Core.Exporting
|
||||||
|
|
@ -28,7 +29,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
|
|
||||||
public Snowflake? Before { get; }
|
public Snowflake? Before { get; }
|
||||||
|
|
||||||
public IPartitioner Partitoner { get; }
|
public PartitionLimit PartitionLimit { get; }
|
||||||
|
|
||||||
public bool ShouldDownloadMedia { get; }
|
public bool ShouldDownloadMedia { get; }
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
ExportFormat format,
|
ExportFormat format,
|
||||||
Snowflake? after,
|
Snowflake? after,
|
||||||
Snowflake? before,
|
Snowflake? before,
|
||||||
IPartitioner partitioner,
|
PartitionLimit partitionLimit,
|
||||||
bool shouldDownloadMedia,
|
bool shouldDownloadMedia,
|
||||||
bool shouldReuseMedia,
|
bool shouldReuseMedia,
|
||||||
string dateFormat)
|
string dateFormat)
|
||||||
|
|
@ -54,7 +55,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
Format = format;
|
Format = format;
|
||||||
After = after;
|
After = after;
|
||||||
Before = before;
|
Before = before;
|
||||||
Partitoner = partitioner;
|
PartitionLimit = partitionLimit;
|
||||||
ShouldDownloadMedia = shouldDownloadMedia;
|
ShouldDownloadMedia = shouldDownloadMedia;
|
||||||
ShouldReuseMedia = shouldReuseMedia;
|
ShouldReuseMedia = shouldReuseMedia;
|
||||||
DateFormat = dateFormat;
|
DateFormat = dateFormat;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ByteSizeLib;
|
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Exporting;
|
|
||||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
|
||||||
using DiscordChatExporter.Core.Exporting.Writers;
|
using DiscordChatExporter.Core.Exporting.Writers;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
namespace DiscordChatExporter.Core.Exporting
|
||||||
{
|
{
|
||||||
internal partial class MessageExporter : IAsyncDisposable
|
internal partial class MessageExporter : IAsyncDisposable
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly ExportContext _context;
|
private readonly ExportContext _context;
|
||||||
|
|
||||||
private long _messageCount;
|
|
||||||
private int _partitionIndex;
|
private int _partitionIndex;
|
||||||
private MessageWriter? _writer;
|
private MessageWriter? _writer;
|
||||||
|
|
||||||
|
|
@ -24,17 +18,6 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsPartitionLimitReached()
|
|
||||||
{
|
|
||||||
if (_writer is null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _context.Request.Partitoner.IsLimitReached(
|
|
||||||
new ExportPartitioningContext(_messageCount, _writer.SizeInBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask ResetWriterAsync()
|
private async ValueTask ResetWriterAsync()
|
||||||
{
|
{
|
||||||
if (_writer is not null)
|
if (_writer is not null)
|
||||||
|
|
@ -48,7 +31,8 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
private async ValueTask<MessageWriter> GetWriterAsync()
|
private async ValueTask<MessageWriter> GetWriterAsync()
|
||||||
{
|
{
|
||||||
// Ensure partition limit has not been exceeded
|
// Ensure partition limit has not been exceeded
|
||||||
if (_writer != null && IsPartitionLimitReached())
|
if (_writer is not null &&
|
||||||
|
_context.Request.PartitionLimit.IsReached(_writer.MessagesWritten, _writer.BytesWritten))
|
||||||
{
|
{
|
||||||
await ResetWriterAsync();
|
await ResetWriterAsync();
|
||||||
_partitionIndex++;
|
_partitionIndex++;
|
||||||
|
|
@ -74,7 +58,6 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
{
|
{
|
||||||
var writer = await GetWriterAsync();
|
var writer = await GetWriterAsync();
|
||||||
await writer.WriteMessageAsync(message);
|
await writer.WriteMessageAsync(message);
|
||||||
_messageCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync() => await ResetWriterAsync();
|
public async ValueTask DisposeAsync() => await ResetWriterAsync();
|
||||||
|
|
@ -82,9 +65,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
|
|
||||||
internal partial class MessageExporter
|
internal partial class MessageExporter
|
||||||
{
|
{
|
||||||
private static string GetPartitionFilePath(
|
private static string GetPartitionFilePath(string baseFilePath, int partitionIndex)
|
||||||
string baseFilePath,
|
|
||||||
int partitionIndex)
|
|
||||||
{
|
{
|
||||||
// First partition - don't change file name
|
// First partition - don't change file name
|
||||||
if (partitionIndex <= 0)
|
if (partitionIndex <= 0)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Partitioners
|
|
||||||
{
|
|
||||||
public class ExportPartitioningContext
|
|
||||||
{
|
|
||||||
public long MessageCount { get; }
|
|
||||||
public long SizeInBytes { get; }
|
|
||||||
|
|
||||||
public ExportPartitioningContext(long messageCount, long sizeInBytes)
|
|
||||||
{
|
|
||||||
MessageCount = messageCount;
|
|
||||||
SizeInBytes = sizeInBytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
|
||||||
{
|
|
||||||
public class FileSizePartitioner : IPartitioner
|
|
||||||
{
|
|
||||||
private long _bytesPerFile;
|
|
||||||
|
|
||||||
public FileSizePartitioner(long bytesPerFile)
|
|
||||||
{
|
|
||||||
_bytesPerFile = bytesPerFile;
|
|
||||||
}
|
|
||||||
public bool IsLimitReached(ExportPartitioningContext context)
|
|
||||||
{
|
|
||||||
return context.SizeInBytes >= _bytesPerFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
|
||||||
{
|
|
||||||
public interface IPartitioner
|
|
||||||
{
|
|
||||||
bool IsLimitReached(ExportPartitioningContext context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
|
||||||
{
|
|
||||||
public class MessageCountPartitioner : IPartitioner
|
|
||||||
{
|
|
||||||
|
|
||||||
private int _messagesPerPartition;
|
|
||||||
|
|
||||||
public MessageCountPartitioner(int messagesPerPartition)
|
|
||||||
{
|
|
||||||
_messagesPerPartition = messagesPerPartition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsLimitReached(ExportPartitioningContext context)
|
|
||||||
{
|
|
||||||
return context.MessageCount > 0 &&
|
|
||||||
_messagesPerPartition != 0 &&
|
|
||||||
context.MessageCount % _messagesPerPartition == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
using DiscordChatExporter.Core.Exporting;
|
|
||||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
|
||||||
{
|
|
||||||
public class NullPartitioner : IPartitioner
|
|
||||||
{
|
|
||||||
public bool IsLimitReached(ExportPartitioningContext context)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||||
|
{
|
||||||
|
public class FileSizePartitionLimit : PartitionLimit
|
||||||
|
{
|
||||||
|
private readonly long _limit;
|
||||||
|
|
||||||
|
public FileSizePartitionLimit(long limit) => _limit = limit;
|
||||||
|
|
||||||
|
public override bool IsReached(long messagesWritten, long bytesWritten) =>
|
||||||
|
bytesWritten >= _limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||||
|
{
|
||||||
|
public class MessageCountPartitionLimit : PartitionLimit
|
||||||
|
{
|
||||||
|
private readonly long _limit;
|
||||||
|
|
||||||
|
public MessageCountPartitionLimit(long limit) => _limit = limit;
|
||||||
|
|
||||||
|
public override bool IsReached(long messagesWritten, long bytesWritten) =>
|
||||||
|
messagesWritten >= _limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||||
|
{
|
||||||
|
public class NullPartitionLimit : PartitionLimit
|
||||||
|
{
|
||||||
|
public static NullPartitionLimit Instance { get; } = new();
|
||||||
|
|
||||||
|
public override bool IsReached(long messagesWritten, long bytesWritten) => false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||||
|
{
|
||||||
|
public abstract partial class PartitionLimit
|
||||||
|
{
|
||||||
|
public abstract bool IsReached(long messagesWritten, long bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class PartitionLimit
|
||||||
|
{
|
||||||
|
public static PartitionLimit Parse(string value)
|
||||||
|
{
|
||||||
|
var fileSize = FileSize.TryParse(value);
|
||||||
|
if (fileSize is not null)
|
||||||
|
return new FileSizePartitionLimit(fileSize.Value.TotalBytes);
|
||||||
|
|
||||||
|
var messageCount = int.Parse(value);
|
||||||
|
return new MessageCountPartitionLimit(messageCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -58,6 +58,8 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
|
|
||||||
public override async ValueTask WriteMessageAsync(Message message)
|
public override async ValueTask WriteMessageAsync(Message message)
|
||||||
{
|
{
|
||||||
|
await base.WriteMessageAsync(message);
|
||||||
|
|
||||||
// Author ID
|
// Author ID
|
||||||
await _writer.WriteAsync(CsvEncode(message.Author.Id.ToString()));
|
await _writer.WriteAsync(CsvEncode(message.Author.Id.ToString()));
|
||||||
await _writer.WriteAsync(',');
|
await _writer.WriteAsync(',');
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
@inherits MiniRazor.TemplateBase<DiscordChatExporter.Core.Exporting.Writers.Html.LayoutTemplateContext>
|
@inherits MiniRazor.TemplateBase<DiscordChatExporter.Core.Exporting.Writers.Html.PostambleTemplateContext>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="postamble">
|
<div class="postamble">
|
||||||
<div class="postamble__entry">Exported @Model.MessageCount.ToString("N0") message(s)</div>
|
<div class="postamble__entry">Exported @Model.MessagesWritten.ToString("N0") message(s)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
|
{
|
||||||
|
internal class PostambleTemplateContext
|
||||||
|
{
|
||||||
|
public ExportContext ExportContext { get; }
|
||||||
|
|
||||||
|
public long MessagesWritten { get; }
|
||||||
|
|
||||||
|
public PostambleTemplateContext(ExportContext exportContext, long messagesWritten)
|
||||||
|
{
|
||||||
|
ExportContext = exportContext;
|
||||||
|
MessagesWritten = messagesWritten;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
@using System.Threading.Tasks
|
@using System.Threading.Tasks
|
||||||
@using Tyrrrz.Extensions
|
@using Tyrrrz.Extensions
|
||||||
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
@inherits MiniRazor.TemplateBase<DiscordChatExporter.Core.Exporting.Writers.Html.LayoutTemplateContext>
|
@inherits MiniRazor.TemplateBase<DiscordChatExporter.Core.Exporting.Writers.Html.PreambleTemplateContext>
|
||||||
|
|
||||||
@{
|
@{
|
||||||
string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date);
|
string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
{
|
{
|
||||||
internal class LayoutTemplateContext
|
internal class PreambleTemplateContext
|
||||||
{
|
{
|
||||||
public ExportContext ExportContext { get; }
|
public ExportContext ExportContext { get; }
|
||||||
|
|
||||||
public string ThemeName { get; }
|
public string ThemeName { get; }
|
||||||
|
|
||||||
public long MessageCount { get; }
|
public PreambleTemplateContext(ExportContext exportContext, string themeName)
|
||||||
|
|
||||||
public LayoutTemplateContext(ExportContext exportContext, string themeName, long messageCount)
|
|
||||||
{
|
{
|
||||||
ExportContext = exportContext;
|
ExportContext = exportContext;
|
||||||
ThemeName = themeName;
|
ThemeName = themeName;
|
||||||
MessageCount = messageCount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,8 +14,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
|
|
||||||
private readonly List<Message> _messageGroupBuffer = new();
|
private readonly List<Message> _messageGroupBuffer = new();
|
||||||
|
|
||||||
private long _messageCount;
|
|
||||||
|
|
||||||
public HtmlMessageWriter(Stream stream, ExportContext context, string themeName)
|
public HtmlMessageWriter(Stream stream, ExportContext context, string themeName)
|
||||||
: base(stream, context)
|
: base(stream, context)
|
||||||
{
|
{
|
||||||
|
|
@ -25,7 +23,7 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
|
|
||||||
public override async ValueTask WritePreambleAsync()
|
public override async ValueTask WritePreambleAsync()
|
||||||
{
|
{
|
||||||
var templateContext = new LayoutTemplateContext(Context, _themeName, _messageCount);
|
var templateContext = new PreambleTemplateContext(Context, _themeName);
|
||||||
|
|
||||||
await _writer.WriteLineAsync(
|
await _writer.WriteLineAsync(
|
||||||
await PreambleTemplate.RenderAsync(templateContext)
|
await PreambleTemplate.RenderAsync(templateContext)
|
||||||
|
|
@ -43,6 +41,8 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
|
|
||||||
public override async ValueTask WriteMessageAsync(Message message)
|
public override async ValueTask WriteMessageAsync(Message message)
|
||||||
{
|
{
|
||||||
|
await base.WriteMessageAsync(message);
|
||||||
|
|
||||||
// If message group is empty or the given message can be grouped, buffer the given message
|
// If message group is empty or the given message can be grouped, buffer the given message
|
||||||
if (!_messageGroupBuffer.Any() || MessageGroup.CanJoin(_messageGroupBuffer.Last(), message))
|
if (!_messageGroupBuffer.Any() || MessageGroup.CanJoin(_messageGroupBuffer.Last(), message))
|
||||||
{
|
{
|
||||||
|
|
@ -56,9 +56,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
_messageGroupBuffer.Clear();
|
_messageGroupBuffer.Clear();
|
||||||
_messageGroupBuffer.Add(message);
|
_messageGroupBuffer.Add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment message count
|
|
||||||
_messageCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask WritePostambleAsync()
|
public override async ValueTask WritePostambleAsync()
|
||||||
|
|
@ -67,7 +64,7 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
if (_messageGroupBuffer.Any())
|
if (_messageGroupBuffer.Any())
|
||||||
await WriteMessageGroupAsync(MessageGroup.Join(_messageGroupBuffer));
|
await WriteMessageGroupAsync(MessageGroup.Join(_messageGroupBuffer));
|
||||||
|
|
||||||
var templateContext = new LayoutTemplateContext(Context, _themeName, _messageCount);
|
var templateContext = new PostambleTemplateContext(Context, MessagesWritten);
|
||||||
|
|
||||||
await _writer.WriteLineAsync(
|
await _writer.WriteLineAsync(
|
||||||
await PostambleTemplate.RenderAsync(templateContext)
|
await PostambleTemplate.RenderAsync(templateContext)
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
{
|
{
|
||||||
private readonly Utf8JsonWriter _writer;
|
private readonly Utf8JsonWriter _writer;
|
||||||
|
|
||||||
private long _messageCount;
|
|
||||||
|
|
||||||
public JsonMessageWriter(Stream stream, ExportContext context)
|
public JsonMessageWriter(Stream stream, ExportContext context)
|
||||||
: base(stream, context)
|
: base(stream, context)
|
||||||
{
|
{
|
||||||
|
|
@ -211,6 +209,8 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
|
|
||||||
public override async ValueTask WriteMessageAsync(Message message)
|
public override async ValueTask WriteMessageAsync(Message message)
|
||||||
{
|
{
|
||||||
|
await base.WriteMessageAsync(message);
|
||||||
|
|
||||||
_writer.WriteStartObject();
|
_writer.WriteStartObject();
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
|
|
@ -279,8 +279,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
|
|
||||||
_writer.WriteEndObject();
|
_writer.WriteEndObject();
|
||||||
await _writer.FlushAsync();
|
await _writer.FlushAsync();
|
||||||
|
|
||||||
_messageCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask WritePostambleAsync()
|
public override async ValueTask WritePostambleAsync()
|
||||||
|
|
@ -288,7 +286,7 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
// Message array (end)
|
// Message array (end)
|
||||||
_writer.WriteEndArray();
|
_writer.WriteEndArray();
|
||||||
|
|
||||||
_writer.WriteNumber("messageCount", _messageCount);
|
_writer.WriteNumber("messageCount", MessagesWritten);
|
||||||
|
|
||||||
// Root object (end)
|
// Root object (end)
|
||||||
_writer.WriteEndObject();
|
_writer.WriteEndObject();
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,23 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
|
|
||||||
protected ExportContext Context { get; }
|
protected ExportContext Context { get; }
|
||||||
|
|
||||||
|
public long MessagesWritten { get; private set; }
|
||||||
|
|
||||||
|
public long BytesWritten => Stream.Length;
|
||||||
|
|
||||||
protected MessageWriter(Stream stream, ExportContext context)
|
protected MessageWriter(Stream stream, ExportContext context)
|
||||||
{
|
{
|
||||||
Stream = stream;
|
Stream = stream;
|
||||||
Context = context;
|
Context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long SizeInBytes => Stream.Length;
|
|
||||||
|
|
||||||
public virtual ValueTask WritePreambleAsync() => default;
|
public virtual ValueTask WritePreambleAsync() => default;
|
||||||
|
|
||||||
public abstract ValueTask WriteMessageAsync(Message message);
|
public virtual ValueTask WriteMessageAsync(Message message)
|
||||||
|
{
|
||||||
|
MessagesWritten++;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual ValueTask WritePostambleAsync() => default;
|
public virtual ValueTask WritePostambleAsync() => default;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
{
|
{
|
||||||
private readonly TextWriter _writer;
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
private long _messageCount;
|
|
||||||
|
|
||||||
public PlainTextMessageWriter(Stream stream, ExportContext context)
|
public PlainTextMessageWriter(Stream stream, ExportContext context)
|
||||||
: base(stream, context)
|
: base(stream, context)
|
||||||
{
|
{
|
||||||
|
|
@ -130,26 +128,29 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
|
|
||||||
public override async ValueTask WriteMessageAsync(Message message)
|
public override async ValueTask WriteMessageAsync(Message message)
|
||||||
{
|
{
|
||||||
|
await base.WriteMessageAsync(message);
|
||||||
|
|
||||||
|
// Header
|
||||||
await WriteMessageHeaderAsync(message);
|
await WriteMessageHeaderAsync(message);
|
||||||
|
|
||||||
|
// Content
|
||||||
if (!string.IsNullOrWhiteSpace(message.Content))
|
if (!string.IsNullOrWhiteSpace(message.Content))
|
||||||
await _writer.WriteLineAsync(FormatMarkdown(message.Content));
|
await _writer.WriteLineAsync(FormatMarkdown(message.Content));
|
||||||
|
|
||||||
await _writer.WriteLineAsync();
|
await _writer.WriteLineAsync();
|
||||||
|
|
||||||
|
// Attachments, embeds, reactions
|
||||||
await WriteAttachmentsAsync(message.Attachments);
|
await WriteAttachmentsAsync(message.Attachments);
|
||||||
await WriteEmbedsAsync(message.Embeds);
|
await WriteEmbedsAsync(message.Embeds);
|
||||||
await WriteReactionsAsync(message.Reactions);
|
await WriteReactionsAsync(message.Reactions);
|
||||||
|
|
||||||
await _writer.WriteLineAsync();
|
await _writer.WriteLineAsync();
|
||||||
|
|
||||||
_messageCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask WritePostambleAsync()
|
public override async ValueTask WritePostambleAsync()
|
||||||
{
|
{
|
||||||
await _writer.WriteLineAsync('='.Repeat(62));
|
await _writer.WriteLineAsync('='.Repeat(62));
|
||||||
await _writer.WriteLineAsync($"Exported {_messageCount:N0} message(s)");
|
await _writer.WriteLineAsync($"Exported {MessagesWritten:N0} message(s)");
|
||||||
await _writer.WriteLineAsync('='.Repeat(62));
|
await _writer.WriteLineAsync('='.Repeat(62));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace DiscordChatExporter.Gui.Converters
|
||||||
if (value is ExportFormat exportFormatValue)
|
if (value is ExportFormat exportFormatValue)
|
||||||
return exportFormatValue.GetDisplayName();
|
return exportFormatValue.GetDisplayName();
|
||||||
|
|
||||||
return default(string);
|
return default(string?);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
using DiscordChatExporter.Core.Exporting;
|
|
||||||
using DiscordChatExporter.Gui.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Data;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Gui.Converters
|
|
||||||
{
|
|
||||||
[ValueConversion(typeof(ExportFormat), typeof(string))]
|
|
||||||
public class PartitionFormatToStringConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public static PartitionFormatToStringConverter Instance { get; } = new();
|
|
||||||
|
|
||||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value is PartitionFormat partitionFormatValue)
|
|
||||||
return partitionFormatValue.GetDisplayName();
|
|
||||||
|
|
||||||
return default(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
using DiscordChatExporter.Gui.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Data;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Gui.Converters
|
|
||||||
{
|
|
||||||
[ValueConversion(typeof(DateTimeOffset?), typeof(DateTime?))]
|
|
||||||
public class PartitionFormatToTextBoxHintConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public static PartitionFormatToTextBoxHintConverter Instance { get; } = new();
|
|
||||||
|
|
||||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value is PartitionFormat partitionFormat)
|
|
||||||
return partitionFormat switch
|
|
||||||
{
|
|
||||||
PartitionFormat.FileSize => "MB per partition",
|
|
||||||
PartitionFormat.MessageCount => "Messages per partition",
|
|
||||||
_ => default(string)
|
|
||||||
};
|
|
||||||
|
|
||||||
return default(DateTime?);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
using DiscordChatExporter.Gui.Internal;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Data;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Gui.Converters
|
|
||||||
{
|
|
||||||
[ValueConversion(typeof(DateTimeOffset?), typeof(DateTime?))]
|
|
||||||
public class PartitionFormatToTooltipConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public static PartitionFormatToTextBoxHintConverter Instance { get; } = new();
|
|
||||||
|
|
||||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value is PartitionFormat partitionFormat)
|
|
||||||
return partitionFormat switch
|
|
||||||
{
|
|
||||||
PartitionFormat.FileSize => "Split output into partitions close to this file size",
|
|
||||||
PartitionFormat.MessageCount => "Split output into partitions limited to this number of messages",
|
|
||||||
_ => default(string)
|
|
||||||
};
|
|
||||||
|
|
||||||
return default(DateTime?);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Exporting;
|
using DiscordChatExporter.Core.Exporting;
|
||||||
using DiscordChatExporter.Gui.Internal;
|
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
using Tyrrrz.Settings;
|
using Tyrrrz.Settings;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Gui.Services
|
namespace DiscordChatExporter.Gui.Services
|
||||||
|
|
@ -23,9 +23,7 @@ namespace DiscordChatExporter.Gui.Services
|
||||||
|
|
||||||
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||||
|
|
||||||
public PartitionFormat LastPartitionFormat { get; set; } = PartitionFormat.MessageCount;
|
public string? LastPartitionLimitValue { get; set; }
|
||||||
|
|
||||||
public int? LastPartitionLimit { get; set; }
|
|
||||||
|
|
||||||
public bool LastShouldDownloadMedia { get; set; }
|
public bool LastShouldDownloadMedia { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Gui.Internal
|
|
||||||
{
|
|
||||||
public enum PartitionFormat
|
|
||||||
{
|
|
||||||
MessageCount,
|
|
||||||
FileSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PartitionFormatExtensions
|
|
||||||
{
|
|
||||||
public static string GetDisplayName(this PartitionFormat format) => format switch
|
|
||||||
{
|
|
||||||
PartitionFormat.MessageCount => "Message count",
|
|
||||||
PartitionFormat.FileSize => "File size (MB)",
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DiscordChatExporter.Gui.Internal;
|
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Exporting;
|
using DiscordChatExporter.Core.Exporting;
|
||||||
|
|
@ -47,12 +46,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
|
|
||||||
public DateTimeOffset? Before => BeforeDate?.Add(BeforeTime ?? TimeSpan.Zero);
|
public DateTimeOffset? Before => BeforeDate?.Add(BeforeTime ?? TimeSpan.Zero);
|
||||||
|
|
||||||
public IReadOnlyList<PartitionFormat> AvailablePartitionFormats =>
|
public string? PartitionLimitValue { get; set; }
|
||||||
Enum.GetValues(typeof(PartitionFormat)).Cast<PartitionFormat>().ToArray();
|
|
||||||
|
|
||||||
public PartitionFormat SelectedPartitionFormat { get; set; }
|
|
||||||
|
|
||||||
public int? PartitionLimit { get; set; }
|
|
||||||
|
|
||||||
public bool ShouldDownloadMedia { get; set; }
|
public bool ShouldDownloadMedia { get; set; }
|
||||||
|
|
||||||
|
|
@ -61,7 +55,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
public bool IsAdvancedSectionDisplayedByDefault =>
|
public bool IsAdvancedSectionDisplayedByDefault =>
|
||||||
After != default ||
|
After != default ||
|
||||||
Before != default ||
|
Before != default ||
|
||||||
PartitionLimit != default ||
|
!string.IsNullOrWhiteSpace(PartitionLimitValue) ||
|
||||||
ShouldDownloadMedia != default;
|
ShouldDownloadMedia != default;
|
||||||
|
|
||||||
public ExportSetupViewModel(DialogManager dialogManager, SettingsService settingsService)
|
public ExportSetupViewModel(DialogManager dialogManager, SettingsService settingsService)
|
||||||
|
|
@ -71,18 +65,15 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
|
|
||||||
// Persist preferences
|
// Persist preferences
|
||||||
SelectedFormat = _settingsService.LastExportFormat;
|
SelectedFormat = _settingsService.LastExportFormat;
|
||||||
PartitionLimit = _settingsService.LastPartitionLimit;
|
PartitionLimitValue = _settingsService.LastPartitionLimitValue;
|
||||||
ShouldDownloadMedia = _settingsService.LastShouldDownloadMedia;
|
ShouldDownloadMedia = _settingsService.LastShouldDownloadMedia;
|
||||||
SelectedPartitionFormat = _settingsService.LastPartitionFormat;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Confirm()
|
public void Confirm()
|
||||||
{
|
{
|
||||||
// Persist preferences
|
// Persist preferences
|
||||||
_settingsService.LastExportFormat = SelectedFormat;
|
_settingsService.LastExportFormat = SelectedFormat;
|
||||||
_settingsService.LastPartitionLimit = PartitionLimit;
|
_settingsService.LastPartitionLimitValue = PartitionLimitValue;
|
||||||
_settingsService.LastPartitionFormat = SelectedPartitionFormat;
|
|
||||||
_settingsService.LastShouldDownloadMedia = ShouldDownloadMedia;
|
_settingsService.LastShouldDownloadMedia = ShouldDownloadMedia;
|
||||||
|
|
||||||
// If single channel - prompt file path
|
// If single channel - prompt file path
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Exceptions;
|
using DiscordChatExporter.Core.Exceptions;
|
||||||
using DiscordChatExporter.Core.Exporting;
|
using DiscordChatExporter.Core.Exporting;
|
||||||
|
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using DiscordChatExporter.Gui.Internal;
|
|
||||||
using DiscordChatExporter.Gui.Services;
|
using DiscordChatExporter.Gui.Services;
|
||||||
using DiscordChatExporter.Gui.Utils;
|
using DiscordChatExporter.Gui.Utils;
|
||||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||||
|
|
@ -65,18 +65,16 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
DisplayName = $"{App.Name} v{App.VersionString}";
|
DisplayName = $"{App.Name} v{App.VersionString}";
|
||||||
|
|
||||||
// Update busy state when progress manager changes
|
// Update busy state when progress manager changes
|
||||||
ProgressManager.Bind(o => o.IsActive,
|
ProgressManager.Bind(o => o.IsActive, (_, _) =>
|
||||||
(sender, args) => IsBusy = ProgressManager.IsActive
|
IsBusy = ProgressManager.IsActive
|
||||||
);
|
);
|
||||||
|
|
||||||
ProgressManager.Bind(o => o.IsActive,
|
ProgressManager.Bind(o => o.IsActive, (_, _) =>
|
||||||
(sender, args) => IsProgressIndeterminate =
|
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1)
|
||||||
ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ProgressManager.Bind(o => o.Progress,
|
ProgressManager.Bind(o => o.Progress, (_, _) =>
|
||||||
(sender, args) => IsProgressIndeterminate =
|
IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1)
|
||||||
ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,6 +205,10 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var partitionLimit = !string.IsNullOrWhiteSpace(dialog.PartitionLimitValue)
|
||||||
|
? PartitionLimit.Parse(dialog.PartitionLimitValue)
|
||||||
|
: NullPartitionLimit.Instance;
|
||||||
|
|
||||||
var request = new ExportRequest(
|
var request = new ExportRequest(
|
||||||
dialog.Guild!,
|
dialog.Guild!,
|
||||||
channel!,
|
channel!,
|
||||||
|
|
@ -214,7 +216,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
dialog.SelectedFormat,
|
dialog.SelectedFormat,
|
||||||
dialog.After?.Pipe(Snowflake.FromDate),
|
dialog.After?.Pipe(Snowflake.FromDate),
|
||||||
dialog.Before?.Pipe(Snowflake.FromDate),
|
dialog.Before?.Pipe(Snowflake.FromDate),
|
||||||
CreatePartitioner(),
|
partitionLimit,
|
||||||
dialog.ShouldDownloadMedia,
|
dialog.ShouldDownloadMedia,
|
||||||
_settingsService.ShouldReuseMedia,
|
_settingsService.ShouldReuseMedia,
|
||||||
_settingsService.DateFormat
|
_settingsService.DateFormat
|
||||||
|
|
@ -237,19 +239,6 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
// Notify of overall completion
|
// Notify of overall completion
|
||||||
if (successfulExportCount > 0)
|
if (successfulExportCount > 0)
|
||||||
Notifications.Enqueue($"Successfully exported {successfulExportCount} channel(s)");
|
Notifications.Enqueue($"Successfully exported {successfulExportCount} channel(s)");
|
||||||
|
|
||||||
IPartitioner CreatePartitioner()
|
|
||||||
{
|
|
||||||
var partitionFormat = dialog.SelectedPartitionFormat;
|
|
||||||
var partitionLimit = dialog.PartitionLimit;
|
|
||||||
|
|
||||||
return (partitionFormat, partitionLimit) switch
|
|
||||||
{
|
|
||||||
(PartitionFormat.MessageCount, int messageLimit) => new MessageCountPartitioner(messageLimit),
|
|
||||||
(PartitionFormat.FileSize, int fileSizeLimit) => new FileSizePartitioner(fileSizeLimit),
|
|
||||||
_ => new NullPartitioner()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,43 +126,12 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Partitioning -->
|
<!-- Partitioning -->
|
||||||
<Grid Name="PartitioningGrid">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="1*" />
|
|
||||||
<ColumnDefinition Width="1*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<ComboBox
|
|
||||||
Name="PartitionFormatComboBox"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="0"
|
|
||||||
Margin="16,8"
|
|
||||||
materialDesign:HintAssist.Hint="Partition by"
|
|
||||||
materialDesign:HintAssist.IsFloating="True"
|
|
||||||
IsReadOnly="True"
|
|
||||||
ItemsSource="{Binding AvailablePartitionFormats}"
|
|
||||||
SelectedItem="{Binding SelectedPartitionFormat}">
|
|
||||||
<ComboBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<TextBlock Text="{Binding Converter={x:Static converters:PartitionFormatToStringConverter.Instance}}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</ComboBox.ItemTemplate>
|
|
||||||
</ComboBox>
|
|
||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
Name="PartitionTextBox"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="1"
|
|
||||||
Margin="16,8"
|
Margin="16,8"
|
||||||
materialDesign:HintAssist.Hint="{Binding SelectedPartitionFormat, Converter={x:Static converters:PartitionFormatToTooltipConverter.Instance}}"
|
materialDesign:HintAssist.Hint="Partition limit"
|
||||||
materialDesign:HintAssist.IsFloating="True"
|
materialDesign:HintAssist.IsFloating="True"
|
||||||
Text="{Binding PartitionLimit, TargetNullValue=''}"
|
Text="{Binding PartitionLimitValue}"
|
||||||
ToolTip="{Binding SelectedPartitionFormat, Converter={x:Static converters:PartitionFormatToTooltipConverter.Instance}}" />
|
ToolTip="Split output into partitions, each limited to this number of message (e.g. 100) or file size (e.g. 10mb)" />
|
||||||
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Download media -->
|
<!-- Download media -->
|
||||||
<Grid Margin="16,16" ToolTip="Download referenced media content (user avatars, attached files, embedded images, etc)">
|
<Grid Margin="16,16" ToolTip="Download referenced media content (user avatars, attached files, embedded images, etc)">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue