diff --git a/PowerKit.Tests/LockFileTests.cs b/PowerKit.Tests/LockFileTests.cs new file mode 100644 index 0000000..7c1dc5b --- /dev/null +++ b/PowerKit.Tests/LockFileTests.cs @@ -0,0 +1,52 @@ +using System.IO; +using FluentAssertions; +using PowerKit; +using Xunit; + +namespace PowerKit.Tests; + +public class LockFileTests +{ + [Fact] + public void TryAcquire_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + + // Act + using var lockFile = LockFile.TryAcquire(tempFile.Path); + + // Assert + lockFile.Should().NotBeNull(); + File.Exists(tempFile.Path).Should().BeTrue(); + } + + [Fact] + public void TryAcquire_AlreadyLocked_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + using var lockFile = LockFile.TryAcquire(tempFile.Path); + + // Act + using var lockFile2 = LockFile.TryAcquire(tempFile.Path); + + // Assert + lockFile2.Should().BeNull(); + } + + [Fact] + public void Dispose_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + var lockFile = LockFile.TryAcquire(tempFile.Path); + + // Act + lockFile!.Dispose(); + + // Assert: lock can be reacquired after disposal + using var lockFile2 = LockFile.TryAcquire(tempFile.Path); + lockFile2.Should().NotBeNull(); + } +} diff --git a/PowerKit/LockFile.cs b/PowerKit/LockFile.cs new file mode 100644 index 0000000..ced66ff --- /dev/null +++ b/PowerKit/LockFile.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; + +namespace PowerKit; + +/// +/// Represents a file-based lock that prevents concurrent access to a shared resource. +/// +internal partial class LockFile(FileStream fileStream) : IDisposable +{ + /// + public void Dispose() => fileStream.Dispose(); +} + +internal partial class LockFile +{ + /// + /// Tries to acquire a lock on the specified file path. + /// Returns if the lock could not be acquired. + /// + public static LockFile? TryAcquire(string filePath) + { + try + { + var fileStream = File.Open( + filePath, + FileMode.OpenOrCreate, + FileAccess.ReadWrite, + FileShare.None + ); + + return new LockFile(fileStream); + } + // This is the most specific exception for "access denied" + catch (IOException) + { + return null; + } + } +}