Added FileLocker to avoid exceptions from cross thread IO operations

This commit is contained in:
ruri
2020-03-31 02:52:22 +02:00
4 changed files with 91 additions and 32 deletions
+29 -32
View File
@@ -1,5 +1,6 @@
using RuriLib.Functions.Conditions;
using RuriLib.Functions.Conversions;
using RuriLib.Functions.Files;
using RuriLib.LS;
using RuriLib.Models;
using RuriLib.ViewModels;
@@ -604,7 +605,7 @@ namespace RuriLib
case UtilityGroup.File:
var file = ReplaceValues(filePath, data);
ThrowIfNotInCWD(file);
Files.ThrowIfNotInCWD(file);
switch (fileAction)
{
@@ -613,43 +614,55 @@ namespace RuriLib
break;
case FileAction.Read:
data.Variables.Set(new CVar(variableName, File.ReadAllText(file), isCapture));
lock (FileLocker.GetLock(file))
data.Variables.Set(new CVar(variableName, File.ReadAllText(file), isCapture));
break;
case FileAction.ReadLines:
data.Variables.Set(new CVar(variableName, File.ReadAllLines(file).ToList(), isCapture));
lock (FileLocker.GetLock(file))
data.Variables.Set(new CVar(variableName, File.ReadAllLines(file).ToList(), isCapture));
break;
case FileAction.Write:
CreatePath(file);
File.WriteAllText(file, replacedInput.Unescape());
Files.CreatePath(file);
lock (FileLocker.GetLock(file))
File.WriteAllText(file, replacedInput.Unescape());
break;
case FileAction.WriteLines:
CreatePath(file);
File.WriteAllLines(file, ReplaceValuesRecursive(inputString, data).Select(i => i.Unescape()));
Files.CreatePath(file);
lock (FileLocker.GetLock(file))
File.WriteAllLines(file, ReplaceValuesRecursive(inputString, data).Select(i => i.Unescape()));
break;
case FileAction.Append:
File.AppendAllText(file, replacedInput.Unescape());
Files.CreatePath(file);
lock (FileLocker.GetLock(file))
File.AppendAllText(file, replacedInput.Unescape());
break;
case FileAction.AppendLines:
File.AppendAllLines(file, ReplaceValuesRecursive(inputString, data).Select(i => i.Unescape()));
Files.CreatePath(file);
lock (FileLocker.GetLock(file))
File.AppendAllLines(file, ReplaceValuesRecursive(inputString, data).Select(i => i.Unescape()));
break;
case FileAction.Copy:
var fileCopyLocation = ReplaceValues(inputString, data);
ThrowIfNotInCWD(fileCopyLocation);
CreatePath(fileCopyLocation);
File.Copy(file, fileCopyLocation);
Files.ThrowIfNotInCWD(fileCopyLocation);
Files.CreatePath(fileCopyLocation);
lock (FileLocker.GetLock(file))
lock (FileLocker.GetLock(fileCopyLocation))
File.Copy(file, fileCopyLocation);
break;
case FileAction.Move:
var fileMoveLocation = ReplaceValues(inputString, data);
ThrowIfNotInCWD(fileMoveLocation);
CreatePath(fileMoveLocation);
File.Move(file, fileMoveLocation);
Files.ThrowIfNotInCWD(fileMoveLocation);
Files.CreatePath(fileMoveLocation);
lock (FileLocker.GetLock(file))
lock (FileLocker.GetLock(fileMoveLocation))
File.Move(file, fileMoveLocation);
break;
}
data.Log(new LogEntry($"Executed action {fileAction} on file {file}", isCapture ? Colors.Tomato : Colors.Yellow));
@@ -657,7 +670,7 @@ namespace RuriLib
case UtilityGroup.Folder:
var folder = ReplaceValues(folderPath, data);
ThrowIfNotInCWD(folder);
Files.ThrowIfNotInCWD(folder);
switch (folderAction)
{
@@ -678,21 +691,5 @@ namespace RuriLib
}
catch(Exception ex) { data.Log(new LogEntry(ex.Message, Colors.Tomato)); }
}
private void ThrowIfNotInCWD(string path)
{
if (!path.IsSubPathOf(Directory.GetCurrentDirectory()))
{
throw new UnauthorizedAccessException("For security reasons, you cannot interact with paths outside of the current working directory");
}
}
private void CreatePath(string file)
{
if (!Directory.Exists(Path.GetDirectoryName(file)))
{
Directory.CreateDirectory(Path.GetDirectoryName(file));
}
}
}
}
+35
View File
@@ -0,0 +1,35 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RuriLib
{
/// <summary>
/// Singleton class that manages application-wide file locking to avoid cross thread IO operations on the same file.
/// </summary>
public static class FileLocker
{
/// <summary>
/// Maps file names to lockable objects.
/// </summary>
public static Hashtable Hashtable = new Hashtable();
/// <summary>
/// Gets a lock by file name or creates one if it doesn't exist.
/// </summary>
/// <param name="fileName">The name of the file to access</param>
/// <returns>An object that can be used in a lock statement.</returns>
public static object GetLock(string fileName)
{
if (!Hashtable.ContainsKey(fileName))
{
Hashtable.Add(fileName, new object());
}
return Hashtable[fileName];
}
}
}
+26
View File
@@ -94,5 +94,31 @@ namespace RuriLib.Functions.Files
return Regex.Replace(name, invalidRegStr, underscore ? "_" : "");
}
/// <summary>
/// Throws an UnauthorizedAccessException if the path is not part of the current working directory.
/// </summary>
/// <param name="path">The absolute or relative path.</param>
public static void ThrowIfNotInCWD(string path)
{
if (!path.IsSubPathOf(Directory.GetCurrentDirectory()))
{
throw new UnauthorizedAccessException("For security reasons, you cannot interact with paths outside of the current working directory");
}
}
/// <summary>
/// Creates the folder structure that contains a certain files if it doesn't already exist.
/// </summary>
/// <param name="file">The absolute or relative path to the file.</param>
public static void CreatePath(string file)
{
var dirName = Path.GetDirectoryName(file);
if (!string.IsNullOrWhiteSpace(dirName) && !Directory.Exists(dirName))
{
Directory.CreateDirectory(dirName);
}
}
}
}
+1
View File
@@ -254,6 +254,7 @@
<Compile Include="CaptchaServices\RuCaptcha.cs" />
<Compile Include="CaptchaServices\Service.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="FileLocker.cs" />
<Compile Include="Functions\Conversion\Conversion.cs" />
<Compile Include="Functions\Crypto\Crypto.cs" />
<Compile Include="Functions\Download\Download.cs" />