Beispiel #1
0
        private FileStreamPair OpenAutoSaveFileStreamPair(AutoSaveFileNamePair autoSaveFileNamePair)
        {
            try
            {
                var fileStreamPair = FileStreamPair.Create(
                    AutoSaveTextFile.OpenExistingAutoSaveFile,
                    Path.Combine(AppDataSubFolder, autoSaveFileNamePair.FileName1),
                    Path.Combine(AppDataSubFolder, autoSaveFileNamePair.FileName2));

                if (AutoSaveTextFile.CanAutoSaveTo(fileStreamPair.FileStream1) &&
                    AutoSaveTextFile.CanAutoSaveTo(fileStreamPair.FileStream2))
                {
                    return(fileStreamPair);
                }

                fileStreamPair.Dispose();
            }
            catch (Exception autoSaveLoadException)
            {
                // Only trace exceptions resulting from e.g. a missing LOCALAPPDATA subfolder or insufficient access.
                autoSaveLoadException.Trace();
            }

            return(null);
        }
Beispiel #2
0
        private void QueryAutoSaveFile(WorkingCopyTextFile sender, QueryAutoSaveFileEventArgs e)
        {
            // Only open auto-save files if they can be stored in autoSaveSetting.
            FileStreamPair fileStreamPair = null;

            try
            {
                fileStreamPair           = FileStreamPair.Create(CreateUniqueNewAutoSaveFileStream, CreateUniqueNewAutoSaveFileStream);
                e.AutoSaveFileStreamPair = fileStreamPair;

                ownerSession.AutoSave.Persist(
                    autoSaveProperty,
                    new AutoSaveFileNamePair(
                        Path.GetFileName(fileStreamPair.FileStream1.Name),
                        Path.GetFileName(fileStreamPair.FileStream2.Name)));
            }
            catch (Exception autoSaveLoadException)
            {
                if (fileStreamPair != null)
                {
                    fileStreamPair.Dispose();
                }

                // Only trace exceptions resulting from e.g. a missing LOCALAPPDATA subfolder or insufficient access.
                autoSaveLoadException.Trace();
            }
        }
Beispiel #3
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;
            }
        }
 private FileStreamPair AutoSaveFiles() => FileStreamPair.Create(
     AutoSaveTextFile.OpenExistingAutoSaveFile,
     fileFixture.GetPath(TargetFile.AutoSaveFile1),
     fileFixture.GetPath(TargetFile.AutoSaveFile2));