/// <summary> /// Gets the stream of a cache entry. /// DO NOT CLOSE THE STREAM RETURNED BY THIS METHOD. /// </summary> /// <param name="name"></param> /// <returns></returns> public StreamEx GetEntry(String name) { logger.Debug("Getting cache entry: ID = 0x{0}", name); // Check if there is such an entry to get if (!entriesMap.ContainsKey(name)) { logger.Error("Attempting to get an entry that does not exist: ID = 0x{0}", name); throw new KeyNotFoundException(); } // Get the entry CacheEntry metadata = entries[entriesMap[name]]; PartialStreamEx stream = new PartialStreamEx(dataStream, metadata.Address, metadata.Length); return(stream); }
/// <summary> /// Adds data into cache. /// In case of a duplicate entry, the old entry will be overwritten. /// </summary> /// <param name="name">Name of the entry.</param> /// <param name="data">Stream containing data to be cached.</param> public void AddEntry(String name, StreamEx data) { logger.Debug("Adding cache entry: ID = {0}; Length = {1}", name, data.Length); // If the entry name is too long, throw error if (Encoding.UTF8.GetByteCount(name) > (IndexAlignment - 0x10)) { throw new ArgumentOutOfRangeException("name", String.Format("Name too long! Name must be less than {0} bytes long when encoded.", IndexAlignment - 0x10)); } // If the entry already exists, delete it if (entriesMap.ContainsKey(name)) { RemoveEntry(name); logger.Trace("Attempting to add a duplicate entry, removing old data."); } // Find a hole to write the data Int64 address; KeyValuePair <long, long> hole = dataHoles.FirstOrDefault(t => t.Value >= data.Length); if (!hole.Equals(default(KeyValuePair <Int64, Int64>))) { logger.Trace("Hole found: Address = 0x{0}; Length = 0x{1}", hole.Key.ToHexString(8), hole.Value.ToHexString(8)); // Appropriate hole found, write data first address = hole.Key; // Use partial stream to avoid overflows PartialStreamEx partialStream = new PartialStreamEx(dataStream, address, hole.Value); data.CopyTo(partialStream); // Reduce hole size dataHoles.Remove(hole.Key); long newHoleAddress = Align(hole.Key + data.Length, DataAlignment); long newHoleLength = hole.Value - Align(data.Length, DataAlignment); Debug.Assert(newHoleLength > 0, "Hole size should be greater or equal than zero after resize."); Debug.Assert(newHoleAddress % DataAlignment == 0, "New hole address should be aligned."); Debug.Assert(newHoleLength % DataAlignment == 0, "New hole length should be aligned."); // Add it back if there is still hole after the write if (newHoleLength > DataAlignment) { logger.Trace("Hole reduced to: Address = 0x{0}; Length = 0x{1}", newHoleAddress.ToHexString(8), newHoleLength.ToHexString(8)); dataHoles.Add(newHoleAddress, newHoleLength); } } else { logger.Trace("No holes large enough, writing to the end of data file."); address = dataStream.Seek(0, SeekOrigin.End); // Write Data data.CopyTo(dataStream); // Pad for alignment long fill = Align(dataStream.Position, DataAlignment) - dataStream.Position; dataStream.WriteBytes(new byte[fill]); Debug.Assert(dataStream.Position % DataAlignment == 0, "Data stream position should be aligned after write."); } // Find an empty slot in the index file long indexAddress = indexStream.Seek(0, SeekOrigin.End); // Set it to end of file first if (indexHoles.Count > 0) { indexAddress = indexHoles.Dequeue(); // If there are empty index slots, use those instead logger.Trace("Found an empty index slot at 0x{0}", indexAddress.ToHexString(8)); } Debug.Assert(indexAddress % 0x10 == 0, "Index address should be aligned to 0x10."); // Write the index indexStream.Position = indexAddress; indexStream.WriteInt64(address); indexStream.WriteInt64(data.Length); byte[] encodedName = Encoding.UTF8.GetBytes(name); indexStream.WriteBytes(encodedName); indexStream.WriteBytes(new Byte[IndexAlignment - 0x10 - encodedName.Length]); // Padding // Save the index entries.Add(address, new CacheEntry { Name = name, Address = address, Length = data.Length, MetadataAddress = indexAddress }); entriesMap.Add(name, address); // Force write data to stream indexStream.Flush(); dataStream.Flush(); }