mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-03 14:34:08 +02:00
Refactor locking mechanism & improve error reporting for failed locks
This commit is contained in:
parent
651d9be57c
commit
ae1c59847f
@ -1,58 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core.Application;
|
|
||||||
|
|
||||||
namespace TweetDuck.Application{
|
|
||||||
class LockHandler : IAppLockHandler{
|
|
||||||
private const int WaitRetryDelay = 250;
|
|
||||||
private const int RestoreFailTimeout = 2000;
|
|
||||||
private const int CloseNaturallyTimeout = 10000;
|
|
||||||
private const int CloseKillTimeout = 5000;
|
|
||||||
|
|
||||||
bool IAppLockHandler.RestoreProcess(Process process){
|
|
||||||
if (process.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
|
||||||
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)process.Id, 0);
|
|
||||||
|
|
||||||
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IAppLockHandler.CloseProcess(Process process){
|
|
||||||
try{
|
|
||||||
if (process.CloseMainWindow()){
|
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseNaturallyTimeout, WaitRetryDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!process.HasExited){
|
|
||||||
process.Kill();
|
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseKillTimeout, WaitRetryDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.HasExited){
|
|
||||||
process.Dispose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
|
||||||
bool hasExited = CheckProcessExited(process);
|
|
||||||
process.Dispose();
|
|
||||||
return hasExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool CheckProcessExited(Process process){
|
|
||||||
process.Refresh();
|
|
||||||
return process.HasExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
129
Management/LockManager.cs
Normal file
129
Management/LockManager.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using TweetDuck.Dialogs;
|
||||||
|
using TweetDuck.Utils;
|
||||||
|
using TweetLib.Core.Systems.Startup;
|
||||||
|
|
||||||
|
namespace TweetDuck.Management{
|
||||||
|
sealed class LockManager{
|
||||||
|
private const int WaitRetryDelay = 250;
|
||||||
|
private const int RestoreFailTimeout = 2000;
|
||||||
|
private const int CloseNaturallyTimeout = 10000;
|
||||||
|
private const int CloseKillTimeout = 5000;
|
||||||
|
|
||||||
|
private readonly LockFile lockFile;
|
||||||
|
|
||||||
|
public LockManager(string path){
|
||||||
|
this.lockFile = new LockFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Lock(bool wasRestarted){
|
||||||
|
return wasRestarted ? LaunchAfterRestart() : LaunchNormally();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Unlock(){
|
||||||
|
return lockFile.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking
|
||||||
|
|
||||||
|
private bool LaunchNormally(){
|
||||||
|
LockResult lockResult = lockFile.Lock();
|
||||||
|
|
||||||
|
if (lockResult is LockResult.HasProcess info){
|
||||||
|
if (!RestoreProcess(info.Process) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
if (!CloseProcess(info.Process)){
|
||||||
|
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Dispose();
|
||||||
|
lockResult = lockFile.Lock();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lockResult is LockResult.Fail fail){
|
||||||
|
ShowGenericException(fail);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (lockResult != LockResult.Success){
|
||||||
|
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool LaunchAfterRestart(){
|
||||||
|
LockResult lockResult = lockFile.LockWait(10000, WaitRetryDelay);
|
||||||
|
|
||||||
|
while(lockResult != LockResult.Success){
|
||||||
|
if (lockResult is LockResult.Fail fail){
|
||||||
|
ShowGenericException(fail);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (!FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lockResult = lockFile.LockWait(5000, WaitRetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
private static void ShowGenericException(LockResult.Fail fail){
|
||||||
|
Program.Reporter.HandleException("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", false, fail.Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RestoreProcess(Process process){
|
||||||
|
if (process.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||||
|
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)process.Id, 0);
|
||||||
|
|
||||||
|
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CloseProcess(Process process){
|
||||||
|
try{
|
||||||
|
if (process.CloseMainWindow()){
|
||||||
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
|
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseNaturallyTimeout, WaitRetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.HasExited){
|
||||||
|
process.Kill();
|
||||||
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
|
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseKillTimeout, WaitRetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.HasExited){
|
||||||
|
process.Dispose();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
||||||
|
bool hasExited = CheckProcessExited(process);
|
||||||
|
process.Dispose();
|
||||||
|
return hasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CheckProcessExited(Process process){
|
||||||
|
process.Refresh();
|
||||||
|
return process.HasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
Program.cs
40
Program.cs
@ -16,7 +16,6 @@
|
|||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
using TweetLib.Core.Collections;
|
using TweetLib.Core.Collections;
|
||||||
using TweetLib.Core.Systems.Startup;
|
|
||||||
using TweetLib.Core.Utils;
|
using TweetLib.Core.Utils;
|
||||||
using Win = System.Windows.Forms;
|
using Win = System.Windows.Forms;
|
||||||
|
|
||||||
@ -72,7 +71,6 @@ static Program(){
|
|||||||
|
|
||||||
Lib.Initialize(new App.Builder{
|
Lib.Initialize(new App.Builder{
|
||||||
ErrorHandler = Reporter,
|
ErrorHandler = Reporter,
|
||||||
LockHandler = new LockHandler(),
|
|
||||||
SystemHandler = new SystemHandler(),
|
SystemHandler = new SystemHandler(),
|
||||||
ResourceHandler = Resources
|
ResourceHandler = Resources
|
||||||
});
|
});
|
||||||
@ -95,42 +93,8 @@ private static void Main(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgRestart)){
|
if (!LockManager.Lock(Arguments.HasFlag(Arguments.ArgRestart))){
|
||||||
LockManager.Result lockResult = LockManager.LockWait(10000);
|
return;
|
||||||
|
|
||||||
while(lockResult != LockManager.Result.Success){
|
|
||||||
if (lockResult == LockManager.Result.Fail){
|
|
||||||
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (!FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lockResult = LockManager.LockWait(5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
LockManager.Result lockResult = LockManager.Lock();
|
|
||||||
|
|
||||||
if (lockResult == LockManager.Result.HasProcess){
|
|
||||||
if (!LockManager.RestoreLockingProcess() && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
|
|
||||||
if (!LockManager.CloseLockingProcess()){
|
|
||||||
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lockResult = LockManager.Lock();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lockResult != LockManager.Result.Success){
|
|
||||||
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.LoadAll();
|
Config.LoadAll();
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Application\LockHandler.cs" />
|
<Compile Include="Management\LockManager.cs" />
|
||||||
<Compile Include="Application\SystemHandler.cs" />
|
<Compile Include="Application\SystemHandler.cs" />
|
||||||
<Compile Include="Browser\Adapters\CefScriptExecutor.cs" />
|
<Compile Include="Browser\Adapters\CefScriptExecutor.cs" />
|
||||||
<Compile Include="Browser\Bridge\PropertyBridge.cs" />
|
<Compile Include="Browser\Bridge\PropertyBridge.cs" />
|
||||||
@ -436,4 +436,4 @@ IF EXIST "$(ProjectDir)bld\post_build.exe" (
|
|||||||
</Target>
|
</Target>
|
||||||
<Import Project="packages\CefSharp.Common.81.3.100\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.81.3.100\build\CefSharp.Common.targets')" />
|
<Import Project="packages\CefSharp.Common.81.3.100\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.81.3.100\build\CefSharp.Common.targets')" />
|
||||||
<Import Project="packages\CefSharp.WinForms.81.3.100\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.81.3.100\build\CefSharp.WinForms.targets')" />
|
<Import Project="packages\CefSharp.WinForms.81.3.100\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.81.3.100\build\CefSharp.WinForms.targets')" />
|
||||||
</Project>
|
</Project>
|
@ -5,7 +5,6 @@ namespace TweetLib.Core{
|
|||||||
public sealed class App{
|
public sealed class App{
|
||||||
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
|
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
|
||||||
public static IAppErrorHandler ErrorHandler { get; private set; }
|
public static IAppErrorHandler ErrorHandler { get; private set; }
|
||||||
public static IAppLockHandler LockHandler { get; private set; }
|
|
||||||
public static IAppSystemHandler SystemHandler { get; private set; }
|
public static IAppSystemHandler SystemHandler { get; private set; }
|
||||||
public static IAppResourceHandler ResourceHandler { get; private set; }
|
public static IAppResourceHandler ResourceHandler { get; private set; }
|
||||||
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
|
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
|
||||||
@ -14,7 +13,6 @@ public sealed class App{
|
|||||||
|
|
||||||
public sealed class Builder{
|
public sealed class Builder{
|
||||||
public IAppErrorHandler? ErrorHandler { get; set; }
|
public IAppErrorHandler? ErrorHandler { get; set; }
|
||||||
public IAppLockHandler? LockHandler { get; set; }
|
|
||||||
public IAppSystemHandler? SystemHandler { get; set; }
|
public IAppSystemHandler? SystemHandler { get; set; }
|
||||||
public IAppResourceHandler? ResourceHandler { get; set; }
|
public IAppResourceHandler? ResourceHandler { get; set; }
|
||||||
|
|
||||||
@ -22,7 +20,6 @@ public sealed class Builder{
|
|||||||
|
|
||||||
internal void Initialize(){
|
internal void Initialize(){
|
||||||
App.ErrorHandler = Validate(ErrorHandler, nameof(ErrorHandler))!;
|
App.ErrorHandler = Validate(ErrorHandler, nameof(ErrorHandler))!;
|
||||||
App.LockHandler = Validate(LockHandler, nameof(LockHandler))!;
|
|
||||||
App.SystemHandler = Validate(SystemHandler, nameof(SystemHandler))!;
|
App.SystemHandler = Validate(SystemHandler, nameof(SystemHandler))!;
|
||||||
App.ResourceHandler = Validate(ResourceHandler, nameof(ResourceHandler))!;
|
App.ResourceHandler = Validate(ResourceHandler, nameof(ResourceHandler))!;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace TweetLib.Core.Application{
|
|
||||||
public interface IAppLockHandler{
|
|
||||||
bool RestoreProcess(Process process);
|
|
||||||
bool CloseProcess(Process process);
|
|
||||||
}
|
|
||||||
}
|
|
125
lib/TweetLib.Core/Systems/Startup/LockFile.cs
Normal file
125
lib/TweetLib.Core/Systems/Startup/LockFile.cs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
|
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.ErrorHandler.Log(e.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)];
|
||||||
|
fileStream.Read(bytes, 0, bytes.Length);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,162 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace TweetLib.Core.Systems.Startup{
|
|
||||||
public sealed class LockManager{
|
|
||||||
private const int RetryDelay = 250;
|
|
||||||
|
|
||||||
public enum Result{
|
|
||||||
Success, HasProcess, Fail
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly string file;
|
|
||||||
private FileStream? lockStream;
|
|
||||||
private Process? lockingProcess;
|
|
||||||
|
|
||||||
public LockManager(string file){
|
|
||||||
this.file = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock file
|
|
||||||
|
|
||||||
private bool ReleaseLockFileStream(){
|
|
||||||
if (lockStream != null){
|
|
||||||
lockStream.Dispose();
|
|
||||||
lockStream = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Result TryCreateLockFile(){
|
|
||||||
void CreateLockFileStream(){
|
|
||||||
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
||||||
lockStream.Write(BitConverter.GetBytes(CurrentProcessID), 0, sizeof(int));
|
|
||||||
lockStream.Flush(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
CreateLockFileStream();
|
|
||||||
return Result.Success;
|
|
||||||
}catch(DirectoryNotFoundException){
|
|
||||||
try{
|
|
||||||
CreateLockFileStream();
|
|
||||||
return Result.Success;
|
|
||||||
}catch{
|
|
||||||
ReleaseLockFileStream();
|
|
||||||
return Result.Fail;
|
|
||||||
}
|
|
||||||
}catch(IOException){
|
|
||||||
return Result.HasProcess;
|
|
||||||
}catch{
|
|
||||||
ReleaseLockFileStream();
|
|
||||||
return Result.Fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock management
|
|
||||||
|
|
||||||
public Result Lock(){
|
|
||||||
if (lockStream != null){
|
|
||||||
return Result.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result initialResult = TryCreateLockFile();
|
|
||||||
|
|
||||||
if (initialResult == Result.HasProcess){
|
|
||||||
try{
|
|
||||||
int pid;
|
|
||||||
|
|
||||||
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
|
||||||
byte[] bytes = new byte[sizeof(int)];
|
|
||||||
fileStream.Read(bytes, 0, bytes.Length);
|
|
||||||
pid = BitConverter.ToInt32(bytes, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
Process foundProcess = Process.GetProcessById(pid);
|
|
||||||
|
|
||||||
if (MatchesCurrentProcess(foundProcess)){
|
|
||||||
lockingProcess = foundProcess;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
foundProcess.Close();
|
|
||||||
}
|
|
||||||
}catch{
|
|
||||||
// GetProcessById throws ArgumentException if the process is missing
|
|
||||||
// Process.MainModule can throw exceptions in some cases
|
|
||||||
}
|
|
||||||
|
|
||||||
return lockingProcess == null ? Result.Fail : Result.HasProcess;
|
|
||||||
}catch{
|
|
||||||
return Result.Fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return initialResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result LockWait(int timeout){
|
|
||||||
for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
|
|
||||||
Result result = Lock();
|
|
||||||
|
|
||||||
if (result == Result.HasProcess){
|
|
||||||
Thread.Sleep(RetryDelay);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Unlock(){
|
|
||||||
if (ReleaseLockFileStream()){
|
|
||||||
try{
|
|
||||||
File.Delete(file);
|
|
||||||
}catch(Exception e){
|
|
||||||
App.ErrorHandler.Log(e.ToString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locking process
|
|
||||||
|
|
||||||
public bool RestoreLockingProcess(){
|
|
||||||
return lockingProcess != null && App.LockHandler.RestoreProcess(lockingProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CloseLockingProcess(){
|
|
||||||
if (lockingProcess != null && App.LockHandler.CloseProcess(lockingProcess)){
|
|
||||||
lockingProcess = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utilities
|
|
||||||
|
|
||||||
private static int CurrentProcessID{
|
|
||||||
get{
|
|
||||||
using Process me = Process.GetCurrentProcess();
|
|
||||||
return me.Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
|
|
||||||
private static bool MatchesCurrentProcess(Process process){
|
|
||||||
using Process current = Process.GetCurrentProcess();
|
|
||||||
return current.MainModule.FileVersionInfo.InternalName == process.MainModule.FileVersionInfo.InternalName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
38
lib/TweetLib.Core/Systems/Startup/LockResult.cs
Normal file
38
lib/TweetLib.Core/Systems/Startup/LockResult.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace TweetLib.Core.Systems.Startup{
|
||||||
|
public class LockResult{
|
||||||
|
private readonly string name;
|
||||||
|
|
||||||
|
private LockResult(string name){
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString(){
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LockResult Success { get; } = new LockResult("Success");
|
||||||
|
|
||||||
|
public sealed class Fail : LockResult{
|
||||||
|
public Exception Exception { get; }
|
||||||
|
|
||||||
|
public Fail(Exception exception) : base("Fail"){
|
||||||
|
this.Exception = exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class HasProcess : LockResult, IDisposable{
|
||||||
|
public Process Process { get; }
|
||||||
|
|
||||||
|
public HasProcess(Process process) : base("HasProcess"){
|
||||||
|
this.Process = process;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
Process.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user