diff --git a/src/SyncTrayzor/Pages/ViewerViewModel.cs b/src/SyncTrayzor/Pages/ViewerViewModel.cs index 24bbd357..399d6275 100644 --- a/src/SyncTrayzor/Pages/ViewerViewModel.cs +++ b/src/SyncTrayzor/Pages/ViewerViewModel.cs @@ -7,20 +7,22 @@ using CefSharp; using CefSharp.Wpf; using SyncTrayzor.Services.Config; +using System.IO; using System.Threading; -using System.Threading.Tasks; using System.Windows; using SyncTrayzor.Services; using SyncTrayzor.Properties; using Microsoft.WindowsAPICodePack.Dialogs; using CefSharp.Handler; -using PropertyChanged; +using SyncTrayzor.Localization; namespace SyncTrayzor.Pages { public class ViewerViewModel : Screen, IResourceRequestHandlerFactory, ILifeSpanHandler, IContextMenuHandler, IDisposable { + private const int ErrorSharingViolation = unchecked((int)0x80070020); + private readonly IWindowManager windowManager; private readonly ISyncthingManager syncthingManager; private readonly IProcessStartProvider processStartProvider; @@ -30,6 +32,7 @@ public class ViewerViewModel : Screen, IResourceRequestHandlerFactory, ILifeSpan private readonly CustomResourceRequestHandler customResourceRequestHandler; private readonly object cultureLock = new(); // This can be read from many threads + private readonly Lock _initLock = new(); private CultureInfo? culture; private double zoomLevel; @@ -109,58 +112,83 @@ protected override void OnInitialActivate() language = culture!.Name; } - var cefLogFile = pathsProvider.LogFilePath + @"\cef.log"; - var settings = new CefSettings() + lock (_initLock) { - RemoteDebuggingPort = AppSettings.Instance.CefRemoteDebuggingPort, - // We really only want to set the LocalStorage path, but we don't have that level of control.... - CachePath = pathsProvider.CefCachePath, - LogFile = cefLogFile, - IgnoreCertificateErrors = true, - LogSeverity = LogSeverity.Warning, - AcceptLanguageList = language, - Locale = language - }; - - // CEF will attempt de-elevating the process when launched as admin. This is done by relaunching - // the current process in unprivileged mode. However, we're not equipped to handle such a - // process restart cleanly, so ask CEF to not relaunch. Running as admin is not ideal for security, - // but the user wants it... - settings.CefCommandLineArgs.Add("do-not-de-elevate"); - - // System proxy settings (which also specify a proxy for localhost) shouldn't affect us - settings.CefCommandLineArgs.Add("no-proxy-server", "1"); - settings.CefCommandLineArgs.Add("disable-cache", "1"); - settings.CefCommandLineArgs.Add("disable-extensions", "1"); - - if (configuration.DisableHardwareRendering) - { - settings.CefCommandLineArgs.Add("disable-gpu"); - settings.CefCommandLineArgs.Add("disable-gpu-vsync"); + // It may happen that we have another SyncTrazyor instance using the same CEF cache path + // This will cause a generic CEF initialization failure. Try to catch this early + // by acquiring the log file. + var cefCachePath = pathsProvider.CefCachePath; + var cefLock = cefCachePath + @"\lockfile"; try { - settings.CefCommandLineArgs.Add("disable-gpu-compositing"); + using var lockFile = new FileStream(cefLock, FileMode.OpenOrCreate, FileAccess.ReadWrite, + FileShare.None); + } + catch (IOException e) when (e.HResult == ErrorSharingViolation) + { + var msg = Localizer.Translate("ViewerView_CefCacheLocked", cefCachePath); + var title = Localizer.Translate("ViewerView_CefCacheLocked_Title"); + windowManager.ShowMessageBox(msg, title, MessageBoxButton.OK, MessageBoxImage.Error); + Environment.Exit(1); } - catch (ArgumentException) + catch (Exception) { - // CefSharp 139+ may set this by default, ignore if unable to set the flag. + // Ignore potentially unrelated errors } - settings.CefCommandLineArgs.Add("disable-application-cache"); - } + var cefLogFile = pathsProvider.LogFilePath + @"\cef.log"; + var settings = new CefSettings() + { + RemoteDebuggingPort = AppSettings.Instance.CefRemoteDebuggingPort, + // We really only want to set the LocalStorage path, but we don't have that level of control.... + CachePath = cefCachePath, + LogFile = cefLogFile, + IgnoreCertificateErrors = true, + LogSeverity = LogSeverity.Warning, + AcceptLanguageList = language, + Locale = language + }; + + // CEF will attempt de-elevating the process when launched as admin. This is done by relaunching + // the current process in unprivileged mode. However, we're not equipped to handle such a + // process restart cleanly, so ask CEF to not relaunch. Running as admin is not ideal for security, + // but the user wants it... + settings.CefCommandLineArgs.Add("do-not-de-elevate"); + + // System proxy settings (which also specify a proxy for localhost) shouldn't affect us + settings.CefCommandLineArgs.Add("no-proxy-server", "1"); + settings.CefCommandLineArgs.Add("disable-cache", "1"); + settings.CefCommandLineArgs.Add("disable-extensions", "1"); + + if (configuration.DisableHardwareRendering) + { + settings.CefCommandLineArgs.Add("disable-gpu"); + settings.CefCommandLineArgs.Add("disable-gpu-vsync"); + try + { + settings.CefCommandLineArgs.Add("disable-gpu-compositing"); + } + catch (ArgumentException) + { + // CefSharp 139+ may set this by default, ignore if unable to set the flag. + } + + settings.CefCommandLineArgs.Add("disable-application-cache"); + } - try - { - if (!Cef.Initialize(settings)) + try { - throw new InvalidOperationException("Unable to initialize CEF."); + if (!Cef.Initialize(settings)) + { + throw new InvalidOperationException("Unable to initialize CEF."); + } + } + catch (InvalidOperationException e) + { + throw new AggregateException( + $"SyncTrayzor was unable to initialize its embedded browser.\nWhen reporting this issue, please attach the CEF logfile @ \n{cefLogFile}\n in addition to this message and the normal SyncTrayzor logfile.", + e); } - } - catch (InvalidOperationException e) - { - throw new AggregateException( - $"SyncTrayzor was unable to initialize its embedded browser.\nWhen reporting this issue, please attach the CEF logfile @ \n{cefLogFile}\n in addition to this message and the normal SyncTrayzor logfile.", - e); } } diff --git a/src/SyncTrayzor/Properties/Resources.resx b/src/SyncTrayzor/Properties/Resources.resx index 5abf94ff..b9d3c241 100644 --- a/src/SyncTrayzor/Properties/Resources.resx +++ b/src/SyncTrayzor/Properties/Resources.resx @@ -1014,4 +1014,12 @@ Please donate to the syncthing project. [File Deleted] When the base file for a conflict is not found, this placeholder is shown instead of the actual file name + + SyncTrayzor detected that another instance is currently running using the same configuration. Please stop any other SyncTrayzor instances, or restart your system to close them. + Shown when the embedded browser cannot acquire exclusive access to its cache folder, because another SyncTrayzor instance is running in a different location. + + + Conflicting SyncTrayzor processes detected + Title of the window when the ViewerView_CefCacheLocked message is displayed +