diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index 2599d280b724486bbd79849faddbc33bd529da97..1e0772ffa11ff293ca4e6c523e210e11f02fba98 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -772,6 +772,7 @@ <Compile Include="General\CRC.cs" /> <Compile Include="General\ErrorItem.cs" /> <Compile Include="General\ErrorLogger.cs" /> + <Compile Include="General\FileLockChecker.cs" /> <Compile Include="General\MurmurHash2.cs" /> <Compile Include="General\SavePurpose.cs" /> <Compile Include="General\UpdateChecker.cs" /> diff --git a/Source/Core/General/FileLockChecker.cs b/Source/Core/General/FileLockChecker.cs new file mode 100644 index 0000000000000000000000000000000000000000..34d976eebf5ec550bb9bb23f751b76d2efee7132 --- /dev/null +++ b/Source/Core/General/FileLockChecker.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace CodeImp.DoomBuilder +{ + internal static class FileLockChecker + { + internal class FileLockCheckResult //mxd + { + public string Error; + public List<string> ProcessInfos = new List<string>(); + } + + [StructLayout(LayoutKind.Sequential)] + private struct RM_UNIQUE_PROCESS + { + public int dwProcessId; + public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; + } + + private const int RmRebootReasonNone = 0; + private const int CCH_RM_MAX_APP_NAME = 255; + private const int CCH_RM_MAX_SVC_NAME = 63; + + private enum RM_APP_TYPE + { + RmUnknownApp = 0, + RmMainWindow = 1, + RmOtherWindow = 2, + RmService = 3, + RmExplorer = 4, + RmConsole = 5, + RmCritical = 1000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private 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; + } + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] + private 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)] + private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); + + [DllImport("rstrtmgr.dll")] + private static extern int RmEndSession(uint pSessionHandle); + + [DllImport("rstrtmgr.dll")] + private static extern int RmGetList(uint dwSessionHandle, + out uint pnProcInfoNeeded, + ref uint pnProcInfo, + [In, Out] RM_PROCESS_INFO[] rgAffectedApps, + ref uint lpdwRebootReasons); + + /// <summary> + /// Find out what process(es) have a lock on the specified file. + /// </summary> + /// <param name="path">Path of the file.</param> + /// <returns>Processes locking the file</returns> + /// <remarks>See also: + /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx + /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing) + /// + /// </remarks> + static public FileLockCheckResult CheckFile(string path) + { + uint handle; + string key = Guid.NewGuid().ToString(); + FileLockCheckResult result = new FileLockCheckResult(); //mxd + + int res = RmStartSession(out handle, 0, key); + if(res != 0) + { + result.Error = "Could not begin restart session. Unable to determine file locker."; //mxd + return result; + } + + try + { + const int ERROR_MORE_DATA = 234; + uint pnProcInfoNeeded, + pnProcInfo = 0, + lpdwRebootReasons = RmRebootReasonNone; + + string[] resources = new[] { path }; // Just checking on one resource. + res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); + if(res != 0) + { + result.Error = "Could not register resource."; //mxd + return result; + } + + //Note: there's a race condition here -- the first call to RmGetList() returns + // the total number of process. However, when we call RmGetList() again to get + // the actual processes this number may have increased. + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); + if(res == ERROR_MORE_DATA) + { + // Create an array to store the process results + 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) + { + List<Process> processes = new List<Process>((int)pnProcInfo); + + // Enumerate all of the results and add them to the + // list to be returned + for(int i = 0; i < pnProcInfo; i++) + { + try + { + processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); + } + // catch the error -- in case the process is no longer running + catch(ArgumentException) {} + } + + //mxd + foreach(Process process in processes) + { + if(General.ThisAssembly.Location == process.MainModule.FileName) continue; //don't count ourselves + result.ProcessInfos.Add(Path.GetFileName(process.MainModule.FileName) + + " ('" + process.MainModule.FileName + + "', started at " + process.StartTime + ")"); + } + } + else + { + result.Error = "ERROR " + res + ". Could not list processes locking resource."; //mxd + return result; + } + } + else if(res != 0) + { + result.Error = "ERROR " + res + ". Could not list processes locking resource. Failed to get size of result."; //mxd + return result; + } + } + finally + { + RmEndSession(handle); + } + + return result; + } + } +} diff --git a/Source/Core/General/General.cs b/Source/Core/General/General.cs index 36d2eadffd5993829b408c2b56a20395e45346e6..403c79cfb64ffe8c84ec9b9b9a81e392808a4584 100644 --- a/Source/Core/General/General.cs +++ b/Source/Core/General/General.cs @@ -729,21 +729,7 @@ namespace CodeImp.DoomBuilder } //mxd. Check for updates? - if(General.Settings.CheckForUpdates) - { - if(!File.Exists(Path.Combine(apppath, "Updater.exe"))) - { - General.ErrorLogger.Add(ErrorType.Warning, "Update check failed: Updater.exe does not exist!"); - } - else if(!File.Exists(Path.Combine(apppath, "Updater.ini"))) - { - General.ErrorLogger.Add(ErrorType.Warning, "Update check failed: Updater.ini does not exist!"); - } - else - { - UpdateChecker.PerformCheck(false); - } - } + if(General.Settings.CheckForUpdates) UpdateChecker.PerformCheck(false); // Run application from the main window Application.Run(mainwindow); @@ -1076,13 +1062,6 @@ namespace CodeImp.DoomBuilder [BeginAction("newmap")] internal static void NewMap() { - //mxd - if(map != null && map.Launcher.GameEngineRunning) - { - ShowWarningMessage("Cannot create a map while game engine is running" + Environment.NewLine + "Please close '" + map.ConfigSettings.TestProgram + "' first.", MessageBoxButtons.OK); - return; - } - MapOptions newoptions = new MapOptions(); // Cancel volatile mode, if any @@ -1160,13 +1139,6 @@ namespace CodeImp.DoomBuilder internal static void ActionCloseMap() { CloseMap(); } internal static bool CloseMap() { - //mxd - if(map != null && map.Launcher.GameEngineRunning) - { - ShowWarningMessage("Cannot close the map while game engine is running" + Environment.NewLine + "Please close '" + map.ConfigSettings.TestProgram + "' first.", MessageBoxButtons.OK); - return false; - } - // Cancel volatile mode, if any editing.DisengageVolatileMode(); @@ -1220,12 +1192,6 @@ namespace CodeImp.DoomBuilder [BeginAction("openmap")] internal static void OpenMap() { - if(map != null && map.Launcher.GameEngineRunning) //mxd - { - ShowWarningMessage("Cannot open a map while game engine is running" + Environment.NewLine + "Please close '" + map.ConfigSettings.TestProgram + "' first.", MessageBoxButtons.OK); - return; - } - // Cancel volatile mode, if any editing.DisengageVolatileMode(); @@ -1264,13 +1230,6 @@ namespace CodeImp.DoomBuilder return; } - //mxd - if(map.Launcher.GameEngineRunning) - { - ShowWarningMessage("Cannot change the map while game engine is running" + Environment.NewLine + "Please close '" + map.ConfigSettings.TestProgram + "' first.", MessageBoxButtons.OK); - return; - } - // Cancel volatile mode, if any Editing.DisengageVolatileMode(); @@ -1457,14 +1416,6 @@ namespace CodeImp.DoomBuilder internal static bool SaveMap() { if(map == null) return false; - - //mxd - if (map.Launcher.GameEngineRunning) - { - ShowWarningMessage("Cannot save the map while game engine is running" + Environment.NewLine + "Please close '" + map.ConfigSettings.TestProgram + "' first.", MessageBoxButtons.OK); - return false; - } - bool result = false; // Cancel volatile mode, if any @@ -1535,14 +1486,6 @@ namespace CodeImp.DoomBuilder internal static bool SaveMapAs() { if(map == null) return false; - - //mxd - if(map.Launcher.GameEngineRunning) - { - ShowWarningMessage("Cannot save the map while a game engine is running" + Environment.NewLine + "Please close '" + map.ConfigSettings.TestProgram + "' first.", MessageBoxButtons.OK); - return false; - } - bool result = false; // Cancel volatile mode, if any @@ -1615,14 +1558,6 @@ namespace CodeImp.DoomBuilder internal static bool SaveMapInto() { if(map == null) return false; - - //mxd - if(map.Launcher.GameEngineRunning) - { - ShowWarningMessage("Cannot save the map while game engine is running" + Environment.NewLine + "Please close '" + map.ConfigSettings.TestProgram + "' first.", MessageBoxButtons.OK); - return false; - } - bool result = false; // Cancel volatile mode, if any diff --git a/Source/Core/General/Launcher.cs b/Source/Core/General/Launcher.cs index 23cf3d7cfb77cff7b5b51cc23cbff24142046a93..4f761e29315fe603161e87b9b3a7be5309226bdd 100644 --- a/Source/Core/General/Launcher.cs +++ b/Source/Core/General/Launcher.cs @@ -49,7 +49,6 @@ namespace CodeImp.DoomBuilder #region ================== Properties public string TempWAD { get { return tempwad; } } - public bool GameEngineRunning { get { return process != null; } } //mxd #endregion @@ -251,16 +250,9 @@ namespace CodeImp.DoomBuilder General.Editing.Mode.OnMapTestEnd(true); } - // This saves the map to a temporary file and launches a test wit hthe given skill + // This saves the map to a temporary file and launches a test with the given skill public void TestAtSkill(int skill) { - //mxd - if (process != null) - { - General.ShowWarningMessage("Game engine is already running." + Environment.NewLine + " Please close '" + General.Map.ConfigSettings.TestProgram + "' before testing again", MessageBoxButtons.OK); - return; - } - Cursor oldcursor = Cursor.Current; // Check if configuration is OK @@ -362,21 +354,6 @@ namespace CodeImp.DoomBuilder if(General.Editing.Mode is ClassicMode) General.MainWindow.RedrawDisplay(); } - //mxd - public void StopGameEngine() - { - //mxd. Terminate process? - if(process != null) - { - process.CloseMainWindow(); - process.Close(); - process = null; - - // Remove temporary file - try { if(File.Exists(tempwad)) File.Delete(tempwad); } catch(Exception) { } - } - } - //mxd private void ProcessOnExited(object sender, EventArgs eventArgs) { diff --git a/Source/Core/General/MapManager.cs b/Source/Core/General/MapManager.cs index b2a447e87cc4f83a5cce0a2f6a82bb5ed14f9559..b4763d01c1554af621b26e9b4b6c8cdac9f44a3c 100644 --- a/Source/Core/General/MapManager.cs +++ b/Source/Core/General/MapManager.cs @@ -577,13 +577,29 @@ namespace CodeImp.DoomBuilder // Initializes for an existing map internal bool SaveMap(string newfilepathname, SavePurpose purpose) { - MapSet outputset; - string nodebuildername, settingsfile; - StatusInfo oldstatus; + //mxd. Check if the target file is locked + FileLockChecker.FileLockCheckResult checkresult = FileLockChecker.CheckFile(newfilepathname); + if(!string.IsNullOrEmpty(checkresult.Error)) + { + General.ShowErrorMessage("Unable to save the map: target file is locked by another process. " + + Environment.NewLine + "Also, unable to get the name of the offending process:" + + Environment.NewLine + Environment.NewLine + checkresult.Error + , MessageBoxButtons.OK); + return false; + } + if(checkresult.ProcessInfos.Count > 0) + { + General.ShowErrorMessage("Unable to save the map: target file is locked by the following process" + (checkresult.ProcessInfos.Count > 1 ? "es" : "") + ":" + + Environment.NewLine + Environment.NewLine + + string.Join(Environment.NewLine + Environment.NewLine, checkresult.ProcessInfos.ToArray()) + , MessageBoxButtons.OK); + return false; + } + + string settingsfile; WAD targetwad; int index; bool includenodes; - string origmapname; General.WriteLogLine("Saving map to file: " + newfilepathname); @@ -614,7 +630,7 @@ namespace CodeImp.DoomBuilder if (changed) { // Make a copy of the map data - outputset = map.Clone(); + MapSet outputset = map.Clone(); // Remove all flags from all 3D Start things foreach (Thing t in outputset.Things) @@ -629,6 +645,7 @@ namespace CodeImp.DoomBuilder } // Do we need sidedefs compression? + StatusInfo oldstatus; if (map.Sidedefs.Count > io.MaxSidedefs) { // Compress sidedefs @@ -684,15 +701,12 @@ namespace CodeImp.DoomBuilder outputset.Dispose(); // Get the corresponding nodebuilder - nodebuildername = (purpose == SavePurpose.Testing) ? configinfo.NodebuilderTest : configinfo.NodebuilderSave; + string nodebuildername = (purpose == SavePurpose.Testing) ? configinfo.NodebuilderTest : configinfo.NodebuilderSave; // Build the nodes oldstatus = General.MainWindow.Status; General.MainWindow.DisplayStatus(StatusType.Busy, "Building map nodes..."); - if (!string.IsNullOrEmpty(nodebuildername)) - includenodes = BuildNodes(nodebuildername, true); - else - includenodes = false; + includenodes = (!string.IsNullOrEmpty(nodebuildername) && BuildNodes(nodebuildername, true)); General.MainWindow.DisplayStatus(oldstatus); } else @@ -705,7 +719,7 @@ namespace CodeImp.DoomBuilder data.Suspend(); // Determine original map name - origmapname = (options.PreviousName != "" && purpose != SavePurpose.IntoFile) ? options.PreviousName : options.CurrentName; + string origmapname = (options.PreviousName != "" && purpose != SavePurpose.IntoFile) ? options.PreviousName : options.CurrentName; string origwadfile = string.Empty; //mxd try diff --git a/Source/Core/General/UpdateChecker.cs b/Source/Core/General/UpdateChecker.cs index 3ac9e5a06c3bc13632f3e65d18bb9d2841bd26fd..c2876ae923c8c70165eaf01c87fa2c33af093bd0 100644 --- a/Source/Core/General/UpdateChecker.cs +++ b/Source/Core/General/UpdateChecker.cs @@ -16,6 +16,7 @@ namespace CodeImp.DoomBuilder internal static void PerformCheck(bool verbosemode) { + // Update check already runing? if(worker != null && worker.IsBusy) { if(verbosemode) @@ -30,6 +31,7 @@ namespace CodeImp.DoomBuilder return; } + // Start checking verbose = verbosemode; worker = new BackgroundWorker(); worker.DoWork += DoWork; @@ -40,7 +42,23 @@ namespace CodeImp.DoomBuilder private static void DoWork(object sender, DoWorkEventArgs e) { - string url = GetDownloadUrl(Path.Combine(General.AppPath, "Updater.ini")); + string updaterpath = Path.Combine(General.AppPath, "Updater.exe"); + if(!File.Exists(updaterpath)) + { + errordesc = "Update check failed: '" + updaterpath + "' does not exist!"; + e.Cancel = true; + return; + } + + string inipath = Path.Combine(General.AppPath, "Updater.ini"); + if(!File.Exists(inipath)) + { + errordesc = "Update check failed: '" + inipath + "' does not exist!"; + e.Cancel = true; + return; + } + + string url = GetDownloadUrl(inipath); if(string.IsNullOrEmpty(url)) { errordesc = "Update check failed: failed to get update url from Updater.ini!"; @@ -50,6 +68,7 @@ namespace CodeImp.DoomBuilder // Get local revision number int localrev = General.ThisAssembly.GetName().Version.Revision; + int actuallocalrev = localrev; if(!verbose) localrev = Math.Max(localrev, General.Settings.IgnoredRemoteRevision); // Get remote revision number @@ -80,7 +99,7 @@ namespace CodeImp.DoomBuilder if(remoterev > localrev) { // Get changelog info - string changelog = GetChangelog(url, localrev); + string changelog = GetChangelog(url, actuallocalrev); if(string.IsNullOrEmpty(changelog)) { diff --git a/Source/Core/Windows/MainForm.cs b/Source/Core/Windows/MainForm.cs index 9e756cf0d53b00fd95e50a56afe28460c245dc36..3facf31424664c2266ec585eac6d4e7fea18fd4e 100644 --- a/Source/Core/Windows/MainForm.cs +++ b/Source/Core/Windows/MainForm.cs @@ -650,10 +650,7 @@ namespace CodeImp.DoomBuilder.Windows protected override void OnFormClosing(FormClosingEventArgs e) { base.OnFormClosing(e); - if(e.CloseReason == CloseReason.ApplicationExitCall) return; - if(General.Map != null && General.Map.Launcher.GameEngineRunning) - General.Map.Launcher.StopGameEngine(); //mxd // Close the map if(General.CloseMap())