diff --git a/TwitchDownloaderCLI.Tests/TwitchDownloaderCLI.Tests.csproj b/TwitchDownloaderCLI.Tests/TwitchDownloaderCLI.Tests.csproj
index 553e82c1..9de12c84 100644
--- a/TwitchDownloaderCLI.Tests/TwitchDownloaderCLI.Tests.csproj
+++ b/TwitchDownloaderCLI.Tests/TwitchDownloaderCLI.Tests.csproj
@@ -10,10 +10,16 @@
-
-
-
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/TwitchDownloaderCLI/Models/LogLevel.cs b/TwitchDownloaderCLI/Models/Enums.cs
similarity index 71%
rename from TwitchDownloaderCLI/Models/LogLevel.cs
rename to TwitchDownloaderCLI/Models/Enums.cs
index e472e17a..6b6cd172 100644
--- a/TwitchDownloaderCLI/Models/LogLevel.cs
+++ b/TwitchDownloaderCLI/Models/Enums.cs
@@ -13,4 +13,12 @@ internal enum LogLevel
Error = 1 << 5,
Ffmpeg = 1 << 6,
}
+
+ public enum OverwriteBehavior
+ {
+ Overwrite,
+ Exit,
+ Rename,
+ Prompt,
+ }
}
\ No newline at end of file
diff --git a/TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs
index 1f9c89bc..92c66843 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs
@@ -1,14 +1,19 @@
using CommandLine;
+using TwitchDownloaderCLI.Models;
namespace TwitchDownloaderCLI.Modes.Arguments
{
[Verb("cache", HelpText = "Manage the working cache")]
- internal sealed class CacheArgs : TwitchDownloaderArgs
+ internal sealed class CacheArgs : ITwitchDownloaderArgs
{
[Option('c', "clear", Default = false, Required = false, HelpText = "Clears the default cache folder.")]
public bool ClearCache { get; set; }
[Option("force-clear", Default = false, Required = false, HelpText = "Clears the default cache folder, bypassing the confirmation prompt")]
public bool ForceClearCache { get; set; }
+
+ // Interface args
+ public bool? ShowBanner { get; set; }
+ public LogLevel LogLevel { get; set; }
}
}
diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs
index 2d4eaadb..f062be49 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs
@@ -5,7 +5,7 @@
namespace TwitchDownloaderCLI.Modes.Arguments
{
[Verb("chatdownload", HelpText = "Downloads the chat from a VOD or clip")]
- internal sealed class ChatDownloadArgs : TwitchDownloaderArgs
+ internal sealed class ChatDownloadArgs : IFileCollisionArgs, ITwitchDownloaderArgs
{
[Option('u', "id", Required = true, HelpText = "The ID or URL of the VOD or clip to download that chat of.")]
public string Id { get; set; }
@@ -45,5 +45,10 @@ internal sealed class ChatDownloadArgs : TwitchDownloaderArgs
[Option("temp-path", Default = "", HelpText = "Path to temporary folder to use for cache.")]
public string TempFolder { get; set; }
+
+ // Interface args
+ public OverwriteBehavior OverwriteBehavior { get; set; }
+ public bool? ShowBanner { get; set; }
+ public LogLevel LogLevel { get; set; }
}
}
\ No newline at end of file
diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs
index f8afe990..188554b8 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs
@@ -4,7 +4,7 @@
namespace TwitchDownloaderCLI.Modes.Arguments
{
[Verb("chatrender", HelpText = "Renders a chat JSON as a video")]
- internal sealed class ChatRenderArgs : TwitchDownloaderArgs
+ internal sealed class ChatRenderArgs : IFileCollisionArgs, ITwitchDownloaderArgs
{
[Option('i', "input", Required = true, HelpText = "Path to JSON chat file input.")]
public string InputFile { get; set; }
@@ -152,5 +152,10 @@ internal sealed class ChatRenderArgs : TwitchDownloaderArgs
[Option("scale-highlight-indent", Default = 1.0, HelpText = "Number to scale highlight indent size (sub messages).")]
public double ScaleAccentIndent { get; set; }
+
+ // Interface args
+ public OverwriteBehavior OverwriteBehavior { get; set; }
+ public bool? ShowBanner { get; set; }
+ public LogLevel LogLevel { get; set; }
}
}
\ No newline at end of file
diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs
index 7818dc6d..5f5f9275 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs
@@ -5,7 +5,7 @@
namespace TwitchDownloaderCLI.Modes.Arguments
{
[Verb("chatupdate", HelpText = "Updates the embedded emotes, badges, bits, and trims a chat JSON and/or converts a JSON chat to another format.")]
- internal sealed class ChatUpdateArgs : TwitchDownloaderArgs
+ internal sealed class ChatUpdateArgs : IFileCollisionArgs, ITwitchDownloaderArgs
{
[Option('i', "input", Required = true, HelpText = "Path to input file. Valid extensions are: .json, .json.gz.")]
public string InputFile { get; set; }
@@ -13,7 +13,7 @@ internal sealed class ChatUpdateArgs : TwitchDownloaderArgs
[Option('o', "output", Required = true, HelpText = "Path to output file. File extension will be used to determine new chat type. Valid extensions are: .json, .html, and .txt.")]
public string OutputFile { get; set; }
- [Option('c', "compression", Default = ChatCompression.None, HelpText = "Compresses an output json chat file using a specified compression, usually resulting in 40-90% size reductions. Valid values are: None, Gzip.")]
+ [Option("compression", Default = ChatCompression.None, HelpText = "Compresses an output json chat file using a specified compression, usually resulting in 40-90% size reductions. Valid values are: None, Gzip.")]
public ChatCompression Compression { get; set; }
[Option('E', "embed-missing", Default = false, HelpText = "Embeds missing emotes, badges, and cheermotes. Already embedded images will be untouched.")]
@@ -42,5 +42,10 @@ internal sealed class ChatUpdateArgs : TwitchDownloaderArgs
[Option("temp-path", Default = "", HelpText = "Path to temporary folder to use for cache.")]
public string TempFolder { get; set; }
+
+ // Interface args
+ public OverwriteBehavior OverwriteBehavior { get; set; }
+ public bool? ShowBanner { get; set; }
+ public LogLevel LogLevel { get; set; }
}
}
diff --git a/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs
index ebbdf978..1b901f60 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs
@@ -1,9 +1,10 @@
using CommandLine;
+using TwitchDownloaderCLI.Models;
namespace TwitchDownloaderCLI.Modes.Arguments
{
[Verb("clipdownload", HelpText = "Downloads a clip from Twitch")]
- internal sealed class ClipDownloadArgs : TwitchDownloaderArgs
+ internal sealed class ClipDownloadArgs : IFileCollisionArgs, ITwitchDownloaderArgs
{
[Option('u', "id", Required = true, HelpText = "The ID or URL of the clip to download.")]
public string Id { get; set; }
@@ -25,5 +26,10 @@ internal sealed class ClipDownloadArgs : TwitchDownloaderArgs
[Option("temp-path", Default = "", HelpText = "Path to temporary caching folder.")]
public string TempFolder { get; set; }
+
+ // Interface args
+ public OverwriteBehavior OverwriteBehavior { get; set; }
+ public bool? ShowBanner { get; set; }
+ public LogLevel LogLevel { get; set; }
}
}
\ No newline at end of file
diff --git a/TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs
index c3988428..2d2ff505 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs
@@ -1,11 +1,16 @@
using CommandLine;
+using TwitchDownloaderCLI.Models;
namespace TwitchDownloaderCLI.Modes.Arguments
{
[Verb("ffmpeg", HelpText = "Manage standalone ffmpeg")]
- internal sealed class FfmpegArgs : TwitchDownloaderArgs
+ internal sealed class FfmpegArgs : ITwitchDownloaderArgs
{
[Option('d', "download", Default = false, Required = false, HelpText = "Downloads FFmpeg as a standalone file.")]
public bool DownloadFfmpeg { get; set; }
+
+ // Interface args
+ public bool? ShowBanner { get; set; }
+ public LogLevel LogLevel { get; set; }
}
}
diff --git a/TwitchDownloaderCLI/Modes/Arguments/IFileCollisionArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/IFileCollisionArgs.cs
new file mode 100644
index 00000000..9fa70774
--- /dev/null
+++ b/TwitchDownloaderCLI/Modes/Arguments/IFileCollisionArgs.cs
@@ -0,0 +1,11 @@
+using CommandLine;
+using TwitchDownloaderCLI.Models;
+
+namespace TwitchDownloaderCLI.Modes.Arguments
+{
+ internal interface IFileCollisionArgs
+ {
+ [Option("collision", Default = OverwriteBehavior.Prompt, HelpText = "Sets the handling of output file name collisions. Valid values are: Overwrite, Exit, Rename, Prompt.")]
+ public OverwriteBehavior OverwriteBehavior { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TwitchDownloaderCLI/Modes/Arguments/TwitchDownloaderArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ITwitchDownloaderArgs.cs
similarity index 91%
rename from TwitchDownloaderCLI/Modes/Arguments/TwitchDownloaderArgs.cs
rename to TwitchDownloaderCLI/Modes/Arguments/ITwitchDownloaderArgs.cs
index 07945f8e..21b09a3d 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/TwitchDownloaderArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/ITwitchDownloaderArgs.cs
@@ -3,7 +3,7 @@
namespace TwitchDownloaderCLI.Modes.Arguments
{
- internal abstract class TwitchDownloaderArgs
+ internal interface ITwitchDownloaderArgs
{
[Option("banner", Default = true, HelpText = "Displays a banner containing version and copyright information.")]
public bool? ShowBanner { get; set; }
diff --git a/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs
index a19bdf0b..96270d75 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs
@@ -1,14 +1,20 @@
using CommandLine;
+using TwitchDownloaderCLI.Models;
namespace TwitchDownloaderCLI.Modes.Arguments
{
[Verb("tsmerge", HelpText = "Concatenates multiple .ts/.tsv/.tsa/.m2t/.m2ts (MPEG Transport Stream) files into a single file")]
- internal sealed class TsMergeArgs : TwitchDownloaderArgs
+ internal sealed class TsMergeArgs : IFileCollisionArgs, ITwitchDownloaderArgs
{
[Option('i', "input", Required = true, HelpText = "Path a text file containing the absolute paths of the files to concatenate, separated by newlines. M3U/M3U8 is also supported.")]
public string InputList { get; set; }
[Option('o', "output", Required = true, HelpText = "Path to output file.")]
public string OutputFile { get; set; }
+
+ // Interface args
+ public OverwriteBehavior OverwriteBehavior { get; set; }
+ public bool? ShowBanner { get; set; }
+ public LogLevel LogLevel { get; set; }
}
}
diff --git a/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs
index d0b93b6c..2e2e6f0c 100644
--- a/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs
+++ b/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs
@@ -4,7 +4,7 @@
namespace TwitchDownloaderCLI.Modes.Arguments
{
[Verb("videodownload", HelpText = "Downloads a stream VOD from Twitch")]
- internal sealed class VideoDownloadArgs : TwitchDownloaderArgs
+ internal sealed class VideoDownloadArgs : IFileCollisionArgs, ITwitchDownloaderArgs
{
[Option('u', "id", Required = true, HelpText = "The ID or URL of the VOD to download.")]
public string Id { get; set; }
@@ -35,5 +35,10 @@ internal sealed class VideoDownloadArgs : TwitchDownloaderArgs
[Option("temp-path", Default = "", HelpText = "Path to temporary caching folder.")]
public string TempFolder { get; set; }
+
+ // Interface args
+ public OverwriteBehavior OverwriteBehavior { get; set; }
+ public bool? ShowBanner { get; set; }
+ public LogLevel LogLevel { get; set; }
}
}
diff --git a/TwitchDownloaderCLI/Modes/CacheHandler.cs b/TwitchDownloaderCLI/Modes/CacheHandler.cs
index 28953a68..f2f8122b 100644
--- a/TwitchDownloaderCLI/Modes/CacheHandler.cs
+++ b/TwitchDownloaderCLI/Modes/CacheHandler.cs
@@ -27,21 +27,15 @@ private static void PromptClearCache()
Console.WriteLine("Are you sure you want to clear the cache? This should really only be done if the program isn't working correctly.");
while (true)
{
- Console.Write("[Y]es / [N]o: ");
+ Console.Write("[Y] Yes / [N] No: ");
var userInput = Console.ReadLine()!.Trim().ToLower();
switch (userInput)
{
- case "y":
- case "ye":
- case "yes":
+ case "y" or "yes":
ClearTempCache();
return;
- case "n":
- case "no":
+ case "n" or "no":
return;
- default:
- Console.Write("Invalid input. ");
- continue;
}
}
}
diff --git a/TwitchDownloaderCLI/Modes/DownloadChat.cs b/TwitchDownloaderCLI/Modes/DownloadChat.cs
index f0eb9fb8..c799ee01 100644
--- a/TwitchDownloaderCLI/Modes/DownloadChat.cs
+++ b/TwitchDownloaderCLI/Modes/DownloadChat.cs
@@ -16,13 +16,14 @@ internal static void Download(ChatDownloadArgs inputOptions)
{
var progress = new CliTaskProgress(inputOptions.LogLevel);
- var downloadOptions = GetDownloadOptions(inputOptions, progress);
+ var collisionHandler = new FileCollisionHandler(inputOptions);
+ var downloadOptions = GetDownloadOptions(inputOptions, collisionHandler, progress);
var chatDownloader = new ChatDownloader(downloadOptions, progress);
chatDownloader.DownloadAsync(CancellationToken.None).Wait();
}
- private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOptions, ITaskLogger logger)
+ private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger)
{
if (inputOptions.Id is null)
{
@@ -64,7 +65,8 @@ private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOpti
BttvEmotes = (bool)inputOptions.BttvEmotes!,
FfzEmotes = (bool)inputOptions.FfzEmotes!,
StvEmotes = (bool)inputOptions.StvEmotes!,
- TempFolder = inputOptions.TempFolder
+ TempFolder = inputOptions.TempFolder,
+ FileCollisionCallback = collisionHandler.HandleCollisionCallback,
};
return downloadOptions;
diff --git a/TwitchDownloaderCLI/Modes/DownloadClip.cs b/TwitchDownloaderCLI/Modes/DownloadClip.cs
index 8f2f3a28..04da80e7 100644
--- a/TwitchDownloaderCLI/Modes/DownloadClip.cs
+++ b/TwitchDownloaderCLI/Modes/DownloadClip.cs
@@ -21,13 +21,14 @@ internal static void Download(ClipDownloadArgs inputOptions)
FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress);
}
- var downloadOptions = GetDownloadOptions(inputOptions, progress);
+ var collisionHandler = new FileCollisionHandler(inputOptions);
+ var downloadOptions = GetDownloadOptions(inputOptions, collisionHandler, progress);
var clipDownloader = new ClipDownloader(downloadOptions, progress);
clipDownloader.DownloadAsync(new CancellationToken()).Wait();
}
- private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOptions, ITaskLogger logger)
+ private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger)
{
if (inputOptions.Id is null)
{
@@ -50,7 +51,8 @@ private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOpti
ThrottleKib = inputOptions.ThrottleKib,
FfmpegPath = string.IsNullOrWhiteSpace(inputOptions.FfmpegPath) ? FfmpegHandler.FfmpegExecutableName : Path.GetFullPath(inputOptions.FfmpegPath),
EncodeMetadata = inputOptions.EncodeMetadata!.Value,
- TempFolder = inputOptions.TempFolder
+ TempFolder = inputOptions.TempFolder,
+ FileCollisionCallback = collisionHandler.HandleCollisionCallback,
};
return downloadOptions;
diff --git a/TwitchDownloaderCLI/Modes/DownloadVideo.cs b/TwitchDownloaderCLI/Modes/DownloadVideo.cs
index 9a9bc596..0e3bc722 100644
--- a/TwitchDownloaderCLI/Modes/DownloadVideo.cs
+++ b/TwitchDownloaderCLI/Modes/DownloadVideo.cs
@@ -19,13 +19,14 @@ internal static void Download(VideoDownloadArgs inputOptions)
FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress);
- var downloadOptions = GetDownloadOptions(inputOptions, progress);
+ var collisionHandler = new FileCollisionHandler(inputOptions);
+ var downloadOptions = GetDownloadOptions(inputOptions, collisionHandler, progress);
var videoDownloader = new VideoDownloader(downloadOptions, progress);
videoDownloader.DownloadAsync(new CancellationToken()).Wait();
}
- private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOptions, ITaskLogger logger)
+ private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger)
{
if (inputOptions.Id is null)
{
@@ -76,7 +77,8 @@ private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOp
"Run 'TwitchDownloaderCLI cache help' for more information.");
return Array.Empty();
- }
+ },
+ FileCollisionCallback = collisionHandler.HandleCollisionCallback,
};
return downloadOptions;
diff --git a/TwitchDownloaderCLI/Modes/MergeTs.cs b/TwitchDownloaderCLI/Modes/MergeTs.cs
index 2f717304..24e1f502 100644
--- a/TwitchDownloaderCLI/Modes/MergeTs.cs
+++ b/TwitchDownloaderCLI/Modes/MergeTs.cs
@@ -14,18 +14,20 @@ internal static void Merge(TsMergeArgs inputOptions)
progress.LogInfo("The TS merger is experimental and is subject to change without notice in future releases.");
- var mergeOptions = GetMergeOptions(inputOptions);
+ var collisionHandler = new FileCollisionHandler(inputOptions);
+ var mergeOptions = GetMergeOptions(inputOptions, collisionHandler);
var tsMerger = new TsMerger(mergeOptions, progress);
tsMerger.MergeAsync(new CancellationToken()).Wait();
}
- private static TsMergeOptions GetMergeOptions(TsMergeArgs inputOptions)
+ private static TsMergeOptions GetMergeOptions(TsMergeArgs inputOptions, FileCollisionHandler collisionHandler)
{
TsMergeOptions mergeOptions = new()
{
OutputFile = inputOptions.OutputFile,
- InputFile = inputOptions.InputList
+ InputFile = inputOptions.InputList,
+ FileCollisionCallback = collisionHandler.HandleCollisionCallback,
};
return mergeOptions;
diff --git a/TwitchDownloaderCLI/Modes/RenderChat.cs b/TwitchDownloaderCLI/Modes/RenderChat.cs
index 75e8ba03..957f8878 100644
--- a/TwitchDownloaderCLI/Modes/RenderChat.cs
+++ b/TwitchDownloaderCLI/Modes/RenderChat.cs
@@ -19,13 +19,15 @@ internal static void Render(ChatRenderArgs inputOptions)
FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress);
- var renderOptions = GetRenderOptions(inputOptions, progress);
+ var collisionHandler = new FileCollisionHandler(inputOptions);
+ var renderOptions = GetRenderOptions(inputOptions, collisionHandler, progress);
+
using var chatRenderer = new ChatRenderer(renderOptions, progress);
chatRenderer.ParseJsonAsync().Wait();
chatRenderer.RenderVideoAsync(new CancellationToken()).Wait();
}
- private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, ITaskLogger logger)
+ private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger)
{
ChatRenderOptions renderOptions = new()
{
@@ -77,7 +79,7 @@ private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, I
"twitter" or "twemoji" => EmojiVendor.TwitterTwemoji,
"google" or "notocolor" => EmojiVendor.GoogleNotoColor,
"system" or "none" => EmojiVendor.None,
- _ => throw new NotSupportedException("Invalid emoji vendor. Valid values are: 'twitter' / 'twemoji', and 'google' / 'notocolor'")
+ _ => throw new NotSupportedException("Invalid emoji vendor. Valid values are: 'twitter' / 'twemoji', 'google' / 'notocolor', and 'system' / 'none'")
},
SkipDriveWaiting = inputOptions.SkipDriveWaiting,
EmoteScale = inputOptions.ScaleEmote,
@@ -93,6 +95,7 @@ private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, I
DisperseCommentOffsets = inputOptions.DisperseCommentOffsets,
AlternateMessageBackgrounds = inputOptions.AlternateMessageBackgrounds,
AdjustUsernameVisibility = inputOptions.AdjustUsernameVisibility,
+ FileCollisionCallback = collisionHandler.HandleCollisionCallback,
};
if (renderOptions.GenerateMask && renderOptions.BackgroundColor.Alpha == 255 && !(renderOptions.AlternateMessageBackgrounds! && renderOptions.AlternateBackgroundColor.Alpha != 255))
diff --git a/TwitchDownloaderCLI/Modes/UpdateChat.cs b/TwitchDownloaderCLI/Modes/UpdateChat.cs
index 2dd7b0a0..dd17007d 100644
--- a/TwitchDownloaderCLI/Modes/UpdateChat.cs
+++ b/TwitchDownloaderCLI/Modes/UpdateChat.cs
@@ -16,14 +16,15 @@ internal static void Update(ChatUpdateArgs inputOptions)
{
var progress = new CliTaskProgress(inputOptions.LogLevel);
- var updateOptions = GetUpdateOptions(inputOptions, progress);
+ var collisionHandler = new FileCollisionHandler(inputOptions);
+ var updateOptions = GetUpdateOptions(inputOptions, collisionHandler, progress);
var chatUpdater = new ChatUpdater(updateOptions, progress);
chatUpdater.ParseJsonAsync().Wait();
chatUpdater.UpdateAsync(new CancellationToken()).Wait();
}
- private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, ITaskLogger logger)
+ private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger)
{
if (!File.Exists(inputOptions.InputFile))
{
@@ -76,7 +77,8 @@ private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, I
FfzEmotes = (bool)inputOptions.FfzEmotes!,
StvEmotes = (bool)inputOptions.StvEmotes!,
TextTimestampFormat = inputOptions.TimeFormat,
- TempFolder = inputOptions.TempFolder
+ TempFolder = inputOptions.TempFolder,
+ FileCollisionCallback = collisionHandler.HandleCollisionCallback,
};
return updateOptions;
diff --git a/TwitchDownloaderCLI/Program.cs b/TwitchDownloaderCLI/Program.cs
index 3149b62d..070da004 100644
--- a/TwitchDownloaderCLI/Program.cs
+++ b/TwitchDownloaderCLI/Program.cs
@@ -29,7 +29,7 @@ private static void Main(string[] args)
parserResult.WithNotParsed(errors => WriteHelpText(errors, parserResult, parser.Settings));
CoreLicensor.EnsureFilesExist(AppContext.BaseDirectory);
- WriteApplicationBanner((TwitchDownloaderArgs)parserResult.Value);
+ WriteApplicationBanner((ITwitchDownloaderArgs)parserResult.Value);
parserResult
.WithParsed(DownloadVideo.Download)
@@ -74,7 +74,7 @@ private static void WriteHelpText(IEnumerable errors, ParserResult
diff --git a/TwitchDownloaderWPF/TwitchTasks/ChatDownloadTask.cs b/TwitchDownloaderWPF/TwitchTasks/ChatDownloadTask.cs
index 45e00a97..e7846026 100644
--- a/TwitchDownloaderWPF/TwitchTasks/ChatDownloadTask.cs
+++ b/TwitchDownloaderWPF/TwitchTasks/ChatDownloadTask.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -17,24 +18,14 @@ class ChatDownloadTask : ITwitchTask
public int Progress
{
get => _progress;
- set
- {
- if (value == _progress) return;
- _progress = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _progress, value);
}
private TwitchTaskStatus _status = TwitchTaskStatus.Ready;
public TwitchTaskStatus Status
{
get => _status;
- private set
- {
- if (value == _status) return;
- _status = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _status, value);
}
public ChatDownloadOptions DownloadOptions { get; init; }
@@ -46,12 +37,7 @@ private set
public TwitchTaskException Exception
{
get => _exception;
- private set
- {
- if (Equals(value, _exception)) return;
- _exception = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _exception, value);
}
public string OutputFile => DownloadOptions.Filename;
@@ -60,12 +46,7 @@ private set
public bool CanCancel
{
get => _canCancel;
- private set
- {
- if (value == _canCancel) return;
- _canCancel = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _canCancel, value);
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -147,5 +128,13 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
+ private bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value)) return false;
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
}
}
diff --git a/TwitchDownloaderWPF/TwitchTasks/ChatRenderTask.cs b/TwitchDownloaderWPF/TwitchTasks/ChatRenderTask.cs
index 26a70ba8..6bd991d3 100644
--- a/TwitchDownloaderWPF/TwitchTasks/ChatRenderTask.cs
+++ b/TwitchDownloaderWPF/TwitchTasks/ChatRenderTask.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -17,24 +18,14 @@ class ChatRenderTask : ITwitchTask
public int Progress
{
get => _progress;
- set
- {
- if (value == _progress) return;
- _progress = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _progress, value);
}
private TwitchTaskStatus _status = TwitchTaskStatus.Ready;
public TwitchTaskStatus Status
{
get => _status;
- private set
- {
- if (value == _status) return;
- _status = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _status, value);
}
public ChatRenderOptions DownloadOptions { get; init; }
@@ -46,12 +37,7 @@ private set
public TwitchTaskException Exception
{
get => _exception;
- private set
- {
- if (Equals(value, _exception)) return;
- _exception = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _exception, value);
}
public string OutputFile => DownloadOptions.OutputFile;
@@ -60,12 +46,7 @@ private set
public bool CanCancel
{
get => _canCancel;
- private set
- {
- if (value == _canCancel) return;
- _canCancel = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _canCancel, value);
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -166,5 +147,13 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
+ private bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value)) return false;
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
}
}
diff --git a/TwitchDownloaderWPF/TwitchTasks/ChatUpdateTask.cs b/TwitchDownloaderWPF/TwitchTasks/ChatUpdateTask.cs
index d523cbea..48648be6 100644
--- a/TwitchDownloaderWPF/TwitchTasks/ChatUpdateTask.cs
+++ b/TwitchDownloaderWPF/TwitchTasks/ChatUpdateTask.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -17,24 +18,14 @@ class ChatUpdateTask : ITwitchTask
public int Progress
{
get => _progress;
- set
- {
- if (value == _progress) return;
- _progress = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _progress, value);
}
private TwitchTaskStatus _status = TwitchTaskStatus.Ready;
public TwitchTaskStatus Status
{
get => _status;
- private set
- {
- if (value == _status) return;
- _status = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _status, value);
}
public ChatUpdateOptions UpdateOptions { get; init; }
@@ -46,12 +37,7 @@ private set
public TwitchTaskException Exception
{
get => _exception;
- private set
- {
- if (Equals(value, _exception)) return;
- _exception = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _exception, value);
}
public string OutputFile => UpdateOptions.OutputFile;
@@ -60,12 +46,7 @@ private set
public bool CanCancel
{
get => _canCancel;
- private set
- {
- if (value == _canCancel) return;
- _canCancel = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _canCancel, value);
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -148,5 +129,13 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
+ private bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value)) return false;
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
}
}
diff --git a/TwitchDownloaderWPF/TwitchTasks/ClipDownloadTask.cs b/TwitchDownloaderWPF/TwitchTasks/ClipDownloadTask.cs
index c8ddfbcc..b5ea1a73 100644
--- a/TwitchDownloaderWPF/TwitchTasks/ClipDownloadTask.cs
+++ b/TwitchDownloaderWPF/TwitchTasks/ClipDownloadTask.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -17,24 +18,14 @@ class ClipDownloadTask : ITwitchTask
public int Progress
{
get => _progress;
- set
- {
- if (value == _progress) return;
- _progress = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _progress, value);
}
private TwitchTaskStatus _status = TwitchTaskStatus.Ready;
public TwitchTaskStatus Status
{
get => _status;
- private set
- {
- if (value == _status) return;
- _status = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _status, value);
}
public ClipDownloadOptions DownloadOptions { get; init; }
@@ -46,12 +37,7 @@ private set
public TwitchTaskException Exception
{
get => _exception;
- private set
- {
- if (Equals(value, _exception)) return;
- _exception = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _exception, value);
}
public string OutputFile => DownloadOptions.Filename;
@@ -60,12 +46,7 @@ private set
public bool CanCancel
{
get => _canCancel;
- private set
- {
- if (value == _canCancel) return;
- _canCancel = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _canCancel, value);
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -147,5 +128,13 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
+ private bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value)) return false;
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
}
}
diff --git a/TwitchDownloaderWPF/TwitchTasks/ITwitchTask.cs b/TwitchDownloaderWPF/TwitchTasks/ITwitchTask.cs
index 6445ca35..118e90dc 100644
--- a/TwitchDownloaderWPF/TwitchTasks/ITwitchTask.cs
+++ b/TwitchDownloaderWPF/TwitchTasks/ITwitchTask.cs
@@ -18,7 +18,7 @@ public enum TwitchTaskStatus
public interface ITwitchTask : INotifyPropertyChanged
{
TaskData Info { get; set; }
- int Progress { get; set; }
+ int Progress { get; }
TwitchTaskStatus Status { get; }
CancellationTokenSource TokenSource { get; set; }
ITwitchTask DependantTask { get; set; }
diff --git a/TwitchDownloaderWPF/TwitchTasks/VodDownloadTask.cs b/TwitchDownloaderWPF/TwitchTasks/VodDownloadTask.cs
index 3f0447e5..111693f0 100644
--- a/TwitchDownloaderWPF/TwitchTasks/VodDownloadTask.cs
+++ b/TwitchDownloaderWPF/TwitchTasks/VodDownloadTask.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -17,24 +18,14 @@ class VodDownloadTask : ITwitchTask
public int Progress
{
get => _progress;
- set
- {
- if (value == _progress) return;
- _progress = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _progress, value);
}
private TwitchTaskStatus _status = TwitchTaskStatus.Ready;
public TwitchTaskStatus Status
{
get => _status;
- private set
- {
- if (value == _status) return;
- _status = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _status, value);
}
public VideoDownloadOptions DownloadOptions { get; init; }
@@ -46,12 +37,7 @@ private set
public TwitchTaskException Exception
{
get => _exception;
- private set
- {
- if (Equals(value, _exception)) return;
- _exception = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _exception, value);
}
public string OutputFile => DownloadOptions.Filename;
@@ -60,12 +46,7 @@ private set
public bool CanCancel
{
get => _canCancel;
- private set
- {
- if (value == _canCancel) return;
- _canCancel = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _canCancel, value);
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -147,5 +128,13 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
+ private bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value)) return false;
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
}
}
diff --git a/TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml.cs b/TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml.cs
index 03d914c0..9ca8485f 100644
--- a/TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml.cs
+++ b/TwitchDownloaderWPF/WindowOldVideoCacheManager.xaml.cs
@@ -122,12 +122,7 @@ public GridItem(DirectoryInfo directoryInfo)
public bool ShouldDelete
{
get => _shouldDelete;
- set
- {
- if (value == _shouldDelete) return;
- _shouldDelete = value;
- OnPropertyChanged();
- }
+ set => SetField(ref _shouldDelete, value);
}
public int Age { get; }
@@ -139,12 +134,7 @@ public bool ShouldDelete
public string Size
{
get => _size;
- private set
- {
- if (value == _size) return;
- _size = value;
- OnPropertyChanged();
- }
+ private set => SetField(ref _size, value);
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -154,6 +144,14 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+ private bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value)) return false;
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
+
public long CalculateSize()
{
var sizeBytes = Directory.EnumerateFiles().Sum(file => file.Length);
diff --git a/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs b/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs
index 19ed1a9b..366265e7 100644
--- a/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs
+++ b/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs
@@ -100,6 +100,11 @@ public WindowQueueOptions(List dataList)
textFolder.Text = queueFolder;
}
+ private FileInfo HandleFileCollisionCallback(FileInfo fileInfo)
+ {
+ return Dispatcher.Invoke(() => FileCollisionService.HandleCollisionCallback(fileInfo, Application.Current.MainWindow));
+ }
+
private void btnQueue_Click(object sender, RoutedEventArgs e)
{
if (_parentPage != null)
@@ -114,6 +119,8 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
}
VideoDownloadOptions downloadOptions = vodDownloadPage.GetOptions(null, textFolder.Text);
+ downloadOptions.FileCollisionCallback = HandleFileCollisionCallback;
+
VodDownloadTask downloadTask = new VodDownloadTask
{
DownloadOptions = downloadOptions,
@@ -142,6 +149,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
chatOptions.DownloadFormat = ChatFormat.Text;
chatOptions.EmbedData = checkEmbed.IsChecked.GetValueOrDefault();
chatOptions.Filename = Path.Combine(folderPath, Path.GetFileNameWithoutExtension(downloadOptions.Filename) + "." + chatOptions.DownloadFormat);
+ chatOptions.FileCollisionCallback = HandleFileCollisionCallback;
if (downloadOptions.TrimBeginning)
{
@@ -180,6 +188,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
renderOptions.OutputFile = Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower());
}
renderOptions.InputFile = chatOptions.Filename;
+ renderOptions.FileCollisionCallback = HandleFileCollisionCallback;
ChatRenderTask renderTask = new ChatRenderTask
{
@@ -224,7 +233,8 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
: -1,
TempFolder = Settings.Default.TempPath,
EncodeMetadata = clipDownloadPage.CheckMetadata.IsChecked!.Value,
- FfmpegPath = "ffmpeg"
+ FfmpegPath = "ffmpeg",
+ FileCollisionCallback = HandleFileCollisionCallback,
};
ClipDownloadTask downloadTask = new ClipDownloadTask
@@ -258,6 +268,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
chatOptions.Filename = Path.Combine(folderPath, FilenameService.GetFilename(Settings.Default.TemplateChat, downloadTask.Info.Title, chatOptions.Id,
clipDownloadPage.currentVideoTime, clipDownloadPage.textStreamer.Text, TimeSpan.Zero, clipDownloadPage.clipLength,
clipDownloadPage.viewCount.ToString(), clipDownloadPage.game) + "." + chatOptions.FileExtension);
+ chatOptions.FileCollisionCallback = HandleFileCollisionCallback;
ChatDownloadTask chatTask = new ChatDownloadTask
{
@@ -284,6 +295,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
renderOptions.OutputFile = Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower());
}
renderOptions.InputFile = chatOptions.Filename;
+ renderOptions.FileCollisionCallback = HandleFileCollisionCallback;
ChatRenderTask renderTask = new ChatRenderTask
{
@@ -322,6 +334,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
chatOptions.TrimBeginning ? TimeSpan.FromSeconds(chatOptions.TrimBeginningTime) : TimeSpan.Zero,
chatOptions.TrimEnding ? TimeSpan.FromSeconds(chatOptions.TrimEndingTime) : chatDownloadPage.vodLength,
chatDownloadPage.viewCount.ToString(), chatDownloadPage.game) + "." + chatOptions.FileExtension);
+ chatOptions.FileCollisionCallback = HandleFileCollisionCallback;
ChatDownloadTask chatTask = new ChatDownloadTask
{
@@ -343,6 +356,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
{
ChatRenderOptions renderOptions = MainWindow.pageChatRender.GetOptions(Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), '.' + MainWindow.pageChatRender.comboFormat.Text.ToLower()));
renderOptions.InputFile = chatOptions.Filename;
+ renderOptions.FileCollisionCallback = HandleFileCollisionCallback;
ChatRenderTask renderTask = new ChatRenderTask
{
@@ -380,6 +394,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
chatOptions.TrimBeginning ? TimeSpan.FromSeconds(chatOptions.TrimBeginningTime) : TimeSpan.Zero,
chatOptions.TrimEnding ? TimeSpan.FromSeconds(chatOptions.TrimEndingTime) : chatUpdatePage.VideoLength,
chatUpdatePage.ViewCount.ToString(), chatUpdatePage.Game) + "." + chatOptions.FileExtension);
+ chatOptions.FileCollisionCallback = HandleFileCollisionCallback;
ChatUpdateTask chatTask = new ChatUpdateTask
{
@@ -415,6 +430,8 @@ private void btnQueue_Click(object sender, RoutedEventArgs e)
string filePath = Path.Combine(folderPath, Path.GetFileNameWithoutExtension(fileName) + "." + fileFormat.ToLower());
ChatRenderOptions renderOptions = MainWindow.pageChatRender.GetOptions(filePath);
renderOptions.InputFile = fileName;
+ renderOptions.FileCollisionCallback = HandleFileCollisionCallback;
+
ChatRenderTask renderTask = new ChatRenderTask
{
DownloadOptions = renderOptions,
@@ -472,7 +489,8 @@ private void EnqueueDataList()
DownloadThreads = Settings.Default.VodDownloadThreads,
ThrottleKib = Settings.Default.DownloadThrottleEnabled
? Settings.Default.MaximumBandwidthKib
- : -1
+ : -1,
+ FileCollisionCallback = HandleFileCollisionCallback,
};
downloadOptions.Filename = Path.Combine(folderPath, FilenameService.GetFilename(Settings.Default.TemplateVod, taskData.Title, taskData.Id, taskData.Time, taskData.Streamer,
downloadOptions.TrimBeginning ? downloadOptions.TrimBeginningTime : TimeSpan.Zero,
@@ -508,7 +526,8 @@ private void EnqueueDataList()
: -1,
TempFolder = Settings.Default.TempPath,
EncodeMetadata = Settings.Default.EncodeClipMetadata,
- FfmpegPath = "ffmpeg"
+ FfmpegPath = "ffmpeg",
+ FileCollisionCallback = HandleFileCollisionCallback,
};
ClipDownloadTask downloadTask = new ClipDownloadTask
@@ -538,7 +557,8 @@ private void EnqueueDataList()
TimeFormat = TimestampFormat.Relative,
Id = taskData.Id,
TrimBeginning = false,
- TrimEnding = false
+ TrimEnding = false,
+ FileCollisionCallback = HandleFileCollisionCallback,
};
if (radioJson.IsChecked == true)
downloadOptions.DownloadFormat = ChatFormat.Json;
@@ -577,6 +597,7 @@ private void EnqueueDataList()
renderOptions.OutputFile = Path.ChangeExtension(downloadOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower());
}
renderOptions.InputFile = downloadOptions.Filename;
+ renderOptions.FileCollisionCallback = HandleFileCollisionCallback;
ChatRenderTask renderTask = new ChatRenderTask
{