mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-06-07 18:04:41 -04:00
Stop redundantly downloading media when re-exporting (#395)
This commit is contained in:
parent
949c9d3f1e
commit
520e023aff
9 changed files with 70 additions and 8 deletions
|
@ -29,6 +29,9 @@ namespace DiscordChatExporter.Cli.Commands.Base
|
||||||
[CommandOption("media", Description = "Download referenced media content.")]
|
[CommandOption("media", Description = "Download referenced media content.")]
|
||||||
public bool ShouldDownloadMedia { get; set; }
|
public bool ShouldDownloadMedia { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("reuse-media", Description = "If the media folder already exists, reuse media inside it to skip downloads.")]
|
||||||
|
public bool ShouldReuseMedia { get; set; }
|
||||||
|
|
||||||
[CommandOption("dateformat", Description = "Date format used in output.")]
|
[CommandOption("dateformat", Description = "Date format used in output.")]
|
||||||
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
|
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
|
||||||
|
|
||||||
|
@ -48,6 +51,7 @@ namespace DiscordChatExporter.Cli.Commands.Base
|
||||||
Before,
|
Before,
|
||||||
PartitionLimit,
|
PartitionLimit,
|
||||||
ShouldDownloadMedia,
|
ShouldDownloadMedia,
|
||||||
|
ShouldReuseMedia,
|
||||||
DateFormat
|
DateFormat
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ namespace DiscordChatExporter.Cli.Commands.Base
|
||||||
Before,
|
Before,
|
||||||
PartitionLimit,
|
PartitionLimit,
|
||||||
ShouldDownloadMedia,
|
ShouldDownloadMedia,
|
||||||
|
ShouldReuseMedia,
|
||||||
DateFormat
|
DateFormat
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
Channels = channels;
|
Channels = channels;
|
||||||
Roles = roles;
|
Roles = roles;
|
||||||
|
|
||||||
_mediaDownloader = new MediaDownloader(request.OutputMediaDirPath);
|
_mediaDownloader = new MediaDownloader(request.OutputMediaDirPath, request.ShouldReuseMedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FormatDate(DateTimeOffset date) => Request.DateFormat switch
|
public string FormatDate(DateTimeOffset date) => Request.DateFormat switch
|
||||||
|
|
|
@ -30,6 +30,8 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
|
|
||||||
public bool ShouldDownloadMedia { get; }
|
public bool ShouldDownloadMedia { get; }
|
||||||
|
|
||||||
|
public bool ShouldReuseMedia { get; }
|
||||||
|
|
||||||
public string DateFormat { get; }
|
public string DateFormat { get; }
|
||||||
|
|
||||||
public ExportRequest(
|
public ExportRequest(
|
||||||
|
@ -41,6 +43,7 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
DateTimeOffset? before,
|
DateTimeOffset? before,
|
||||||
int? partitionLimit,
|
int? partitionLimit,
|
||||||
bool shouldDownloadMedia,
|
bool shouldDownloadMedia,
|
||||||
|
bool shouldReuseMedia,
|
||||||
string dateFormat)
|
string dateFormat)
|
||||||
{
|
{
|
||||||
Guild = guild;
|
Guild = guild;
|
||||||
|
@ -51,6 +54,7 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
Before = before;
|
Before = before;
|
||||||
PartitionLimit = partitionLimit;
|
PartitionLimit = partitionLimit;
|
||||||
ShouldDownloadMedia = shouldDownloadMedia;
|
ShouldDownloadMedia = shouldDownloadMedia;
|
||||||
|
ShouldReuseMedia = shouldReuseMedia;
|
||||||
DateFormat = dateFormat;
|
DateFormat = dateFormat;
|
||||||
|
|
||||||
OutputBaseFilePath = GetOutputBaseFilePath(
|
OutputBaseFilePath = GetOutputBaseFilePath(
|
||||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Domain.Internal;
|
using DiscordChatExporter.Domain.Internal;
|
||||||
|
@ -16,13 +18,16 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient = Singleton.HttpClient;
|
private readonly HttpClient _httpClient = Singleton.HttpClient;
|
||||||
private readonly string _workingDirPath;
|
private readonly string _workingDirPath;
|
||||||
|
|
||||||
|
private readonly bool _reuseMedia;
|
||||||
private readonly AsyncRetryPolicy _httpRequestPolicy;
|
private readonly AsyncRetryPolicy _httpRequestPolicy;
|
||||||
|
|
||||||
private readonly Dictionary<string, string> _pathMap = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> _pathMap = new Dictionary<string, string>();
|
||||||
|
|
||||||
public MediaDownloader(string workingDirPath)
|
public MediaDownloader(string workingDirPath, bool reuseMedia)
|
||||||
{
|
{
|
||||||
_workingDirPath = workingDirPath;
|
_workingDirPath = workingDirPath;
|
||||||
|
_reuseMedia = reuseMedia;
|
||||||
|
|
||||||
_httpRequestPolicy = Policy
|
_httpRequestPolicy = Policy
|
||||||
.Handle<IOException>()
|
.Handle<IOException>()
|
||||||
|
@ -37,11 +42,18 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
return cachedFilePath;
|
return cachedFilePath;
|
||||||
|
|
||||||
var fileName = GetFileNameFromUrl(url);
|
var fileName = GetFileNameFromUrl(url);
|
||||||
var filePath = PathEx.MakeUniqueFilePath(Path.Combine(_workingDirPath, fileName));
|
var filePath = Path.Combine(_workingDirPath, fileName);
|
||||||
|
|
||||||
|
if (!_reuseMedia)
|
||||||
|
{
|
||||||
|
filePath = PathEx.MakeUniqueFilePath(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_reuseMedia || !File.Exists(filePath))
|
||||||
|
{
|
||||||
Directory.CreateDirectory(_workingDirPath);
|
Directory.CreateDirectory(_workingDirPath);
|
||||||
|
|
||||||
await _httpClient.DownloadAsync(url, filePath);
|
await _httpClient.DownloadAsync(url, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
return _pathMap[url] = filePath;
|
return _pathMap[url] = filePath;
|
||||||
});
|
});
|
||||||
|
@ -50,6 +62,23 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
|
|
||||||
internal partial class MediaDownloader
|
internal partial class MediaDownloader
|
||||||
{
|
{
|
||||||
|
private static int URL_HASH_LENGTH = 5;
|
||||||
|
private static string HashUrl(string url)
|
||||||
|
{
|
||||||
|
using (var md5 = MD5.Create())
|
||||||
|
{
|
||||||
|
var inputBytes = Encoding.UTF8.GetBytes(url);
|
||||||
|
var hashBytes = md5.ComputeHash(inputBytes);
|
||||||
|
|
||||||
|
var hashBuilder = new StringBuilder();
|
||||||
|
for (int i = 0; i < hashBytes.Length; i++)
|
||||||
|
{
|
||||||
|
hashBuilder.Append(hashBytes[i].ToString("X2"));
|
||||||
|
}
|
||||||
|
return hashBuilder.ToString().Truncate(URL_HASH_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetRandomFileName() => Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
|
private static string GetRandomFileName() => Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
|
||||||
|
|
||||||
private static string GetFileNameFromUrl(string url)
|
private static string GetFileNameFromUrl(string url)
|
||||||
|
@ -57,7 +86,7 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
var originalFileName = Regex.Match(url, @".+/([^?]*)").Groups[1].Value;
|
var originalFileName = Regex.Match(url, @".+/([^?]*)").Groups[1].Value;
|
||||||
|
|
||||||
var fileName = !string.IsNullOrWhiteSpace(originalFileName)
|
var fileName = !string.IsNullOrWhiteSpace(originalFileName)
|
||||||
? $"{Path.GetFileNameWithoutExtension(originalFileName).Truncate(50)}{Path.GetExtension(originalFileName)}"
|
? $"{Path.GetFileNameWithoutExtension(originalFileName).Truncate(42)}-({HashUrl(url)}){Path.GetExtension(originalFileName)}"
|
||||||
: GetRandomFileName();
|
: GetRandomFileName();
|
||||||
|
|
||||||
return PathEx.EscapePath(fileName);
|
return PathEx.EscapePath(fileName);
|
||||||
|
|
|
@ -16,6 +16,8 @@ namespace DiscordChatExporter.Gui.Services
|
||||||
|
|
||||||
public int ParallelLimit { get; set; } = 1;
|
public int ParallelLimit { get; set; } = 1;
|
||||||
|
|
||||||
|
public bool ShouldReuseMedia { get; set; } = false;
|
||||||
|
|
||||||
public AuthToken? LastToken { get; set; }
|
public AuthToken? LastToken { get; set; }
|
||||||
|
|
||||||
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||||
|
|
|
@ -38,6 +38,12 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
set => _settingsService.ParallelLimit = value.Clamp(1, 10);
|
set => _settingsService.ParallelLimit = value.Clamp(1, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ShouldReuseMedia
|
||||||
|
{
|
||||||
|
get => _settingsService.ShouldReuseMedia;
|
||||||
|
set => _settingsService.ShouldReuseMedia = value;
|
||||||
|
}
|
||||||
|
|
||||||
public SettingsViewModel(SettingsService settingsService)
|
public SettingsViewModel(SettingsService settingsService)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
|
|
|
@ -204,6 +204,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
dialog.Before,
|
dialog.Before,
|
||||||
dialog.PartitionLimit,
|
dialog.PartitionLimit,
|
||||||
dialog.ShouldDownloadMedia,
|
dialog.ShouldDownloadMedia,
|
||||||
|
_settingsService.ShouldReuseMedia,
|
||||||
_settingsService.DateFormat
|
_settingsService.DateFormat
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:s="https://github.com/canton7/Stylet"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
Width="300"
|
Width="310"
|
||||||
d:DataContext="{d:DesignInstance Type=dialogs:SettingsViewModel}"
|
d:DataContext="{d:DesignInstance Type=dialogs:SettingsViewModel}"
|
||||||
Style="{DynamicResource MaterialDesignRoot}"
|
Style="{DynamicResource MaterialDesignRoot}"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
@ -65,6 +65,21 @@
|
||||||
IsChecked="{Binding IsTokenPersisted}" />
|
IsChecked="{Binding IsTokenPersisted}" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
|
<!-- Reuse Media -->
|
||||||
|
<DockPanel
|
||||||
|
Background="Transparent"
|
||||||
|
LastChildFill="False"
|
||||||
|
ToolTip="If the media folder already exists, reuse media inside it to skip downloads">
|
||||||
|
<TextBlock
|
||||||
|
Margin="16,8"
|
||||||
|
DockPanel.Dock="Left"
|
||||||
|
Text="Reuse previously downloaded media" />
|
||||||
|
<ToggleButton
|
||||||
|
Margin="16,8"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
IsChecked="{Binding ShouldReuseMedia}" />
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
<!-- Date format -->
|
<!-- Date format -->
|
||||||
<TextBox
|
<TextBox
|
||||||
Margin="16,8"
|
Margin="16,8"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue