1 |
/**************************************************************************** |http://www.koders.com/csharp/fid17630DC520D00C18D8844EEBC8AD8D31F6A8D574.aspx?s=md5 | Copyright (c) 2007 Novell, Inc. | All Rights Reserved. | | This program is free software; you can redistribute it and/or | modify it under the terms of version 2 of the GNU General Public License as | published by the Free Software Foundation. | | This program is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU General Public License | along with this program; if not, contact Novell, Inc. | | To contact Novell about this file by physical or electronic mail, | you may find current contact information at www.novell.com | | Author: Russ Young | | Thanks to Andrew Tridgell for the rsync algorythm. | http://samba.anu.edu.au/rsync/tech_report/ |***************************************************************************/ using System; using System.IO; using System.Collections; using System.Threading; using Simias.Storage; namespace Simias.Sync.Delta { #region HashData /// <summary> /// Class used to keep track of the file Blocks and hash /// codes assosiated with the block. /// </summary> public class HashData { /// <summary> /// The serialized size of the instance. /// </summary> public static int InstanceSize = 4 + 4 + 16; /// <summary> /// The Block number that this hash represents. 0 based. /// </summary> public int BlockNumber; /// <summary> /// The Weak or quick hash of this block. /// </summary> public UInt32 WeakHash; /// <summary> /// The strong hash of this block. /// </summary> public byte[] StrongHash; /// <summary> /// Constructs a HashData Object. /// </summary> /// <param name="blockNumber"></param> /// <param name="weakHash"></param> /// <param name="strongHash"></param> public HashData(int blockNumber, UInt32 weakHash, byte[] strongHash) { this.BlockNumber = blockNumber; this.WeakHash = weakHash; this.StrongHash = strongHash; } /// <summary> /// /// </summary> /// <param name="reader"></param> public HashData(BinaryReader reader) { BlockNumber = reader.ReadInt32(); WeakHash = reader.ReadUInt32(); StrongHash = reader.ReadBytes(16); } /// <summary> /// /// </summary> /// <param name="writer"></param> public void Serialize(BinaryWriter writer) { writer.Write(BlockNumber); writer.Write(WeakHash); writer.Write(StrongHash); } } #endregion #region HashMap /// <summary> /// Used to Write and Get HashMaps of a stream. /// </summary> public class HashMap { const string MapFilePrefix = ".simias.map."; Collection collection; BaseFileNode node; string file; static int MaxThreadCount = 20; static int ThreadCount = 0; static Queue mapQ = new Queue(); static AutoResetEvent queueEvent = new AutoResetEvent(false); delegate void HashMapDelegate(); static int version = 1; internal struct HashFileHeader { static byte[] signature = {(byte)'!', (byte)'M', (byte)'a', (byte)'P', (byte)'f', (byte)'I', (byte)'l', (byte)'e'}; static int headerSize = 24; /// <summary> /// /// </summary> /// <param name="reader"></param> /// <param name="blockSize"></param> /// <param name="entryCount"></param> /// <param name="nodeRev">The revision of the node.</param> /// <returns></returns> internal static bool ReadHeader(BinaryReader reader, out int blockSize, out int entryCount, ulong nodeRev) { byte[] sig = reader.ReadBytes(8); blockSize = reader.ReadInt32(); int ver = reader.ReadInt32(); ulong fileRev = reader.ReadUInt64(); entryCount = 0; if (sig.Length == signature.Length) { for (int i= 0; i < sig.Length; ++i) { if (sig[i] != signature[i]) return false; } if (version != ver) return false; if (fileRev != nodeRev) return false; entryCount = (int)((reader.BaseStream.Length - headerSize)/ HashData.InstanceSize); return true; } return false; } /// <summary> /// /// </summary> /// <param name="writer"></param> /// <param name="blockSize"></param> /// <param name="nodeRev">The revision of the node.</param> internal static void WriteHeader(BinaryWriter writer, int blockSize, ulong nodeRev) { writer.Write(signature); writer.Write(blockSize); writer.Write(version); writer.Write(nodeRev); } } // We want to keep Map file size small enough that it will work over a 56Kb/Sec modem. // Lets try to get the file in 60 seconds. // In 60 seconds we can transfer 56k / 8 = 430,080 bytes of data. // In 60 seconds we can transfer 17920 blocks rount to 18000 // We are assuming that larger files are less likely to change. static int maxBlocks = 18000; /// <summary> /// /// </summary> /// <param name="collection"></param> /// <param name="node"></param> internal HashMap(Collection collection, BaseFileNode node) { this.collection = collection; this.node = node; file = Path.Combine(collection.ManagedPath, MapFilePrefix + node.ID); } /// <summary> /// /// </summary> private static void HashMapThread() { // BUGBUG Encryption Here. // Not needed map is generated on client. // Now see if we have any work queued. while (true) { // If we have had no work for 5 min exit thread. bool timedOut = !queueEvent.WaitOne(5 * 60 * 1000, false); try { while (true) { HashMapDelegate hmd; lock (mapQ) { hmd = mapQ.Count > 0 ? mapQ.Dequeue() as HashMapDelegate : null; if (hmd == null) { if (timedOut) { // Exit the thread. --ThreadCount; return; } break; } } try { hmd(); } catch { /* Don't let the thread go away.*/ } } } catch{} } } /// <summary> /// /// </summary> public void CreateHashMapFile() { FileStream mapSrcStream = File.Open(node.GetFullPath(collection), FileMode.Open, FileAccess.Read, FileShare.Read); int blockSize = CalculateBlockSize(mapSrcStream.Length); try { string mapFile = file; string tmpMapFile = mapFile + ".tmp"; // Copy the current file to a tmp name. if (File.Exists(mapFile)) File.Move(mapFile, tmpMapFile); BinaryWriter writer = new BinaryWriter( File.OpenWrite(tmpMapFile)); // Write the header. HashFileHeader.WriteHeader(writer, blockSize, node.LocalIncarnation); try { mapSrcStream.Position = 0; HashMap.SerializeHashMap(mapSrcStream, writer, blockSize); writer.Close(); File.Move(tmpMapFile, mapFile); File.SetCreationTime(mapFile, node.CreationTime); File.SetLastWriteTime(mapFile, node.LastWriteTime); } catch (Exception ex) { writer.Close(); writer = null; File.Delete(mapFile); if (File.Exists(tmpMapFile)) File.Move(tmpMapFile, mapFile); throw ex; } finally { if (File.Exists(tmpMapFile)) File.Delete(tmpMapFile); } } finally { // Close the file. mapSrcStream.Close(); } } /// <summary> /// Calculate the block size to use for hash blocks /// </summary> /// <param name="streamSize">The size of the file.</param> /// <returns>The blockSize</returns> private static int CalculateBlockSize(long streamSize) { long size = streamSize / HashMap.maxBlocks; if (size < 0x1000) return 0x1000; if (size < 0x2000) return 0x2000; if (size < 0x4000) return 0x4000; if (size < 0x8000) return 0x8000; if (size < 0x10000) return 0x10000; if (size < 0x20000) return 0x20000; if (size < 0x40000) return 0x40000; return 0x80000; } /// <summary> /// /// </summary> internal void CreateHashMap() { // Delete the file now. Delete(); bool startThread = false; lock (mapQ) { mapQ.Enqueue(new HashMapDelegate(CreateHashMapFile)); if (ThreadCount == 0 || (mapQ.Count > 1 && ThreadCount < MaxThreadCount)) { // Startup a thread. startThread = true; ++ThreadCount; } } if (startThread) { Thread thread = new Thread(new ThreadStart(HashMapThread)); thread.IsBackground = true; thread.Start(); } queueEvent.Set(); } internal static void Delete(Collection collection, BaseFileNode node) { new HashMap(collection, node).Delete(); } internal void Delete() { if (File.Exists(file)) File.Delete(file); } /// <summary> /// Gets the array of HashMap /// </summary> /// <param name="entryCount">The number of hash entries.</param> /// <param name="blockSize">The size of the data blocks that were hashed.</param> /// <param name="create">If true create hashmap on error.</param> /// <param name="mapRev">The desired map revision.</param> /// <returns></returns> internal FileStream GetHashMapStream(out int entryCount, out int blockSize, bool create, ulong mapRev) { if (File.Exists(file)) { FileStream stream = File.OpenRead(file); if (HashFileHeader.ReadHeader(new BinaryReader(stream), out blockSize, out entryCount, mapRev)) { return stream; } stream.Close(); } if (create) this.CreateHashMap(); else Delete(); entryCount = 0; blockSize = 0; return null; } /// <summary> /// Store the Hash Map /// </summary> public void StoreHashMapFile(Stream stream, int Size) { string mapFile = file; // Delete the existing file, any way we are going to store the new one if(File.Exists(mapFile)) Delete(); FileStream HashStream = File.OpenWrite(mapFile); try { StreamStream ss = new StreamStream(HashStream); ss.Write(stream, Size); } catch (Exception ex) { throw ex; } finally { HashStream.Close(); } } /// <summary> /// /// </summary> /// <param name="reader"></param> /// <param name="count"></param> /// <returns></returns> public static HashData[] DeSerializeHashMap(BinaryReader reader, int count) { HashData[] fileMap = new HashData[count]; for (int i = 0; i < count; ++i) { fileMap[i] = new HashData(reader); } return fileMap; } /// <summary> /// Return the number of Blocks that the HashMap will need. /// </summary> /// <param name="streamSize"></param> /// <returns></returns> public static int GetBlockCount(long streamSize, out int blockSize) { blockSize = CalculateBlockSize(streamSize); return (int)((streamSize + blockSize -1)/ blockSize); } /// <summary> /// Serialized the Hash Created from the input stream to the writer stream. /// </summary> /// <param name="inStream">The stream of raw data to create the HashMap from.</param> /// <param name="writer">The stream to write the HashMap to.</param> /// <param name="blockSize">The size of the hashed data blocks.</param> public static void SerializeHashMap(Stream inStream, BinaryWriter writer, int blockSize) { // //if (inStream.Length <= blockSize) //{ // return; //} byte[] buffer = new byte[blockSize]; StrongHash sh = new StrongHash(); WeakHash wh = new WeakHash(); int bytesRead; int currentBlock = 0; // Compute the hash codes. inStream.Position = 0; while ((bytesRead = inStream.Read(buffer, 0, blockSize)) != 0) { new HashData( currentBlock++, wh.ComputeHash(buffer, 0, (UInt16)bytesRead), sh.ComputeHash(buffer, 0, bytesRead)).Serialize(writer); } } } #endregion } |
相关文章: