/// <summary> /// Write data to file /// </summary> /// <param name="key">Key which identifies the data. If the key already exists, previous data will be overwritten.</param> /// <param name="data">Data bytes to store</param> public void Write(Guid key, byte[] data, int offset, int length) { lock (sync) { CheckDisposed(); GrowFile(data.Length == 0 ? 1 : length); HeaderCluster entry = null; int safeCopyClusterNumber = -1; if (index.ContainsKey(key)) { safeCopyClusterNumber = (int)index[key]; entry = AllocateNewHeader(key, length); index[key] = entry.ClusterNumber; MarkAsSafeCopy(safeCopyClusterNumber); } else { entry = AllocateNewHeader(key, length); index.Add(key, entry.ClusterNumber); } // Write to allocated head int dataOffset = offset; DataCluster cluster = entry; byte[] buffer = new byte[clusterSize]; do { int headerBytes = (cluster is HeaderCluster ? HeaderCluster.HeaderBytes : DataCluster.HeaderBytes); int remainingBytes = clusterSize - headerBytes; // Position to start of the stream PositionToCluster(cluster.ClusterNumber); // Write cluster header information to prepared buffer WriteCluster(cluster, buffer); // See how many bytes we are writing int writtenBytes = Math.Min(remainingBytes, length - (dataOffset - offset)); // Copy the data from the incomming buffer to prepared buffer Array.Copy(data, dataOffset, buffer, headerBytes, writtenBytes); // Write the prepared buffer fs.Write(buffer, 0, clusterSize); // Advance the dataOffset dataOffset += remainingBytes; // Move to next cluster cluster = cluster.NextCluster; }while ((dataOffset - offset) < length); if (safeCopyClusterNumber != -1) { DeleteChain(safeCopyClusterNumber, true); } } }
/// <summary> /// Reads the chain of clusters /// </summary> /// <param name="clusterNumber">Cluster number</param> /// <returns>Cluster chain</returns> private DataCluster ReadChain(int clusterNumber) { if (clusterNumber == -1) { return(null); } // Position to start of cluster PositionToCluster(clusterNumber); byte[] buffer = new byte[HeaderCluster.HeaderBytes]; fs.Read(buffer, 0, buffer.Length); ClusterFlags flags = (ClusterFlags)buffer[0]; if ((flags & ClusterFlags.StreamHeader) != 0) { HeaderCluster cluster = new HeaderCluster( flags, new Guid(new byte[] { buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18], buffer[19], buffer[20] }), clusterNumber, BitConverter.ToInt32(buffer, 21), ReadChain(BitConverter.ToInt32(buffer, 1)) ); return(cluster); } else { if ((flags & ClusterFlags.StreamData) != 0) { DataCluster cluster = new DataCluster( flags, clusterNumber, ReadChain(BitConverter.ToInt32(buffer, 1))); return(cluster); } else { return(null); } } }
/// <summary> /// Writes cluster information to byte array /// </summary> /// <param name="cluster">Cluster to write</param> /// <param name="buffer">Byte array</param> private static void WriteCluster(DataCluster cluster, byte[] buffer) { if (cluster is HeaderCluster) { HeaderCluster head = cluster as HeaderCluster; // Store information in format [Flags][Next][Key][Length] buffer[0] = (byte)cluster.Flags; Array.Copy(BitConverter.GetBytes(head.NextCluster == null ? -1 : head.NextCluster.ClusterNumber), 0, buffer, 1, 4); Array.Copy(head.Key.ToByteArray(), 0, buffer, 5, 16); Array.Copy(BitConverter.GetBytes(head.DataLength), 0, buffer, 21, 4); } else { #if DEBUG Debug.Assert(cluster is DataCluster); #endif buffer[0] = (byte)cluster.Flags; Array.Copy(BitConverter.GetBytes(cluster.NextCluster == null ? -1 : cluster.NextCluster.ClusterNumber), 0, buffer, 1, 4); } }
/// <summary> /// Takes empty clusters and reserves a header cluster /// </summary> /// <param name="key">Key for the data</param> /// <param name="length">Bytes required</param> /// <returns>Head allocation</returns> private HeaderCluster AllocateNewHeader(Guid key, int length) { HeaderCluster head = new HeaderCluster(ClusterFlags.StreamHeader, key, emptyClusters.Dequeue(), length, null); int remainingLength = length - (clusterSize - HeaderCluster.HeaderBytes); DataCluster prev = head; while (remainingLength > 0) { DataCluster data = new DataCluster(ClusterFlags.StreamData, emptyClusters.Dequeue(), null); remainingLength -= (clusterSize - DataCluster.HeaderBytes); prev.NextCluster = data; prev = data; } return(head); }
/// <summary> /// Reads the chain of clusters /// </summary> /// <param name="clusterNumber">Cluster number</param> /// <returns>Cluster chain</returns> private DataCluster ReadChain(int clusterNumber) { if (clusterNumber == -1) { return null; } // Position to start of cluster PositionToCluster(clusterNumber); byte[] buffer = new byte[HeaderCluster.HeaderBytes]; fs.Read(buffer, 0, buffer.Length); ClusterFlags flags = (ClusterFlags)buffer[0]; if ((flags & ClusterFlags.StreamHeader) != 0) { HeaderCluster cluster = new HeaderCluster( flags, new Guid(new byte[] { buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18], buffer[19], buffer[20] }), clusterNumber, BitConverter.ToInt32(buffer, 21), ReadChain(BitConverter.ToInt32(buffer, 1)) ); return cluster; } else { if ((flags & ClusterFlags.StreamData) != 0) { DataCluster cluster = new DataCluster( flags, clusterNumber, ReadChain(BitConverter.ToInt32(buffer, 1))); return cluster; } else { return null; } } }
/// <summary> /// Takes empty clusters and reserves a header cluster /// </summary> /// <param name="key">Key for the data</param> /// <param name="length">Bytes required</param> /// <returns>Head allocation</returns> private HeaderCluster AllocateNewHeader(Guid key, int length) { HeaderCluster head = new HeaderCluster(ClusterFlags.StreamHeader, key, emptyClusters.Dequeue(), length, null); int remainingLength = length - (clusterSize - HeaderCluster.HeaderBytes); DataCluster prev = head; while (remainingLength > 0) { DataCluster data = new DataCluster(ClusterFlags.StreamData, emptyClusters.Dequeue(), null); remainingLength -= (clusterSize - DataCluster.HeaderBytes); prev.NextCluster = data; prev = data; } return head; }