Ejemplo n.º 1
0
        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);
        };
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        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);
        };
Ejemplo n.º 4
0
        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;
            }
        }