/// <summary> /// Initializes a new instance of the <see cref="JsonStoreWriter"/> class. /// </summary> /// <param name="name">The name of the application that generated the persisted files, or the root name of the files.</param> /// <param name="path">The directory in which the main persisted file resides or will reside, or null to create a volatile data store.</param> /// <param name="createSubdirectory">If true, a numbered subdirectory is created for this store.</param> /// <param name="extension">The extension for the underlying file.</param> public JsonStoreWriter(string name, string path, bool createSubdirectory = true, string extension = DefaultExtension) : base(extension) { ushort id = 0; this.Name = name; this.Path = System.IO.Path.GetFullPath(path); if (createSubdirectory) { // if the root directory already exists, look for the next available id if (Directory.Exists(this.Path)) { var existingIds = Directory.EnumerateDirectories(this.Path, this.Name + ".????") .Select(d => d.Split('.').Last()) .Where(n => ushort.TryParse(n, out ushort i)) .Select(n => ushort.Parse(n)); id = (ushort)(existingIds.Count() == 0 ? 0 : existingIds.Max() + 1); } this.Path = System.IO.Path.Combine(this.Path, $"{this.Name}.{id:0000}"); } if (!Directory.Exists(this.Path)) { Directory.CreateDirectory(this.Path); } string dataPath = System.IO.Path.Combine(this.Path, StoreCommon.GetDataFileName(this.Name) + this.Extension); this.streamWriter = File.CreateText(dataPath); this.jsonWriter = new JsonTextWriter(this.streamWriter); this.jsonWriter.WriteStartArray(); }
/// <summary> /// Initializes a new instance of the <see cref="JsonStoreReader"/> class. /// </summary> /// <param name="name">The name of the application that generated the persisted files, or the root name of the files</param> /// <param name="path">The directory in which the main persisted file resides or will reside, or null to create a volatile data store</param> /// <param name="dataSchemaString">JSON schema used to validate data stream.</param> /// <param name="extension">The extension for the underlying file.</param> /// <param name="preloadSchemas">Dictionary of URis to JSON schemas to preload before validating any JSON. Would likely include schemas references by the catalog and data schemas.</param> public JsonStoreReader(string name, string path, string dataSchemaString, string extension = DefaultExtension, IDictionary <Uri, string> preloadSchemas = null) : base(dataSchemaString, extension, preloadSchemas) { this.Name = name; this.Path = StoreCommon.GetPathToLatestVersion(name, path); // load catalog string metadataPath = System.IO.Path.Combine(this.Path, StoreCommon.GetCatalogFileName(this.Name) + this.Extension); using (var file = File.OpenText(metadataPath)) using (var reader = new JsonTextReader(file)) using (var validatingReader = new JSchemaValidatingReader(reader)) { validatingReader.Schema = this.CatalogSchema; validatingReader.ValidationEventHandler += (s, e) => throw new InvalidDataException(e.Message); this.catalog = this.Serializer.Deserialize <List <JsonStreamMetadata> >(validatingReader); } // compute originating time interval this.originatingTimeInterval = TimeInterval.Empty; foreach (var metadata in this.catalog) { var metadataTimeInterval = new TimeInterval(metadata.FirstMessageOriginatingTime, metadata.LastMessageOriginatingTime); this.originatingTimeInterval = TimeInterval.Coverage(new TimeInterval[] { this.originatingTimeInterval, metadataTimeInterval }); } }
private void WriteCatalog() { string metadataPath = System.IO.Path.Combine(this.Path, StoreCommon.GetCatalogFileName(this.Name) + this.Extension); using (var file = File.CreateText(metadataPath)) using (var writer = new JsonTextWriter(file)) { this.Serializer.Serialize(writer, this.catalog.Values.ToList()); } }
private void WriteCatalog() { string metadataPath = System.IO.Path.Combine(this.Path, StoreCommon.GetCatalogFileName(this.Name) + this.Extension); using (var file = File.CreateText(metadataPath)) using (var writer = new JsonTextWriter(file)) using (var validatingWriter = new JSchemaValidatingWriter(writer)) { validatingWriter.Schema = this.CatalogSchema; validatingWriter.ValidationEventHandler += (s, e) => throw new InvalidDataException(e.Message); this.Serializer.Serialize(validatingWriter, this.catalog.Values.ToList()); } }
/// <summary> /// Seek to envelope in stream according to specified replay descriptor. /// </summary> /// <param name="descriptor">The replay descriptor.</param> public void Seek(ReplayDescriptor descriptor) { this.descriptor = descriptor; // load data string dataPath = System.IO.Path.Combine(this.Path, StoreCommon.GetDataFileName(this.Name) + this.Extension); this.streamReader?.Dispose(); this.streamReader = File.OpenText(dataPath); this.jsonReader = new JsonTextReader(this.streamReader); this.validatingReader = new JSchemaValidatingReader(this.jsonReader) { Schema = this.DataSchema }; this.validatingReader.ValidationEventHandler += (s, e) => throw new InvalidDataException(e.Message); // iterate through data store until we either reach the end or we find the start of the replay descriptor while (this.validatingReader.Read()) { // data stores are arrays of messages, messages start as objects if (this.validatingReader.TokenType == JsonToken.StartObject) { // read envelope if (!this.validatingReader.Read() || !this.ReadEnvelope(out this.envelope)) { throw new InvalidDataException("Messages must be an ordered object: {\"Envelope\": <Envelope>, \"Data\": <Data>}. Deserialization needs to read the envelope before the data to know what type of data to deserialize."); } if (this.descriptor.Interval.Left < (descriptor.UseOriginatingTime ? this.envelope.OriginatingTime : this.envelope.Time)) { // found start of interval break; } // skip data if (!this.ReadData(out this.data)) { throw new InvalidDataException("Messages must be an ordered object: {\"Envelope\": <Envelope>, \"Data\": <Data>}. Deserialization needs to read the envelope before the data to know what type of data to deserialize."); } } } }
/// <summary> /// Repairs an invalid store. /// </summary> /// <param name="name">The name of the store to check.</param> /// <param name="path">The path of the store to check.</param> /// <param name="deleteOldStore">Indicates whether the original store should be deleted.</param> public static void Repair(string name, string path, bool deleteOldStore = true) { string storePath = StoreCommon.GetPathToLatestVersion(name, path); string tempFolderPath = Path.Combine(path, $"Repair-{Guid.NewGuid()}"); // call Crop over the entire store interval to regenerate and repair the streams in the store Store.Crop((name, storePath), (name, tempFolderPath), TimeInterval.Infinite); // create a _BeforeRepair folder in which to save the original store files var beforeRepairPath = Path.Combine(storePath, $"BeforeRepair-{Guid.NewGuid()}"); Directory.CreateDirectory(beforeRepairPath); // Move the original store files to the BeforeRepair folder. Do this even if the deleteOldStore // flag is true, as deleting the original store files immediately may occasionally fail. This can // happen because the InfiniteFileReader disposes of its MemoryMappedView in a background // thread, which may still be in progress. If deleteOldStore is true, we will delete the // BeforeRepair folder at the very end (by which time any open MemoryMappedViews will likely // have finished disposing). foreach (var file in Directory.EnumerateFiles(storePath)) { var fileInfo = new FileInfo(file); File.Move(file, Path.Combine(beforeRepairPath, fileInfo.Name)); } // move the repaired store files to the original folder foreach (var file in Directory.EnumerateFiles(Path.Combine(tempFolderPath, $"{name}.0000"))) { var fileInfo = new FileInfo(file); File.Move(file, Path.Combine(storePath, fileInfo.Name)); } // cleanup temporary folder Directory.Delete(tempFolderPath, true); if (deleteOldStore) { // delete the old store files Directory.Delete(beforeRepairPath, true); } }
/// <summary> /// Gets the simple reader for the specified stream binding. /// </summary> /// <param name="streamBinding">The stream binding.</param> /// <returns>The simple reader.</returns> public ISimpleReader GetReader(StreamBinding streamBinding) { if (streamBinding == null) { throw new ArgumentNullException(nameof(streamBinding)); } var key = Tuple.Create(streamBinding.StoreName, streamBinding.StorePath); if (this.dataStoreReaders.Contains(key)) { return(this.dataStoreReaders[key].GetReader()); } key = Tuple.Create(streamBinding.StoreName, StoreCommon.GetPathToLatestVersion(streamBinding.StoreName, streamBinding.StorePath)); if (this.dataStoreReaders.Contains(key)) { return(this.dataStoreReaders[key].GetReader()); } return(null); }
/// <summary> /// Initializes a new instance of the <see cref="JsonStoreReader"/> class. /// </summary> /// <param name="name">The name of the application that generated the persisted files, or the root name of the files.</param> /// <param name="path">The directory in which the main persisted file resides or will reside, or null to create a volatile data store.</param> /// <param name="extension">The extension for the underlying file.</param> public JsonStoreReader(string name, string path, string extension = DefaultExtension) : base(extension) { this.Name = name; this.Path = StoreCommon.GetPathToLatestVersion(name, path); // load catalog string metadataPath = System.IO.Path.Combine(this.Path, StoreCommon.GetCatalogFileName(this.Name) + this.Extension); using (var file = File.OpenText(metadataPath)) using (var reader = new JsonTextReader(file)) { this.catalog = this.Serializer.Deserialize <List <JsonStreamMetadata> >(reader); } // compute originating time interval this.originatingTimeInterval = TimeInterval.Empty; foreach (var metadata in this.catalog) { var metadataTimeInterval = new TimeInterval(metadata.FirstMessageOriginatingTime, metadata.LastMessageOriginatingTime); this.originatingTimeInterval = TimeInterval.Coverage(new TimeInterval[] { this.originatingTimeInterval, metadataTimeInterval }); } }
/// <summary> /// Initializes a new instance of the <see cref="JsonStoreWriter"/> class. /// </summary> /// <param name="name">The name of the application that generated the persisted files, or the root name of the files</param> /// <param name="path">The directory in which the main persisted file resides or will reside, or null to create a volatile data store</param> /// <param name="dataSchemaString">JSON schema used to validate data stream.</param> /// <param name="createSubdirectory">If true, a numbered subdirectory is created for this store</param> /// <param name="extension">The extension for the underlying file.</param> /// <param name="preloadSchemas">Dictionary of URis to JSON schemas to preload before validating any JSON. Would likely include schemas references by the catalog and data schemas.</param> public JsonStoreWriter(string name, string path, string dataSchemaString, bool createSubdirectory = true, string extension = DefaultExtension, IDictionary <Uri, string> preloadSchemas = null) : base(dataSchemaString, extension, preloadSchemas) { ushort id = 0; this.Name = name; this.Path = System.IO.Path.GetFullPath(path); if (createSubdirectory) { // if the root directory already exists, look for the next available id if (Directory.Exists(this.Path)) { var existingIds = Directory.EnumerateDirectories(this.Path, this.Name + ".????") .Select(d => d.Split('.').Last()) .Where(n => ushort.TryParse(n, out ushort i)) .Select(n => ushort.Parse(n)); id = (ushort)(existingIds.Count() == 0 ? 0 : existingIds.Max() + 1); } this.Path = System.IO.Path.Combine(this.Path, $"{this.Name}.{id:0000}"); } if (!Directory.Exists(this.Path)) { Directory.CreateDirectory(this.Path); } string dataPath = System.IO.Path.Combine(this.Path, StoreCommon.GetDataFileName(this.Name) + this.Extension); this.streamWriter = File.CreateText(dataPath); this.jsonWriter = new JsonTextWriter(this.streamWriter); this.validatingWriter = new JSchemaValidatingWriter(this.jsonWriter); this.validatingWriter.Schema = this.DataSchema; this.validatingWriter.ValidationEventHandler += (s, e) => throw new InvalidDataException(e.Message); this.validatingWriter.WriteStartArray(); }
/// <summary> /// Indicates whether the specified store file exists. /// </summary> /// <param name="name">The name of the store to check.</param> /// <param name="path">The path of the store to check.</param> /// <returns>Returns true if the store exists.</returns> public static bool Exists(string name, string path) { return((path == null) ? InfiniteFileReader.IsActive(StoreCommon.GetCatalogFileName(name), path) : StoreCommon.TryGetPathToLatestVersion(name, path, out _)); }