mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Fixed copying with multiple conflict handling flags,
made canceling and pausing of tasks more robust
This commit is contained in:
@@ -50,7 +50,7 @@ abstract class BackgroundTask
|
||||
|
||||
public bool Ended => Finished || Aborted;
|
||||
|
||||
public bool DeleteWhenStopped { get; set; }
|
||||
public bool DeleteWhenEnded { get; set; }
|
||||
|
||||
public abstract RunResult Run();
|
||||
|
||||
@@ -63,10 +63,7 @@ abstract class BackgroundTask
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (State == .Ready || State == .Running)
|
||||
{
|
||||
State = .Paused;
|
||||
}
|
||||
_taskManager.Pause(this);
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
|
||||
@@ -20,6 +20,8 @@ class BackgroundTaskManager
|
||||
private append BackgroundTaskList _waitingTasks = .();
|
||||
private append Monitor _queueLock = .();
|
||||
|
||||
private append Dictionary<BackgroundTask, BackgroundTask.RunState> _deferredStateChanges = .();
|
||||
|
||||
public void Init()
|
||||
{
|
||||
_run = true;
|
||||
@@ -54,6 +56,25 @@ class BackgroundTaskManager
|
||||
BackgroundTask.RunResult runResult = task.Run();
|
||||
_runningTask = null;
|
||||
|
||||
if (_deferredStateChanges.TryGetValue(task, let desiredState))
|
||||
{
|
||||
// If the task already finished or aborted by itself, we don't care about the desired state, because it no longer applies.
|
||||
if (runResult != .Finished && runResult != .Abort)
|
||||
{
|
||||
switch (desiredState)
|
||||
{
|
||||
case .Paused:
|
||||
runResult = .Pause;
|
||||
case .Aborted:
|
||||
runResult = .Abort;
|
||||
default:
|
||||
Log.EngineLogger.Error($"Tasks can't explicitly switch to state {desiredState}.");
|
||||
}
|
||||
}
|
||||
|
||||
_deferredStateChanges.Remove(task);
|
||||
}
|
||||
|
||||
switch (runResult)
|
||||
{
|
||||
case .Continue:
|
||||
@@ -71,14 +92,14 @@ class BackgroundTaskManager
|
||||
case .Finished:
|
||||
task.State = .Finished;
|
||||
|
||||
if (task.DeleteWhenStopped)
|
||||
if (task.DeleteWhenEnded)
|
||||
{
|
||||
delete task;
|
||||
}
|
||||
case .Abort:
|
||||
task.State = .Aborted;
|
||||
|
||||
if (task.DeleteWhenStopped)
|
||||
if (task.DeleteWhenEnded)
|
||||
{
|
||||
delete task;
|
||||
}
|
||||
@@ -123,21 +144,10 @@ class BackgroundTaskManager
|
||||
Log.EngineLogger.AssertDebug(task._taskManager == this);
|
||||
Log.EngineLogger.AssertDebug(!task.Ended);
|
||||
|
||||
Application.Instance.InvokeOnMainThread(new () =>
|
||||
{
|
||||
if (task.State == .Paused)
|
||||
{
|
||||
task.State = .Ready;
|
||||
|
||||
using (_queueLock.Enter())
|
||||
{
|
||||
Log.EngineLogger.AssertDebug(!_readyQueue.Contains(task));
|
||||
// We assume this was a user interaction so this task will get some priority.
|
||||
_readyQueue.AddFirst(task);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (task.State == .Paused)
|
||||
{
|
||||
_deferredStateChanges.Add(task, .Ready);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Pause(BackgroundTask task)
|
||||
@@ -145,20 +155,10 @@ class BackgroundTaskManager
|
||||
Log.EngineLogger.AssertDebug(task._taskManager == this);
|
||||
Log.EngineLogger.AssertDebug(!task.Ended);
|
||||
|
||||
Application.Instance.InvokeOnMainThread(new () =>
|
||||
{
|
||||
if (task.State == .Ready)
|
||||
{
|
||||
task.State = .Ready;
|
||||
|
||||
using (_queueLock.Enter())
|
||||
{
|
||||
Log.EngineLogger.AssertDebug(!_waitingTasks.Contains(task));
|
||||
_waitingTasks.AddLast(task);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (task.State == .Ready || task.State == .Running)
|
||||
{
|
||||
_deferredStateChanges.Add(task, .Paused);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AbortTask(BackgroundTask task)
|
||||
@@ -166,24 +166,10 @@ class BackgroundTaskManager
|
||||
Log.EngineLogger.AssertDebug(task._taskManager == this);
|
||||
Log.EngineLogger.AssertDebug(!task.Ended);
|
||||
|
||||
Application.Instance.InvokeOnMainThread(new () =>
|
||||
{
|
||||
if (task.State != .Aborted)
|
||||
{
|
||||
task.State = .Aborted;
|
||||
|
||||
using (_queueLock.Enter())
|
||||
{
|
||||
_waitingTasks.Remove(task);
|
||||
}
|
||||
|
||||
if (task.DeleteWhenStopped)
|
||||
{
|
||||
delete task;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (task.State != .Aborted)
|
||||
{
|
||||
_deferredStateChanges.Add(task, .Aborted);
|
||||
}
|
||||
}
|
||||
|
||||
public void ImGuiRender()
|
||||
@@ -211,6 +197,59 @@ class BackgroundTaskManager
|
||||
wtask.OnRenderPopup();
|
||||
wtask = wtask._next;
|
||||
}
|
||||
|
||||
for (var (task, desiredState) in _deferredStateChanges)
|
||||
{
|
||||
bool deleteEntry = true;
|
||||
|
||||
switch (desiredState)
|
||||
{
|
||||
case .Ready:
|
||||
task.State = .Ready;
|
||||
// We assume this was a user interaction so this task will get priority.
|
||||
_readyQueue.AddFirst(task);
|
||||
case .Paused:
|
||||
if (task.Ready)
|
||||
{
|
||||
Log.EngineLogger.AssertDebug(!_waitingTasks.Contains(task));
|
||||
_waitingTasks.AddLast(task);
|
||||
}
|
||||
else if (task.Running)
|
||||
{
|
||||
// The task is running, we have to hope that it cooperates and handle the rest in the worker thread.
|
||||
deleteEntry = false;
|
||||
}
|
||||
|
||||
task.State = .Paused;
|
||||
case .Aborted:
|
||||
if (task.Ready || task.Paused)
|
||||
{
|
||||
_waitingTasks.Remove(task);
|
||||
|
||||
task.State = .Aborted;
|
||||
|
||||
if (task.DeleteWhenEnded)
|
||||
{
|
||||
delete task;
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
else if (task.Running)
|
||||
{
|
||||
task.State = .Aborted;
|
||||
|
||||
// The task is running, we have to hope that it cooperates and handle the rest in the worker thread.
|
||||
deleteEntry = false;
|
||||
}
|
||||
default:
|
||||
Log.EngineLogger.Error($"Tasks can't explicitly switch to state {desiredState}.");
|
||||
}
|
||||
|
||||
if (deleteEntry)
|
||||
{
|
||||
@task.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.IO;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using ImGui;
|
||||
using System.Threading;
|
||||
|
||||
namespace GlitchyEditor.Multithreading;
|
||||
|
||||
@@ -14,20 +15,25 @@ class CopyBackgroundTask : BackgroundTask
|
||||
private int _totalEntriesToCopy;
|
||||
private append Queue<CopyInfo> _pathsToCopy = .() ~ ClearAndDeleteItems!(_);
|
||||
private append Queue<CopyInfo> _scanQueue = .() ~ ClearAndDeleteItems!(_);
|
||||
/// Contains the file paths, that will definitely exist after copying. This is used for file renaming during scanning.
|
||||
private append HashSet<StringView> _targetPaths = .();
|
||||
|
||||
private enum OverwriteMode
|
||||
private enum ConflictResolutionMode
|
||||
{
|
||||
None,
|
||||
OverwriteFile = 1,
|
||||
KeepBoth = 2,
|
||||
CombineDirectories = 4,
|
||||
Skip = 8
|
||||
SkipFiles = 8,
|
||||
SkipDirectories = 16,
|
||||
SkipNotFound = 32,
|
||||
IgnoreUnexpectedErrors = 64
|
||||
}
|
||||
|
||||
private OverwriteMode _nextFileMode;
|
||||
private OverwriteMode _allFilesMode;
|
||||
private ConflictResolutionMode _nextFileMode;
|
||||
private ConflictResolutionMode _allFilesMode;
|
||||
|
||||
private OverwriteMode CurrentFileMode => _nextFileMode | _allFilesMode;
|
||||
private ConflictResolutionMode CurrentFileMode => _nextFileMode | _allFilesMode;
|
||||
|
||||
private class CopyInfo
|
||||
{
|
||||
@@ -35,14 +41,14 @@ class CopyBackgroundTask : BackgroundTask
|
||||
// If this entry is actually inside a directory we copy, this contains the path of this entry inside this directory.
|
||||
public append String SubPath = .();
|
||||
public append String TargetPath = .();
|
||||
public OverwriteMode OverwriteMode;
|
||||
public ConflictResolutionMode ConflictResolutionMode;
|
||||
|
||||
[AllowAppend]
|
||||
public this(StringView sourcePath, StringView targetPath, OverwriteMode overwriteMode)
|
||||
public this(StringView sourcePath, StringView targetPath, ConflictResolutionMode conflictResolutionMode)
|
||||
{
|
||||
SourcePath.Set(sourcePath);
|
||||
TargetPath.Set(targetPath);
|
||||
OverwriteMode = overwriteMode;
|
||||
ConflictResolutionMode = conflictResolutionMode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +76,10 @@ class CopyBackgroundTask : BackgroundTask
|
||||
{
|
||||
case None;
|
||||
case TargetDirectoryIsNotDirectory;
|
||||
case SourceDoesntExist(String SourcePath);
|
||||
case TargetFileExists(String FileName);
|
||||
case TargetDirectoryExists(String DirectoryName);
|
||||
case EntryNotFound(String SourcePath);
|
||||
case UnexpectedError(String Message);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -80,12 +87,14 @@ class CopyBackgroundTask : BackgroundTask
|
||||
{
|
||||
case .None:
|
||||
case .TargetDirectoryIsNotDirectory:
|
||||
case .SourceDoesntExist(let SourcePath):
|
||||
delete SourcePath;
|
||||
case .TargetFileExists(let FileName):
|
||||
delete FileName;
|
||||
case .TargetDirectoryExists(let DirectoryName):
|
||||
delete DirectoryName;
|
||||
case .EntryNotFound(let SourcePath):
|
||||
delete SourcePath;
|
||||
case .UnexpectedError(let Message):
|
||||
delete Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,10 +110,16 @@ class CopyBackgroundTask : BackgroundTask
|
||||
}
|
||||
}
|
||||
|
||||
void AddToCopyQueue(CopyInfo entry, StringView targetPath, ConflictResolutionMode conflictResolutionMode)
|
||||
{
|
||||
entry.TargetPath.Set(targetPath);
|
||||
entry.ConflictResolutionMode = conflictResolutionMode;
|
||||
_pathsToCopy.Add(entry);
|
||||
_targetPaths.Add(entry.TargetPath);
|
||||
}
|
||||
|
||||
while (!_scanQueue.IsEmpty && Running)
|
||||
{
|
||||
//Thread.Sleep(1000);
|
||||
|
||||
currentEntry = _scanQueue.PopFront();
|
||||
|
||||
let sourcePath = (StringView)currentEntry.SourcePath;
|
||||
@@ -117,49 +132,90 @@ class CopyBackgroundTask : BackgroundTask
|
||||
|
||||
if (Directory.Exists(sourcePath))
|
||||
{
|
||||
if (Directory.Exists(targetPath))
|
||||
bool skip = false;
|
||||
|
||||
if (Directory.Exists(targetPath) || _targetPaths.Contains(targetPath))
|
||||
{
|
||||
switch (CurrentFileMode)
|
||||
if (CurrentFileMode.HasFlag(.KeepBoth))
|
||||
{
|
||||
Path.FindFreePath(_targetDirectoryPath, fileName, "", targetPath, fileName, _targetPaths);
|
||||
}
|
||||
else if (CurrentFileMode.HasFlag(.SkipDirectories))
|
||||
{
|
||||
skip = true;
|
||||
}
|
||||
else if (Enum.HasAnyFlag(CurrentFileMode, .CombineDirectories))
|
||||
{
|
||||
// Intentionally do nothing.
|
||||
}
|
||||
else
|
||||
{
|
||||
case .KeepBoth, .CombineDirectories, .Skip:
|
||||
default:
|
||||
return .Err(.TargetDirectoryExists(new String(fileName)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!CurrentFileMode.HasFlag(.Skip))
|
||||
if (skip)
|
||||
{
|
||||
_pathsToCopy.Add(currentEntry);
|
||||
currentEntry.OverwriteMode = CurrentFileMode;
|
||||
delete currentEntry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Current node is ready, add it to copy-queue.
|
||||
AddToCopyQueue(currentEntry, targetPath, CurrentFileMode);
|
||||
|
||||
// Add nested files and directories to the scan-queue.
|
||||
for (FileFindEntry e in Directory.Enumerate(currentEntry.SourcePath))
|
||||
{
|
||||
let entryPath = scope String();
|
||||
e.GetFilePath(entryPath);
|
||||
|
||||
let newEntry = new CopyInfo(entryPath, "", .None);
|
||||
let newEntry = new CopyInfo(entryPath, targetPath, .None);
|
||||
_scanQueue.Add(newEntry);
|
||||
|
||||
Path.Combine(newEntry.SubPath, currentEntry.SubPath, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
_nextFileMode = .None;
|
||||
}
|
||||
else if (File.Exists(sourcePath))
|
||||
{
|
||||
if (File.Exists(targetPath))
|
||||
bool skip = false;
|
||||
|
||||
if (File.Exists(targetPath) || _targetPaths.Contains(targetPath))
|
||||
{
|
||||
switch (CurrentFileMode)
|
||||
if (CurrentFileMode.HasFlag(.KeepBoth))
|
||||
{
|
||||
let fileExtension = scope String();
|
||||
Path.GetExtension(targetPath, fileExtension);
|
||||
|
||||
StringView fileNameWithoutExtension = fileName.Substring(0..<^fileExtension.Length);
|
||||
|
||||
Path.FindFreePath(_targetDirectoryPath, fileNameWithoutExtension, fileExtension, targetPath, fileName, _targetPaths);
|
||||
}
|
||||
else if (CurrentFileMode.HasFlag(.SkipFiles))
|
||||
{
|
||||
skip = true;
|
||||
}
|
||||
else if (Enum.HasAnyFlag(CurrentFileMode, .OverwriteFile))
|
||||
{
|
||||
// Intentionally do nothing.
|
||||
}
|
||||
else
|
||||
{
|
||||
case .KeepBoth, .OverwriteFile, .Skip:
|
||||
default:
|
||||
return .Err(.TargetFileExists(new String(fileName)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!CurrentFileMode.HasFlag(.Skip))
|
||||
if (skip)
|
||||
{
|
||||
_pathsToCopy.Add(currentEntry);
|
||||
currentEntry.OverwriteMode = CurrentFileMode;
|
||||
delete currentEntry;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddToCopyQueue(currentEntry, targetPath, CurrentFileMode);
|
||||
}
|
||||
|
||||
_nextFileMode = .None;
|
||||
}
|
||||
}
|
||||
@@ -175,78 +231,76 @@ class CopyBackgroundTask : BackgroundTask
|
||||
{
|
||||
_currentError.Dispose();
|
||||
_currentError = .None;
|
||||
if (CollectFilesToCopy() case .Err(out _currentError))
|
||||
if (CollectFilesToCopy() case .Err(out _currentError) || Paused)
|
||||
{
|
||||
return .Pause;
|
||||
}
|
||||
|
||||
while (!_pathsToCopy.IsEmpty && Running)
|
||||
{
|
||||
//Thread.Sleep(1000);
|
||||
CopyInfo currentPath = _pathsToCopy.PopFront();
|
||||
|
||||
defer
|
||||
if (CopyPath(currentPath) case .Err(out _currentError) || Paused)
|
||||
{
|
||||
delete currentPath;
|
||||
return .Pause;
|
||||
}
|
||||
|
||||
CopyPath(currentPath);
|
||||
delete currentPath;
|
||||
}
|
||||
|
||||
return .Finished;
|
||||
}
|
||||
|
||||
private Result<void> CopyPath(CopyInfo copyInfo)
|
||||
private Result<void, CopyError> CopyPath(CopyInfo copyInfo)
|
||||
{
|
||||
if (Directory.Exists(copyInfo.SourcePath))
|
||||
{
|
||||
//bool forceOverwrite = false;
|
||||
if (Directory.Exists(copyInfo.TargetPath))
|
||||
{
|
||||
switch (CurrentFileMode)
|
||||
if (copyInfo.ConflictResolutionMode.HasFlag(.CombineDirectories))
|
||||
{
|
||||
case .KeepBoth:
|
||||
String fileName = scope .();
|
||||
Path.GetFileName(copyInfo.SourcePath, fileName);
|
||||
|
||||
Path.FindFreePath(_targetDirectoryPath, fileName, "", copyInfo.TargetPath..Clear());
|
||||
case .CombineDirectories:
|
||||
// We don't have to do anything, because we will be using the target directory.
|
||||
return .Ok;
|
||||
default:
|
||||
return .Err;//(.TargetDirectoryExists);
|
||||
}
|
||||
else if (!CurrentFileMode.HasFlag(.IgnoreUnexpectedErrors))
|
||||
{
|
||||
return .Err(.UnexpectedError(new $"The target directory already exists, but it wasn't properly handled during the scanning phase."));
|
||||
}
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(copyInfo.TargetPath);
|
||||
if (Directory.CreateDirectory(copyInfo.TargetPath) case .Err(let err) &&
|
||||
!CurrentFileMode.HasFlag(.IgnoreUnexpectedErrors))
|
||||
{
|
||||
return .Err(.UnexpectedError(new $"Failed to create Directory {copyInfo.TargetPath}: {err}"));
|
||||
}
|
||||
}
|
||||
else if (File.Exists(copyInfo.SourcePath))
|
||||
{
|
||||
bool forceOverwrite = false;
|
||||
if (File.Exists(copyInfo.TargetPath))
|
||||
{
|
||||
switch (copyInfo.OverwriteMode)
|
||||
if (copyInfo.ConflictResolutionMode.HasFlag(.OverwriteFile))
|
||||
{
|
||||
case .KeepBoth:
|
||||
String fileExtension = scope .();
|
||||
Path.GetExtension(copyInfo.TargetPath, fileExtension);
|
||||
|
||||
String fileName = scope .();
|
||||
Path.GetFileName(copyInfo.SourcePath, fileName);
|
||||
|
||||
Path.FindFreePath(_targetDirectoryPath, fileName.Substring(0..<^fileExtension.Length), fileExtension, copyInfo.TargetPath..Clear());
|
||||
case .OverwriteFile:
|
||||
forceOverwrite = true;
|
||||
default:
|
||||
return .Err;//(.TargetFileExists);
|
||||
}
|
||||
else if (!CurrentFileMode.HasFlag(.IgnoreUnexpectedErrors))
|
||||
{
|
||||
return .Err(.UnexpectedError(new $"The target file already exists, but it wasn't properly handled during the scanning phase."));
|
||||
}
|
||||
}
|
||||
|
||||
File.Copy(copyInfo.SourcePath, copyInfo.TargetPath, forceOverwrite);
|
||||
if (File.Copy(copyInfo.SourcePath, copyInfo.TargetPath, forceOverwrite) case .Err(let err) &&
|
||||
!CurrentFileMode.HasFlag(.IgnoreUnexpectedErrors))
|
||||
{
|
||||
return .Err(.UnexpectedError(new $"Failed to copy File {copyInfo.TargetPath}: {err}"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO error
|
||||
return .Err;
|
||||
if (copyInfo.ConflictResolutionMode.HasFlag(.SkipNotFound))
|
||||
return .Ok;
|
||||
else
|
||||
return .Err(.EntryNotFound(new String(copyInfo.SourcePath)));
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
@@ -254,7 +308,9 @@ class CopyBackgroundTask : BackgroundTask
|
||||
|
||||
public override void OnRenderPopup()
|
||||
{
|
||||
if (ImGui.Begin("Copying files..."))
|
||||
String title = scope .("Copying files...");
|
||||
|
||||
if (ImGui.Begin(title, null, .NoDocking | .NoCollapse | .Modal | .NoResize | .AlwaysAutoResize))
|
||||
{
|
||||
if (ScanningFiles)
|
||||
{
|
||||
@@ -267,89 +323,188 @@ class CopyBackgroundTask : BackgroundTask
|
||||
ImGui.ProgressBar(copiedFiles / _totalEntriesToCopy, .(-1, 0), scope $"Copied {copiedFiles} / {_totalEntriesToCopy} files.");
|
||||
}
|
||||
|
||||
|
||||
switch (_currentError)
|
||||
{
|
||||
case .None:
|
||||
// Do nothing
|
||||
case .TargetDirectoryIsNotDirectory:
|
||||
// This shouldn't be possible
|
||||
Debug.Break();
|
||||
case .SourceDoesntExist(let SourcePath):
|
||||
// This isn't good!
|
||||
case .TargetFileExists(let fileName):
|
||||
ImGui.PushStyleColor(.Text, 0xFF0000FF);
|
||||
ImGui.TextUnformatted(scope $"A file with the name \"{fileName}\" already exists.");
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (ImGui.Button("Overwrite"))
|
||||
{
|
||||
_nextFileMode = .OverwriteFile;
|
||||
Continue();
|
||||
}
|
||||
ImGui.AttachTooltip("Overwrites the existing file. Will ask again if another conflict occurs.");
|
||||
|
||||
if (ImGui.Button("Overwrite All"))
|
||||
{
|
||||
_allFilesMode = .OverwriteFile;
|
||||
Continue();
|
||||
}
|
||||
ImGui.AttachTooltip("Overwrites all existing files.");
|
||||
|
||||
if (ImGui.Button("Keep both"))
|
||||
{
|
||||
_nextFileMode = .KeepBoth;
|
||||
Continue();
|
||||
}
|
||||
ImGui.AttachTooltip("Keeps both the existing file as well as the new one, differentiates by adding a number to the name. Will ask again if another conflict occurs.");
|
||||
|
||||
if (ImGui.Button("Keep all"))
|
||||
{
|
||||
_allFilesMode = .KeepBoth;
|
||||
Continue();
|
||||
}
|
||||
ImGui.AttachTooltip("Keeps both the existing files as well as the new ones, differentiates by adding a number to the name.");
|
||||
|
||||
if (ImGui.Button("Cancel"))
|
||||
{
|
||||
Abort();
|
||||
}
|
||||
case .TargetDirectoryExists(let directoryName):
|
||||
ImGui.PushStyleColor(.Text, 0xFF0000FF);
|
||||
ImGui.TextUnformatted(scope $"A file with the name \"{directoryName}\" already exists.");
|
||||
ImGui.TextUnformatted(scope $"A directory with the name \"{directoryName}\" already exists.");
|
||||
ImGui.PopStyleColor();
|
||||
case .EntryNotFound(let SourcePath):
|
||||
ImGui.PushStyleColor(.Text, 0xFF0000FF);
|
||||
ImGui.TextUnformatted(scope $"\"{SourcePath}\" doesn't exist.");
|
||||
ImGui.PopStyleColor();
|
||||
case .UnexpectedError(let Message):
|
||||
ImGui.PushStyleColor(.Text, 0xFF0000FF);
|
||||
ImGui.TextUnformatted(scope $"Unexpected error: {Message}");
|
||||
ImGui.PopStyleColor();
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
if (ImGui.Button("Combine"))
|
||||
if (ImGui.BeginTable("buttonTable", 2))
|
||||
{
|
||||
switch (_currentError)
|
||||
{
|
||||
_nextFileMode = .CombineDirectories;
|
||||
Continue();
|
||||
}
|
||||
ImGui.AttachTooltip("Merges the copied directory into the target directory. Will ask again if another conflict occurs.");
|
||||
case .None:
|
||||
// Do nothing
|
||||
case .TargetDirectoryIsNotDirectory:
|
||||
// This shouldn't be possible
|
||||
Debug.Break();
|
||||
case .TargetFileExists(let fileName):
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
|
||||
if (ImGui.Button("Combine All"))
|
||||
{
|
||||
_allFilesMode = .CombineDirectories;
|
||||
Continue();
|
||||
}
|
||||
ImGui.AttachTooltip("Merges all copied directories into the target directories.");
|
||||
if (ImGui.Button("Overwrite the target file."))
|
||||
{
|
||||
_nextFileMode = .OverwriteFile;
|
||||
Continue();
|
||||
}
|
||||
ImGui.TableSetColumnIndex(1);
|
||||
if (ImGui.Button("Overwrite all conflicting target files."))
|
||||
{
|
||||
_allFilesMode |= .OverwriteFile;
|
||||
Continue();
|
||||
}
|
||||
|
||||
if (ImGui.Button("Keep both"))
|
||||
{
|
||||
_nextFileMode = .KeepBoth;
|
||||
Continue();
|
||||
}
|
||||
ImGui.AttachTooltip("Renames the copied directory, so that they no longer conflict. Will ask again if another conflict occurs.");
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
|
||||
if (ImGui.Button("Keep all"))
|
||||
if (ImGui.Button("Rename copied file."))
|
||||
{
|
||||
_nextFileMode = .KeepBoth;
|
||||
Continue();
|
||||
}
|
||||
ImGui.TableSetColumnIndex(1);
|
||||
if (ImGui.Button("Rename all conflicting copied files."))
|
||||
{
|
||||
_allFilesMode |= .KeepBoth;
|
||||
Continue();
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
|
||||
if (ImGui.Button("Skip this file."))
|
||||
{
|
||||
_nextFileMode = .SkipFiles;
|
||||
Continue();
|
||||
}
|
||||
ImGui.TableSetColumnIndex(1);
|
||||
if (ImGui.Button("Skip all conflicting files."))
|
||||
{
|
||||
_allFilesMode |= .SkipFiles;
|
||||
Continue();
|
||||
}
|
||||
|
||||
case .TargetDirectoryExists(let directoryName):
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
|
||||
if (ImGui.Button("Combine with target."))
|
||||
{
|
||||
_nextFileMode |= .CombineDirectories;
|
||||
Continue();
|
||||
}
|
||||
ImGui.TableSetColumnIndex(1);
|
||||
if (ImGui.Button("Combine all conflicting directories."))
|
||||
{
|
||||
_allFilesMode |= .CombineDirectories;
|
||||
Continue();
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
|
||||
if (ImGui.Button("Rename copied directory."))
|
||||
{
|
||||
_nextFileMode |= .KeepBoth;
|
||||
Continue();
|
||||
}
|
||||
ImGui.TableSetColumnIndex(1);
|
||||
if (ImGui.Button("Rename all conflicting directories."))
|
||||
{
|
||||
_allFilesMode |= .KeepBoth;
|
||||
Continue();
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
|
||||
if (ImGui.Button("Skip this directory."))
|
||||
{
|
||||
_nextFileMode |= .SkipDirectories;
|
||||
Continue();
|
||||
}
|
||||
ImGui.TableSetColumnIndex(1);
|
||||
if (ImGui.Button("Skip all conflicting directories."))
|
||||
{
|
||||
_allFilesMode |= .SkipFiles;
|
||||
Continue();
|
||||
}
|
||||
case .EntryNotFound(let SourcePath):
|
||||
if (ImGui.Button("Skip"))
|
||||
{
|
||||
_nextFileMode |= .SkipNotFound;
|
||||
Continue();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Skip all"))
|
||||
{
|
||||
_allFilesMode |= .SkipNotFound;
|
||||
Continue();
|
||||
}
|
||||
case .UnexpectedError(let Message):
|
||||
if (ImGui.Button("Retry"))
|
||||
{
|
||||
Continue();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Ignore"))
|
||||
{
|
||||
_nextFileMode |= .IgnoreUnexpectedErrors;
|
||||
Continue();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Ignore all unexpected"))
|
||||
{
|
||||
_allFilesMode |= .IgnoreUnexpectedErrors;
|
||||
Continue();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
if (_currentError case .None)
|
||||
{
|
||||
_allFilesMode = .KeepBoth;
|
||||
Continue();
|
||||
if (Paused)
|
||||
{
|
||||
if (ImGui.Button("Continue"))
|
||||
{
|
||||
Continue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.Button("Pause"))
|
||||
{
|
||||
Pause();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
}
|
||||
ImGui.AttachTooltip("Renames the copied directory, so that they no longer conflict.");
|
||||
|
||||
if (ImGui.Button("Cancel"))
|
||||
{
|
||||
Abort();
|
||||
}
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
ImGui.End();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace System.Collections;
|
||||
|
||||
extension HashSet<T> : ICollection<T> where T : IHashable
|
||||
{
|
||||
public void ICollection<T>.Add(T item)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
|
||||
public void CopyTo(Span<T> span)
|
||||
{
|
||||
int i = 0;
|
||||
for (T item in this)
|
||||
{
|
||||
span[i] = item;
|
||||
i++;
|
||||
|
||||
if (i >= span.Length)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,4 +19,9 @@ extension Enum
|
||||
else
|
||||
ClearFlag<T>(ref value, flag);
|
||||
}
|
||||
|
||||
public static bool HasAnyFlag<T>(T value, T flags) where T : enum
|
||||
{
|
||||
return (value.Underlying & flags.Underlying) != default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Collections;
|
||||
|
||||
namespace System.IO;
|
||||
|
||||
@@ -34,8 +35,8 @@ extension Path
|
||||
path.Remove(0, 1);
|
||||
}
|
||||
|
||||
|
||||
public static void Combine(String target, params StringView[] components)
|
||||
// Compared to the original Combine, this one makes sure we don't add multiple seperators. Also makes sure, the path contains only the main Separator char.
|
||||
new public static void Combine(String target, params StringView[] components)
|
||||
{
|
||||
for (var component in components)
|
||||
{
|
||||
@@ -54,25 +55,24 @@ extension Path
|
||||
* @param wantedFileName The wanted name of the file. If it already exists a number will be put behind it.
|
||||
* @param fileExtension The file extension. This should contain the dot.
|
||||
* @param outFreePath The string that will contain the resulting filepath. Note: This will be cleared before writing the file name.
|
||||
* @param outFreeFilename Optional, if set to a string, the final file name that is free will be stored in this varialbe.
|
||||
* @param blockedPaths Optional, can be used to provide a collection of paths that will be considered as existing.
|
||||
*/
|
||||
public static void FindFreePath(StringView targetDirectory, StringView wantedName, StringView fileExtension, String outFreePath)
|
||||
public static void FindFreePath(StringView targetDirectory, StringView wantedName, StringView fileExtension, String outFreePath, String outFreeFilename = null, ICollection<StringView> blockedPaths = null)
|
||||
{
|
||||
int fileNumber = 0;
|
||||
|
||||
String currentFileName = scope $"{wantedName}{fileExtension}";
|
||||
String currentFileName = outFreeFilename ?? scope .();
|
||||
currentFileName.SetF($"{wantedName}{fileExtension}");
|
||||
while (true)
|
||||
{
|
||||
Path.Combine(outFreePath..Clear(), targetDirectory, currentFileName);
|
||||
|
||||
FileInfo targetInfo = scope FileInfo(outFreePath);
|
||||
|
||||
if (!targetInfo.Exists)
|
||||
{
|
||||
if (!File.Exists(outFreePath) && !Directory.Exists(outFreePath) && !(blockedPaths?.Contains(outFreePath) ?? false))
|
||||
break;
|
||||
}
|
||||
|
||||
fileNumber++;
|
||||
currentFileName..Clear().AppendF($"{wantedName} ({fileNumber}){fileExtension}");
|
||||
currentFileName.SetF($"{wantedName} ({fileNumber}){fileExtension}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user