/// <summary> /// Performs a get synchronously /// </summary> /// <param name="id">The ID of the file to get</param> /// <param name="hash">The hash of the files contents</param> /// <returns>The result of the get operation</returns> public UnityCacheClientGetResult Get(Guid id, string hash) { UnityCacheClientGetResult result = new UnityCacheClientGetResult(); result.Id = id; result.Hash = hash; byte[] command = Encoding.ASCII.GetBytes("g"); this.stream.WriteByte(command[0]); UnityCacheUtilities.SendIdAndHashOnStream(this.stream, id, hash); this.stream.Read(command, 0, command.Length); string strResult = Encoding.ASCII.GetString(command); if (strResult == "-") { result.Result = CacheResult.CacheMiss; // Read and toss the hash since we don't need it UnityCacheUtilities.ReadGuid(this.stream); UnityCacheUtilities.ReadHash(this.stream); } else if (strResult == "+") { result.Result = CacheResult.CacheHit; // Read the length of the file byte[] buffer = new byte[16]; this.stream.Read(buffer, 0, 16); ulong bytesToBeRead = UnityCacheUtilities.GetAsciiBytesAsUInt64(buffer); // Read the ID and hash. Toss this, we don't need it UnityCacheUtilities.ReadGuid(this.stream); UnityCacheUtilities.ReadHash(this.stream); // Read the reply from the server buffer = new byte[bytesToBeRead]; result.Data = buffer; int offset = 0; while (bytesToBeRead > 0) { int len = (bytesToBeRead > (ulong)this.streamBlockSize) ? this.streamBlockSize : (int)bytesToBeRead; ulong bytesReturned = (ulong)this.stream.Read(buffer, offset, len); bytesToBeRead -= (ulong)bytesReturned; offset += (int)bytesReturned; } } return(result); }
/// <summary> /// Puts a file to the server /// </summary> /// <param name="id">The id of the file</param> /// <param name="hash">The hash of the file</param> /// <param name="data">The data contained in the file</param> public void Put(Guid id, string hash, byte[] data) { if (data == null) { throw new ArgumentNullException("data", "Argument cannot be null"); } byte[] command = Encoding.ASCII.GetBytes("p"); this.stream.WriteByte(command[0]); // Send the length as ASCII ulong dataLength = (ulong)data.Length; string lengthStr = dataLength.ToString("X16", CultureInfo.InvariantCulture); byte[] lenBytes = Encoding.ASCII.GetBytes(lengthStr); this.stream.Write(lenBytes, 0, lenBytes.Length); UnityCacheUtilities.SendIdAndHashOnStream(this.stream, id, hash); this.stream.Write(data, 0, data.Length); }
/// <summary> /// Process the client put request /// </summary> /// <param name="stream">The stream to the client requesting the put</param> private void ProcessPut(NetworkStream stream) { byte[] buffer = new byte[16]; stream.Read(buffer, 0, 16); ulong bytesToBeRead = UnityCacheUtilities.GetAsciiBytesAsUInt64(buffer); // Read ID Guid id = UnityCacheUtilities.ReadGuid(stream); // Read HASH string hash = UnityCacheUtilities.ReadHash(stream); logger.Info("PUT: {0} {1}", id, hash); FileStream fileStream = this.fileManager.GetTemporaryFile(id, hash); buffer = new byte[this.streamBlockSize]; while (bytesToBeRead > 0) { int len = (bytesToBeRead > (ulong)this.streamBlockSize) ? this.streamBlockSize : (int)bytesToBeRead; ulong bytesReturned = (ulong)stream.Read(buffer, 0, len); fileStream.Write(buffer, 0, (int)bytesReturned); bytesToBeRead -= (ulong)bytesReturned; } fileStream.Close(); this.fileManager.CompleteFile(id, hash); // Notify listeners a get was processed if (this.OnPutProcessed != null) { this.OnPutProcessed(this, new EventArgs()); } }
/// <summary> /// Processes the get command /// </summary> /// <param name="stream">The stream to the client</param> private void ProcessGet(NetworkStream stream) { // Read ID Guid id = UnityCacheUtilities.ReadGuid(stream); string hash = UnityCacheUtilities.ReadHash(stream); if (!CacheFile.IsFileCached(this.fileManager.Root, id, hash)) { logger.Info("GET: Cache miss. {0} {1}", id, hash); // File is not cached // Send command it's not cached byte[] code = new byte[1]; code[0] = 45; stream.Write(code, 0, 1); // Send id and hash UnityCacheUtilities.SendIdAndHashOnStream(stream, id, hash); } else { logger.Info("GET: Cache hit. {0} {1}", id, hash); using (MemoryStream memoryStream = new MemoryStream(49)) { // File is cached, send the response byte[] code = new byte[1]; code[0] = 43; memoryStream.Write(code, 0, 1); // Send the file size in bytes ulong bytesToBeWritten = CacheFile.GetFileSizeBytes(this.fileManager.Root, id, hash); // Dumb off by 1 hack byte[] fileSizeBytes = UnityCacheUtilities.GetUlongAsAsciiBytes(bytesToBeWritten); memoryStream.Write(fileSizeBytes, 0, fileSizeBytes.Length); // Send id and hash UnityCacheUtilities.SendIdAndHashOnStream(memoryStream, id, hash); // Send the file bytes FileStream fileStream = this.fileManager.GetReadFileStream(id, hash); byte[] buffer = new byte[this.streamBlockSize]; // Workaround to get enough bytes into a single packet so the Unity client doesn't choke byte[] header = memoryStream.GetBuffer(); stream.Write(header, 0, header.Length); while (bytesToBeWritten > 0) { int byteCount = (bytesToBeWritten > (ulong)this.streamBlockSize) ? this.streamBlockSize : (int)bytesToBeWritten; fileStream.Read(buffer, 0, byteCount); bytesToBeWritten -= (ulong)byteCount; stream.Write(buffer, 0, byteCount); } fileStream.Close(); } } // Notify listeners a get was processed if (this.OnGetProcessed != null) { this.OnGetProcessed(this, new EventArgs()); } }
public void EndToEndTest() { signal = new ManualResetEvent(false); UnityCacheServer server = new UnityCacheServer(); using (UnityCacheClient client = new UnityCacheClient("localhost")) { server.OnPutProcessed += server_OnPutProcessed; try { // Start the server Assert.AreEqual <ServerStatus>(server.Status, ServerStatus.Stopped); server.Start(); Assert.AreEqual <ServerStatus>(server.Status, ServerStatus.Running); client.Connect(); Guid id = Guid.NewGuid(); MD5 md5Hash; string hash; using (md5Hash = MD5.Create()) { hash = UnityCacheUtilities.ByteArrayToString(md5Hash.ComputeHash(Encoding.UTF8.GetBytes(DateTime.Now.ToString()))); } Console.WriteLine("Requesting ID: {0}, Hash {1}", id, hash); UnityCacheClientGetResult result; // Perform a get for (int x = 0; x < 100; x++) { // Verify that sending the same command over and over works correctly result = client.Get(id, hash); Assert.AreEqual <CacheResult>(result.Result, CacheResult.CacheMiss); } // Perform a put int dataLen = 1024 * 10 + DateTime.Now.Second % 2; // Test that even/odd file lengths work correctly randomly byte[] data = new byte[dataLen]; Random r = new Random(); for (int x = 0; x < data.Length; x++) { data[x] = (byte)r.Next(); } client.Put(id, hash, data); // Wait for the server to process the request since there isn't an ACK signal.WaitOne(); // Fetch the file we just put result = client.Get(id, hash); Assert.AreEqual <CacheResult>(result.Result, CacheResult.CacheHit); Assert.AreEqual(result.Data.Length, dataLen); for (int x = 0; x < data.Length; x++) { Assert.AreEqual <byte>(data[x], result.Data[x], "Data does not match at position {0}", x); } } finally { if (server.Status == ServerStatus.Running) { server.Stop(); } } } }