using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using TweetLib.Utils.Static; namespace TweetLib.Core.Systems.Startup { public sealed class LockFile { private static int CurrentProcessID { get { using Process me = Process.GetCurrentProcess(); return me.Id; } } private readonly string path; private FileStream? stream; public LockFile(string path) { this.path = path; } private void CreateLockFileStream() { stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); stream.Write(BitConverter.GetBytes(CurrentProcessID), 0, sizeof(int)); stream.Flush(true); } private bool ReleaseLockFileStream() { if (stream != null) { stream.Dispose(); stream = null; return true; } else { return false; } } public LockResult Lock() { if (stream != null) { return LockResult.Success; } try { CreateLockFileStream(); return LockResult.Success; } catch (DirectoryNotFoundException) { try { FileUtils.CreateDirectoryForFile(path); CreateLockFileStream(); return LockResult.Success; } catch (Exception e) { ReleaseLockFileStream(); return new LockResult.Fail(e); } } catch (IOException e) { return DetermineLockingProcessOrFail(e); } catch (Exception e) { ReleaseLockFileStream(); return new LockResult.Fail(e); } } public LockResult LockWait(int timeout, int retryDelay) { for (int elapsed = 0; elapsed < timeout; elapsed += retryDelay) { var result = Lock(); if (result is LockResult.HasProcess info) { info.Dispose(); Thread.Sleep(retryDelay); } else { return result; } } return Lock(); } public bool Unlock() { if (ReleaseLockFileStream()) { try { File.Delete(path); } catch (Exception e) { App.Logger.Error(e.ToString()); return false; } } return true; } [SuppressMessage("ReSharper", "PossibleNullReferenceException")] private LockResult DetermineLockingProcessOrFail(Exception originalException) { try { int pid; using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { byte[] bytes = new byte[sizeof(int)]; if (fileStream.Read(bytes, 0, bytes.Length) != bytes.Length) { throw new IOException("Read fewer bytes than requested!"); } pid = BitConverter.ToInt32(bytes, 0); } try { var foundProcess = Process.GetProcessById(pid); using var currentProcess = Process.GetCurrentProcess(); if (currentProcess.MainModule.FileVersionInfo.InternalName == foundProcess.MainModule.FileVersionInfo.InternalName) { return new LockResult.HasProcess(foundProcess); } else { foundProcess.Close(); } } catch { // GetProcessById throws ArgumentException if the process is missing // Process.MainModule can throw exceptions in some cases } return new LockResult.Fail(originalException); } catch (Exception e) { return new LockResult.Fail(e); } } } }