mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2025-12-20 15:59:47 -06:00
unit tests
This commit is contained in:
@@ -24,6 +24,8 @@
|
||||
<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" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<ProjectReference Include="..\PluginTestLibrary\PluginTestLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -86,33 +86,42 @@ File shrunk in size by: {{ difference | file_size }} / {{ percent }}%
|
||||
return _MessageTemplates;
|
||||
}
|
||||
}
|
||||
|
||||
internal IAppriseApi? Api;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = args.GetPluginSettings<PluginSettings>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings?.Endpoint))
|
||||
if (Api == null)
|
||||
{
|
||||
args.Logger?.WLog("No endpoint set");
|
||||
return 2;
|
||||
}
|
||||
var settings = args.GetPluginSettings<PluginSettings>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings?.ServerUrl))
|
||||
{
|
||||
args.Logger?.WLog("No server URL set");
|
||||
return 2;
|
||||
}
|
||||
var endpoint = settings?.Endpoint;
|
||||
var serverUrl = settings?.ServerUrl;
|
||||
|
||||
string url = settings.ServerUrl;
|
||||
if (url.EndsWith("/") == false)
|
||||
url += "/";
|
||||
if (settings.Endpoint.EndsWith("/"))
|
||||
url += settings.Endpoint[1..];
|
||||
else
|
||||
url += settings.Endpoint;
|
||||
if (string.IsNullOrWhiteSpace(endpoint))
|
||||
{
|
||||
args.Logger?.WLog("No endpoint set");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(serverUrl))
|
||||
{
|
||||
args.Logger?.WLog("No server URL set");
|
||||
return 2;
|
||||
}
|
||||
|
||||
string url = serverUrl;
|
||||
if (url.EndsWith('/') == false)
|
||||
url += "/";
|
||||
if (endpoint.EndsWith('/'))
|
||||
url += endpoint[1..];
|
||||
else
|
||||
url += endpoint;
|
||||
Api = new AppriseApi(url);
|
||||
}
|
||||
|
||||
string message = args.RenderTemplate!(this.Message);
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
@@ -121,24 +130,9 @@ File shrunk in size by: {{ difference | file_size }} / {{ percent }}%
|
||||
return 2;
|
||||
}
|
||||
|
||||
object data = new
|
||||
{
|
||||
body = message,
|
||||
tag = Tag?.Any() != true ? "all" : String.Join(";", this.Tag),
|
||||
type = this.MessageType?.EmptyAsNull() ?? "info"
|
||||
};
|
||||
var result = Api.Send(args.Logger!, MessageType, Tag, message);
|
||||
|
||||
var json = JsonSerializer.Serialize(data);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
using var httpClient = new HttpClient();
|
||||
var response = httpClient.PostAsync(url, content).Result;
|
||||
if (response.IsSuccessStatusCode)
|
||||
return 1;
|
||||
|
||||
string error = response.Content.ReadAsStringAsync().Result;
|
||||
args.Logger?.WLog("Error from Apprise: " + error);
|
||||
return 2;
|
||||
return result ? 1 : 2;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
50
Apprise/IAppriseApi.cs
Normal file
50
Apprise/IAppriseApi.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FileFlows.Apprise;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for Apprise
|
||||
/// </summary>
|
||||
public interface IAppriseApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a message
|
||||
/// </summary>
|
||||
/// <param name="logger">the logger to use</param>
|
||||
/// <param name="type">the type of message to send</param>
|
||||
/// <param name="tags">the tags</param>
|
||||
/// <param name="message">the message</param>
|
||||
/// <returns>the result</returns>
|
||||
bool Send(ILogger logger, string? type, string[]? tags, string message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apprise URL instance
|
||||
/// </summary>
|
||||
/// <param name="url">the URL to send to</param>
|
||||
public class AppriseApi(string url) : IAppriseApi
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Send(ILogger logger, string? type, string[]? tags, string message)
|
||||
{
|
||||
|
||||
object data = new
|
||||
{
|
||||
body = message,
|
||||
tag = tags?.Any() != true ? "all" : string.Join(";", tags),
|
||||
type = type?.EmptyAsNull() ?? "info"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(data);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
using var httpClient = new HttpClient();
|
||||
var response = httpClient.PostAsync(url, content).Result;
|
||||
if (response.IsSuccessStatusCode)
|
||||
return true;
|
||||
|
||||
string error = response.Content.ReadAsStringAsync().Result;
|
||||
logger?.WLog("Error from Apprise: " + error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,54 +2,58 @@
|
||||
|
||||
using FileFlows.Apprise.Communication;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Apprise.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class AppriseTests
|
||||
public class AppriseTests : TestBase
|
||||
{
|
||||
private Mock<IAppriseApi> mockApi = new();
|
||||
private NodeParameters Args = null!;
|
||||
|
||||
protected override void TestStarting()
|
||||
{
|
||||
Args = new NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
Args.RenderTemplate = (arg) => arg;
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Apprise_Basic_All()
|
||||
{
|
||||
var args = new NodeParameters("test.file", new TestLogger(), false, string.Empty, null!);;
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
};
|
||||
|
||||
var node = new FileFlows.Apprise.Communication.Apprise();
|
||||
node.Api = mockApi.Object;
|
||||
mockApi.Setup(x => x.Send(It.IsAny<ILogger>(), It.IsAny<string?>(), It.IsAny<string[]?>(), It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
node.Message = "a message";
|
||||
Assert.AreEqual(1, node.Execute(args));
|
||||
Assert.AreEqual(1, node.Execute(Args));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apprise_Basic_Valid()
|
||||
{
|
||||
var args = new NodeParameters("test.file", new TestLogger(), false, string.Empty, null!);;
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
};
|
||||
|
||||
var node = new FileFlows.Apprise.Communication.Apprise();
|
||||
node.Api = mockApi.Object;
|
||||
mockApi.Setup(x => x.Send(It.IsAny<ILogger>(), It.IsAny<string?>(), It.IsAny<string[]?>(), It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
node.Message = "a message";
|
||||
node.Tag = new[] { "test" };
|
||||
Assert.AreEqual(1, node.Execute(args));
|
||||
node.Tag = [ "test" ];
|
||||
Assert.AreEqual(1, node.Execute(Args));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apprise_Basic_Invalid()
|
||||
{
|
||||
var args = new NodeParameters("test.file", new TestLogger(), false, string.Empty, null!);
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
};
|
||||
|
||||
var node = new FileFlows.Apprise.Communication.Apprise();
|
||||
node.Api = mockApi.Object;
|
||||
mockApi.Setup(x => x.Send(It.IsAny<ILogger>(), It.IsAny<string?>(), It.IsAny<string[]?>(), It.IsAny<string>()))
|
||||
.Returns(false);
|
||||
|
||||
node.Message = "a message";
|
||||
node.Tag = new[] { "invalid" };
|
||||
Assert.AreEqual(2, node.Execute(args));
|
||||
node.Tag = [ "invalid" ];
|
||||
Assert.AreEqual(2, node.Execute(Args));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.Apprise.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
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
//using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using FileFlows.AudioNodes.Helpers;
|
||||
using TagLib.Matroska;
|
||||
|
||||
namespace FileFlows.AudioNodes;
|
||||
@@ -13,19 +14,27 @@ public class AudioInfoHelper
|
||||
private string ffprobe;
|
||||
private ILogger Logger;
|
||||
|
||||
internal static IFFmpegHelper ffmpegHelper;
|
||||
internal static bool UNIT_TEST = false;
|
||||
|
||||
public AudioInfoHelper(string ffMpegExe, string ffprobe, ILogger logger)
|
||||
{
|
||||
this.ffMpegExe = ffMpegExe;
|
||||
this.ffprobe = ffprobe;
|
||||
if (ffmpegHelper == null)
|
||||
ffmpegHelper = new FFmpegHelper(ffMpegExe, ffprobe);
|
||||
this.Logger = logger;
|
||||
}
|
||||
|
||||
public Result<AudioInfo> Read(string filename)
|
||||
{
|
||||
if (System.IO.File.Exists(filename) == false)
|
||||
return Result<AudioInfo>.Fail("File not found: " + filename);
|
||||
if (string.IsNullOrEmpty(ffMpegExe) || System.IO.File.Exists(ffMpegExe) == false)
|
||||
return Result<AudioInfo>.Fail("FFmpeg not found: " + (ffMpegExe ?? "not passed in"));
|
||||
if (UNIT_TEST == false)
|
||||
{
|
||||
if (System.IO.File.Exists(filename) == false)
|
||||
return Result<AudioInfo>.Fail("File not found: " + filename);
|
||||
if (string.IsNullOrEmpty(ffMpegExe) || System.IO.File.Exists(ffMpegExe) == false)
|
||||
return Result<AudioInfo>.Fail("FFmpeg not found: " + (ffMpegExe ?? "not passed in"));
|
||||
}
|
||||
|
||||
var mi = new AudioInfo();
|
||||
var result = ReadFromFFprobe(filename);
|
||||
@@ -45,7 +54,7 @@ public class AudioInfoHelper
|
||||
|
||||
try
|
||||
{
|
||||
var ffmpegOutput = ExecuteFfmpeg(ffMpegExe, filename);
|
||||
var ffmpegOutput = ffmpegHelper.ReadFFmpeg(filename);
|
||||
if (ffmpegOutput.Failed(out string error))
|
||||
return Result<AudioInfo>.Fail("Failed reading ffmpeg info: " + error);
|
||||
var output = ffmpegOutput.Value;
|
||||
@@ -197,7 +206,7 @@ public class AudioInfoHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
var ffprobeResult = ExecuteFfprobe(ffprobe, file);
|
||||
var ffprobeResult = ffmpegHelper.ReadFFprobe(file);
|
||||
if (ffprobeResult.Failed(out string error))
|
||||
{
|
||||
Logger.WLog("Failed reading information from FFprobe: " + Environment.NewLine + error);
|
||||
@@ -210,27 +219,27 @@ public class AudioInfoHelper
|
||||
return Result<AudioInfo>.Fail(result.Error);
|
||||
|
||||
var audioInfo = new AudioInfo();
|
||||
audioInfo.Album = result.Value.Tags.Album;
|
||||
audioInfo.Artist = result.Value.Tags.Artist;
|
||||
audioInfo.Album = result.Value.Tags?.Album;
|
||||
audioInfo.Artist = result.Value.Tags?.Artist;
|
||||
audioInfo.Bitrate = result.Value.Bitrate;
|
||||
audioInfo.Codec = result.Value.FormatName;
|
||||
if (DateTime.TryParse(result.Value.Tags.Date ?? string.Empty, out DateTime date))
|
||||
if (DateTime.TryParse(result.Value.Tags?.Date ?? string.Empty, out DateTime date))
|
||||
audioInfo.Date = date;
|
||||
else if (int.TryParse(result.Value.Tags.Date ?? string.Empty, out int year))
|
||||
else if (int.TryParse(result.Value.Tags?.Date ?? string.Empty, out int year))
|
||||
audioInfo.Date = new DateTime(year, 1, 1);
|
||||
|
||||
if (int.TryParse(result.Value.Tags.Disc ?? string.Empty, out int disc))
|
||||
if (int.TryParse(result.Value.Tags?.Disc ?? string.Empty, out int disc))
|
||||
audioInfo.Disc = disc;
|
||||
audioInfo.Duration = (long)result.Value.Duration.TotalSeconds;
|
||||
audioInfo.Genres = result.Value.Tags?.Genre
|
||||
?.Split(new string[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries)?.Select(x => x.Trim())
|
||||
?.ToArray();
|
||||
audioInfo.Title = result.Value.Tags.Title;
|
||||
if (int.TryParse(result.Value.Tags.Track, out int track))
|
||||
audioInfo.Title = result.Value.Tags?.Title;
|
||||
if (int.TryParse(result.Value.Tags?.Track, out int track))
|
||||
audioInfo.Track = track;
|
||||
if (int.TryParse(result.Value.Tags.TotalDiscs, out int totalDiscs))
|
||||
if (int.TryParse(result.Value.Tags?.TotalDiscs, out int totalDiscs))
|
||||
audioInfo.TotalDiscs = totalDiscs;
|
||||
if (int.TryParse(result.Value.Tags.TotalTracks, out int totalTracks))
|
||||
if (int.TryParse(result.Value.Tags?.TotalTracks, out int totalTracks))
|
||||
audioInfo.TotalTracks = totalTracks;
|
||||
|
||||
return audioInfo;
|
||||
@@ -241,105 +250,6 @@ public class AudioInfoHelper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the ffmpeg process with the given file and returns the output or an error message.
|
||||
/// </summary>
|
||||
/// <param name="ffmpeg">The path to the ffmpeg executable.</param>
|
||||
/// <param name="file">The file to be probed by ffprobe.</param>
|
||||
/// <returns>A Result object containing the output string if successful, or an error message if not.</returns>
|
||||
public Result<string> ExecuteFfmpeg(string ffmpeg, string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = ffmpeg,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
ArgumentList = {
|
||||
"-hide_banner",
|
||||
"-i",
|
||||
file
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
|
||||
bool exited = process.WaitForExit(60000);
|
||||
if (exited == false)
|
||||
{
|
||||
process.Kill();
|
||||
string pkOutput = process.StandardError.ReadToEnd()?.EmptyAsNull() ?? process.StandardOutput.ReadToEnd();
|
||||
return Result<string>.Fail("Process timed out." + Environment.NewLine + pkOutput);
|
||||
}
|
||||
|
||||
// we use error here, since we're not specify an output file, FFmpeg will report it as an error, but we don't care
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
|
||||
return output?.EmptyAsNull() ?? error;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<string>.Fail($"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the ffprobe process with the given file and returns the output or an error message.
|
||||
/// </summary>
|
||||
/// <param name="ffprobe">The path to the ffprobe executable.</param>
|
||||
/// <param name="file">The file to be probed by ffprobe.</param>
|
||||
/// <returns>A Result object containing the output string if successful, or an error message if not.</returns>
|
||||
public Result<string> ExecuteFfprobe(string ffprobe, string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = ffprobe,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
ArgumentList = {
|
||||
"-v", "error",
|
||||
"-select_streams", "a:0",
|
||||
"-show_format",
|
||||
"-of", "json",
|
||||
file
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
|
||||
bool exited = process.WaitForExit(60000);
|
||||
if (exited == false)
|
||||
{
|
||||
process.Kill();
|
||||
string pkOutput = process.StandardError.ReadToEnd()?.EmptyAsNull() ?? process.StandardOutput.ReadToEnd();
|
||||
return Result<string>.Fail("Process timed out." + Environment.NewLine + pkOutput);
|
||||
}
|
||||
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
|
||||
if (string.IsNullOrEmpty(error) == false)
|
||||
return Result<string>.Fail($"Failed reading ffmpeg info: {error}");
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<string>.Fail($"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public AudioInfo ReadMetaData(string file)
|
||||
{
|
||||
@@ -372,11 +282,20 @@ public class AudioInfoHelper
|
||||
|
||||
public void ParseFileNameInfo(string filename, AudioInfo mi)
|
||||
{
|
||||
TagLib.File? tfile = null;
|
||||
try
|
||||
{
|
||||
using var tfile = TagLib.File.Create(filename);
|
||||
|
||||
var fileInfo = new System.IO.FileInfo(filename);
|
||||
try
|
||||
{
|
||||
tfile = TagLib.File.Create(filename);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
var ext = FileHelper.GetExtension(filename);
|
||||
string shortFileName = FileHelper.GetShortFileName(filename);
|
||||
|
||||
bool dirty = false;
|
||||
|
||||
@@ -387,26 +306,28 @@ public class AudioInfoHelper
|
||||
{
|
||||
dirty = true;
|
||||
mi.Disc = disc;
|
||||
tfile.Tag.Disc = Convert.ToUInt32(disc);
|
||||
if(tfile != null)
|
||||
tfile.Tag.Disc = Convert.ToUInt32(disc);
|
||||
}
|
||||
}
|
||||
|
||||
if (mi.Track < 1)
|
||||
{
|
||||
var trackMatch = Regex.Match(fileInfo.Name, @"[\-_\s\.]+([\d]{1,2})[\-_\s\.]+");
|
||||
var trackMatch = Regex.Match(shortFileName, @"[\-_\s\.]*([\d]{1,2})[\-_\s\.]+");
|
||||
if (trackMatch.Success)
|
||||
{
|
||||
string trackString = trackMatch.Value;
|
||||
if (int.TryParse(Regex.Match(trackString, @"[\d]+").Value, out int track))
|
||||
{
|
||||
mi.Track = track;
|
||||
tfile.Tag.Track = Convert.ToUInt32(track);
|
||||
if(tfile != null)
|
||||
tfile.Tag.Track = Convert.ToUInt32(track);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string album = fileInfo.Directory.Name;
|
||||
string album = FileHelper.GetDirectoryName(filename);
|
||||
var yearMatch = Regex.Match(album, @"(?<=(\())[\d]{4}(?!=\))");
|
||||
if (yearMatch.Success)
|
||||
{
|
||||
@@ -417,7 +338,8 @@ public class AudioInfoHelper
|
||||
if (int.TryParse(yearMatch.Value, out int year))
|
||||
{
|
||||
mi.Date = new DateTime(year, 1, 1);
|
||||
tfile.Tag.Year = Convert.ToUInt32(year);
|
||||
if(tfile != null)
|
||||
tfile.Tag.Year = Convert.ToUInt32(year);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -429,17 +351,19 @@ public class AudioInfoHelper
|
||||
mi.Album = album;
|
||||
if (string.IsNullOrEmpty(album) == false)
|
||||
{
|
||||
tfile.Tag.Album = mi.Album;
|
||||
if(tfile != null)
|
||||
tfile.Tag.Album = mi.Album;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(mi.Artist))
|
||||
{
|
||||
mi.Artist = fileInfo.Directory.Parent.Name;
|
||||
mi.Artist = FileHelper.GetDirectoryName(FileHelper.GetDirectory(filename));
|
||||
if (string.IsNullOrEmpty(mi.Artist) == false)
|
||||
{
|
||||
tfile.Tag.AlbumArtists = new[] { mi.Artist };
|
||||
if(tfile != null)
|
||||
tfile.Tag.AlbumArtists = new[] { mi.Artist };
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -447,20 +371,21 @@ public class AudioInfoHelper
|
||||
// the title
|
||||
if (string.IsNullOrEmpty(mi.Title))
|
||||
{
|
||||
int titleIndex = fileInfo.Name.LastIndexOf(" - ");
|
||||
int titleIndex = shortFileName.LastIndexOf(" - ", StringComparison.Ordinal);
|
||||
if (titleIndex > 0)
|
||||
{
|
||||
mi.Title = fileInfo.Name.Substring(titleIndex + 3);
|
||||
if (string.IsNullOrEmpty(fileInfo.Extension) == false)
|
||||
mi.Title = shortFileName[(titleIndex + 3)..];
|
||||
if (string.IsNullOrEmpty(ext) == false)
|
||||
{
|
||||
mi.Title = mi.Title.Replace(fileInfo.Extension, "");
|
||||
tfile.Tag.Title = mi.Title;
|
||||
mi.Title = mi.Title.Replace(ext, "");
|
||||
if(tfile != null)
|
||||
tfile.Tag.Title = mi.Title;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dirty)
|
||||
if(dirty && tfile != null)
|
||||
tfile.Save();
|
||||
|
||||
}
|
||||
@@ -468,6 +393,8 @@ public class AudioInfoHelper
|
||||
{
|
||||
Logger?.WLog("Failed parsing Audio info from filename: " + ex.Message + Environment.NewLine + ex.StackTrace);
|
||||
}
|
||||
if(tfile != null)
|
||||
tfile.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,11 @@
|
||||
<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" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<ProjectReference Include="..\PluginTestLibrary\PluginTestLibrary.csproj" />
|
||||
<Content Include="Tests\Resources\**\*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
|
||||
@@ -7,4 +7,7 @@ global using System.Collections.Generic;
|
||||
global using FileFlows.Plugin;
|
||||
global using FileFlows.Plugin.Attributes;
|
||||
|
||||
global using FileHelper = FileFlows.Plugin.Helpers.FileHelper;
|
||||
global using FileHelper = FileFlows.Plugin.Helpers.FileHelper;
|
||||
#if(DEBUG)
|
||||
global using PluginTestLibrary;
|
||||
#endif
|
||||
119
AudioNodes/Helpers/IFFmpegHelper.cs
Normal file
119
AudioNodes/Helpers/IFFmpegHelper.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace FileFlows.AudioNodes.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for FFmpeg Helper
|
||||
/// </summary>
|
||||
public interface IFFmpegHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the info for a file
|
||||
/// </summary>
|
||||
/// <param name="file">the path to the file to read</param>
|
||||
/// <returns>the file output</returns>
|
||||
Result<string> ReadFFmpeg(string file);
|
||||
|
||||
/// <summary>
|
||||
/// Reads the info for a file from FFprobe
|
||||
/// </summary>
|
||||
/// <param name="file">the path to the file to read</param>
|
||||
/// <returns>the file output</returns>
|
||||
Result<string> ReadFFprobe(string file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FFmpeg Helper
|
||||
/// </summary>
|
||||
/// <param name="ffmpeg">the FFmpeg executable</param>
|
||||
/// <param name="ffprobe">the FFprobe executable</param>
|
||||
public class FFmpegHelper(string ffmpeg, string ffprobe) : IFFmpegHelper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Result<string> ReadFFmpeg(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = ffmpeg,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
ArgumentList = {
|
||||
"-hide_banner",
|
||||
"-i",
|
||||
file
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
|
||||
bool exited = process.WaitForExit(60000);
|
||||
if (exited == false)
|
||||
{
|
||||
process.Kill();
|
||||
string pkOutput = process.StandardError.ReadToEnd()?.EmptyAsNull() ?? process.StandardOutput.ReadToEnd();
|
||||
return Result<string>.Fail("Process timed out." + Environment.NewLine + pkOutput);
|
||||
}
|
||||
|
||||
// we use error here, since we're not specify an output file, FFmpeg will report it as an error, but we don't care
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
|
||||
return output?.EmptyAsNull() ?? error;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<string>.Fail($"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<string> ReadFFprobe(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = ffprobe,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
ArgumentList = {
|
||||
"-v", "error",
|
||||
"-select_streams", "a:0",
|
||||
"-show_format",
|
||||
"-of", "json",
|
||||
file
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
|
||||
bool exited = process.WaitForExit(60000);
|
||||
if (exited == false)
|
||||
{
|
||||
process.Kill();
|
||||
string pkOutput = process.StandardError.ReadToEnd()?.EmptyAsNull() ?? process.StandardOutput.ReadToEnd();
|
||||
return Result<string>.Fail("Process timed out." + Environment.NewLine + pkOutput);
|
||||
}
|
||||
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
|
||||
if (string.IsNullOrEmpty(error) == false)
|
||||
return Result<string>.Fail($"Failed reading ffmpeg info: {error}");
|
||||
|
||||
return output;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<string>.Fail($"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +1,76 @@
|
||||
namespace FileFlows.AudioNodes
|
||||
namespace FileFlows.AudioNodes;
|
||||
|
||||
public class AudioFile : AudioNode
|
||||
{
|
||||
using System.ComponentModel;
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Attributes;
|
||||
public override int Outputs => 1;
|
||||
public override FlowElementType Type => FlowElementType.Input;
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/audio-nodes/audio-file";
|
||||
|
||||
public class AudioFile : AudioNode
|
||||
private Dictionary<string, object> _Variables;
|
||||
public override Dictionary<string, object> Variables => _Variables;
|
||||
public AudioFile()
|
||||
{
|
||||
public override int Outputs => 1;
|
||||
public override FlowElementType Type => FlowElementType.Input;
|
||||
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/audio-nodes/audio-file";
|
||||
|
||||
private Dictionary<string, object> _Variables;
|
||||
public override Dictionary<string, object> Variables => _Variables;
|
||||
public AudioFile()
|
||||
_Variables = new Dictionary<string, object>()
|
||||
{
|
||||
_Variables = new Dictionary<string, object>()
|
||||
{
|
||||
{ "mi.Album", "Album" },
|
||||
{ "mi.Artist", "Artist" },
|
||||
{ "mi.ArtistThe", "Artist, The" },
|
||||
{ "mi.Bitrate", 845 },
|
||||
{ "mi.Channels", 2 },
|
||||
{ "mi.Codec", "flac" },
|
||||
{ "mi.Date", new DateTime(2020, 05, 23) },
|
||||
{ "mi.Year", 2020 },
|
||||
{ "mi.Duration", 256 },
|
||||
{ "mi.Encoder", "FLAC 1.2.1" },
|
||||
{ "mi.Frequency", 44100 },
|
||||
{ "mi.Genres", new [] { "Pop", "Rock" } },
|
||||
{ "mi.Language", "English" },
|
||||
{ "mi.Title", "Song Title" },
|
||||
{ "mi.Track", 2 },
|
||||
{ "mi.Disc", 2 },
|
||||
{ "mi.TotalDiscs", 2 }
|
||||
};
|
||||
}
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
var ffmpegExeResult = GetFFmpeg(args);
|
||||
if (ffmpegExeResult.Failed(out string ffmpegError))
|
||||
{
|
||||
args.FailureReason = ffmpegError;
|
||||
args.Logger?.ELog(ffmpegError);
|
||||
return -1;
|
||||
}
|
||||
string ffmpegExe = ffmpegExeResult.Value;
|
||||
|
||||
var ffprobeResult = GetFFprobe(args);
|
||||
if (ffprobeResult.Failed(out string ffprobeError))
|
||||
{
|
||||
args.FailureReason = ffprobeError;
|
||||
args.Logger?.ELog(ffprobeError);
|
||||
return -1;
|
||||
}
|
||||
string ffprobe = ffprobeResult.Value;
|
||||
|
||||
|
||||
if (args.FileService.FileCreationTimeUtc(args.WorkingFile).Success(out DateTime createTime))
|
||||
args.Variables["ORIGINAL_CREATE_UTC"] = createTime;
|
||||
if (args.FileService.FileLastWriteTimeUtc(args.WorkingFile).Success(out DateTime writeTime))
|
||||
args.Variables["ORIGINAL_LAST_WRITE_UTC"] = writeTime;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (ReadAudioFileInfo(args, ffmpegExe, ffprobe, LocalWorkingFile))
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.Logger.ELog("Failed processing AudioFile: " + ex.Message);
|
||||
args.FailureReason = "Failed processing AudioFile: " + ex.Message;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
{ "mi.Album", "Album" },
|
||||
{ "mi.Artist", "Artist" },
|
||||
{ "mi.ArtistThe", "Artist, The" },
|
||||
{ "mi.Bitrate", 845 },
|
||||
{ "mi.Channels", 2 },
|
||||
{ "mi.Codec", "flac" },
|
||||
{ "mi.Date", new DateTime(2020, 05, 23) },
|
||||
{ "mi.Year", 2020 },
|
||||
{ "mi.Duration", 256 },
|
||||
{ "mi.Encoder", "FLAC 1.2.1" },
|
||||
{ "mi.Frequency", 44100 },
|
||||
{ "mi.Genres", new [] { "Pop", "Rock" } },
|
||||
{ "mi.Language", "English" },
|
||||
{ "mi.Title", "Song Title" },
|
||||
{ "mi.Track", 2 },
|
||||
{ "mi.Disc", 2 },
|
||||
{ "mi.TotalDiscs", 2 }
|
||||
};
|
||||
}
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
var ffmpegExeResult = GetFFmpeg(args);
|
||||
if (ffmpegExeResult.Failed(out string ffmpegError))
|
||||
{
|
||||
args.FailureReason = ffmpegError;
|
||||
args.Logger?.ELog(ffmpegError);
|
||||
return -1;
|
||||
}
|
||||
string ffmpegExe = ffmpegExeResult.Value;
|
||||
|
||||
var ffprobeResult = GetFFprobe(args);
|
||||
if (ffprobeResult.Failed(out string ffprobeError))
|
||||
{
|
||||
args.FailureReason = ffprobeError;
|
||||
args.Logger?.ELog(ffprobeError);
|
||||
return -1;
|
||||
}
|
||||
string ffprobe = ffprobeResult.Value;
|
||||
|
||||
|
||||
if (args.FileService.FileCreationTimeUtc(args.WorkingFile).Success(out DateTime createTime))
|
||||
args.Variables["ORIGINAL_CREATE_UTC"] = createTime;
|
||||
if (args.FileService.FileLastWriteTimeUtc(args.WorkingFile).Success(out DateTime writeTime))
|
||||
args.Variables["ORIGINAL_LAST_WRITE_UTC"] = writeTime;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (ReadAudioFileInfo(args, ffmpegExe, ffprobe, LocalWorkingFile))
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.Logger.ELog("Failed processing AudioFile: " + ex.Message);
|
||||
args.FailureReason = "Failed processing AudioFile: " + ex.Message;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -349,6 +349,11 @@ namespace FileFlows.AudioNodes
|
||||
args.Logger?.ILog($"Audio file already '{DefaultExtension}' at bitrate '{AudioInfo.Bitrate} bps ({(AudioInfo.Bitrate / 1000)} KBps)'");
|
||||
return 2;
|
||||
}
|
||||
if(AudioInfo.Bitrate <= Bitrate * 1024) // this bitrate is in Kbps, whereas AudioInfo.Bitrate is bytes per second
|
||||
{
|
||||
args.Logger?.ILog($"Audio file already '{DefaultExtension}' at bitrate '{AudioInfo.Bitrate} bps ({(AudioInfo.Bitrate / 1024)} KBps)'");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,6 @@ namespace FileFlows.AudioNodes.Tests;
|
||||
[TestClass]
|
||||
public class AudioBitrateMatchesTests
|
||||
{
|
||||
const string file = @"/home/john/Music/test/test.mp3";
|
||||
readonly string ffmpegExe = (OperatingSystem.IsLinux() ? "/usr/bin/ffmpeg" : @"C:\utils\ffmpeg\ffmpeg.exe");
|
||||
readonly string ffprobe = (OperatingSystem.IsLinux() ? "/usr/bin/ffprobe" : @"C:\utils\ffmpeg\ffprobe.exe");
|
||||
|
||||
[TestMethod]
|
||||
public void AudioInfo_SplitTrack()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#if(DEBUG)
|
||||
using AudioNodes.Tests;
|
||||
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.AudioNodes.Tests;
|
||||
|
||||
@@ -13,69 +14,41 @@ using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
[TestClass]
|
||||
public class AudioFileNormalizationTests
|
||||
public class AudioFileNormalizationTests : AudioTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void AudioFileNormalization_Mp3()
|
||||
{
|
||||
var args = GetNodeParameters();
|
||||
|
||||
var audioFile = new AudioFile();
|
||||
audioFile.PreExecute(args);
|
||||
audioFile.Execute(args); // need to read the Audio info and set it
|
||||
|
||||
const string file = @"D:\music\unprocessed\01-billy_joel-movin_out.mp3";
|
||||
|
||||
AudioFileNormalization node = new ();
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
AudioFileNormalization node = new();
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
[TestMethod]
|
||||
public void AudioFileNormalization_Bulk()
|
||||
{
|
||||
|
||||
foreach (var file in Directory.GetFiles(@"D:\music\unprocessed"))
|
||||
{
|
||||
|
||||
AudioFileNormalization node = new();
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void AudioFileNormalization_ConvertFlacToMp3()
|
||||
{
|
||||
var args = GetNodeParameters(AudioFlac);
|
||||
|
||||
const string file = @"D:\music\flacs\03-billy_joel-dont_ask_me_why.flac";
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
var audioFile = new AudioFile();
|
||||
audioFile.PreExecute(args);
|
||||
audioFile.Execute(args); // need to read the Audio info and set it0
|
||||
|
||||
ConvertToMP3 convertNode = new();
|
||||
convertNode.PreExecute(args);
|
||||
int output = convertNode.Execute(args);
|
||||
|
||||
|
||||
AudioFileNormalization normalNode = new();
|
||||
normalNode.PreExecute(args);
|
||||
output = normalNode.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,19 @@
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AudioNodes.Tests;
|
||||
using FileFlows.AudioNodes.Helpers;
|
||||
using Moq;
|
||||
|
||||
namespace FileFlows.AudioNodes.Tests;
|
||||
[TestClass]
|
||||
public class AudioInfoTests: AudioTestBase
|
||||
{
|
||||
const string file = @"/home/john/Music/test/test.wav";
|
||||
readonly string ffmpegExe = (OperatingSystem.IsLinux() ? "/usr/local/bin/ffmpeg" : @"C:\utils\ffmpeg\ffmpeg.exe");
|
||||
readonly string ffprobe = (OperatingSystem.IsLinux() ? "/usr/local/bin/ffprobe" : @"C:\utils\ffmpeg\ffprobe.exe");
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void AudioInfo_SplitTrack()
|
||||
{
|
||||
var args = GetNodeParameters(TestFile_Mp3);
|
||||
var args = GetNodeParameters();
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
var result = af.Execute(args); // need to read the Audio info and set it
|
||||
@@ -26,63 +23,46 @@ public class AudioInfoTests: AudioTestBase
|
||||
|
||||
var AudioInfo = args.Parameters["AudioInfo"] as AudioInfo;
|
||||
|
||||
Assert.AreEqual(4, AudioInfo.Track);
|
||||
Assert.AreEqual(8, AudioInfo.Track);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioInfo_NormalTrack()
|
||||
{
|
||||
const string file = @"\\oracle\Audio\Taylor Swift\Speak Now\Taylor Swift - Speak Now - 08 - Never Grow Up.mp3";
|
||||
|
||||
var args = GetNodeParameters(file, false);
|
||||
var AudioInfo = new AudioInfoHelper(ffmpeg, ffprobe, args.Logger).Read(args.WorkingFile);
|
||||
var args = GetNodeParameters();
|
||||
var AudioInfo = new AudioInfoHelper(FFprobe, FFprobe, Logger).Read(args.WorkingFile);
|
||||
|
||||
Assert.AreEqual(8, AudioInfo.Value.Track);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioInfo_LargeWav()
|
||||
{
|
||||
string file = Path.Combine(TestPath, "large-wav.wav");
|
||||
|
||||
var args = GetNodeParameters(file, false);
|
||||
var AudioInfo = new AudioInfoHelper(ffmpeg, ffprobe, args.Logger).Read(args.WorkingFile);
|
||||
|
||||
Assert.AreEqual(96000, AudioInfo.Value.Frequency);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioInfo_GetMetaData()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
foreach (string file in Directory.GetFiles(@"/home/john/Music/test"))
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => ffmpegExe;
|
||||
var args = GetNodeParameters();
|
||||
|
||||
// laod the variables
|
||||
Assert.AreEqual(1, new AudioFile().Execute(args));
|
||||
// load the variables
|
||||
var audioFile = new AudioFile();
|
||||
audioFile.PreExecute(args);
|
||||
Assert.AreEqual(1, audioFile.Execute(args));
|
||||
|
||||
var audio = new AudioInfoHelper(ffmpegExe, ffprobe, args.Logger).Read(args.WorkingFile).Value;
|
||||
var audio = new AudioInfoHelper(FFmpeg, FFprobe, Logger).Read(args.WorkingFile).Value;
|
||||
|
||||
string folder = args.ReplaceVariables("{audio.ArtistThe} ({audio.Year})");
|
||||
Assert.AreEqual($"{audio.Artist} ({audio.Date.Year})", folder);
|
||||
string folder = args.ReplaceVariables("{audio.ArtistThe} ({audio.Year})");
|
||||
Assert.AreEqual($"{audio.Artist} ({audio.Date.Year})", folder);
|
||||
|
||||
string fname = args.ReplaceVariables("{audio.Artist} - {audio.Album} - {audio.Track:##} - {audio.Title}");
|
||||
Assert.AreEqual($"{audio.Artist} - {audio.Track.ToString("00")} - {audio.Title}", fname);
|
||||
}
|
||||
string fname = args.ReplaceVariables("{audio.Artist} - {audio.Album} - {audio.Track|##} - {audio.Title}");
|
||||
Assert.AreEqual($"{audio.Artist} - {audio.Album} - {audio.Track:00} - {audio.Title}", fname);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioInfo_FileNameMetadata()
|
||||
{
|
||||
const string ffmpegExe = @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
var logger = new TestLogger();
|
||||
string file = @"\\jor-el\Audio\Meat Loaf\Bat out of Hell II- Back Into Hell… (1993)\Meat Loaf - Bat out of Hell II- Back Into Hell… - 03 - I’d Do Anything for Love (but I Won’t Do That).flac";
|
||||
|
||||
var audio = new AudioInfo();
|
||||
|
||||
new AudioInfoHelper(ffmpegExe, ffprobe, logger).ParseFileNameInfo(file, audio);
|
||||
string file =
|
||||
"/media/Meat Loaf/Bat out of Hell II- Back Into Hell… (1993)/03 - I’d Do Anything for Love (but I Won’t Do That).mp3";
|
||||
|
||||
new AudioInfoHelper(FFmpeg, FFprobe, Logger).ParseFileNameInfo(file, audio);
|
||||
|
||||
Assert.AreEqual("Meat Loaf", audio.Artist);
|
||||
Assert.AreEqual("Bat out of Hell II- Back Into Hell…", audio.Album);
|
||||
@@ -90,19 +70,16 @@ public class AudioInfoTests: AudioTestBase
|
||||
Assert.AreEqual("I’d Do Anything for Love (but I Won’t Do That)", audio.Title);
|
||||
Assert.AreEqual(3, audio.Track);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void AudioInfo_Bitrate()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
var file = @"/home/john/Music/test/test.mp3";
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => ffmpegExe;
|
||||
var args = GetNodeParameters(AudioOgg);
|
||||
|
||||
// load the variables
|
||||
Assert.AreEqual(1, new AudioFile().Execute(args));
|
||||
var audioFile = new AudioFile();
|
||||
audioFile.PreExecute(args);
|
||||
Assert.AreEqual(1, audioFile.Execute(args));
|
||||
|
||||
// convert to 192
|
||||
var convert = new ConvertAudio();
|
||||
@@ -113,7 +90,7 @@ public class AudioInfoTests: AudioTestBase
|
||||
int result = convert.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
var audio = new AudioInfoHelper(ffmpegExe, ffprobe, args.Logger).Read(args.WorkingFile).Value;
|
||||
var audio = new AudioInfoHelper(FFmpeg, FFprobe, Logger).Read(args.WorkingFile).Value;
|
||||
Assert.AreEqual(192 * 1024, audio.Bitrate);
|
||||
|
||||
var md = new Dictionary<string, object>();
|
||||
@@ -129,12 +106,9 @@ public class AudioInfoTests: AudioTestBase
|
||||
convert.PreExecute(args);
|
||||
result = convert.Execute(args);
|
||||
Assert.AreEqual(2, result);
|
||||
|
||||
string log = logger.ToString();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[RequiresUnreferencedCode("")]
|
||||
public void AudioFormatInfoTest()
|
||||
{
|
||||
string ffmpegOutput = @"{
|
||||
@@ -163,7 +137,6 @@ public class AudioInfoTests: AudioTestBase
|
||||
}
|
||||
}";
|
||||
|
||||
// Deserialize the JSON using System.Text.Json
|
||||
var result = FFprobeAudioInfo.Parse(ffmpegOutput);
|
||||
Assert.IsFalse(result.IsFailed);
|
||||
var audioFormatInfo = result.Value;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using FileFlows.AudioNodes.Tests;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace AudioNodes.Tests;
|
||||
|
||||
public abstract class AudioTestBase : TestBase
|
||||
{
|
||||
protected NodeParameters GetNodeParameters(string file, bool isDirectory = false)
|
||||
{
|
||||
var args = new NodeParameters(file, Logger, isDirectory, string.Empty, new LocalFileService());
|
||||
|
||||
args.GetToolPathActual = (string tool) =>
|
||||
{
|
||||
if (tool.ToLowerInvariant() == "ffmpeg")
|
||||
return ffmpeg;
|
||||
if (tool.ToLowerInvariant() == "ffprobe")
|
||||
return ffprobe;
|
||||
return null;
|
||||
};
|
||||
args.TempPath = TempPath;
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -8,38 +8,26 @@ using FileFlows.Plugin.Services;
|
||||
namespace FileFlows.AudioNodes.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class ConvertTests : AudioTestBase
|
||||
public class ConvertTests : AudioTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void Convert_FlacToAac()
|
||||
{
|
||||
//const string file = @"/home/john/Music/unprocessed/Aqua - Aquarium - 03 - Barbie Girl.flac";
|
||||
const string file = "/home/john/Music/unprocessed/Christina Perri - Lovestrong. (2011) - 04 - Distance.mp3";
|
||||
|
||||
foreach (var codec in new[] { "MP3", "aac", "ogg"})
|
||||
foreach (var codec in new[] { "MP3", "aac"} )
|
||||
{
|
||||
foreach (int quality in new[] { 0, 10 })
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
ConvertAudio node = new();
|
||||
node.Codec = codec;
|
||||
node.Bitrate = quality + 10;
|
||||
node.HighEfficiency = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, new LocalFileService());
|
||||
args.GetToolPathActual = (string tool) =>
|
||||
{
|
||||
if(tool.ToLowerInvariant().Contains("ffmpeg")) return @"/usr/local/bin/ffmpeg";
|
||||
if(tool.ToLowerInvariant().Contains("ffprobe")) return @"/usr/local/bin/ffprobe";
|
||||
return tool;
|
||||
};
|
||||
args.TempPath = @"/home/john/temp";
|
||||
var args = GetNodeParameters(AudioFlac);
|
||||
var af = new AudioFile();
|
||||
Assert.IsTrue(af.PreExecute(args));
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
Assert.IsTrue(node.PreExecute(args));
|
||||
int output = node.Execute(args);
|
||||
|
||||
var log = logger.ToString();
|
||||
Assert.AreEqual(1, output);
|
||||
var fi = new FileInfo(args.WorkingFile);
|
||||
File.Move(args.WorkingFile, Path.Combine(fi.DirectoryName, quality + fi.Extension), true);
|
||||
@@ -50,29 +38,28 @@ public class ConvertTests : AudioTestBase
|
||||
[TestMethod]
|
||||
public void Convert_FlacToMp3()
|
||||
{
|
||||
|
||||
const string file = @"D:\music\unprocessed\01-billy_joel-you_may_be_right.flac";
|
||||
|
||||
var args = GetNodeParameters(AudioFlac);
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
|
||||
ConvertToMP3 node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_Mp3ToWAV()
|
||||
{
|
||||
|
||||
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
|
||||
|
||||
var args = GetNodeParameters();
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
|
||||
ConvertToWAV node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
@@ -81,33 +68,17 @@ public class ConvertTests : AudioTestBase
|
||||
[TestMethod]
|
||||
public void Convert_Mp3ToOgg()
|
||||
{
|
||||
|
||||
const string file = @"/home/john/Music/unprocessed/The Cranberries - No Need to Argue (1994) - 04 - Zombie.mp3";
|
||||
|
||||
//ConvertToOGG node = new();
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, new LocalFileService());;
|
||||
args.GetToolPathActual = (string tool) =>
|
||||
{
|
||||
if(tool.ToLowerInvariant() == "ffprobe")
|
||||
return @"/usr/local/bin/ffprobe";
|
||||
if(tool.ToLowerInvariant() == "ffmpeg")
|
||||
return @"/usr/local/bin/ffmpeg";
|
||||
return string.Empty;
|
||||
};
|
||||
args.TempPath = @"/home/john/Music/temp";
|
||||
var args = GetNodeParameters();
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
//int output = node.Execute(args);
|
||||
|
||||
var ele = new ConvertAudio();
|
||||
ele.Codec = "libopus";
|
||||
ele.Codec = "ogg";
|
||||
ele.Bitrate = 320;
|
||||
ele.PreExecute(args);
|
||||
int output = ele.Execute(args);
|
||||
|
||||
TestContext.WriteLine(logger.ToString());
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
@@ -115,7 +86,7 @@ public class ConvertTests : AudioTestBase
|
||||
[TestMethod]
|
||||
public void Convert_AacHighEfficient()
|
||||
{
|
||||
var args = GetNodeParameters(TestFile_Mp3);
|
||||
var args = GetNodeParameters();
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
@@ -132,7 +103,7 @@ public class ConvertTests : AudioTestBase
|
||||
[TestMethod]
|
||||
public void Convert_Mp3ToMp3_Bitrate()
|
||||
{
|
||||
var args = GetNodeParameters(TestFile_Mp3);
|
||||
var args = GetNodeParameters();
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
@@ -145,10 +116,11 @@ public class ConvertTests : AudioTestBase
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_Mp3ToMp3_Bitrate_Variable()
|
||||
{
|
||||
var args = GetNodeParameters(TestFile_Mp3);
|
||||
var args = GetNodeParameters();
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
@@ -165,96 +137,34 @@ public class ConvertTests : AudioTestBase
|
||||
[TestMethod]
|
||||
public void Convert_Mp3_AlreadyMp3()
|
||||
{
|
||||
|
||||
const string file = @"D:\videos\Audio\13-the_cranberries-why.mp3";
|
||||
|
||||
var args = GetNodeParameters();
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
|
||||
ConvertAudio node = new();
|
||||
node.SkipIfCodecMatches = true;
|
||||
node.Codec = "mp3";
|
||||
|
||||
node.Bitrate = 192;
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(2, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_VideoToMp3()
|
||||
{
|
||||
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
|
||||
ConvertToMP3 node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
//new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_VideoToAac()
|
||||
{
|
||||
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
|
||||
ConvertToAAC node = new();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, new TestLogger(), false, string.Empty, null);;
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
//new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_TwoPass()
|
||||
{
|
||||
|
||||
const string file = @"D:\music\flacs\01-billy_joel-you_may_be_right.flac";
|
||||
|
||||
ConvertToAAC node = new();
|
||||
var logger = new TestLogger();
|
||||
var args = new FileFlows.Plugin.NodeParameters(file, logger, false, string.Empty, null);
|
||||
args.GetToolPathActual = (string tool) => @"C:\utils\ffmpeg\ffmpeg.exe";
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
node.Normalize = true;
|
||||
int output = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Convert_TwoPass_VideoFile()
|
||||
{
|
||||
|
||||
const string file = @"D:\videos\testfiles\basic.mkv";
|
||||
|
||||
ConvertToAAC node = new();
|
||||
var args = GetNodeParameters(file);
|
||||
var args = GetNodeParameters();
|
||||
var af = new AudioFile();
|
||||
af.PreExecute(args);
|
||||
af.Execute(args); // need to read the Audio info and set it
|
||||
|
||||
args.TempPath = @"D:\music\temp";
|
||||
new AudioFile().Execute(args); // need to read the Audio info and set it
|
||||
ConvertToAAC node = new();
|
||||
node.Normalize = true;
|
||||
node.PreExecute(args);
|
||||
int output = node.Execute(args);
|
||||
|
||||
string log = Logger.ToString();
|
||||
TestContext.WriteLine(log);
|
||||
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace FileFlows.AudioNodes.Tests;
|
||||
|
||||
|
||||
[TestClass]
|
||||
public class CreateAudioBookTests : AudioTestBase
|
||||
public class CreateAudioBookTests : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void CreateAudioBookTest_01()
|
||||
|
||||
@@ -7,46 +7,42 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
namespace AudioNodes.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class EmbedArtworkTests : AudioTestBase
|
||||
public class EmbedArtworkTests : TestBase
|
||||
{
|
||||
|
||||
[TestMethod]
|
||||
public void SingleArtwork()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
const string file = "/home/john/Music/unprocessed/Aqua/Aquarium (1997)/Aqua - Aquarium - 03 - Barbie Girl.flac";
|
||||
var args = new NodeParameters(file, logger, false, string.Empty, new LocalFileService())
|
||||
var args = new NodeParameters(file, Logger, false, string.Empty, new LocalFileService())
|
||||
{
|
||||
LibraryFileName = file
|
||||
};
|
||||
|
||||
args.GetToolPathActual = (string tool) => ffmpeg;
|
||||
args.GetToolPathActual = (string tool) => "ffmpeg";
|
||||
args.TempPath = @"/home/john/Music/temp";
|
||||
var ele = new EmbedArtwork();
|
||||
var output = ele.Execute(args);
|
||||
|
||||
var log = logger.ToString();
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CovertArtwork()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
//const string file = "/home/john/Music/unprocessed/Aqua/Aquarium (1997)/zombie.mp3";
|
||||
const string file =
|
||||
"/home/john/Music/unprocessed/Aqua/Aquarius (2000)/Aqua - Aquarius - 01 - Cartoon Heroes.flac";
|
||||
var args = new NodeParameters(file, logger, false, string.Empty, new LocalFileService())
|
||||
var args = new NodeParameters(file, Logger, false, string.Empty, new LocalFileService())
|
||||
{
|
||||
LibraryFileName = file
|
||||
};
|
||||
|
||||
args.GetToolPathActual = (string tool) => ffmpeg;
|
||||
args.GetToolPathActual = (string tool) => "ffmpeg";
|
||||
args.TempPath = @"/home/john/Music/temp";
|
||||
var ele = new EmbedArtwork();
|
||||
var output = ele.Execute(args);
|
||||
|
||||
var log = logger.ToString();
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
@@ -76,14 +72,11 @@ public class EmbedArtworkTests : AudioTestBase
|
||||
|
||||
convertNode.PreExecute(args);
|
||||
var result = convertNode.Execute(args);
|
||||
var log = Logger.ToString();
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
var ele = new EmbedArtwork();
|
||||
var output = ele.Execute(args);
|
||||
|
||||
log = Logger.ToString();
|
||||
TestContext.WriteLine(log);
|
||||
Assert.AreEqual(1, output);
|
||||
System.IO.File.Move(args.WorkingFile,
|
||||
FileHelper.Combine(FileHelper.GetDirectory(args.WorkingFile),
|
||||
|
||||
BIN
AudioNodes/Tests/Resources/audio.flac
Normal file
BIN
AudioNodes/Tests/Resources/audio.flac
Normal file
Binary file not shown.
BIN
AudioNodes/Tests/Resources/audio.mp3
Normal file
BIN
AudioNodes/Tests/Resources/audio.mp3
Normal file
Binary file not shown.
BIN
AudioNodes/Tests/Resources/audio.ogg
Normal file
BIN
AudioNodes/Tests/Resources/audio.ogg
Normal file
Binary file not shown.
BIN
AudioNodes/Tests/Resources/audio.wav
Normal file
BIN
AudioNodes/Tests/Resources/audio.wav
Normal file
Binary file not shown.
@@ -1,95 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using FileFlows.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileFlows.AudioNodes.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// A logger for tests that stores the logs in memory
|
||||
/// </summary>
|
||||
public class TestLogger : ILogger
|
||||
{
|
||||
private readonly List<string> Messages = new();
|
||||
|
||||
/// <summary>
|
||||
/// Writes an information log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void ILog(params object[] args)
|
||||
=> Log(LogType.Info, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an debug log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void DLog(params object[] args)
|
||||
=> Log(LogType.Debug, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an warning log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void WLog(params object[] args)
|
||||
=> Log(LogType.Warning, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an error log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void ELog(params object[] args)
|
||||
=> Log(LogType.Error, args);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tail of the log
|
||||
/// </summary>
|
||||
/// <param name="length">the number of messages to get</param>
|
||||
public string GetTail(int length = 50)
|
||||
{
|
||||
if (Messages.Count <= length)
|
||||
return string.Join(Environment.NewLine, Messages);
|
||||
return string.Join(Environment.NewLine, Messages.TakeLast(50));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message
|
||||
/// </summary>
|
||||
/// <param name="type">the type of log to record</param>
|
||||
/// <param name="args">the arguments of the message</param>
|
||||
private void Log(LogType type, params object[] args)
|
||||
{
|
||||
string message = type + " -> " + string.Join(", ", args.Select(x =>
|
||||
x == null ? "null" :
|
||||
x.GetType().IsPrimitive ? x.ToString() :
|
||||
x is string ? x.ToString() :
|
||||
System.Text.Json.JsonSerializer.Serialize(x)));
|
||||
Writer?.Invoke(message);
|
||||
Messages.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional writer
|
||||
/// </summary>
|
||||
public Action<string> Writer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the entire log as a string
|
||||
/// </summary>
|
||||
/// <returns>the entire log</returns>
|
||||
public override string ToString()
|
||||
=> string.Join(Environment.NewLine, Messages);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the log contains the text
|
||||
/// </summary>
|
||||
/// <param name="text">the text to check for</param>
|
||||
/// <returns>true if it contains it, otherwise false</returns>
|
||||
public bool Contains(string text)
|
||||
=> Messages.Any(x => x.Contains(text));
|
||||
}
|
||||
|
||||
#endif
|
||||
71
AudioNodes/Tests/_AudioTestBase.cs
Normal file
71
AudioNodes/Tests/_AudioTestBase.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using TagLib.Riff;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace AudioNodes.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Test base dor the audio tests
|
||||
/// </summary>
|
||||
public abstract class AudioTestBase : TestBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The resources test file directory
|
||||
/// </summary>
|
||||
protected static readonly string ResourcesTestFilesDir = "Tests/Resources";
|
||||
|
||||
/// <summary>
|
||||
/// Audio MP3 file
|
||||
/// </summary>
|
||||
protected static readonly string AudioMp3 = ResourcesTestFilesDir + "/audio.mp3";
|
||||
|
||||
/// <summary>
|
||||
/// Audio OGG file
|
||||
/// </summary>
|
||||
protected static readonly string AudioOgg = ResourcesTestFilesDir + "/audio.ogg";
|
||||
|
||||
/// <summary>
|
||||
/// Audio FLAC file
|
||||
/// </summary>
|
||||
protected static readonly string AudioFlac = ResourcesTestFilesDir + "/audio.flac";
|
||||
|
||||
/// <summary>
|
||||
/// Audio WAV file
|
||||
/// </summary>
|
||||
protected static readonly string AudioWav = ResourcesTestFilesDir + "/audio.wav";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FFmpeg location
|
||||
/// </summary>
|
||||
protected static string FFmpeg { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FFprobe location
|
||||
/// </summary>
|
||||
protected static string FFprobe { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Node Parameters
|
||||
/// </summary>
|
||||
/// <param name="filename">the file to initialise, will use AudioMp3 if not set</param>
|
||||
/// <returns>the node parameters</returns>
|
||||
public NodeParameters GetNodeParameters(string? filename = null)
|
||||
{
|
||||
filename ??= AudioMp3;
|
||||
var args = new NodeParameters(filename, Logger, false, string.Empty, new LocalFileService());
|
||||
args.InitFile(filename);
|
||||
|
||||
FFmpeg = File.Exists("/usr/local/bin/ffmpeg") ? "/usr/local/bin/ffmpeg" : "ffmpeg";
|
||||
FFprobe = File.Exists("/usr/local/bin/ffprobe") ? "/usr/local/bin/ffprobe" : "ffprobe";
|
||||
|
||||
args.GetToolPathActual = (tool) =>
|
||||
{
|
||||
if(tool.ToLowerInvariant().Contains("ffmpeg")) return FFmpeg;
|
||||
if(tool.ToLowerInvariant().Contains("ffprobe")) return FFprobe;
|
||||
return tool;
|
||||
};
|
||||
args.TempPath = TempPath;
|
||||
return args;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,497 +0,0 @@
|
||||
#if(DEBUG)
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
using System.IO;
|
||||
using FileFlows.AudioNodes.Tests;
|
||||
|
||||
namespace AudioNodes.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)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
return;
|
||||
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
var filePermissions = FileHelper.ConvertLinuxPermissionsToUnixFileMode(permissions.Value);
|
||||
if (filePermissions == UnixFileMode.None)
|
||||
{
|
||||
logMethod("SetPermissions: Invalid file permissions: " + permissions.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
File.SetUnixFileMode(path, filePermissions);
|
||||
logMethod($"SetPermissions: Permission [{filePermissions}] set on file: " + path);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,71 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.IO;
|
||||
|
||||
namespace FileFlows.AudioNodes.Tests;
|
||||
|
||||
[TestClass]
|
||||
public abstract class TestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The test context instance
|
||||
/// </summary>
|
||||
private TestContext testContextInstance;
|
||||
|
||||
internal TestLogger Logger = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the test context
|
||||
/// </summary>
|
||||
public TestContext TestContext
|
||||
{
|
||||
get => testContextInstance;
|
||||
set => testContextInstance = value;
|
||||
}
|
||||
|
||||
public string TestPath { get; private set; }
|
||||
public string TempPath { get; private set; }
|
||||
public string FfmpegPath { get; private set; }
|
||||
|
||||
public readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
public readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
protected readonly string ffmpeg = (OperatingSystem.IsLinux() ? "/usr/local/bin/ffmpeg" : @"C:\utils\ffmpeg\ffmpeg.exe");
|
||||
protected readonly string ffprobe = (OperatingSystem.IsLinux() ? "/usr/local/bin/ffprobe" : @"C:\utils\ffmpeg\ffprobe.exe");
|
||||
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
Logger.Writer = (msg) => TestContext.WriteLine(msg);
|
||||
this.TestPath = this.TestPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/test-files/audio" : @"d:\audio\testfiles");
|
||||
this.TempPath = this.TempPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/temp" : @"d:\audio\temp");
|
||||
this.FfmpegPath = this.FfmpegPath?.EmptyAsNull() ?? (IsLinux ? "/usr/local/bin/ffmpeg" : @"C:\utils\ffmpeg\ffmpeg.exe");
|
||||
|
||||
this.TestPath = this.TestPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
this.TempPath = this.TempPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
this.FfmpegPath = this.FfmpegPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
|
||||
if (Directory.Exists(this.TempPath) == false)
|
||||
Directory.CreateDirectory(this.TempPath);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void CleanUp()
|
||||
{
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
}
|
||||
|
||||
protected virtual void TestStarting()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected string TestFile_Mp3 => Path.Combine(TestPath, "mp3.mp3");
|
||||
protected string TestFile_Flac => Path.Combine(TestPath, "flac.flac");
|
||||
protected string TestFile_Wav => Path.Combine(TestPath, "wav.wav");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -25,6 +25,7 @@ This plugin is required for FileFlows to work.</Description>
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<ProjectReference Include="..\PluginTestLibrary\PluginTestLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Plugin">
|
||||
|
||||
@@ -67,6 +67,7 @@ public class Renamer : Node
|
||||
newFile = newFile.Replace(" ", " ");
|
||||
newFile = Regex.Replace(newFile, @"\s(\.[\w\d]+)$", "$1");
|
||||
newFile = newFile.Replace(" \\", "\\");
|
||||
newFile = newFile.Replace(" /", "/");
|
||||
|
||||
args.Logger?.ILog("New File: " + newFile);
|
||||
|
||||
|
||||
@@ -3,4 +3,7 @@ global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using System.Threading.Tasks;
|
||||
#if(DEBUG)
|
||||
global using PluginTestLibrary;
|
||||
#endif
|
||||
@@ -3,6 +3,7 @@
|
||||
using System.IO;
|
||||
using FileFlows.BasicNodes.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace BasicNodes.Tests;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Text.RegularExpressions;
|
||||
using FileFlows.Plugin;
|
||||
using Moq;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace BasicNodes.Tests;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
[TestClass]
|
||||
public class MoveTests2
|
||||
public class MoveTests2 : TestBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -18,15 +18,17 @@ public class MoveTests2
|
||||
[TestMethod]
|
||||
public void MoveTests_AdditionalFiles()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
var fileService = new LocalFileService();
|
||||
var args = new NodeParameters(@"/home/john/Videos/move-me/basic.mkv", logger, false, string.Empty, fileService);
|
||||
var additionalFile = System.IO.Path.Combine(TempPath, Guid.NewGuid() + ".srt");
|
||||
System.IO.File.WriteAllText(additionalFile, Guid.NewGuid().ToString());
|
||||
var destPath = System.IO.Path.Combine(TempPath, Guid.NewGuid().ToString());
|
||||
|
||||
var args = new NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.InitFile(TempFile);
|
||||
|
||||
var ele = new MoveFile();
|
||||
ele.AdditionalFiles = new[] { "*.srt" };
|
||||
ele.DestinationPath = "/home/john/Videos/converted";
|
||||
ele.AdditionalFiles = ["*.srt"];
|
||||
ele.DestinationPath = destPath;
|
||||
var result = ele.Execute(args);
|
||||
var log = logger.ToString();
|
||||
Assert.AreEqual(1, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,71 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace BasicNodes.Tests
|
||||
|
||||
using FileFlows.BasicNodes.Functions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace BasicNodes.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class PatternReplacerTests : TestBase
|
||||
{
|
||||
using FileFlows.BasicNodes.Functions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class PatternReplacerTests
|
||||
[TestMethod]
|
||||
public void PatternReplacer_Basic()
|
||||
{
|
||||
[TestMethod]
|
||||
public void PatternReplacer_Basic()
|
||||
var testFile = System.IO.Path.Combine(TempPath, "Seinfeld.mkv");
|
||||
System.IO.File.Move(TempFile, testFile);
|
||||
PatternReplacer node = new PatternReplacer();
|
||||
node.Replacements = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
PatternReplacer node = new PatternReplacer();
|
||||
node.Replacements = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("Seinfeld", "Batman")
|
||||
};
|
||||
node.UnitTest = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Seinfeld.mkv", new TestLogger(), false, string.Empty, null);;
|
||||
new ("Seinfeld", "Batman")
|
||||
};
|
||||
node.UnitTest = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(testFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.InitFile(testFile);
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.AreEqual(@"c:\test\Batman.mkv", args.WorkingFile);
|
||||
}
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.AreEqual(System.IO.Path.Combine(TempPath, "Batman.mkv"), args.WorkingFile);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PatternReplacer_Regex()
|
||||
[TestMethod]
|
||||
public void PatternReplacer_Regex()
|
||||
{
|
||||
var testFile = System.IO.Path.Combine(TempPath, "Seinfeld S03E06.mkv");
|
||||
System.IO.File.Move(TempFile, testFile);
|
||||
PatternReplacer node = new PatternReplacer();
|
||||
node.Replacements = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
PatternReplacer node = new PatternReplacer();
|
||||
node.Replacements = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>(@"s([\d]+)e([\d]+)", "$1x$2"),
|
||||
new KeyValuePair<string, string>(@"0([1-9]+x[\d]+)", "$1"),
|
||||
};
|
||||
node.UnitTest = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Seinfeld S03E06.mkv", new TestLogger(), false, string.Empty, null);;
|
||||
new (@"s([\d]+)e([\d]+)", "$1x$2"),
|
||||
new (@"0([1-9]+x[\d]+)", "$1"),
|
||||
};
|
||||
node.UnitTest = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(testFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.InitFile(testFile);
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.AreEqual(@"c:\test\Seinfeld 3x06.mkv", args.WorkingFile);
|
||||
}
|
||||
[TestMethod]
|
||||
public void PatternReplacer_Empty()
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.AreEqual(System.IO.Path.Combine(TempPath, "Seinfeld 3x06.mkv"), args.WorkingFile);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PatternReplacer_Empty()
|
||||
{
|
||||
var testFile = System.IO.Path.Combine(TempPath, "Seinfeld.h265.mkv");
|
||||
System.IO.File.Move(TempFile, testFile);
|
||||
PatternReplacer node = new PatternReplacer();
|
||||
node.Replacements = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
PatternReplacer node = new PatternReplacer();
|
||||
node.Replacements = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>(@"\.h265", "EMPTY")
|
||||
};
|
||||
node.UnitTest = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Seinfeld.h265.mkv", new TestLogger(), false, string.Empty, null);;
|
||||
|
||||
var result = node.RunReplacements(args, args.WorkingFile);
|
||||
Assert.AreEqual(@"c:\test\Seinfeld.mkv", result);
|
||||
}
|
||||
new (@"\.h265", "EMPTY")
|
||||
};
|
||||
node.UnitTest = true;
|
||||
var args = new FileFlows.Plugin.NodeParameters(testFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.InitFile(testFile);
|
||||
|
||||
var result = node.RunReplacements(args, args.WorkingFile);
|
||||
Assert.AreEqual(System.IO.Path.Combine(TempPath, "Seinfeld.mkv"), result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -12,39 +12,15 @@ public class RenamerTests : TestBase
|
||||
[TestMethod]
|
||||
public void Renamer_Extension()
|
||||
{
|
||||
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\testfile.mkv", Logger, false, string.Empty, new LocalFileService());
|
||||
var ext = new FileInfo(TempFile).Extension;
|
||||
var args = new FileFlows.Plugin.NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.Variables = new Dictionary<string, object>
|
||||
{
|
||||
{ "movie.Title", "Ghostbusters" },
|
||||
{ "movie.Year", 1984 },
|
||||
{ "viResolution", "1080P" }
|
||||
};
|
||||
args.SetWorkingFile($@"c:\temp\{Guid.NewGuid()}.mkv", dontDelete: true);
|
||||
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{movie.Title} ({movie.Year})\{movie.Title} [{viResolution}]{ext}";
|
||||
node.LogOnly = true;
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
string expectedShort = $@"c:\temp\Ghostbusters (1984){Path.DirectorySeparatorChar}Ghostbusters [1080P].mkv";
|
||||
Assert.IsTrue(Logger.Contains($"Renaming file to: " + expectedShort));
|
||||
}
|
||||
[TestMethod]
|
||||
public void Renamer_Extension_DoubleDot()
|
||||
{
|
||||
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\testfile.mkv", Logger, false, string.Empty, new LocalFileService());
|
||||
args.Variables = new Dictionary<string, object>
|
||||
{
|
||||
{ "movie.Title", "Ghostbusters" },
|
||||
{ "movie.Year", 1984 },
|
||||
{ "viResolution", "1080P" }
|
||||
};
|
||||
args.SetWorkingFile($@"c:\temp\{Guid.NewGuid()}.mkv", dontDelete: true);
|
||||
args.InitFile(TempFile);
|
||||
|
||||
|
||||
Renamer node = new Renamer();
|
||||
@@ -54,22 +30,45 @@ public class RenamerTests : TestBase
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
string expectedShort = $@"c:\temp\Ghostbusters (1984){Path.DirectorySeparatorChar}Ghostbusters [1080P].mkv";
|
||||
string expectedShort = $"{TempPath}/Ghostbusters (1984)/Ghostbusters [1080P]{ext}";
|
||||
Assert.IsTrue(Logger.Contains($"Renaming file to: " + expectedShort));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Renamer_Extension_DoubleDot()
|
||||
{
|
||||
var ext = new FileInfo(TempFile).Extension;
|
||||
var args = new FileFlows.Plugin.NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.Variables = new Dictionary<string, object>
|
||||
{
|
||||
{ "movie.Title", "Ghostbusters" },
|
||||
{ "movie.Year", 1984 },
|
||||
{ "viResolution", "1080P" }
|
||||
};
|
||||
args.InitFile(TempFile);
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{movie.Title} ({movie.Year})\{movie.Title} [{viResolution}].{ext}";
|
||||
node.LogOnly = true;
|
||||
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
string expectedShort = $"{TempPath}/Ghostbusters (1984)/Ghostbusters [1080P]{ext}";
|
||||
Assert.IsTrue(Logger.Contains($"Renaming file to: " + expectedShort));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Renamer_Empty_SquareBrackets()
|
||||
{
|
||||
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\testfile.mkv", Logger, false, string.Empty, new LocalFileService());
|
||||
var ext = new FileInfo(TempFile).Extension;
|
||||
var args = new FileFlows.Plugin.NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.Variables = new Dictionary<string, object>
|
||||
{
|
||||
{ "movie.Title", "Ghostbusters" },
|
||||
{ "movie.Year", 1984 }
|
||||
};
|
||||
args.SetWorkingFile($@"c:\temp\{Guid.NewGuid()}.mkv", dontDelete: true);
|
||||
|
||||
args.InitFile(TempFile);
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{movie.Title} ({movie.Year})\{movie.Title} [{viResolution}] {movie.Year}.{ext}";
|
||||
@@ -78,21 +77,21 @@ public class RenamerTests : TestBase
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
string expectedShort = $@"c:\temp\Ghostbusters (1984){Path.DirectorySeparatorChar}Ghostbusters 1984.mkv";
|
||||
string expectedShort = $"{TempPath}/Ghostbusters (1984){Path.DirectorySeparatorChar}Ghostbusters 1984{ext}";
|
||||
Assert.IsTrue(Logger.Contains($"Renaming file to: " + expectedShort));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Renamer_Empty_RoundBrackets()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\testfile.mkv", Logger, false, string.Empty, new LocalFileService());
|
||||
var ext = new FileInfo(TempFile).Extension;
|
||||
var args = new FileFlows.Plugin.NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.Variables = new Dictionary<string, object>
|
||||
{
|
||||
{ "movie.Title", "Ghostbusters" },
|
||||
{ "viResolution", "1080p" }
|
||||
};
|
||||
args.SetWorkingFile($@"c:\temp\{Guid.NewGuid()}.mkv", dontDelete: true);
|
||||
|
||||
args.InitFile(TempFile);
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{movie.Title} ({movie.Year})\{movie.Title} ({movie.Year}) {viResolution!}.{ext}";
|
||||
@@ -101,21 +100,21 @@ public class RenamerTests : TestBase
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
string expectedShort = $@"c:\temp\Ghostbusters{Path.DirectorySeparatorChar}Ghostbusters 1080P.mkv";
|
||||
string expectedShort = $"{TempPath}/Ghostbusters{Path.DirectorySeparatorChar}Ghostbusters 1080P{ext}";
|
||||
Assert.IsTrue(Logger.Contains($"Renaming file to: " + expectedShort));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Renamer_Empty_SquareBrackets_Extension()
|
||||
{
|
||||
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\testfile.mkv", Logger, false, string.Empty, new LocalFileService());
|
||||
var ext = new FileInfo(TempFile).Extension;
|
||||
var args = new FileFlows.Plugin.NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.Variables = new Dictionary<string, object>
|
||||
{
|
||||
{ "movie.Title", "Ghostbusters" },
|
||||
{ "movie.Year", 1984 }
|
||||
};
|
||||
args.SetWorkingFile($@"c:\temp\{Guid.NewGuid()}.mkv", dontDelete: true);
|
||||
|
||||
args.InitFile(TempFile);
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{movie.Title} ({movie.Year})\{movie.Title} [{viResolution}].{ext}";
|
||||
@@ -124,7 +123,7 @@ public class RenamerTests : TestBase
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
string expectedShort = $@"c:\temp\Ghostbusters (1984){Path.DirectorySeparatorChar}Ghostbusters.mkv";
|
||||
string expectedShort = $"{TempPath}/Ghostbusters (1984){Path.DirectorySeparatorChar}Ghostbusters{ext}";
|
||||
Assert.IsTrue(Logger.Contains($"Renaming file to: " + expectedShort));
|
||||
}
|
||||
|
||||
@@ -132,15 +131,15 @@ public class RenamerTests : TestBase
|
||||
[TestMethod]
|
||||
public void Renamer_Colon()
|
||||
{
|
||||
var args = new FileFlows.Plugin.NodeParameters(@"c:\test\testfile.mkv", Logger, false, string.Empty, new LocalFileService());
|
||||
var ext = new FileInfo(TempFile).Extension;
|
||||
var args = new FileFlows.Plugin.NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
args.Variables = new Dictionary<string, object>
|
||||
{
|
||||
{ "movie.Title", "Batman Unlimited: Mech vs Mutants" },
|
||||
{ "movie.Year", 2016 },
|
||||
{ "ext", "mkv" }
|
||||
};
|
||||
args.SetWorkingFile($@"c:\temp\{Guid.NewGuid()}.mkv", dontDelete: true);
|
||||
|
||||
args.InitFile(TempFile);
|
||||
|
||||
Renamer node = new Renamer();
|
||||
node.Pattern = @"{movie.Title} ({movie.Year})\{movie.Title}.{ext}";
|
||||
@@ -149,7 +148,7 @@ public class RenamerTests : TestBase
|
||||
var result = node.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
string expectedShort = $@"c:\temp\Batman Unlimited - Mech vs Mutants (2016){Path.DirectorySeparatorChar}Batman Unlimited - Mech vs Mutants.mkv";
|
||||
string expectedShort = $"{TempPath}/Batman Unlimited - Mech vs Mutants (2016){Path.DirectorySeparatorChar}Batman Unlimited - Mech vs Mutants{ext}";
|
||||
Assert.IsTrue(Logger.Contains($"Renaming file to: " + expectedShort));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
|
||||
namespace BasicNodes.Tests;
|
||||
|
||||
public class TestFileService : IFileService
|
||||
{
|
||||
public List<string> Files { get; set; }
|
||||
|
||||
public Result<string[]> GetFiles(string path, string searchPattern = "", bool recursive = false)
|
||||
{
|
||||
if (Files?.Any() != true)
|
||||
return new string[] { };
|
||||
|
||||
if (searchPattern.StartsWith("*"))
|
||||
searchPattern = searchPattern[1..];
|
||||
return Files.Where(x => x.EndsWith(searchPattern)).ToArray();
|
||||
}
|
||||
|
||||
public Result<string[]> GetDirectories(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryExists(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryDelete(string path, bool recursive = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryMove(string path, string destination)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> DirectoryCreate(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<DateTime> DirectoryCreationTimeUtc(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<DateTime> DirectoryLastWriteTimeUtc(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> FileExists(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<FileInformation> FileInfo(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> FileDelete(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<long> FileSize(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<DateTime> FileCreationTimeUtc(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<DateTime> FileLastWriteTimeUtc(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> FileMove(string path, string destination, bool overwrite = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> FileCopy(string path, string destination, bool overwrite = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> FileAppendAllText(string path, string text)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool FileIsLocal(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<string> GetLocalPath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> Touch(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<long> DirectorySize(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> SetCreationTimeUtc(string path, DateTime date)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<bool> SetLastWriteTimeUtc(string path, DateTime date)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public char PathSeparator { get; init; }
|
||||
public ReplaceVariablesDelegate ReplaceVariables { get; set; }
|
||||
public ILogger? Logger { get; set; }
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,90 +0,0 @@
|
||||
#if (DEBUG)
|
||||
|
||||
namespace BasicNodes.Tests;
|
||||
|
||||
using FileFlows.Plugin;
|
||||
|
||||
/// <summary>
|
||||
/// A logger for tests that stores the logs in memory
|
||||
/// </summary>
|
||||
public class TestLogger : ILogger
|
||||
{
|
||||
private readonly List<string> Messages = new();
|
||||
|
||||
/// <summary>
|
||||
/// Writes an information log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void ILog(params object[] args)
|
||||
=> Log(LogType.Info, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an debug log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void DLog(params object[] args)
|
||||
=> Log(LogType.Debug, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an warning log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void WLog(params object[] args)
|
||||
=> Log(LogType.Warning, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an error log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void ELog(params object[] args)
|
||||
=> Log(LogType.Error, args);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tail of the log
|
||||
/// </summary>
|
||||
/// <param name="length">the number of messages to get</param>
|
||||
public string GetTail(int length = 50)
|
||||
{
|
||||
if (Messages.Count <= length)
|
||||
return string.Join(Environment.NewLine, Messages);
|
||||
return string.Join(Environment.NewLine, Messages.TakeLast(50));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message
|
||||
/// </summary>
|
||||
/// <param name="type">the type of log to record</param>
|
||||
/// <param name="args">the arguments of the message</param>
|
||||
private void Log(LogType type, params object[] args)
|
||||
{
|
||||
string message = type + " -> " + string.Join(", ", args.Select(x =>
|
||||
x == null ? "null" :
|
||||
x.GetType().IsPrimitive ? x.ToString() :
|
||||
x is string ? x.ToString() :
|
||||
System.Text.Json.JsonSerializer.Serialize(x)));
|
||||
Writer?.Invoke(message);
|
||||
Messages.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional writer
|
||||
/// </summary>
|
||||
public Action<string> Writer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the entire log as a string
|
||||
/// </summary>
|
||||
/// <returns>the entire log</returns>
|
||||
public override string ToString()
|
||||
=> string.Join(Environment.NewLine, Messages);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the log contains the text
|
||||
/// </summary>
|
||||
/// <param name="text">the text to check for</param>
|
||||
/// <returns>true if it contains it, otherwise false</returns>
|
||||
public bool Contains(string text)
|
||||
=> Messages.Any(x => x.Contains(text));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -18,6 +18,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>
|
||||
<None Remove="ChecksumNodes.en.json" />
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
#if(DEBUG)
|
||||
|
||||
#if(DEBUG)
|
||||
|
||||
namespace ChecksumNodes.Tests
|
||||
namespace ChecksumNodes.Tests;
|
||||
|
||||
using PluginTestLibrary;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class ChecksumTests : TestBase
|
||||
{
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class ChecksumTests
|
||||
[TestMethod]
|
||||
public void Checksum_MD5_Large()
|
||||
{
|
||||
var args = new NodeParameters(TempFile, Logger, false, "", new LocalFileService());
|
||||
var output = new MD5().Execute(args);
|
||||
Assert.IsTrue(args.Variables.ContainsKey("MD5"));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(args.Variables["MD5"] as string));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Checksum_MD5_Large()
|
||||
{
|
||||
var args = new NodeParameters(@"D:\videos\Injustice.mkv", new TestLogger(), false, "", null!);
|
||||
var output = new MD5().Execute(args);
|
||||
Assert.IsTrue(args.Variables.ContainsKey("MD5"));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(args.Variables["MD5"] as string));
|
||||
}
|
||||
[TestMethod]
|
||||
public void Checksum_SHA1_Large()
|
||||
{
|
||||
var args = new NodeParameters(TempFile, Logger, false, "", new LocalFileService());
|
||||
var output = new SHA1().Execute(args);
|
||||
Assert.IsTrue(args.Variables.ContainsKey("SHA1"));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(args.Variables["SHA1"] as string));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Checksum_SHA1_Large()
|
||||
{
|
||||
var args = new NodeParameters(@"D:\videos\Injustice.mkv", new TestLogger(), false, "", null!);
|
||||
var output = new SHA1().Execute(args);
|
||||
Assert.IsTrue(args.Variables.ContainsKey("SHA1"));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(args.Variables["SHA1"] as string));
|
||||
}
|
||||
[TestMethod]
|
||||
public void Checksum_SHA256_Large()
|
||||
{
|
||||
var args = new NodeParameters(TempFile, Logger, false, "", new LocalFileService());
|
||||
var output = new SHA256().Execute(args);
|
||||
Assert.IsTrue(args.Variables.ContainsKey("SHA256"));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(args.Variables["SHA256"] as string));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Checksum_SHA256_Large()
|
||||
{
|
||||
var args = new NodeParameters(@"D:\videos\Injustice.mkv", new TestLogger(), false, "", null!);
|
||||
var output = new SHA256().Execute(args);
|
||||
Assert.IsTrue(args.Variables.ContainsKey("SHA256"));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(args.Variables["SHA256"] as string));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Checksum_SHA512_Large()
|
||||
{
|
||||
var args = new NodeParameters(@"D:\videos\Injustice.mkv", new TestLogger(), false, "", null!);
|
||||
var output = new SHA512().Execute(args);
|
||||
Assert.IsTrue(args.Variables.ContainsKey("SHA512"));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(args.Variables["SHA512"] as string));
|
||||
}
|
||||
[TestMethod]
|
||||
public void Checksum_SHA512_Large()
|
||||
{
|
||||
var args = new NodeParameters(TempFile, Logger, false, "", new LocalFileService());
|
||||
var output = new SHA512().Execute(args);
|
||||
Assert.IsTrue(args.Variables.ContainsKey("SHA512"));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(args.Variables["SHA512"] as string));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace ChecksumNodes.Tests
|
||||
{
|
||||
using FileFlows.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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 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
|
||||
@@ -91,28 +91,32 @@ File shrunk in size by: {{ difference | file_size }} / {{ percent }}%
|
||||
}
|
||||
}
|
||||
|
||||
const int colorInfo = 0x1F61E6;
|
||||
const int colorSuccess= 0x80E61F;
|
||||
const int colorError = 0xE7421F;
|
||||
const int colorFailure = 0xC61FE6;
|
||||
const int colorWarning = 0xE6C71F;
|
||||
/// <summary>
|
||||
/// Gets or sets the API instance
|
||||
/// </summary>
|
||||
internal IDiscordApi? Api { get; set; }
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = args.GetPluginSettings<PluginSettings>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings?.WebhookId))
|
||||
if (Api == null)
|
||||
{
|
||||
args.Logger?.WLog("No webhook id set");
|
||||
return 2;
|
||||
}
|
||||
var settings = args.GetPluginSettings<PluginSettings>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings?.WebhookToken))
|
||||
{
|
||||
args.Logger?.WLog("No webhook token set");
|
||||
return 2;
|
||||
if (string.IsNullOrWhiteSpace(settings?.WebhookId))
|
||||
{
|
||||
args.Logger?.WLog("No webhook id set");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings?.WebhookToken))
|
||||
{
|
||||
args.Logger?.WLog("No webhook token set");
|
||||
return 2;
|
||||
}
|
||||
|
||||
Api = new DiscordApi(settings.WebhookId, settings.WebhookToken);
|
||||
}
|
||||
|
||||
var message = args.RenderTemplate!(Message);
|
||||
@@ -130,55 +134,10 @@ File shrunk in size by: {{ difference | file_size }} / {{ percent }}%
|
||||
message = message.Replace("\\r\\n", "\r\n");
|
||||
message = message.Replace("\\n", "\n");
|
||||
|
||||
object webhook;
|
||||
if (this.MessageType == "Basic")
|
||||
{
|
||||
webhook = new
|
||||
{
|
||||
username = "FileFlows",
|
||||
content = message,
|
||||
avatar_url = "https://fileflows.com/icon.png",
|
||||
};
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
webhook = new
|
||||
{
|
||||
username = "FileFlows",
|
||||
avatar_url = "https://fileflows.com/icon.png",
|
||||
embeds = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
description = message,
|
||||
title,
|
||||
color = MessageType switch
|
||||
{
|
||||
"Success" => colorSuccess,
|
||||
"Warning" => colorWarning,
|
||||
"Error" => colorError,
|
||||
"Failure" => colorFailure,
|
||||
_ => colorInfo,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
string url = $"https://discordapp.com/api/webhooks/{settings.WebhookId}/{settings.WebhookToken}";
|
||||
#pragma warning disable IL2026
|
||||
var content = new StringContent(JsonSerializer.Serialize(webhook), Encoding.UTF8, "application/json");
|
||||
#pragma warning restore IL2026
|
||||
using var httpClient = new HttpClient();
|
||||
var response = httpClient.PostAsync(url, content).Result;
|
||||
if (response.IsSuccessStatusCode)
|
||||
return 1;
|
||||
|
||||
string error = response.Content.ReadAsStringAsync().Result;
|
||||
args.Logger?.WLog("Error from discord: " + error);
|
||||
return 2;
|
||||
var result = MessageType?.ToLowerInvariant() == "basic"
|
||||
? Api.SendBasic(args.Logger!, message)
|
||||
: Api.SendAdvanced(args.Logger!, message, title, MessageType!);
|
||||
return result ? 1 : 2;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
<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" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<ProjectReference Include="..\PluginTestLibrary\PluginTestLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FileFlows.Plugin">
|
||||
|
||||
98
DiscordNodes/IDiscordApi.cs
Normal file
98
DiscordNodes/IDiscordApi.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FileFlows.DiscordNodes;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for Discord API
|
||||
/// </summary>
|
||||
public interface IDiscordApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a basic message
|
||||
/// </summary>
|
||||
/// <param name="logger">the logger</param>
|
||||
/// <param name="message">the message</param>
|
||||
/// <returns>true if successful, otherwise false</returns>
|
||||
bool SendBasic(ILogger logger, string message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends an advanced message
|
||||
/// </summary>
|
||||
/// <param name="logger">the logger</param>
|
||||
/// <param name="message">the message</param>
|
||||
/// <param name="title">the title</param>
|
||||
/// <param name="type">the type</param>
|
||||
/// <returns>true if successful, otherwise false</returns>
|
||||
bool SendAdvanced(ILogger logger, string message, string title, string type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the Discord API
|
||||
/// </summary>
|
||||
/// <param name="webhookId">the ID of the webhook</param>
|
||||
/// <param name="webhookToken">the token of the webhook</param>
|
||||
public class DiscordApi(string webhookId, string webhookToken) : IDiscordApi
|
||||
{
|
||||
|
||||
const int colorInfo = 0x1F61E6;
|
||||
const int colorSuccess= 0x80E61F;
|
||||
const int colorError = 0xE7421F;
|
||||
const int colorFailure = 0xC61FE6;
|
||||
const int colorWarning = 0xE6C71F;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SendBasic(ILogger logger, string message)
|
||||
=> Send(logger, new
|
||||
{
|
||||
username = "FileFlows",
|
||||
content = message,
|
||||
avatar_url = "https://fileflows.com/icon.png",
|
||||
});
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SendAdvanced(ILogger logger, string message, string title, string type)
|
||||
=> Send(logger, new
|
||||
{
|
||||
username = "FileFlows",
|
||||
avatar_url = "https://fileflows.com/icon.png",
|
||||
embeds = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
description = message,
|
||||
title,
|
||||
color = type switch
|
||||
{
|
||||
"Success" => colorSuccess,
|
||||
"Warning" => colorWarning,
|
||||
"Error" => colorError,
|
||||
"Failure" => colorFailure,
|
||||
_ => colorInfo,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Sends the webhook
|
||||
/// </summary>
|
||||
/// <param name="logger">the logger</param>
|
||||
/// <param name="body">the body</param>
|
||||
/// <returns>the result</returns>
|
||||
private bool Send(ILogger logger, object body)
|
||||
{
|
||||
|
||||
string url = $"https://discordapp.com/api/webhooks/{webhookId}/{webhookToken}";
|
||||
#pragma warning disable IL2026
|
||||
var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
|
||||
#pragma warning restore IL2026
|
||||
using var httpClient = new HttpClient();
|
||||
var response = httpClient.PostAsync(url, content).Result;
|
||||
if (response.IsSuccessStatusCode)
|
||||
return true;
|
||||
|
||||
string error = response.Content.ReadAsStringAsync().Result;
|
||||
logger?.WLog("Error from discord: " + error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,40 +2,61 @@
|
||||
|
||||
using FileFlows.DiscordNodes.Communication;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.DiscordNodes.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class DiscordTests
|
||||
{
|
||||
public class DiscordTests : TestBase
|
||||
{
|
||||
private Mock<IDiscordApi> mockApi = new();
|
||||
private NodeParameters Args = null!;
|
||||
|
||||
protected override void TestStarting()
|
||||
{
|
||||
Args = new NodeParameters(TempFile, Logger, false, string.Empty, new LocalFileService());
|
||||
Args.RenderTemplate = (arg) => arg;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Discord_Simple_Message()
|
||||
{
|
||||
var args = new NodeParameters("test.file", new TestLogger(), false, string.Empty, null!);
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
};
|
||||
|
||||
var node = new Discord();
|
||||
node.Api = mockApi.Object;
|
||||
node.Message = "a message\nwith\nsome\nnewlines";
|
||||
var result = node.Execute(args);
|
||||
node.MessageType = "Information";
|
||||
bool sent = false;
|
||||
mockApi.Setup(x =>
|
||||
x.SendAdvanced(It.IsAny<ILogger>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback(() =>
|
||||
{
|
||||
sent = true;
|
||||
})
|
||||
.Returns(true);
|
||||
var result = node.Execute(Args);
|
||||
Assert.AreEqual(1, result);
|
||||
Assert.IsTrue(sent);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Discord_Basic_Message()
|
||||
{
|
||||
var args = new NodeParameters("test.file", new TestLogger(), false, string.Empty, null!);
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
};
|
||||
|
||||
var node = new Discord();
|
||||
node.Api = mockApi.Object;
|
||||
|
||||
node.Message = "a message";
|
||||
node.MessageType = "Basic";
|
||||
Assert.AreEqual(1, node.Execute(args));
|
||||
bool sent = false;
|
||||
mockApi.Setup(x =>
|
||||
x.SendBasic(It.IsAny<ILogger>(), It.IsAny<string>()))
|
||||
.Callback(() =>
|
||||
{
|
||||
sent = true;
|
||||
})
|
||||
.Returns(true);
|
||||
Assert.AreEqual(1, node.Execute(Args));
|
||||
Assert.IsTrue(sent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.DiscordNodes.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
|
||||
@@ -20,6 +20,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="Plugin">
|
||||
|
||||
@@ -1,57 +1,52 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace EmailNodes.Tests
|
||||
using FileFlows.Communication;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace EmailNodes.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class VideoInfoHelperTests : TestBase
|
||||
{
|
||||
using FileFlows.Communication;
|
||||
using FileFlows.EmailNodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[TestClass]
|
||||
public class VideoInfoHelperTests
|
||||
[TestMethod]
|
||||
public void Email_TemplateTest()
|
||||
{
|
||||
[TestMethod]
|
||||
public void Email_TemplateTest()
|
||||
{
|
||||
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
|
||||
var args = new NodeParameters(file, new TestLogger(), false, string.Empty, null!);
|
||||
string test = Guid.NewGuid().ToString("N");
|
||||
args.Variables.Add("TestParameter", test);
|
||||
var node = new SendEmail();
|
||||
node.Body = @"Hello {{TestParameter}}!";
|
||||
string body = node.RenderBody(args);
|
||||
Assert.AreEqual($"Hello {test}!", body);
|
||||
}
|
||||
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
|
||||
var args = new NodeParameters(file, Logger, false, string.Empty, new LocalFileService());
|
||||
string test = Guid.NewGuid().ToString("N");
|
||||
args.Variables.Add("TestParameter", test);
|
||||
var node = new SendEmail();
|
||||
node.Body = @"Hello {{TestParameter}}!";
|
||||
string body = node.RenderBody(args);
|
||||
Assert.AreEqual($"Hello {test}!", body);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Email_TemplateTest2()
|
||||
{
|
||||
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
|
||||
var args = new NodeParameters(file, new TestLogger(), false, string.Empty, null!);
|
||||
string test = Guid.NewGuid().ToString("N");
|
||||
args.Variables.Add("TestParameter", test);
|
||||
var node = new SendEmail();
|
||||
node.Body = @"Hello {{if file.Create.Year > 2021 }}from 2022{{ else }}from before 2022{{ end }}";
|
||||
string body = node.RenderBody(args);
|
||||
Assert.AreEqual($"Hello from before 2022", body);
|
||||
}
|
||||
[TestMethod]
|
||||
public void Email_TemplateTest3()
|
||||
{
|
||||
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
|
||||
var args = new NodeParameters(file, new TestLogger(), false, string.Empty, null!);
|
||||
string test = Guid.NewGuid().ToString("N");
|
||||
args.Variables.Add("TestParameter", test);
|
||||
var node = new SendEmail();
|
||||
node.Body = @"Hello {{ if file.Size > 1_000_000 }}greater than 1million{{else}}small file{{end}}";
|
||||
string body = node.RenderBody(args);
|
||||
Assert.AreEqual($"Hello greater than 1million", body);
|
||||
}
|
||||
[TestMethod]
|
||||
public void Email_TemplateTest2()
|
||||
{
|
||||
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
|
||||
var args = new NodeParameters(file, Logger, false, string.Empty, new LocalFileService());
|
||||
string test = Guid.NewGuid().ToString("N");
|
||||
args.Variables.Add("TestParameter", test);
|
||||
var node = new SendEmail();
|
||||
node.Body = @"Hello {{if file.Create.Year > 2021 }}from 2022{{ else }}from before 2022{{ end }}";
|
||||
string body = node.RenderBody(args);
|
||||
Assert.AreEqual($"Hello from before 2022", body);
|
||||
}
|
||||
[TestMethod]
|
||||
public void Email_TemplateTest3()
|
||||
{
|
||||
const string file = @"D:\music\unprocessed\04-billy_joel-scenes_from_an_italian_restaurant-b2125758.mp3";
|
||||
var args = new NodeParameters(file, Logger, false, string.Empty, new LocalFileService());
|
||||
string test = Guid.NewGuid().ToString("N");
|
||||
args.Variables.Add("TestParameter", test);
|
||||
var node = new SendEmail();
|
||||
node.Body = @"Hello {{ if file.Size > 1_000_000 }}greater than 1million{{else}}small file{{end}}";
|
||||
string body = node.RenderBody(args);
|
||||
Assert.AreEqual($"Hello greater than 1million", body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,55 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace EmailNodes.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 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
|
||||
@@ -22,6 +22,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,16 +2,17 @@
|
||||
|
||||
using FileFlows.Emby.MediaManagement;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Emby.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class EmbyTests
|
||||
public class EmbyTests : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void Emby_Basic()
|
||||
{
|
||||
var args = new NodeParameters(@"/media/movies/Citizen Kane (1941)/Citizen Kane (1941).mp4", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/media/movies/Citizen Kane (1941)/Citizen Kane (1941).mp4", Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
@@ -24,7 +25,7 @@ public class EmbyTests
|
||||
[TestMethod]
|
||||
public void Emby_Fail()
|
||||
{
|
||||
var args = new NodeParameters(@"/media/unknownmovies/The Batman (2022)/The Batman.mkv", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/media/unknownmovies/The Batman (2022)/The Batman.mkv", Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.invalid.json");
|
||||
@@ -37,7 +38,7 @@ public class EmbyTests
|
||||
[TestMethod]
|
||||
public void Emby_Mapped()
|
||||
{
|
||||
var args = new NodeParameters(@"/mnt/movies/Citizen Kane (1941)/Citizen Kane (1941).mp4", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/mnt/movies/Citizen Kane (1941)/Citizen Kane (1941).mp4", Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.Emby.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
|
||||
Binary file not shown.
Binary file not shown.
@@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "Web\Web.csproj", "{F
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nextcloud", "Nextcloud\Nextcloud.csproj", "{A36DD7D5-12BE-4B79-82AA-526E52DC0A3A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginTestLibrary", "PluginTestLibrary\PluginTestLibrary.csproj", "{3D9F9D89-1860-48D9-9CCC-646EFF2F3C25}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -117,6 +119,10 @@ Global
|
||||
{A36DD7D5-12BE-4B79-82AA-526E52DC0A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A36DD7D5-12BE-4B79-82AA-526E52DC0A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A36DD7D5-12BE-4B79-82AA-526E52DC0A3A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3D9F9D89-1860-48D9-9CCC-646EFF2F3C25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3D9F9D89-1860-48D9-9CCC-646EFF2F3C25}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3D9F9D89-1860-48D9-9CCC-646EFF2F3C25}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3D9F9D89-1860-48D9-9CCC-646EFF2F3C25}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -23,6 +23,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>
|
||||
<None Update="i18n\*.json">
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
using FileFlows.Gotify.Communication;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Gotify.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class GotifyTests
|
||||
public class GotifyTests : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void Gotify_Basic_Message()
|
||||
{
|
||||
var args = new NodeParameters("test.file", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters("test.file", Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.Gotify.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,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.ImageNodes.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,480 +0,0 @@
|
||||
#if(DEBUG)
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace FileFlows.ImageNodes.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 ?? string.Empty);
|
||||
|
||||
logMethod(logger.ToString());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -26,6 +26,7 @@ Contains flow elements to lookup movie information from TheMovieDB, and music in
|
||||
<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>
|
||||
<PackageReference Include="ID3" Version="0.6.0" />
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using FileFlows.Plugin;
|
||||
|
||||
namespace MetaNodes.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// A logger for tests that stores the logs in memory
|
||||
/// </summary>
|
||||
public class TestLogger : ILogger
|
||||
{
|
||||
private readonly List<string> Messages = new();
|
||||
|
||||
/// <summary>
|
||||
/// Writes an information log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void ILog(params object[] args)
|
||||
=> Log(LogType.Info, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an debug log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void DLog(params object[] args)
|
||||
=> Log(LogType.Debug, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an warning log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void WLog(params object[] args)
|
||||
=> Log(LogType.Warning, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an error log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void ELog(params object[] args)
|
||||
=> Log(LogType.Error, args);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tail of the log
|
||||
/// </summary>
|
||||
/// <param name="length">the number of messages to get</param>
|
||||
public string GetTail(int length = 50)
|
||||
{
|
||||
if (Messages.Count <= length)
|
||||
return string.Join(Environment.NewLine, Messages);
|
||||
return string.Join(Environment.NewLine, Messages.TakeLast(50));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message
|
||||
/// </summary>
|
||||
/// <param name="type">the type of log to record</param>
|
||||
/// <param name="args">the arguments of the message</param>
|
||||
private void Log(LogType type, params object[] args)
|
||||
{
|
||||
string message = type + " -> " + string.Join(", ", args.Select(x =>
|
||||
x == null ? "null" :
|
||||
x.GetType().IsPrimitive ? x.ToString() :
|
||||
x is string ? x.ToString() :
|
||||
System.Text.Json.JsonSerializer.Serialize(x)));
|
||||
Writer?.Invoke(message);
|
||||
Messages.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional writer
|
||||
/// </summary>
|
||||
public Action<string> Writer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the entire log as a string
|
||||
/// </summary>
|
||||
/// <returns>the entire log</returns>
|
||||
public override string ToString()
|
||||
=> string.Join(Environment.NewLine, Messages);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the log contains the text
|
||||
/// </summary>
|
||||
/// <param name="text">the text to check for</param>
|
||||
/// <returns>true if it contains it, otherwise false</returns>
|
||||
public bool Contains(string text)
|
||||
=> Messages.Any(x => x.Contains(text));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -3,9 +3,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using DM.MovieApi;
|
||||
using DM.MovieApi.MovieDb.Movies;
|
||||
using FileFlows.Plugin;
|
||||
using MetaNodes.TheMovieDb;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace MetaNodes.Tests.TheMovieDb;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using DM.MovieApi.MovieDb.TV;
|
||||
using FileFlows.Plugin;
|
||||
using MetaNodes.TheMovieDb;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace MetaNodes.Tests.TheMovieDb;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using DM.MovieApi.MovieDb.Movies;
|
||||
using DM.MovieApi.MovieDb.TV;
|
||||
using MetaNodes.TheMovieDb;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace MetaNodes.Tests.TheMovieDb;
|
||||
|
||||
|
||||
@@ -1,483 +0,0 @@
|
||||
#if(DEBUG)
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
using System.IO;
|
||||
using FileHelper = FileFlows.Plugin.Helpers.FileHelper;
|
||||
|
||||
namespace MetaNodes.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Local file service
|
||||
/// </summary>
|
||||
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 System.IO.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
|
||||
{
|
||||
System.IO.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 (System.IO.File.Exists(path))
|
||||
System.IO.File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
|
||||
else
|
||||
{
|
||||
System.IO.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 (!System.IO.File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
System.IO.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 (!System.IO.File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
System.IO.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 ((System.IO.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,78 +1,78 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using FileFlows.Plugin;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace MetaNodes.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for the tests
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public abstract class TestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The test context instance
|
||||
/// </summary>
|
||||
private TestContext testContextInstance;
|
||||
|
||||
internal TestLogger Logger = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the test context
|
||||
/// </summary>
|
||||
public TestContext TestContext
|
||||
{
|
||||
get => testContextInstance;
|
||||
set => testContextInstance = value;
|
||||
}
|
||||
|
||||
public string TestPath { get; private set; }
|
||||
public string TempPath { get; private set; }
|
||||
|
||||
public readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
public readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
Logger.Writer = (msg) => TestContext.WriteLine(msg);
|
||||
|
||||
this.TestPath = this.TestPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/test-files/videos" : @"d:\videos\testfiles");
|
||||
this.TempPath = this.TempPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/temp" : @"d:\videos\temp");
|
||||
|
||||
this.TestPath = this.TestPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
this.TempPath = this.TempPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
|
||||
if (Directory.Exists(this.TempPath) == false)
|
||||
Directory.CreateDirectory(this.TempPath);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void CleanUp()
|
||||
{
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
}
|
||||
|
||||
protected virtual void TestStarting()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected NodeParameters GetNodeParameters(string filename)
|
||||
{
|
||||
string tempPath = Path.GetTempPath();
|
||||
string libPath = Path.Combine(tempPath, "media");
|
||||
if (Directory.Exists(libPath) == false)
|
||||
Directory.CreateDirectory(libPath);
|
||||
return new(filename, Logger, false, libPath, new LocalFileService())
|
||||
{
|
||||
LibraryFileName = filename,
|
||||
TempPath = tempPath
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
// #if(DEBUG)
|
||||
//
|
||||
// using System.Runtime.InteropServices;
|
||||
// using FileFlows.Plugin;
|
||||
// using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
//
|
||||
// namespace MetaNodes.Tests;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Base class for the tests
|
||||
// /// </summary>
|
||||
// [TestClass]
|
||||
// public abstract class TestBase
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// The test context instance
|
||||
// /// </summary>
|
||||
// private TestContext testContextInstance;
|
||||
//
|
||||
// internal TestLogger Logger = new();
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Gets or sets the test context
|
||||
// /// </summary>
|
||||
// public TestContext TestContext
|
||||
// {
|
||||
// get => testContextInstance;
|
||||
// set => testContextInstance = value;
|
||||
// }
|
||||
//
|
||||
// public string TestPath { get; private set; }
|
||||
// public string TempPath { get; private set; }
|
||||
//
|
||||
// public readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
// public readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
//
|
||||
// [TestInitialize]
|
||||
// public void TestInitialize()
|
||||
// {
|
||||
// Logger.Writer = (msg) => TestContext.WriteLine(msg);
|
||||
//
|
||||
// this.TestPath = this.TestPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/test-files/videos" : @"d:\videos\testfiles");
|
||||
// this.TempPath = this.TempPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/temp" : @"d:\videos\temp");
|
||||
//
|
||||
// this.TestPath = this.TestPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
// this.TempPath = this.TempPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
//
|
||||
// if (Directory.Exists(this.TempPath) == false)
|
||||
// Directory.CreateDirectory(this.TempPath);
|
||||
// }
|
||||
//
|
||||
// [TestCleanup]
|
||||
// public void CleanUp()
|
||||
// {
|
||||
// TestContext.WriteLine(Logger.ToString());
|
||||
// }
|
||||
//
|
||||
// protected virtual void TestStarting()
|
||||
// {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// protected NodeParameters GetNodeParameters(string filename)
|
||||
// {
|
||||
// string tempPath = Path.GetTempPath();
|
||||
// string libPath = Path.Combine(tempPath, "media");
|
||||
// if (Directory.Exists(libPath) == false)
|
||||
// Directory.CreateDirectory(libPath);
|
||||
// return new(filename, Logger, false, libPath, new LocalFileService())
|
||||
// {
|
||||
// LibraryFileName = filename,
|
||||
// TempPath = tempPath
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// #endif
|
||||
@@ -27,6 +27,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,7 @@
|
||||
|
||||
using FileFlows.Nextcloud.FlowElements;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Nextcloud.Tests;
|
||||
|
||||
|
||||
@@ -1,485 +0,0 @@
|
||||
#if(DEBUG)
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
using System.IO;
|
||||
using FileHelper = FileFlows.Plugin.Helpers.FileHelper;
|
||||
|
||||
namespace FileFlows.Nextcloud.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Local file service
|
||||
/// </summary>
|
||||
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; } = null!;
|
||||
|
||||
/// <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>
|
||||
#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
|
||||
public ReplaceVariablesDelegate ReplaceVariables { get; set; } = null!;
|
||||
#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
|
||||
|
||||
/// <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; } = null!;
|
||||
|
||||
/// <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 System.IO.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
|
||||
{
|
||||
System.IO.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 (System.IO.File.Exists(path))
|
||||
System.IO.File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
|
||||
else
|
||||
{
|
||||
System.IO.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 (!System.IO.File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
System.IO.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 (!System.IO.File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
System.IO.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 ((System.IO.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,64 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FileFlows.Nextcloud.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for the tests
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public abstract class TestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The test context instance
|
||||
/// </summary>
|
||||
private TestContext testContextInstance = null!;
|
||||
|
||||
internal TestLogger Logger = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the test context
|
||||
/// </summary>
|
||||
public TestContext TestContext
|
||||
{
|
||||
get => testContextInstance;
|
||||
set => testContextInstance = value;
|
||||
}
|
||||
|
||||
public string TestPath { get; private set; } = null!;
|
||||
public string TempPath { get; private set; } = null!;
|
||||
|
||||
public readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
public readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
Logger.Writer = (msg) => TestContext.WriteLine(msg);
|
||||
|
||||
this.TestPath = this.TestPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/test-files/videos" : @"d:\videos\testfiles");
|
||||
this.TempPath = this.TempPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/temp" : @"d:\videos\temp");
|
||||
|
||||
this.TestPath = this.TestPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
this.TempPath = this.TempPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
|
||||
if (Directory.Exists(this.TempPath) == false)
|
||||
Directory.CreateDirectory(this.TempPath);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void CleanUp()
|
||||
{
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
}
|
||||
|
||||
protected virtual void TestStarting()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,90 +0,0 @@
|
||||
#if (DEBUG)
|
||||
|
||||
namespace FileFlows.Nextcloud.Tests;
|
||||
|
||||
using FileFlows.Plugin;
|
||||
|
||||
/// <summary>
|
||||
/// A logger for tests that stores the logs in memory
|
||||
/// </summary>
|
||||
public class TestLogger : ILogger
|
||||
{
|
||||
private readonly List<string> Messages = new();
|
||||
|
||||
/// <summary>
|
||||
/// Writes an information log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void ILog(params object[] args)
|
||||
=> Log(LogType.Info, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an debug log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void DLog(params object[] args)
|
||||
=> Log(LogType.Debug, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an warning log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void WLog(params object[] args)
|
||||
=> Log(LogType.Warning, args);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an error log message
|
||||
/// </summary>
|
||||
/// <param name="args">the log parameters</param>
|
||||
public void ELog(params object[] args)
|
||||
=> Log(LogType.Error, args);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tail of the log
|
||||
/// </summary>
|
||||
/// <param name="length">the number of messages to get</param>
|
||||
public string GetTail(int length = 50)
|
||||
{
|
||||
if (Messages.Count <= length)
|
||||
return string.Join(Environment.NewLine, Messages);
|
||||
return string.Join(Environment.NewLine, Messages.TakeLast(50));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message
|
||||
/// </summary>
|
||||
/// <param name="type">the type of log to record</param>
|
||||
/// <param name="args">the arguments of the message</param>
|
||||
private void Log(LogType type, params object[] args)
|
||||
{
|
||||
string message = type + " -> " + string.Join(", ", args.Select(x =>
|
||||
x == null ? "null" :
|
||||
x.GetType().IsPrimitive ? x.ToString() :
|
||||
x is string ? x.ToString() :
|
||||
System.Text.Json.JsonSerializer.Serialize(x)));
|
||||
Writer?.Invoke(message);
|
||||
Messages.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional writer
|
||||
/// </summary>
|
||||
public Action<string> Writer { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the entire log as a string
|
||||
/// </summary>
|
||||
/// <returns>the entire log</returns>
|
||||
public override string ToString()
|
||||
=> string.Join(Environment.NewLine, Messages);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the log contains the text
|
||||
/// </summary>
|
||||
/// <param name="text">the text to check for</param>
|
||||
/// <returns>true if it contains it, otherwise false</returns>
|
||||
public bool Contains(string text)
|
||||
=> Messages.Any(x => x.Contains(text));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -24,6 +24,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,16 +2,17 @@
|
||||
|
||||
using FileFlows.Plex.MediaManagement;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Plex.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class PlexAnayzeTests
|
||||
public class PlexAnayzeTests : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void PlexAnayze_Basic()
|
||||
{
|
||||
var args = new NodeParameters(@"/media/tv/Outrageous Fortune/Season 3/Outrageous Fotune - 3x02.mkv", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/media/tv/Outrageous Fortune/Season 3/Outrageous Fotune - 3x02.mkv", Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
@@ -24,7 +25,8 @@ public class PlexAnayzeTests
|
||||
[TestMethod]
|
||||
public void PlexAnayze_Fail()
|
||||
{
|
||||
var args = new NodeParameters(@"/media/tv/Outrageous Fortune/Season 3/Outrageous Fotune - 3x02a.mkv", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/media/tv/Outrageous Fortune/Season 3/Outrageous Fotune - 3x02a.mkv",
|
||||
Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
@@ -37,7 +39,8 @@ public class PlexAnayzeTests
|
||||
[TestMethod]
|
||||
public void PlexAnayze_Mapping()
|
||||
{
|
||||
var args = new NodeParameters(@"/mnt/movies/The Batman (2022)/The Batman (2022).mkv", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/mnt/movies/The Batman (2022)/The Batman (2022).mkv",
|
||||
Logger, false, string.Empty, new LocalFileService());
|
||||
var settings = new PluginSettings();
|
||||
settings.Mapping = new List<KeyValuePair<string, string>>();
|
||||
settings.Mapping.Add(new KeyValuePair<string, string>("/mnt/movies", "/media/movies"));
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
|
||||
using FileFlows.Plex.MediaManagement;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Plex.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class PlexUpdaterTests
|
||||
public class PlexUpdaterTests : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void Plex_Basic()
|
||||
{
|
||||
var args = new NodeParameters(@"/media/movies/The Batman (2022)/The Batman.mkv", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/media/movies/The Batman (2022)/The Batman.mkv",
|
||||
Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
@@ -24,7 +26,8 @@ public class PlexUpdaterTests
|
||||
[TestMethod]
|
||||
public void Plex_Fail()
|
||||
{
|
||||
var args = new NodeParameters(@"/media/unknownmovies/The Batman (2022)/The Batman.mkv", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/media/unknownmovies/The Batman (2022)/The Batman.mkv",
|
||||
Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
@@ -37,7 +40,8 @@ public class PlexUpdaterTests
|
||||
[TestMethod]
|
||||
public void Plex_Mapping()
|
||||
{
|
||||
var args = new NodeParameters(@"/mnt/movies/The Batman (2022)/The Batman.mkv", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters(@"/mnt/movies/The Batman (2022)/The Batman.mkv",
|
||||
Logger, false, string.Empty, new LocalFileService());
|
||||
var settings = new PluginSettings();
|
||||
settings.Mapping = new List<KeyValuePair<string, string>>();
|
||||
settings.Mapping.Add(new KeyValuePair<string, string>("/mnt/movies", "/media/movies"));
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.Plex.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
|
||||
20
PluginTestLibrary/PluginTestLibrary.csproj
Normal file
20
PluginTestLibrary/PluginTestLibrary.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug'">
|
||||
<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" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<Reference Include="FileFlows.Plugin">
|
||||
<HintPath>..\FileFlows.Plugin.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -1,11 +1,10 @@
|
||||
#if(DEBUG)
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
using System.IO;
|
||||
using FileHelper = FileFlows.Plugin.Helpers.FileHelper;
|
||||
|
||||
namespace BasicNodes.Tests;
|
||||
namespace PluginTestLibrary;
|
||||
|
||||
/// <summary>
|
||||
/// Local file service
|
||||
@@ -16,20 +15,14 @@ public class LocalFileService : IFileService
|
||||
/// Gets or sets the path separator for the file system
|
||||
/// </summary>
|
||||
public char PathSeparator { get; init; } = Path.DirectorySeparatorChar;
|
||||
|
||||
|
||||
public ReplaceVariablesDelegate? ReplaceVariables { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the allowed paths the file service can access
|
||||
/// </summary>
|
||||
public string[] AllowedPaths { get; init; }
|
||||
public string[] AllowedPaths { get; init; } = null!;
|
||||
|
||||
/// <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
|
||||
@@ -39,7 +32,7 @@ public class LocalFileService : IFileService
|
||||
/// <summary>
|
||||
/// Gets or sets the owner:group to use for files
|
||||
/// </summary>
|
||||
public string OwnerGroup { get; set; }
|
||||
public string OwnerGroup { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger used for logging
|
||||
@@ -182,7 +175,7 @@ public class LocalFileService : IFileService
|
||||
Name = fileInfo.Name,
|
||||
FullName = fileInfo.FullName,
|
||||
Length = fileInfo.Length,
|
||||
Directory = fileInfo.DirectoryName
|
||||
Directory = fileInfo.DirectoryName!
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -270,7 +263,7 @@ public class LocalFileService : IFileService
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<bool>.Fail("File does not exist");
|
||||
var destDir = new FileInfo(destination).Directory;
|
||||
var destDir = new FileInfo(destination).Directory!;
|
||||
if (destDir.Exists == false)
|
||||
{
|
||||
destDir.Create();
|
||||
@@ -299,7 +292,7 @@ public class LocalFileService : IFileService
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<bool>.Fail("File does not exist");
|
||||
|
||||
var destDir = new FileInfo(destination).Directory;
|
||||
var destDir = new FileInfo(destination).Directory!;
|
||||
if (destDir.Exists == false)
|
||||
{
|
||||
destDir.Create();
|
||||
@@ -380,7 +373,27 @@ public class LocalFileService : IFileService
|
||||
|
||||
public Result<long> DirectorySize(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return 0;
|
||||
|
||||
if (System.IO.File.Exists(path))
|
||||
path = new FileInfo(path).Directory?.FullName ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return 0;
|
||||
|
||||
if (Directory.Exists(path) == false)
|
||||
return 0;
|
||||
|
||||
try
|
||||
{
|
||||
DirectoryInfo dir = new DirectoryInfo(path);
|
||||
return dir.EnumerateFiles("*.*", SearchOption.AllDirectories).Sum(x => x.Length);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<bool> SetCreationTimeUtc(string path, DateTime date)
|
||||
@@ -453,7 +466,7 @@ public class LocalFileService : IFileService
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPermissions(string path, int? permissions = null, Action<string> logMethod = null)
|
||||
public void SetPermissions(string path, int? permissions = null, Action<string>? logMethod = null)
|
||||
{
|
||||
logMethod ??= (string message) => Logger?.ILog(message);
|
||||
|
||||
@@ -479,5 +492,4 @@ public class LocalFileService : IFileService
|
||||
|
||||
logMethod(logger.ToString());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using FileFlows.Plugin;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.IO;
|
||||
using FileFlows.BasicNodes;
|
||||
using FileFlows.Plugin.Helpers;
|
||||
using FileFlows.Plugin.Services;
|
||||
using Moq;
|
||||
|
||||
namespace BasicNodes.Tests;
|
||||
namespace PluginTestLibrary;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for the tests
|
||||
@@ -19,10 +18,9 @@ public abstract class TestBase
|
||||
/// <summary>
|
||||
/// The test context instance
|
||||
/// </summary>
|
||||
private TestContext testContextInstance;
|
||||
|
||||
internal TestLogger Logger = new();
|
||||
private TestContext testContextInstance = null!;
|
||||
|
||||
protected TestLogger Logger = new();
|
||||
protected Mock<IFileService> MockFileService = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -37,12 +35,13 @@ public abstract class TestBase
|
||||
/// <summary>
|
||||
/// A File created for the test
|
||||
/// </summary>
|
||||
public string TempFile { get; private set; }
|
||||
public string TempFile { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// A path in the temp directory created for the test
|
||||
/// </summary>
|
||||
public string TempPath { get; private set; }
|
||||
|
||||
public string TempPath { get; private set; } = null!;
|
||||
|
||||
public readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
public readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
@@ -52,13 +51,13 @@ public abstract class TestBase
|
||||
FileHelper.DontChangeOwner = true;
|
||||
FileHelper.DontSetPermissions = true;
|
||||
Logger.Writer = (msg) => TestContext.WriteLine(msg);
|
||||
|
||||
|
||||
|
||||
|
||||
TempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
System.IO.Directory.CreateDirectory(TempPath);
|
||||
TempFile = System.IO.Path.Combine(TempPath, Guid.NewGuid() + ".txt");
|
||||
System.IO.File.WriteAllText(TempFile, Guid.NewGuid().ToString());
|
||||
|
||||
|
||||
if (Directory.Exists(this.TempPath) == false)
|
||||
Directory.CreateDirectory(this.TempPath);
|
||||
|
||||
@@ -76,11 +75,40 @@ public abstract class TestBase
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void TestCleanUp()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string GetPluginSettingsJson(string plugin)
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("Docker") == "1")
|
||||
return File.ReadAllText("/plugin-settings/" + plugin + ".json");
|
||||
|
||||
return File.ReadAllText("../../../settings.json");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node parameters for a file
|
||||
/// </summary>
|
||||
/// <param name="filename">the name of the file</param>
|
||||
/// <param name="isDirectory">if the file is a directory</param>
|
||||
/// <returns>the node parameters</returns>
|
||||
protected NodeParameters GetNodeParameters(string filename, bool isDirectory =false)
|
||||
{
|
||||
string tempPath = TempPath;
|
||||
string libPath = Path.Combine(tempPath, "media");
|
||||
if (Directory.Exists(libPath) == false)
|
||||
Directory.CreateDirectory(libPath);
|
||||
var args = new NodeParameters(filename, Logger, isDirectory, libPath, new LocalFileService())
|
||||
{
|
||||
LibraryFileName = filename,
|
||||
TempPath = tempPath
|
||||
};
|
||||
return args;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,8 +1,8 @@
|
||||
#if (DEBUG)
|
||||
using FileFlows.Plugin;
|
||||
|
||||
namespace FileFlows.Web.Tests;
|
||||
#if (DEBUG)
|
||||
|
||||
using FileFlows.Plugin;
|
||||
namespace PluginTestLibrary;
|
||||
|
||||
/// <summary>
|
||||
/// A logger for tests that stores the logs in memory
|
||||
@@ -23,6 +23,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">
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using FileFlows.Pushbullet.Communication;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Pushbullet.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class PushbulletTests
|
||||
public class PushbulletTests : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void Pushbullet_Basic_Message()
|
||||
{
|
||||
var args = new NodeParameters("test.file", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters("test.file", Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../../../Pushbullet.json");
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.Pushbullet.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
|
||||
@@ -23,6 +23,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,16 +2,17 @@
|
||||
|
||||
using FileFlows.Pushover.Communication;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Pushover.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class PushoverTests
|
||||
public class PushoverTests : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void Pushover_Basic_Message()
|
||||
{
|
||||
var args = new NodeParameters("test.file", new TestLogger(), false, string.Empty, null!);
|
||||
var args = new NodeParameters("test.file", Logger, false, string.Empty, new LocalFileService());
|
||||
args.GetPluginSettingsJson = (string input) =>
|
||||
{
|
||||
return File.ReadAllText("../../../../../pushover.json");
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.Pushover.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
|
||||
@@ -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">
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Telegram.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class TelegramTests
|
||||
public class TelegramTests : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void SendMessage()
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
namespace FileFlows.Telegram.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
|
||||
@@ -26,6 +26,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="Plugin">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using FileFlows.Web.FlowElements;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PluginTestLibrary;
|
||||
|
||||
namespace FileFlows.Web.Tests;
|
||||
|
||||
|
||||
@@ -1,485 +0,0 @@
|
||||
#if(DEBUG)
|
||||
using FileFlows.Plugin;
|
||||
using FileFlows.Plugin.Models;
|
||||
using FileFlows.Plugin.Services;
|
||||
using System.IO;
|
||||
using FileHelper = FileFlows.Plugin.Helpers.FileHelper;
|
||||
|
||||
namespace FileFlows.Web.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Local file service
|
||||
/// </summary>
|
||||
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; } = null!;
|
||||
|
||||
/// <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>
|
||||
#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
|
||||
public ReplaceVariablesDelegate ReplaceVariables { get; set; } = null!;
|
||||
#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
|
||||
|
||||
/// <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; } = null!;
|
||||
|
||||
/// <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 System.IO.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
|
||||
{
|
||||
System.IO.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 (System.IO.File.Exists(path))
|
||||
System.IO.File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
|
||||
else
|
||||
{
|
||||
System.IO.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 (!System.IO.File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
System.IO.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 (!System.IO.File.Exists(path))
|
||||
return Result<bool>.Fail("File not found.");
|
||||
|
||||
System.IO.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 ((System.IO.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,64 +0,0 @@
|
||||
#if(DEBUG)
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FileFlows.Web.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for the tests
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public abstract class TestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The test context instance
|
||||
/// </summary>
|
||||
private TestContext testContextInstance = null!;
|
||||
|
||||
internal TestLogger Logger = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the test context
|
||||
/// </summary>
|
||||
public TestContext TestContext
|
||||
{
|
||||
get => testContextInstance;
|
||||
set => testContextInstance = value;
|
||||
}
|
||||
|
||||
public string TestPath { get; private set; } = null!;
|
||||
public string TempPath { get; private set; } = null!;
|
||||
|
||||
public readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
public readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
Logger.Writer = (msg) => TestContext.WriteLine(msg);
|
||||
|
||||
this.TestPath = this.TestPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/test-files/videos" : @"d:\videos\testfiles");
|
||||
this.TempPath = this.TempPath?.EmptyAsNull() ?? (IsLinux ? "~/src/ff-files/temp" : @"d:\videos\temp");
|
||||
|
||||
this.TestPath = this.TestPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
this.TempPath = this.TempPath.Replace("~/", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/");
|
||||
|
||||
if (Directory.Exists(this.TempPath) == false)
|
||||
Directory.CreateDirectory(this.TempPath);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void CleanUp()
|
||||
{
|
||||
TestContext.WriteLine(Logger.ToString());
|
||||
}
|
||||
|
||||
protected virtual void TestStarting()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -27,6 +27,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">
|
||||
|
||||
Reference in New Issue
Block a user