unit tests

This commit is contained in:
John Andrews
2024-08-26 18:16:07 +12:00
parent f92b8bbc5d
commit 444d46bf62
92 changed files with 1108 additions and 4700 deletions

View File

@@ -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>

View File

@@ -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
View 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;
}
}

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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" />

View File

@@ -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

View 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}");
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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 - Id Do Anything for Love (but I Wont 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 - Id Do Anything for Love (but I Wont 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("Id Do Anything for Love (but I Wont 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;

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -7,7 +7,7 @@ namespace FileFlows.AudioNodes.Tests;
[TestClass]
public class CreateAudioBookTests : AudioTestBase
public class CreateAudioBookTests : TestBase
{
[TestMethod]
public void CreateAudioBookTest_01()

View File

@@ -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),

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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

View 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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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">

View File

@@ -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);

View File

@@ -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

View File

@@ -3,6 +3,7 @@
using System.IO;
using FileFlows.BasicNodes.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PluginTestLibrary;
namespace BasicNodes.Tests;

View File

@@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Text.RegularExpressions;
using FileFlows.Plugin;
using Moq;
using PluginTestLibrary;
namespace BasicNodes.Tests;

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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" />

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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">

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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">

View File

@@ -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

View File

@@ -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

View File

@@ -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">

View File

@@ -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");

View File

@@ -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.

View File

@@ -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

View File

@@ -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">

View File

@@ -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");

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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" />

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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">

View File

@@ -2,6 +2,7 @@
using FileFlows.Nextcloud.FlowElements;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PluginTestLibrary;
namespace FileFlows.Nextcloud.Tests;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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">

View File

@@ -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"));

View File

@@ -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"));

View File

@@ -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

View 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>

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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">

View File

@@ -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");

View File

@@ -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

View File

@@ -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">

View File

@@ -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");

View File

@@ -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

View File

@@ -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">

View File

@@ -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()

View File

@@ -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

View File

@@ -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">

View File

@@ -2,6 +2,7 @@
using FileFlows.Web.FlowElements;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PluginTestLibrary;
namespace FileFlows.Web.Tests;

View File

@@ -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

View File

@@ -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

View File

@@ -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">