public UIActionHandlerFunc TryEditCurrentLanguage(MdiTabControl dockHostControl) => perform => { // Only enable in developer mode. if (!GetSetting(DeveloperMode)) { return(UIActionVisibility.Hidden); } // Cannot edit built-in localizer. if (!(CurrentLocalizer is FileLocalizer fileLocalizer)) { return(UIActionVisibility.Hidden); } if (perform) { OpenOrActivateSettingsEditor( dockHostControl, languageEditorBoxes.GetOrAdd(fileLocalizer.LanguageFile.AbsoluteFilePath, key => new Box <SettingsEditor>(null)), () => { // Generate translations into language file if empty. string initialTextGenerator() { var settingCopy = new SettingCopy(fileLocalizer.LanguageFile.Settings.Schema); // Fill with built-in default dictionary, or if not provided, an empty dictionary. settingCopy.AddOrReplace( Localizers.Translations, defaultLocalizerDictionary ?? new Dictionary <LocalizedStringKey, string>()); // And overwrite the existing language file with this. // This doesn't preserve trivia such as comments, whitespace, or even the order in which properties are given. return(fileLocalizer.LanguageFile.GenerateJson( settingCopy.Commit(), SettingWriterOptions.SuppressSettingComments)); } return(CreateSettingsEditor( SyntaxEditorCodeAccessOption.FixedFile, fileLocalizer.LanguageFile, initialTextGenerator, null)); }); } return(UIActionVisibility.Enabled); };
public SettingCopy CreateBuiltIn(Session session) { SettingCopy defaultSettings = new SettingCopy(CreateDefaultSettingsSchema(session)); defaultSettings.AddOrReplace(SettingKeys.Version, 1); defaultSettings.AddOrReplace(SharedSettings.AppDataSubFolderName, SettingKeys.DefaultAppDataSubFolderName); defaultSettings.AddOrReplace(SharedSettings.LocalPreferencesFileName, SharedSettings.DefaultLocalPreferencesFileName); defaultSettings.AddOrReplace(session.DeveloperMode, false); defaultSettings.AddOrReplace(SharedSettings.LangFolderName, SharedSettings.DefaultLangFolderName); defaultSettings.AddOrReplace(SettingKeys.DarkSquareColor, Color.LightBlue); defaultSettings.AddOrReplace(SettingKeys.LightSquareColor, Color.Azure); defaultSettings.AddOrReplace(SettingKeys.LastMoveArrowColor, Color.DimGray); defaultSettings.AddOrReplace(SettingKeys.DisplayLegalTargetSquares, true); defaultSettings.AddOrReplace(SettingKeys.LegalTargetSquaresColor, Color.FromArgb(240, 90, 90)); defaultSettings.AddOrReplace(SettingKeys.FastNavigationPlyCount, 10); return(defaultSettings); }
public UIActionHandlerFunc TryEditPreferencesFile(MdiTabControl dockHostControl) => perform => { if (perform) { OpenOrActivateSettingsEditor( dockHostControl, localSettingsEditorBox, () => { // If the file doesn't exist yet, generate a local settings file with a commented out copy // of the default settings to serve as an example, and to show which settings are available. string initialTextGenerator() { SettingCopy localSettingsExample = new SettingCopy(LocalSettings.Settings.Schema); var defaultSettingsObject = DefaultSettings.Settings; foreach (var property in localSettingsExample.Schema.AllProperties) { if (defaultSettingsObject.Schema.TryGetProperty(property.Name, out SettingProperty defaultSettingProperty) && defaultSettingsObject.TryGetRawValue(defaultSettingProperty, out PValue sourceValue)) { localSettingsExample.AddOrReplaceRaw(property, sourceValue); } } return(LocalSettings.GenerateJson( localSettingsExample.Commit(), SettingWriterOptions.CommentOutProperties)); } return(CreateSettingsEditor( SyntaxEditorCodeAccessOption.FixedFile, LocalSettings, initialTextGenerator, SharedSettings.PreferencesAutoSave)); }); } return(UIActionVisibility.Enabled); };
private Session(SingleInstanceMainForm singleInstanceMainForm, ISettingsProvider settingsProvider, Localizer defaultLocalizer, Dictionary <LocalizedStringKey, string> defaultLocalizerDictionary, Icon applicationIcon) { if (settingsProvider == null) { throw new ArgumentNullException(nameof(settingsProvider)); } if (singleInstanceMainForm == null) { throw new ArgumentNullException(nameof(singleInstanceMainForm)); } if (!singleInstanceMainForm.IsHandleCreated || singleInstanceMainForm.Disposing || singleInstanceMainForm.IsDisposed) { throw new InvalidOperationException($"{nameof(singleInstanceMainForm)} should have its handle created."); } // May be null. this.defaultLocalizerDictionary = defaultLocalizerDictionary; ApplicationIcon = applicationIcon; // This depends on ExecutableFileName. DeveloperMode = new SettingProperty <bool>( new SettingKey(SettingKey.ToSnakeCase(nameof(DeveloperMode))), PType.CLR.Boolean, new SettingComment($"Enables tools which assist with {ExecutableFileNameWithoutExtension} development and debugging.")); // Attempt to load default settings. DefaultSettings = SettingsFile.Create( Path.Combine(ExecutableFolder, DefaultSettingsFileName), settingsProvider.CreateBuiltIn(this)); // Save name of LOCALAPPDATA subfolder for persistent files. AppDataSubFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), GetDefaultSetting(SharedSettings.AppDataSubFolderName)); #if DEBUG // In debug mode, generate default json configuration files from hard coded settings. DeployRuntimeConfigurationFiles(); #endif // Scan Languages subdirectory to load localizers. var langFolderName = GetDefaultSetting(SharedSettings.LangFolderName); registeredLocalizers = Localizers.ScanLocalizers(this, Path.Combine(ExecutableFolder, langFolderName)); LangSetting = new SettingProperty <FileLocalizer>( new SettingKey(LangSettingKey), new PType.KeyedSet <FileLocalizer>(registeredLocalizers)); // Now attempt to get exclusive write access to the .lock file so it becomes a safe mutex. string lockFileName = Path.Combine(AppDataSubFolder, LockFileName); FileStreamPair autoSaveFiles = null; // Retry a maximum number of times. int remainingRetryAttempts = MaxRetryAttemptsForLockFile; while (remainingRetryAttempts >= 0) { try { // Create the folder on startup. Directory.CreateDirectory(AppDataSubFolder); // If this call doesn't throw, exclusive access to the mutex file is obtained. // Then this process is the first instance. // Use a buffer size of 24 rather than the default 4096. lockFile = new FileStream( lockFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, WindowHandleLengthInBytes + MagicLengthInBytes); try { // Immediately empty the file. lockFile.SetLength(0); // Write the window handle to the lock file. // Use BitConverter to convert to and from byte[]. byte[] buffer = BitConverter.GetBytes(singleInstanceMainForm.Handle.ToInt64()); lockFile.Write(buffer, 0, WindowHandleLengthInBytes); // Generate a magic GUID for this instance. // The byte array has a length of 16. TodaysMagic = Guid.NewGuid().ToByteArray(); lockFile.Write(TodaysMagic, 0, MagicLengthInBytes); lockFile.Flush(); autoSaveFiles = OpenAutoSaveFileStreamPair(new AutoSaveFileNamePair(AutoSaveFileName1, AutoSaveFileName2)); // Loop exit point 1: successful write to lockFile. Can auto-save. break; } catch { ReleaseLockFile(lockFile); lockFile = null; } } catch { // Not the first instance. // Then try opening the lock file as read-only. try { // If opening as read-only succeeds, read the contents as bytes. FileStream existingLockFile = new FileStream( lockFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, WindowHandleLengthInBytes + MagicLengthInBytes); using (existingLockFile) { byte[] lockFileBytes = new byte[WindowHandleLengthInBytes + MagicLengthInBytes]; int totalBytesRead = 0; int remainingBytes = WindowHandleLengthInBytes + MagicLengthInBytes; while (remainingBytes > 0) { int bytesRead = existingLockFile.Read(lockFileBytes, totalBytesRead, remainingBytes); if (bytesRead == 0) { break; // Unexpected EOF? } totalBytesRead += bytesRead; remainingBytes -= bytesRead; } // For checking that EOF has been reached, evaluate ReadByte() == -1. if (remainingBytes == 0 && existingLockFile.ReadByte() == -1) { // Parse out the remote window handle and the magic bytes it is expecting. long longValue = BitConverter.ToInt64(lockFileBytes, 0); HandleRef remoteWindowHandle = new HandleRef(null, new IntPtr(longValue)); byte[] remoteExpectedMagic = new byte[MagicLengthInBytes]; Array.Copy(lockFileBytes, 8, remoteExpectedMagic, 0, MagicLengthInBytes); // Tell the singleInstanceMainForm that another instance is active. // Not a clean design to have callbacks going back and forth. // Hard to refactor since we're inside a loop. singleInstanceMainForm.NotifyExistingInstance(remoteWindowHandle, remoteExpectedMagic); // Reference remoteWindowHandle here so it won't be GC'ed until method returns. GC.KeepAlive(remoteWindowHandle); // Loop exit point 2: successful read of the lock file owned by an existing instance. return; } } } catch { } } // If any of the above steps fail, this might be caused by the other instance // shutting down for example, or still being in its startup phase. // In this case, sleep for 100 ms and retry the whole process. Thread.Sleep(PauseTimeBeforeLockRetry); remainingRetryAttempts--; // Loop exit point 3: proceed without auto-saving settings if even after 2 seconds the lock file couldn't be accessed. // This can happen for example if the first instance is running as Administrator and this instance is not. } try { AutoSave = new SettingsAutoSave(settingsProvider.CreateAutoSaveSchema(this), autoSaveFiles); // After creating the auto-save file, look for a local preferences file. // Create a working copy with correct schema first. SettingCopy localSettingsCopy = new SettingCopy(settingsProvider.CreateLocalSettingsSchema(this)); // And then create the local settings file which can overwrite values in default settings. LocalSettings = SettingsFile.Create( Path.Combine(AppDataSubFolder, GetDefaultSetting(SharedSettings.LocalPreferencesFileName)), localSettingsCopy); if (TryGetAutoSaveValue(LangSetting, out FileLocalizer localizer)) { currentLocalizer = localizer; } else { // Select best fit. currentLocalizer = Localizers.BestFit(registeredLocalizers); } // Fall back onto defaults if still null. currentLocalizer = currentLocalizer ?? defaultLocalizer ?? Localizer.Default; } catch { // Must dispose here, because Dispose() is never called if an exception is thrown in a constructor. ReleaseLockFile(lockFile); throw; } }