예제 #1
0
        /// <summary>
        /// Initializes a new instance of <see cref="AutoSaveTextFile{TUpdate}"/>.
        /// </summary>
        /// <param name="remoteState">
        /// Object responsible for converting updates to text.
        /// </param>
        /// <param name="autoSaveFiles">
        /// The <see cref="FileStreamPair"/> containing <see cref="FileStream"/>s to write to.
        /// Any existing contents in the files will be overwritten.
        /// <see cref="AutoSaveTextFile{TUpdate}"/> assumes ownership of the <see cref="FileStream"/>s
        /// so it takes care of disposing it after use.
        /// To be used as an auto-save <see cref="FileStream"/>,
        /// it must support seeking, reading and writing, and not be able to time out.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="remoteState"/> and/or <paramref name="autoSaveFiles"/> are null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// One or both <see cref="FileStream"/>s in <paramref name="autoSaveFiles"/>
        /// do not have the right capabilities to be used as an auto-save file stream.
        /// See also: <seealso cref="CanAutoSaveTo"/>.
        /// </exception>
        public AutoSaveTextFile(RemoteState remoteState, FileStreamPair autoSaveFiles)
        {
            if (remoteState == null)
            {
                throw new ArgumentNullException(nameof(remoteState));
            }
            AutoSaveFiles = autoSaveFiles ?? throw new ArgumentNullException(nameof(autoSaveFiles));

            // Assert capabilities of the file streams.
            VerifyFileStream(autoSaveFiles.FileStream1, nameof(autoSaveFiles));
            VerifyFileStream(autoSaveFiles.FileStream2, nameof(autoSaveFiles));

            // Immediately attempt to load the saved contents from either FileStream.
            // Choose first auto-save file to load from.
            FileStream latestAutoSaveFile = autoSaveFiles.FileStream1.Length == 0 ? autoSaveFiles.FileStream2 : autoSaveFiles.FileStream1;

            string loadedText = null;

            try
            {
                loadedText = Load(latestAutoSaveFile);
            }
            catch (Exception firstLoadException)
            {
                // Trace and try the other auto-save file as a backup.
                firstLoadException.Trace();
            }

            // If null is returned from the first Load(), the integrity check failed.
            if (loadedText == null)
            {
                latestAutoSaveFile = autoSaveFiles.Different(latestAutoSaveFile);

                try
                {
                    loadedText = Load(latestAutoSaveFile);
                }
                catch (Exception secondLoadException)
                {
                    secondLoadException.Trace();
                }
            }

            // Initialize remote state with the loaded text.
            // If both reads failed, loadedText == null.
            remoteState.Initialize(loadedText);

            // Initialize encoder and buffers.
            // Always use UTF8 for auto-saved text files.
            Encoding encoding = Encoding.UTF8;

            encoder       = encoding.GetEncoder();
            buffer        = new char[CharBufferSize];
            encodedBuffer = new byte[encoding.GetMaxByteCount(CharBufferSize)];

            // Set up long running task to keep auto-saving updates.
            updateQueue            = new ConcurrentQueue <TUpdate>();
            cts                    = new CancellationTokenSource();
            autoSaveBackgroundTask = Task.Run(() => AutoSaveLoop(latestAutoSaveFile, remoteState, cts.Token));
        }
예제 #2
0
        private async Task AutoSaveLoop(FileStream lastWrittenToFile, RemoteState remoteState, CancellationToken ct)
        {
            for (; ;)
            {
                // If cancellation is requested, stop waiting so the queue can be emptied as quickly as possible.
                if (!ct.IsCancellationRequested)
                {
                    try
                    {
                        await Task.Delay(AutoSaveDelay, ct);
                    }
                    catch
                    {
                        // If the task was cancelled, empty the queue before leaving this method.
                    }
                }

                // Empty the queue, create a local (thread-safe) list of updates to process.
                bool hasUpdate = updateQueue.TryDequeue(out TUpdate firstUpdate);

                if (!hasUpdate)
                {
                    // Only return if the queue is empty and saved.
                    if (ct.IsCancellationRequested)
                    {
                        break;
                    }
                }
                else
                {
                    List <TUpdate> updates = new List <TUpdate> {
                        firstUpdate
                    };
                    while (updateQueue.TryDequeue(out TUpdate update))
                    {
                        updates.Add(update);
                    }

                    try
                    {
                        if (remoteState.ShouldSave(updates, out string textToSave))
                        {
                            // Alternate between both auto-save files.
                            FileStream targetFile = AutoSaveFiles.Different(lastWrittenToFile);

                            // Only truly necessary in the first iteration if the targetFile was initially a corrupt non-empty file.
                            // Theoretically, two thrown writeExceptions would have the same effect.
                            // In other cases, lastWrittenToFile.SetLength(0) below will already have done this.
                            targetFile.SetLength(0);

                            // Write the contents to the file.
                            await WriteToFileAsync(targetFile, textToSave);

                            // Only truncate the other file when completely successful, to indicate that
                            // the auto-save file which was just saved is in a completely correct format.
                            lastWrittenToFile.SetLength(0);

                            // Switch to writing to the other file in the next iteration.
                            lastWrittenToFile = targetFile;
                        }
                    }
                    catch (Exception writeException)
                    {
                        writeException.Trace();
                    }
                }
            }
        }