mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2025-12-19 23:49:30 -06:00
FF-1765: Added Top Most Only option to delete source directory
This commit is contained in:
@@ -40,6 +40,11 @@ public class DeleteSourceDirectory : Node
|
||||
/// eg if [mkv, mp4, mov, etc] is the list, if any video file is found the directory will not be deleted.
|
||||
/// </summary>
|
||||
[StringArray(2)] public string[] IncludePatterns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if only the top most directory should be deleted, and all parent directories should be left alone
|
||||
/// </summary>
|
||||
[Boolean(3)] public bool TopMostOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the flow element
|
||||
@@ -47,8 +52,71 @@ public class DeleteSourceDirectory : Node
|
||||
/// <param name="args">the node parameters</param>
|
||||
/// <returns>the output to call next, -1 to abort flow, 0 to end flow</returns>
|
||||
public override int Execute(NodeParameters args)
|
||||
=> TopMostOnly ? DeleteTopMostOnly(args) : DeleteFull(args);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes only the top most directory in the library
|
||||
/// </summary>
|
||||
/// <param name="args">the node parameters</param>
|
||||
/// <returns>the output to call next, -1 to abort flow, 0 to end flow</returns>
|
||||
private int DeleteTopMostOnly(NodeParameters args)
|
||||
{
|
||||
string libraryPath = args.LibraryFileName.Substring(0, args.LibraryFileName.Length - args.RelativeFile.Length)
|
||||
string libraryPath = args.LibraryFileName[..^args.RelativeFile.Length]
|
||||
.TrimEnd('/')
|
||||
.TrimEnd('\\');
|
||||
string dir = args.OriginalIsDirectory ? args.LibraryFileName : FileHelper.GetDirectory(args.LibraryFileName);
|
||||
args.Logger?.ILog("Deleting top level directory only: " + dir);
|
||||
var existsResult = args.FileService.DirectoryExists(dir);
|
||||
if (existsResult.Failed(out var error))
|
||||
{
|
||||
args.Logger?.WLog("Failed determining if directory exists: " + error);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (existsResult.Value == false)
|
||||
{
|
||||
args.Logger?.ILog($"Directory '{dir}' no longer exists.");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (IfEmpty)
|
||||
{
|
||||
args.Logger?.ILog($"Checking if directory is empty: {dir}");
|
||||
var emptyResult = args.FileService.DirectoryEmpty(dir, IncludePatterns);
|
||||
if (emptyResult.Failed(out error))
|
||||
{
|
||||
args.Logger?.WLog(error);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (emptyResult.Value == false)
|
||||
{
|
||||
args.Logger?.ILog("Directory is not empty");
|
||||
return 2;
|
||||
}
|
||||
args.Logger?.ILog("Directory is considered empty");
|
||||
}
|
||||
|
||||
args.Logger?.ILog("About to delete directory: " + dir);
|
||||
var deleteResult = args.FileService.DirectoryDelete(dir, true);
|
||||
if (deleteResult.Failed(out error))
|
||||
{
|
||||
args.Logger?.WLog("Failed to deleted directory: " + error);
|
||||
return 2;
|
||||
}
|
||||
|
||||
args.Logger?.ILog("Directory deleted: " + dir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the full library folder
|
||||
/// </summary>
|
||||
/// <param name="args">the node parameters</param>
|
||||
/// <returns>the output to call next, -1 to abort flow, 0 to end flow</returns>
|
||||
private int DeleteFull(NodeParameters args)
|
||||
{
|
||||
string libraryPath = args.LibraryFileName[..^args.RelativeFile.Length]
|
||||
.TrimEnd('/')
|
||||
.TrimEnd('\\');
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
|
||||
<ProjectReference Include="..\PluginTestLibrary\PluginTestLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FileFlows.Plugin">
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using FileFlows.ComicNodes.Comics;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Comic.Tests;
|
||||
|
||||
@@ -15,8 +17,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics/DC/Batman (1939)/Batman - #42 - Batman vs. Joker [old] [great] [amazing].cbr",
|
||||
"/home/john/Comics",true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -46,8 +46,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics/actual",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -73,8 +71,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -100,8 +96,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -128,8 +122,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -154,8 +146,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -181,8 +171,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -208,8 +196,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -235,8 +221,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -262,8 +246,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -289,8 +271,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -316,8 +296,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -344,8 +322,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -371,8 +347,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -397,8 +371,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -423,9 +395,7 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics/Marvel/X-Men/X-Man/Specials/X-Man Annual 2004.cbz",
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -442,6 +412,7 @@ public class ComicInfoTests : TestBase
|
||||
var name = CreateComicInfo.GetNewName(info, "cbz", 3);
|
||||
Assert.AreEqual("X-Man - Annual 2004.cbz", name.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AnnualNoAnnual2()
|
||||
{
|
||||
@@ -450,8 +421,6 @@ public class ComicInfoTests : TestBase
|
||||
"/home/john/Comics",
|
||||
true);
|
||||
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var info = result.Value;
|
||||
Assert.IsNotNull(info);
|
||||
@@ -467,18 +436,14 @@ public class ComicInfoTests : TestBase
|
||||
var name = CreateComicInfo.GetNewName(info, "cbz", 3);
|
||||
Assert.AreEqual("Grimm Fairy Tales Specials - Annual 2016.cbz", name.Value);
|
||||
}
|
||||
|
||||
// [TestMethod]
|
||||
public void PhysicalFileTest()
|
||||
{
|
||||
const string FILE =
|
||||
"/home/john/Comics/DC/Batman (1939)/Batman - #1 - Batman vs. Joker [old] [great] [amazing].cbz";
|
||||
var logger = new TestLogger();
|
||||
var args = new NodeParameters(FILE, logger, false, string.Empty, new LocalFileService())
|
||||
{
|
||||
LibraryFileName = FILE,
|
||||
LibraryPath = "/home/john/Comics"
|
||||
};
|
||||
args.TempPath = TempPath;
|
||||
var args = GetNodeParameters(FILE);
|
||||
args.LibraryPath = "/home/john/Comics";
|
||||
|
||||
var ele = new CreateComicInfo();
|
||||
var result = ele.Execute(args);
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.Comic.Tests;
|
||||
|
||||
using FileFlows.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal class TestLogger : ILogger
|
||||
{
|
||||
private List<string> Messages = new List<string>();
|
||||
|
||||
public void DLog(params object[] args) => Log("DBUG", args);
|
||||
|
||||
public void ELog(params object[] args) => Log("ERRR", args);
|
||||
|
||||
public void ILog(params object[] args) => Log("INFO", args);
|
||||
|
||||
public void WLog(params object[] args) => Log("WARN", args);
|
||||
private void Log(string type, object[] args)
|
||||
{
|
||||
if (args == null || args.Length == 0)
|
||||
return;
|
||||
string message = type + " -> " +
|
||||
string.Join(", ", args.Select(x =>
|
||||
x == null ? "null" :
|
||||
x.GetType().IsPrimitive || x is string ? x.ToString() :
|
||||
System.Text.Json.JsonSerializer.Serialize(x)));
|
||||
Messages.Add(message);
|
||||
}
|
||||
|
||||
public bool Contains(string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return false;
|
||||
|
||||
string log = string.Join(Environment.NewLine, Messages);
|
||||
return log.Contains(message);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Join(Environment.NewLine, this.Messages.ToArray());
|
||||
}
|
||||
|
||||
public string GetTail(int length = 50)
|
||||
{
|
||||
if (length <= 0)
|
||||
length = 50;
|
||||
if (Messages.Count <= length)
|
||||
return string.Join(Environment.NewLine, Messages);
|
||||
return string.Join(Environment.NewLine, Messages.TakeLast(length));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,479 +0,0 @@
|
||||
#if(DEBUG)
|
||||
using FileFlows.Plugin.Helpers;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
|
||||
namespace FileFlows.Comic.Tests;
|
||||
|
||||
|
||||
public class LocalFileService : IFileService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the path separator for the file system
|
||||
/// </summary>
|
||||
public char PathSeparator { get; init; } = Path.DirectorySeparatorChar;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the allowed paths the file service can access
|
||||
/// </summary>
|
||||
public string[]? AllowedPaths { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a function for replacing variables in a string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The function takes a string input, a boolean indicating whether to strip missing variables,
|
||||
/// and a boolean indicating whether to clean special characters.
|
||||
/// </remarks>
|
||||
public ReplaceVariablesDelegate? ReplaceVariables { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the permissions to use for files
|
||||
/// </summary>
|
||||
public int? Permissions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the owner:group to use for files
|
||||
/// </summary>
|
||||
public string? OwnerGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger used for logging
|
||||
/// </summary>
|
||||
public ILogger? Logger { get; set; }
|
||||
|
||||
public Result<string[]> GetFiles(string path, string searchPattern = "", bool recursive = false)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<string[]>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
return Directory.GetFiles(path, searchPattern ?? string.Empty,
|
||||
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
public Result<string[]> GetDirectories(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<string[]>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
return Directory.GetDirectories(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryExists(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryDelete(string path, bool recursive = false)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
Directory.Delete(path, recursive);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryMove(string path, string destination)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
if (IsProtectedPath(ref destination))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + destination);
|
||||
try
|
||||
{
|
||||
Directory.Move(path, destination);
|
||||
SetPermissions(destination);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryCreate(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var dirInfo = new DirectoryInfo(path);
|
||||
if (dirInfo.Exists == false)
|
||||
dirInfo.Create();
|
||||
SetPermissions(path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<DateTime> DirectoryCreationTimeUtc(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<DateTime> DirectoryLastWriteTimeUtc(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> FileExists(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<FileInformation> FileInfo(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<FileInformation>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(path);
|
||||
|
||||
return new FileInformation
|
||||
{
|
||||
CreationTime = fileInfo.CreationTime,
|
||||
CreationTimeUtc = fileInfo.CreationTimeUtc,
|
||||
LastWriteTime = fileInfo.LastWriteTime,
|
||||
LastWriteTimeUtc = fileInfo.LastWriteTimeUtc,
|
||||
Extension = fileInfo.Extension.TrimStart('.'),
|
||||
Name = fileInfo.Name,
|
||||
FullName = fileInfo.FullName,
|
||||
Length = fileInfo.Length,
|
||||
Directory = fileInfo.DirectoryName!
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<FileInformation>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileDelete(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if(fileInfo.Exists)
|
||||
fileInfo.Delete();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<long> FileSize(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<long>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<long>.Fail("File does not exist");
|
||||
return fileInfo.Length;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<long>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<DateTime> FileCreationTimeUtc(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<DateTime>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<DateTime>.Fail("File does not exist");
|
||||
return fileInfo.CreationTimeUtc;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DateTime>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<DateTime> FileLastWriteTimeUtc(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<DateTime>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<DateTime>.Fail("File does not exist");
|
||||
return fileInfo.LastWriteTimeUtc;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DateTime>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileMove(string path, string destination, bool overwrite = true)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
if (IsProtectedPath(ref destination))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + destination);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<bool>.Fail("File does not exist");
|
||||
var destDir = new FileInfo(destination).Directory;
|
||||
if (destDir!.Exists == false)
|
||||
{
|
||||
destDir.Create();
|
||||
SetPermissions(destDir.FullName);
|
||||
}
|
||||
|
||||
fileInfo.MoveTo(destination, overwrite);
|
||||
SetPermissions(destination);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileCopy(string path, string destination, bool overwrite = true)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
if (IsProtectedPath(ref destination))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + destination);
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<bool>.Fail("File does not exist");
|
||||
|
||||
var destDir = new FileInfo(destination).Directory;
|
||||
if (destDir!.Exists == false)
|
||||
{
|
||||
destDir.Create();
|
||||
SetPermissions(destDir.FullName);
|
||||
}
|
||||
|
||||
fileInfo.CopyTo(destination, overwrite);
|
||||
SetPermissions(destination);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> FileAppendAllText(string path, string text)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
File.AppendAllText(path, text);
|
||||
SetPermissions(path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool FileIsLocal(string path) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local path
|
||||
/// </summary>
|
||||
/// <param name="path">the path</param>
|
||||
/// <returns>the local path to the file</returns>
|
||||
public Result<string> GetLocalPath(string path)
|
||||
=> Result<string>.Success(path);
|
||||
|
||||
public Result<bool> Touch(string path)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
|
||||
if (DirectoryExists(path).Is(true))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.SetLastWriteTimeUtc(path, DateTime.UtcNow);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail("Failed to touch directory: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
|
||||
else
|
||||
{
|
||||
File.Create(path);
|
||||
SetPermissions(path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail($"Failed to touch file: '{path}' => {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public Result<long> DirectorySize(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> SetCreationTimeUtc(string path, DateTime date)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
File.SetCreationTimeUtc(path, date);
|
||||
return Result<bool>.Success(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail($"Error setting creation time: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> SetLastWriteTimeUtc(string path, DateTime date)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
File.SetLastWriteTimeUtc(path, date);
|
||||
return Result<bool>.Success(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail($"Error setting last write time: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a path is accessible by the file server
|
||||
/// </summary>
|
||||
/// <param name="path">the path to check</param>
|
||||
/// <returns>true if accessible, otherwise false</returns>
|
||||
private bool IsProtectedPath(ref string path)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
path = path.Replace("/", "\\");
|
||||
else
|
||||
path = path.Replace("\\", "/");
|
||||
|
||||
if(ReplaceVariables != null)
|
||||
path = ReplaceVariables(path, true);
|
||||
|
||||
if (FileHelper.IsSystemDirectory(path))
|
||||
return true; // a system directory, no access
|
||||
|
||||
if (AllowedPaths?.Any() != true)
|
||||
return false; // no allowed paths configured, allow all
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
path = path.ToLowerInvariant();
|
||||
|
||||
for(int i=0;i<AllowedPaths.Length;i++)
|
||||
{
|
||||
string p = OperatingSystem.IsWindows() ? AllowedPaths[i].ToLowerInvariant().TrimEnd('\\') : AllowedPaths[i].TrimEnd('/');
|
||||
if (path.StartsWith(p))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPermissions(string path, int? permissions = null, Action<string>? logMethod = null)
|
||||
{
|
||||
logMethod ??= (string message) => Logger?.ILog(message);
|
||||
|
||||
permissions = permissions != null && permissions > 0 ? permissions : Permissions;
|
||||
if (permissions == null || permissions < 1)
|
||||
permissions = 777;
|
||||
|
||||
|
||||
if ((File.Exists(path) == false && Directory.Exists(path) == false))
|
||||
{
|
||||
logMethod("SetPermissions: File doesnt existing, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
//StringLogger stringLogger = new StringLogger();
|
||||
var logger = new TestLogger();
|
||||
|
||||
bool isFile = new FileInfo(path).Exists;
|
||||
|
||||
FileHelper.SetPermissions(logger, path, file: isFile, permissions: permissions);
|
||||
|
||||
FileHelper.ChangeOwner(logger, path, file: isFile, ownerGroup: OwnerGroup!);
|
||||
|
||||
logMethod(logger.ToString());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FileFlows.Comic.Tests;
|
||||
|
||||
|
||||
[TestClass]
|
||||
public abstract class TestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the test logger
|
||||
/// </summary>
|
||||
internal TestLogger Logger = new TestLogger();
|
||||
|
||||
/// <summary>
|
||||
/// The test context instance
|
||||
/// </summary>
|
||||
private TestContext? testContextInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the test context
|
||||
/// </summary>
|
||||
public TestContext TestContext
|
||||
{
|
||||
get => testContextInstance!;
|
||||
set => testContextInstance = value;
|
||||
}
|
||||
|
||||
public string TempPath = "/home/john/Comics/temp";
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
if (Directory.Exists(this.TempPath) == false)
|
||||
Directory.CreateDirectory(this.TempPath);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Binary file not shown.
Binary file not shown.
@@ -82,6 +82,67 @@ public class LocalFileService : IFileService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<bool> DirectoryEmpty(string path, string[]? includePatterns = null)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
return Result<bool>.Fail("Cannot access protected path: " + path);
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
return Result<bool>.Success(true); // Path doesn't exist, considered empty
|
||||
|
||||
// Get all files in the directory
|
||||
var files = Directory.GetFiles(path);
|
||||
|
||||
// If there are patterns, only count matching files
|
||||
if (includePatterns != null && includePatterns.Length > 0)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
foreach (var pattern in includePatterns)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(file, pattern.Trim(),
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
return Result<bool>.Success(false); // File matches, directory is not empty
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Handle regex exceptions silently, as per your example
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (files.Length > 0)
|
||||
{
|
||||
// No patterns provided, directory is not empty if any file exists
|
||||
return Result<bool>.Success(false);
|
||||
}
|
||||
|
||||
// Check for directories (subdirectories are not affected by includePatterns)
|
||||
var dirs = Directory.GetDirectories(path);
|
||||
if (dirs.Length > 0)
|
||||
return Result<bool>.Success(false); // Directory contains subdirectories, not empty
|
||||
|
||||
return Result<bool>.Success(true); // Directory is empty
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
return Result<bool>.Fail("Unauthorized access to path: " + path + " - " + ex.Message);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
return Result<bool>.Fail("IO error while accessing path: " + path + " - " + ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<bool>.Fail("Error while accessing path: " + path + " - " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryDelete(string path, bool recursive = false)
|
||||
{
|
||||
if (IsProtectedPath(ref path))
|
||||
|
||||
Reference in New Issue
Block a user