/// <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)); }
/// <summary> /// Initializes a new <see cref="WorkingCopyTextFile"/> from an open <see cref="LiveTextFile"/> /// and a <see cref="FileStreamPair"/> from which to load an <see cref="AutoSaveTextFile{TUpdate}"/> with auto-saved local changes. /// Use this constructor for <see cref="LiveTextFile"/> instances which must remain live after this /// <see cref="WorkingCopyTextFile"/> is disposed. /// It is not possible to use <see cref="Replace(string)"/> on <see cref="WorkingCopyTextFile"/> instances /// created with this method. /// </summary> /// <param name="openTextFile"> /// The open text file. /// </param> /// <param name="autoSaveFiles"> /// The <see cref="FileStreamPair"/> from which to load the auto-save file that contains local changes, /// or null to not load from an auto-save file. /// </param> /// <returns> /// The new <see cref="WorkingCopyTextFile"/>. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="openTextFile"/> is null. /// </exception> public static WorkingCopyTextFile FromLiveTextFile(LiveTextFile openTextFile, FileStreamPair autoSaveFiles) => new WorkingCopyTextFile(
/// <summary> /// Initializes a new <see cref="WorkingCopyTextFile"/> from a file path and a <see cref="FileStreamPair"/> /// from which to load an <see cref="AutoSaveTextFile{TUpdate}"/> with auto-saved local changes. /// </summary> /// <param name="path"> /// The path of the file to load and watch, or null to create a new file. /// </param> /// <param name="autoSaveFiles"> /// The <see cref="FileStreamPair"/> from which to load the auto-save file that contains local changes, /// or null to not load from an auto-save file. /// </param> /// <exception cref="ArgumentException"> /// <paramref name="path"/> is empty, contains only whitespace, or contains invalid characters /// (see also <seealso cref="Path.GetInvalidPathChars"/>), or is in an invalid format, /// or is a relative path and its absolute path could not be resolved. /// </exception> /// <exception cref="IOException"> /// <paramref name="path"/> is longer than its maximum length (this is OS specific). /// </exception> /// <exception cref="System.Security.SecurityException"> /// The caller does not have sufficient permissions to read the file. /// </exception> /// <exception cref="NotSupportedException"> /// <paramref name="path"/> is in an invalid format. /// </exception> /// <returns> /// The new <see cref="WorkingCopyTextFile"/>. /// </returns> public static WorkingCopyTextFile Open(string path, FileStreamPair autoSaveFiles) => new WorkingCopyTextFile( path == null ? null : new LiveTextFile(path), autoSaveFiles, isTextFileOwner: true);