diff --git a/src/FileLockFinder/FileLockFinder.csproj b/src/FileLockFinder/FileLockFinder.csproj
new file mode 100644
index 00000000..907b8abc
--- /dev/null
+++ b/src/FileLockFinder/FileLockFinder.csproj
@@ -0,0 +1,26 @@
+
+
+ net8.0
+ FileLockFinder
+ FileLockFinder
+ FileLockFinder
+ Library
+ true
+ ..\Solution Items\Key.snk
+ $(SolutionDir)..\bin\$(Configuration)\plugins
+ $(DefineConstants)
+
+
+
+ False
+
+
+ True
+
+
+
+
+ Key.snk
+
+
+
diff --git a/src/FileLockFinder/LockFinder.cs b/src/FileLockFinder/LockFinder.cs
new file mode 100644
index 00000000..b6f09767
--- /dev/null
+++ b/src/FileLockFinder/LockFinder.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+// Expanded with some helpers from: https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4/
+// Uses Windows Restart Manager.
+// A more involved and cross platform solution to this problem is here: https://github.com/cklutz/LockCheck
+
+
+namespace FileLockFinder
+{
+ public class LockFinder
+ {
+
+ ///
+ /// Method FindLockedProcessName Retrieve the first process name
+ /// that is locking the file at the specified path
+ ///
+ /// The path of a file with a write lock held by a
+ /// process
+ /// The name of the first process found with a lock
+ ///
+ /// Thrown when the file path is not locked
+ ///
+ static public string FindLockedProcessName(string path)
+ {
+ var list = FindLockProcesses(path);
+ if (list.Count == 0)
+ {
+ throw new Exception(
+ "No processes are locking the path specified");
+ }
+ return list[0].ProcessName;
+ }
+
+ ///
+ /// Method CheckIfFileIsLocked Check if the file specified has a
+ /// write lock held by a process
+ ///
+ /// The path of a file being checked if a write lock
+ /// held by a process
+ /// true when one or more processes with lock
+ static public bool CheckIfFileIsLocked(string path)
+ {
+ var list = FindLockProcesses(path);
+ if (list.Count > 0) { return true; }
+ return false;
+ }
+
+ ///
+ /// Used to find processes holding a lock on the file. This would cause
+ /// other usage, such as file truncation or write opretions to throw
+ /// IOException if an exclusive lock is attempted.
+ ///
+ /// Path being checked
+ /// List of processes holding file lock to path
+ ///
+ static public List FindLockProcesses(string path)
+ {
+ var key = Guid.NewGuid().ToString();
+ var processes = new List();
+
+ int res = RmStartSession(out uint handle, 0, key);
+ if (res != 0)
+ {
+ throw new Exception("Could not begin restart session. " +
+ "Unable to determine file locker.");
+ }
+
+ try
+ {
+ uint pnProcInfo = 0;
+ uint lpdwRebootReasons = RmRebootReasonNone;
+ string[] resources = [path];
+
+ res = RmRegisterResources(handle, (uint)resources.Length,
+ resources, 0, null, 0, null);
+ if (res != 0)
+ {
+ throw new Exception("Could not register resource.");
+ }
+ res = RmGetList(handle, out uint pnProcInfoNeeded, ref pnProcInfo, null,
+ ref lpdwRebootReasons);
+ const int ERROR_MORE_DATA = 234;
+ if (res == ERROR_MORE_DATA)
+ {
+ RM_PROCESS_INFO[] processInfo =
+ new RM_PROCESS_INFO[pnProcInfoNeeded];
+ pnProcInfo = pnProcInfoNeeded;
+ // Get the list.
+ res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
+ if (res == 0)
+ {
+ processes = new List((int)pnProcInfo);
+ for (int i = 0; i < pnProcInfo; i++)
+ {
+ try
+ {
+ processes.Add(Process.GetProcessById(processInfo[i].
+ Process.dwProcessId));
+ }
+ catch (ArgumentException) { }
+ }
+ }
+ else
+ {
+ throw new Exception("Could not list processes locking resource");
+ }
+ }
+ else if (res != 0)
+ {
+ throw new Exception("Could not list processes locking resource." +
+ "Failed to get size of result.");
+ }
+ }
+ catch (Exception exception)
+ {
+ Trace.WriteLine(exception.Message);
+ }
+ finally
+ {
+ Trace.WriteLine($"RmEndSession: {RmEndSession(handle)}");
+ }
+
+ return processes;
+ }
+ private const int RmRebootReasonNone = 0;
+ private const int CCH_RM_MAX_APP_NAME = 255;
+ private const int CCH_RM_MAX_SVC_NAME = 63;
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct RM_UNIQUE_PROCESS
+ {
+ public int dwProcessId;
+ public System.Runtime.InteropServices.
+ ComTypes.FILETIME ProcessStartTime;
+ }
+ [DllImport("rstrtmgr.dll",
+ CharSet = CharSet.Auto, SetLastError = true)]
+ static extern int RmGetList(uint dwSessionHandle,
+ out uint pnProcInfoNeeded,
+ ref uint pnProcInfo,
+ [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
+ ref uint lpdwRebootReasons);
+ [StructLayout(LayoutKind.Sequential,
+ CharSet = CharSet.Auto)]
+ struct RM_PROCESS_INFO
+ {
+ public RM_UNIQUE_PROCESS Process;
+ [MarshalAs(UnmanagedType.ByValTStr,
+ SizeConst = CCH_RM_MAX_APP_NAME + 1)]
+ public string strAppName;
+ [MarshalAs(UnmanagedType.ByValTStr,
+ SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
+ public string strServiceShortName;
+ public RM_APP_TYPE ApplicationType;
+ public uint AppStatus;
+ public uint TSSessionId;
+ [MarshalAs(UnmanagedType.Bool)]
+ public bool bRestartable;
+ }
+
+ enum RM_APP_TYPE
+ {
+ RmUnknownApp = 0,
+ RmMainWindow = 1,
+ RmOtherWindow = 2,
+ RmService = 3,
+ RmExplorer = 4,
+ RmConsole = 5,
+ RmCritical = 1000
+ }
+
+ [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern int RmRegisterResources(
+ uint pSessionHandle,
+ UInt32 nFiles,
+ string[] rgsFilenames,
+ UInt32 nApplications,
+ [In] RM_UNIQUE_PROCESS[] rgApplications,
+ UInt32 nServices, string[] rgsServiceNames);
+
+ [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern int RmStartSession(
+ out uint pSessionHandle,
+ int dwSessionFlags,
+ string strSessionKey);
+
+ [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern int RmEndSession(uint pSessionHandle);
+ }
+}
diff --git a/src/LogExpert/Controls/LogTabWindow/LogTabWindow.designer.cs b/src/LogExpert/Controls/LogTabWindow/LogTabWindow.designer.cs
index 8f879281..129c0aeb 100644
--- a/src/LogExpert/Controls/LogTabWindow/LogTabWindow.designer.cs
+++ b/src/LogExpert/Controls/LogTabWindow/LogTabWindow.designer.cs
@@ -6,25 +6,25 @@
namespace LogExpert.Controls.LogTabWindow
{
- partial class LogTabWindow
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
+ partial class LogTabWindow
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
#region Windows Form Designer generated code
@@ -155,6 +155,7 @@ private void InitializeComponent()
tabRenameToolStripMenuItem = new ToolStripMenuItem();
copyPathToClipboardToolStripMenuItem = new ToolStripMenuItem();
findInExplorerToolStripMenuItem = new ToolStripMenuItem();
+ truncateFileToolStripMenuItem = new ToolStripMenuItem();
dragControlDateTime = new DateTimeDragControl();
statusStrip.SuspendLayout();
mainMenuStrip.SuspendLayout();
@@ -1053,7 +1054,7 @@ private void InitializeComponent()
//
tabContextMenuStrip.ForeColor = System.Drawing.SystemColors.ControlText;
tabContextMenuStrip.ImageScalingSize = new System.Drawing.Size(24, 24);
- tabContextMenuStrip.Items.AddRange(new ToolStripItem[] { closeThisTabToolStripMenuItem, closeOtherTabsToolStripMenuItem, closeAllTabsToolStripMenuItem, tabColorToolStripMenuItem, tabRenameToolStripMenuItem, copyPathToClipboardToolStripMenuItem, findInExplorerToolStripMenuItem });
+ tabContextMenuStrip.Items.AddRange(new ToolStripItem[] { closeThisTabToolStripMenuItem, closeOtherTabsToolStripMenuItem, closeAllTabsToolStripMenuItem, tabColorToolStripMenuItem, tabRenameToolStripMenuItem, copyPathToClipboardToolStripMenuItem, findInExplorerToolStripMenuItem, truncateFileToolStripMenuItem });
tabContextMenuStrip.Name = "tabContextMenuStrip";
tabContextMenuStrip.Size = new System.Drawing.Size(197, 158);
//
@@ -1114,6 +1115,14 @@ private void InitializeComponent()
findInExplorerToolStripMenuItem.ToolTipText = "Opens an Explorer window and selects the log file";
findInExplorerToolStripMenuItem.Click += OnFindInExplorerToolStripMenuItemClick;
//
+ // truncateFileToolStripMenuItem
+ //
+ this.truncateFileToolStripMenuItem.Name = "truncateFileToolStripMenuItem";
+ this.truncateFileToolStripMenuItem.Size = new System.Drawing.Size(196, 22);
+ this.truncateFileToolStripMenuItem.Text = "Truncate File";
+ this.truncateFileToolStripMenuItem.ToolTipText = "Try to truncate the file opened in tab";
+ this.truncateFileToolStripMenuItem.Click += new System.EventHandler(this.truncateFileToolStripMenuItem_Click);
+ //
// dragControlDateTime
//
dragControlDateTime.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
@@ -1236,6 +1245,7 @@ private void InitializeComponent()
private ToolStripButton toolStripButtonBubbles;
private ToolStripMenuItem copyPathToClipboardToolStripMenuItem;
private ToolStripMenuItem findInExplorerToolStripMenuItem;
+ private ToolStripMenuItem truncateFileToolStripMenuItem;
private ToolStripMenuItem exportBookmarksToolStripMenuItem;
private ToolStripComboBox groupsComboBoxHighlightGroups;
private ToolStripMenuItem debugToolStripMenuItem;
diff --git a/src/LogExpert/Controls/LogTabWindow/LogTabWindowEventHandlers.cs b/src/LogExpert/Controls/LogTabWindow/LogTabWindowEventHandlers.cs
index 8debf55d..b25eb0f0 100644
--- a/src/LogExpert/Controls/LogTabWindow/LogTabWindowEventHandlers.cs
+++ b/src/LogExpert/Controls/LogTabWindow/LogTabWindowEventHandlers.cs
@@ -811,6 +811,11 @@ private void OnFindInExplorerToolStripMenuItemClick(object sender, EventArgs e)
explorer.Start();
}
+ private void truncateFileToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ CurrentLogWindow?.TryToTruncate();
+ }
+
private void OnExportBookmarksToolStripMenuItemClick(object sender, EventArgs e)
{
CurrentLogWindow?.ExportBookmarkList();
@@ -1018,4 +1023,4 @@ private void OnTabRenameToolStripMenuItemClick(object sender, EventArgs e)
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/src/LogExpert/Controls/LogWindow/LogWindowsPublic.cs b/src/LogExpert/Controls/LogWindow/LogWindowsPublic.cs
index d1298c77..e0564d3f 100644
--- a/src/LogExpert/Controls/LogWindow/LogWindowsPublic.cs
+++ b/src/LogExpert/Controls/LogWindow/LogWindowsPublic.cs
@@ -1,4 +1,5 @@
-using LogExpert.Classes;
+using FileLockFinder;
+using LogExpert.Classes;
using LogExpert.Classes.Bookmark;
using LogExpert.Classes.Columnizer;
using LogExpert.Classes.Filter;
@@ -579,6 +580,26 @@ public void GotoLine(int line)
}
}
+ public void TryToTruncate()
+ {
+ try
+ {
+ if (LockFinder.CheckIfFileIsLocked(Title))
+ {
+ var name = LockFinder.FindLockedProcessName(Title);
+ var status = string.Format("Truncate failed: file is locked by {0}", name);
+ StatusLineText(status);
+ return;
+ }
+
+ File.WriteAllText(Title, "");
+ }catch(Exception E)
+ {
+ StatusLineText("Unexpected issue truncating file");
+ throw E;
+ }
+ }
+
public void StartSearch()
{
_guiStateArgs.MenuEnabled = false;