diff --git a/Server/Controllers/DownloadController.cs b/Server/Controllers/DownloadController.cs index c173f70ee..c0f7a2655 100644 --- a/Server/Controllers/DownloadController.cs +++ b/Server/Controllers/DownloadController.cs @@ -20,7 +20,7 @@ public IActionResult Download() if (System.IO.File.Exists(file) == false) return NotFound(); - return new FileStreamResult(System.IO.File.OpenRead(file), "application/octet-stream") + return new FileStreamResult(FileOpenHelper.OpenRead_NoLocks(file), "application/octet-stream") { FileDownloadName = zipName }; diff --git a/Server/Controllers/PluginController.cs b/Server/Controllers/PluginController.cs index 749e70066..83715094a 100644 --- a/Server/Controllers/PluginController.cs +++ b/Server/Controllers/PluginController.cs @@ -218,7 +218,7 @@ public FileStreamResult DownloadPackage([FromRoute] string package) try { - return File(System.IO.File.OpenRead(file), "application/octet-stream"); + return File(FileOpenHelper.OpenRead_NoLocks(file), "application/octet-stream"); } catch(Exception ex) { diff --git a/Server/Helpers/Gzipper.cs b/Server/Helpers/Gzipper.cs index 6d1aa54a7..8605d1a59 100644 --- a/Server/Helpers/Gzipper.cs +++ b/Server/Helpers/Gzipper.cs @@ -22,7 +22,7 @@ public static bool CompressFile(string inputFile, string outputFile, bool delete if (File.Exists(outputFile)) File.Delete(outputFile); - using (FileStream originalFileStream = File.Open(inputFile, FileMode.Open)) + using (FileStream originalFileStream = FileOpenHelper.OpenRead_NoLocks(inputFile)) { using FileStream compressedFileStream = File.Create(outputFile); using var compressor = new GZipStream(compressedFileStream, CompressionMode.Compress); @@ -55,7 +55,7 @@ public static bool DecompressFile(string inputFile, string outputFile, bool dele if (File.Exists(outputFile)) File.Delete(outputFile); - using FileStream compressedFileStream = File.Open(inputFile, FileMode.Open); + using FileStream compressedFileStream = FileOpenHelper.OpenRead_NoLocks(inputFile); using FileStream outputFileStream = File.Create(outputFile); using var decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress); decompressor.CopyTo(outputFileStream); @@ -96,7 +96,7 @@ public static void CompressToFile(string file, string contents) /// the decompresses file contents public static string DecompressFileToString(string inputFile, int lines = 0, int bytes = 0) { - using FileStream compressedFileStream = File.Open(inputFile, FileMode.Open); + using FileStream compressedFileStream = FileOpenHelper.OpenRead_NoLocks(inputFile); using MemoryStream outputStream = new MemoryStream(); using var decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress); diff --git a/Server/Server.csproj b/Server/Server.csproj index a85aa98a5..c04cb1598 100644 Binary files a/Server/Server.csproj and b/Server/Server.csproj differ diff --git a/Server/Workers/WatchedLibrary.cs b/Server/Workers/WatchedLibrary.cs index f3fb60ab5..16193a55f 100644 --- a/Server/Workers/WatchedLibrary.cs +++ b/Server/Workers/WatchedLibrary.cs @@ -791,7 +791,7 @@ private async Task CanAccess(FileInfo file, int fileSizeDetectionInterval) checkedAccess = true; - using (var fs = File.Open(file.FullName, FileMode.Open)) + using (var fs = FileOpenHelper.OpenForCheckingReadWriteAccess(file.FullName)) { if(fs.CanRead == false) { @@ -924,4 +924,4 @@ private bool QueueContains(string item) return QueuedFiles.Contains(item); } } -} +} \ No newline at end of file diff --git a/ServerShared/FileServices/FileDownloader.cs b/ServerShared/FileServices/FileDownloader.cs index 1792e3714..fee18e99c 100644 --- a/ServerShared/FileServices/FileDownloader.cs +++ b/ServerShared/FileServices/FileDownloader.cs @@ -113,7 +113,7 @@ public async Task> DownloadFile(string path, string destinationPath logger?.ILog("Content-Length header is not present in the response."); } - using FileStream fileStream = File.OpenWrite(destinationPath); + using FileStream fileStream = FileOpenHelper.OpenWrite_NoReadLock(destinationPath, FileMode.Create); int bufferSize; diff --git a/ServerShared/FileServices/FileHasher.cs b/ServerShared/FileServices/FileHasher.cs index e05a6598b..7be3e5852 100644 --- a/ServerShared/FileServices/FileHasher.cs +++ b/ServerShared/FileServices/FileHasher.cs @@ -19,21 +19,21 @@ public static async Task Hash(string filePath) var fileInfo = new FileInfo(filePath); if (!fileInfo.Exists) return string.Empty; - - using(var stream = new BufferedStream(File.OpenRead(filePath), 1200000)) + + using (var stream = new BufferedStream(FileOpenHelper.OpenRead_NoLocks(filePath), 1200000)) { // The rest remains the same var sha = SHA256.Create(); byte[] checksum = await sha.ComputeHashAsync(stream); return BitConverter.ToString(checksum).Replace("-", string.Empty); } - + using var hasher = SHA256.Create(); byte[]? hash; if (fileInfo.Length > 100_000_000) { - using var stream = File.OpenRead(filePath); + using var stream = FileOpenHelper.OpenRead_NoLocks(filePath); const int chunkSize = 100_000_000; // 100MB chunks var buffer = new byte[chunkSize]; @@ -51,7 +51,7 @@ public static async Task Hash(string filePath) } else { - await using var stream = File.OpenRead(filePath); + await using var stream = FileOpenHelper.OpenRead_NoLocks(filePath); hash = await hasher.ComputeHashAsync(stream); } diff --git a/ServerShared/FileServices/FileUploader.cs b/ServerShared/FileServices/FileUploader.cs index e1317cba6..a66f6498a 100644 --- a/ServerShared/FileServices/FileUploader.cs +++ b/ServerShared/FileServices/FileUploader.cs @@ -87,7 +87,7 @@ public async Task<(bool Success, string Error)> UploadFile(string filePath, stri url += "?path=" + HttpUtility.UrlEncode(serverPath); logger.ILog("Uploading file to URL: " + url); - await using var fileStream = File.OpenRead(filePath); + await using var fileStream = FileOpenHelper.OpenRead_NoLocks(filePath); var content = new StreamContent(fileStream); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url); request.Headers.Add("x-executor", executorUid.ToString()); @@ -116,7 +116,7 @@ public async Task<(bool Success, string Error)> UploadFile(string filePath, stri /// The computed SHA256 hash of the file as a byte array. private static async Task CalculateFileHash(string filePath) { - await using var stream = File.OpenRead(filePath); + await using var stream = FileOpenHelper.OpenRead_NoLocks(filePath); return await SHA256.Create().ComputeHashAsync(stream); } diff --git a/ServerShared/Helpers/FileHelper.cs b/ServerShared/Helpers/FileHelper.cs index f42242b25..d5ea79c4d 100644 --- a/ServerShared/Helpers/FileHelper.cs +++ b/ServerShared/Helpers/FileHelper.cs @@ -7,7 +7,6 @@ namespace FileFlows.ServerShared.Helpers; /// public class FileHelper { - /// /// Removes illegal file/path characters from a string /// @@ -46,7 +45,7 @@ public static string CalculateFingerprint(string file) } else { - using var stream = File.OpenRead(file); + using var stream = FileOpenHelper.OpenRead_NoLocks(file); hash = hasher.ComputeHash(stream); } diff --git a/Shared/Helpers/FileOpenHelper.cs b/Shared/Helpers/FileOpenHelper.cs new file mode 100644 index 000000000..463339013 --- /dev/null +++ b/Shared/Helpers/FileOpenHelper.cs @@ -0,0 +1,34 @@ +using System.IO; + +/// +/// Helper class for file open operations +/// +public static class FileOpenHelper +{ + /// + /// Opens a file for reading without a read/write lock + /// This allows other applications to read/write to the file while it is being read + /// + /// the file to open + /// the file stream + public static FileStream OpenRead_NoLocks(string file) + => File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + /// + /// Opens a file for reading and writing without a read/write lock + /// This allows other applications to keep reading/writing the file while it's being opened + /// This is useful to check if CanRead/CanWrite is true on a file + /// Actually writing to the file with this stream might have undesired effects + /// + /// the file to open + /// the file stream + public static FileStream OpenForCheckingReadWriteAccess(string file) + => File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + + /// + /// Opens a file for writing without a write lock + /// This allows other applications to read the file while it is being written to + /// + public static FileStream OpenWrite_NoReadLock(string file, FileMode fileMode) + => File.Open(file, fileMode, FileAccess.Write, FileShare.Read); +} diff --git a/Shared/Helpers/HttpHelper.cs b/Shared/Helpers/HttpHelper.cs index 85df7db99..d078329a8 100644 --- a/Shared/Helpers/HttpHelper.cs +++ b/Shared/Helpers/HttpHelper.cs @@ -143,7 +143,7 @@ public static async Task DownloadFile(string url, string destination) using HttpResponseMessage response = await Client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); await using var streamToReadFrom = await response.Content.ReadAsStreamAsync(); - await using var streamToWriteTo = File.Open(destination, FileMode.Create); + await using var streamToWriteTo = FileOpenHelper.OpenWrite_NoReadLock(destination, FileMode.Create); await streamToReadFrom.CopyToAsync(streamToWriteTo); } catch (Exception ex)