/// <summary> /// Insert or update file into index with the current timestamp. /// </summary> /// <param name="file"></param> /// <returns></returns> public bool Update(MyFile file) { paramPath.Value = file.name; paramType.Value = file.type.ToString(); // to ensure it is stored as a char/string instead of a numeric value paramModtime.Value = file.modtime; paramSize.Value = file.size; paramChecksum.Value = file.checksum; commandInsertOrReplace.ExecuteNonQuery(); return(true); }
private void processOutQueue() { if (outQueue.Count > 0) { setStatus(ClientStatus.SYNCING); sendCommandToServer(Signal.c2s); MyFile myFile = Common.SendFile(outQueue.Dequeue(), socket, dataDir); if (myFile != null) { fileIndex.Update(myFile); // TODO: perform this after server confirmation } processOutQueue(); } setStatus(ClientStatus.READY); }
/// <summary> /// Update or insert a new entry for the file into the database /// </summary> /// <returns> /// Flase if there was a problem during the update /// </returns> /// <param name='user'></param> /// <param name='thisFile'></param> public bool UpdateFile(ServerUser user, MyFile thisFile) { // TODO: use OwnCloud API calls if possible perhaps: http://owncloud.org/dev/apps/database/ string path = "/" + user.id + "/files/" + thisFile.name; string absPath = GetDataDir(user) + thisFile.name; //Server.baseDataDir + path; FileInfo f = new FileInfo(absPath); long mtime = Common.DateTimeToUnixTimestamp(f.LastWriteTimeUtc); DbCommand command_checkExists = dbConnection.CreateCommand(); command_checkExists.CommandText = "SELECT count(id) FROM oc_fscache WHERE path='" + path + "'"; int checkFound = Convert.ToInt32(command_checkExists.ExecuteScalar()); DbCommand command = dbConnection.CreateCommand(); if (checkFound > 0) { command.CommandText = "UPDATE oc_fscache SET mtime='" + mtime + "' WHERE path='" + path + "'"; } else { // if the entry does not exist, insert it instead of updating it long ctime = Common.DateTimeToUnixTimestamp(f.CreationTimeUtc); DbCommand command_getParent = dbConnection.CreateCommand(); command_getParent.CommandText = "SELECT id FROM oc_fscache WHERE path_hash='" + Common.Md5Hash(path.Substring(0, path.LastIndexOf('/'))) + "'"; int parentId = Convert.ToInt32(command_getParent.ExecuteScalar()); string mimetype = MIMEAssistant.GetMIMEType(f.Name); string mimepart = mimetype.Substring(0, mimetype.LastIndexOf('/')); bool writable = true; //!f.IsReadOnly; bool encrypted = false; // ? bool versioned = false; // ? command.CommandText = String.Format("INSERT INTO oc_fscache (parent, name, path, path_hash, size, mtime, ctime, mimetype, mimepart,`user`,writable,encrypted,versioned) " + "VALUES('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', '{9}', '{10}', '{11}', '{12}')", parentId, f.Name, path, Common.Md5Hash(path), f.Length, mtime, ctime, mimetype, mimepart, user.id, writable, encrypted, versioned); } return(command.ExecuteNonQuery() == 1); }
public static MyFile SendFile(String relPath, Socket socket, String baseDir) { MyFile myFile = null; try { String fullPath = baseDir + relPath; byte[] fileName = Encoding.UTF8.GetBytes(relPath); //file name byte[] fileNameLen = BitConverter.GetBytes((Int16)(fileName.Length)); //length of file name byte[] fileData = File.ReadAllBytes(fullPath); //file byte[] fileDataLen = BitConverter.GetBytes(fileData.Length); // file length // TODO: make sure "file length" matches actual file size long modtime = Common.GetModTime(fullPath); // TODO: make timestamps into int byte[] timestamp = BitConverter.GetBytes(modtime); // assume long = int64 = 8 bytes // temporarially calc checksum here, though should be done higher up byte[] checksum = FileChecksumToBytes(fullPath); String checksumString = BitConverter.ToString(checksum).Replace("-", String.Empty).ToLower(); Console.WriteLine("Sending file " + relPath + " " + modtime); socket.Send(fileNameLen);//2 socket.Send(fileName); socket.Send(timestamp); //8 socket.Send(checksum); //16 bytes, or 32 characters? socket.Send(fileDataLen); //4, TODO: set this to 8 bits for files larger then 4GB ? socket.Send(fileData); myFile = new MyFile(relPath, 'f', modtime, fileData.Length, checksumString); } catch (Exception e) { Console.WriteLine("Operation failed: " + e.Message); } return(myFile); }
/// <summary> /// Handle input signals from the client /// </summary> /// <param name="signal"></param> private void handleInput(Signal signal) { Console.WriteLine("Handling input for signal " + signal); switch (signal) { case Signal.c2s: MyFile newFile = Common.ReceiveFile(socket, dataDir); if (newFile != null) { server.serverDB.UpdateFile(User, newFile); } server.SpanCatchupOperation(handle, User.id, signal, newFile.name); break; //case Signal.clientWantsToSend: // String relPath = Common.ReceiveString(socket); // long timestamp = Common.ReceiveTimestamp(socket); // sendCommandToClient(Signal.clientWantsToSend_response); // Common.SendString(socket, relPath); // // reply 'yes' if it refers to a file that does not exist or if the times do not match // if (File.Exists(dataDir + relPath) && Common.GetModTime(dataDir + relPath) == timestamp) { // Common.SendString(socket, "no"); // } // else { // Common.SendString(socket, "yes"); // } // break; case Signal.clientWants: String relPath = Common.ReceiveString(socket); if (File.Exists(dataDir + relPath)) { outQueue.Enqueue(relPath); processOutQueue(); } break; case Signal.deleteOnServer: relPath = Common.ReceiveString(socket); if (Common.DeleteLocal(dataDir + relPath)) { server.serverDB.RemoveFile(User, relPath); } // index.Remove(relPath); // TODO: check return value server.SpanCatchupOperation(handle, User.id, signal, relPath); break; case Signal.createDirectoryOnServer: relPath = Common.ReceiveString(socket); if (Common.CreateLocalDirectory(dataDir + relPath)) { server.serverDB.UpdateFile(User, new MyFile(relPath, 'd', Common.GetModTime(dataDir + relPath) , 0, "0")); } server.SpanCatchupOperation(handle, User.id, signal, relPath); break; case Signal.requestServerFileList: List <List <string> > fileListToSerialize = server.serverDB.GetFileListSerializable(User); String jsonOutStringFiles = JsonConvert.SerializeObject(fileListToSerialize); Console.WriteLine("sending json file list: " + jsonOutStringFiles); try { sendCommandToClient(Signal.requestServerFileList_response); Common.SendString(socket, jsonOutStringFiles); } catch (Exception e) { Console.WriteLine("Error during " + Signal.requestServerFileList_response + e.Message); Common.ExitError(); } break; case Signal.attachaccount: String args = Common.ReceiveString(socket); Console.WriteLine("received " + args); List <string> attachInput = JsonConvert.DeserializeObject <List <string> >(args); String userName = attachInput[0]; String password = attachInput[1]; Dictionary <string, string> jsonOut = new Dictionary <string, string>(); jsonOut.Add("serverMyboxVersion", Common.AppVersion); if (attachUser(userName, password)) { jsonOut.Add("status", "success"); //jsonOut.Add("quota", Account.quota.ToString()); //jsonOut.Add("salt", Account.salt); server.AddToMultiMap(User.id, handle); } else { jsonOut.Add("status", "failed"); jsonOut.Add("error", "login invalid"); close(); // TODO: disconnect the client here } String jsonOutString = JsonConvert.SerializeObject(jsonOut); try { sendCommandToClient(Signal.attachaccount_response); Common.SendString(socket, jsonOutString); } catch (Exception e) { Console.WriteLine("Error during " + Signal.attachaccount_response + e.Message); Common.ExitError(); } Console.WriteLine("attachaccount_response: " + jsonOutString); break; default: Console.WriteLine("Unknown command"); break; } }
/// <summary> /// Compares the client to the client index to the server /// </summary> private void fullSync() { writeMessage("disabling listener"); disableDirListener(); // hack while incoming set gets figured out setStatus(ClientStatus.SYNCING); writeMessage("fullSync started " + DateTime.Now); // TODO: update all time comparisons to respect server/client time differences // populate S // file list according to server bool listReturned = serverDiscussion(Signal.requestServerFileList, Signal.requestServerFileList_response, null); if (!listReturned) { throw new Exception("requestServerFileList did not return in time"); } // file list according to client filesystem Dictionary <String, MyFile> C = getLocalFileList(); // file list according to client index database Dictionary <String, MyFile> I = fileIndex.GetFiles(); // holds the name=>action according to the I vs C vs S comparison Dictionary <String, Signal> fileActionList = new Dictionary <string, Signal>(); // here we make the assumption that fullSync() is only called once and right after initial connection long lastSync = -1; if (fileIndex.FoundAtInit) { lastSync = fileIndex.LastUpdate; } writeMessage("fullSync comparing C=" + C.Count + " to I=" + I.Count + " to S=" + S.Count + " lastSync=" + lastSync); foreach (KeyValuePair <String, MyFile> file in C) { String name = file.Key; MyFile c = file.Value; if (I.ContainsKey(name)) { // TODO: handle conflicts where a file and directory have the same name MyFile i = I[name]; // if it is a file if (!Directory.Exists(dataDir + name)) { if (c.modtime != i.modtime) // if times differ { writeMessage(name + " " + " c.modtime=" + c.modtime + " i.modtime=" + i.modtime); writeMessage(name + " = transfer from client to server since file changed"); fileActionList.Add(name, Signal.c2s); } } I.Remove(name); // if it is a directory, do nothing because the files will already be coppied one by one } else // if it exists in C and not in index, push to server { if (Directory.Exists(dataDir + name)) { writeMessage(name + " = create directory on server since new directory"); fileActionList.Add(name, Signal.createDirectoryOnServer); } else { writeMessage(name + " = transfer from client to server since new file"); fileActionList.Add(name, Signal.c2s); } } } // now process items that are in the index and not on the client foreach (KeyValuePair <String, MyFile> file in I) { // delete file or directory on server writeMessage(file.Key + " = remove from server since it is in I but not C"); fileActionList.Add(file.Key, Signal.deleteOnServer); } writeMessage("finished addressing C vs I"); // TODO: handle case where there is the same name item but it is a file on the client and dir on server foreach (KeyValuePair <String, MyFile> file in C) { String name = file.Key; MyFile c = file.Value; if (S.ContainsKey(name)) { MyFile s = S[name]; // if it is not a directory and the times are different, compare times if (!Directory.Exists(dataDir + name) && c.modtime != s.modtime) { writeMessage(name + " " + lastSync + " c.modtime=" + c.modtime + " s.modtime=" + s.modtime); if (lastSync == -1) { writeMessage(name + " = conflict, since index is gone the newest file cannot be determined"); } else if (c.modtime > lastSync) { if (s.modtime > lastSync) { writeMessage(name + " = conflict (both client and server file are new)"); } else { writeMessage(name + " = transfer from client to server 1"); if (fileActionList.ContainsKey(name)) // can this be turned into a nested function perhaps? { if (fileActionList[name] != Signal.c2s) { // conflict } } else { fileActionList.Add(name, Signal.c2s); } } } else { if (s.modtime > c.modtime) { writeMessage(name + " = transfer from server to client 1"); if (fileActionList.ContainsKey(name)) { if (fileActionList[name] != Signal.clientWants) { // conflict } } else { fileActionList.Add(name, Signal.clientWants); } // TODO: set overlay icon } else { writeMessage(name + " = conflict (both client and server file are old)"); } } } S.Remove(name); } else { if (c.modtime > lastSync) // will occur if index is missing since lastSync will be -1, thus performing a merge { if (Directory.Exists(dataDir + name)) { writeMessage(name + " = create directory on server"); if (fileActionList.ContainsKey(name)) { if (fileActionList[name] != Signal.createDirectoryOnServer) { // conflict } } else { fileActionList.Add(name, Signal.createDirectoryOnServer); } } else { writeMessage(name + " = transfer from client to server 2"); if (fileActionList.ContainsKey(name)) { if (fileActionList[name] != Signal.c2s) { // conflict } } else { fileActionList.Add(name, Signal.c2s); } } } else { writeMessage(name + " = remove on client"); if (fileActionList.ContainsKey(name)) { if (fileActionList[name] != Signal.deleteOnClient) { // conflict } } else { fileActionList.Add(name, Signal.deleteOnClient); } } } } foreach (KeyValuePair <String, MyFile> file in S) { String name = file.Key; MyFile s = file.Value; if (s.modtime > lastSync) // will occur if index is missing since lastSync will be -1, thus performing a merge { if (s.type == 'd') { writeMessage(name + " = create local directory on client"); if (fileActionList.ContainsKey(name)) { if (fileActionList[name] != Signal.createDirectoryOnClient) { // conflict } } else { fileActionList.Add(name, Signal.createDirectoryOnClient); } } else { writeMessage(name + " = transfer from server to client 2"); if (fileActionList.ContainsKey(name)) { if (fileActionList[name] != Signal.clientWants) { // conflict } } else { fileActionList.Add(name, Signal.clientWants); } // TODO: set overlay icon } } else { writeMessage(name + " = remove from server"); // file or directory if (fileActionList.ContainsKey(name)) { if (fileActionList[name] != Signal.deleteOnServer) { // conflict } } else { fileActionList.Add(name, Signal.deleteOnServer); } } } // now process the fileLists writeMessage("Processing " + fileActionList.Count + " items on action list..."); foreach (KeyValuePair <String, Signal> signalItem in fileActionList) { writeMessage(" " + signalItem.Key + " => " + signalItem.Value); switch (signalItem.Value) { case Signal.c2s: outQueue.Enqueue(signalItem.Key); break; case Signal.createDirectoryOnServer: createDirectoryOnServer(signalItem.Key); break; case Signal.deleteOnServer: deleteOnServer(signalItem.Key); break; case Signal.clientWants: requestFile(signalItem.Key); break; case Signal.deleteOnClient: if (Common.DeleteLocal(dataDir + signalItem.Key)) { fileIndex.Remove(signalItem.Key); } break; case Signal.createDirectoryOnClient: if (Common.CreateLocalDirectory(dataDir + signalItem.Key)) { fileIndex.Update(new MyFile(signalItem.Key, 'd', Common.GetModTime(dataDir + signalItem.Key), 0, "0")); } break; default: throw new Exception("Unhandled signal in action list"); } } processOutQueue(); writeMessage("enableing listener since sync is done"); enableDirListener(); writeMessage("Sync finished " + DateTime.Now); if (incommingFiles.Count == 0) { setStatus(ClientStatus.READY); } Thread.Sleep(2000); checkSync(); }
/// <summary> /// Running sync routine that compares the client files to the index. Does not consult the server. /// </summary> private void catchupSync() { writeMessage("disabling listener"); disableDirListener(); // hack while incoming set gets figured out setStatus(ClientStatus.SYNCING); // get full local file list Dictionary <String, MyFile> C = getLocalFileList(); // get index list Dictionary <String, MyFile> I = fileIndex.GetFiles(); // compare to local DB writeMessage("catchupSync comparing C=" + C.Count + " to I=" + I.Count); // TODO: index updates should be transactioned/prepared foreach (KeyValuePair <String, MyFile> file in C) { String name = file.Key; MyFile c = file.Value; if (I.ContainsKey(name)) { // TODO: handle conflicts where a file and directory have the same name MyFile i = I[name]; // if it is a file if (!Directory.Exists(dataDir + name)) { if (c.modtime != i.modtime) // if times differ { writeMessage(name + " c.modtime=" + c.modtime + " i.modtime=" + i.modtime); // if times differ, push the file to the server and update the index writeMessage(name + " = transfer from client to server since file changed"); outQueue.Enqueue(c.name); } } I.Remove(name); // if it is a directory, do nothing because the files will already be coppied one by one } else // if it exists in C and not in index, push to server { if (Directory.Exists(dataDir + name)) { writeMessage(name + " = create directory on server since new directory"); createDirectoryOnServer(name); } else { writeMessage(name + " = transfer from client to server since new file"); outQueue.Enqueue(c.name); } } } // now process items that are in the index and not on the client foreach (KeyValuePair <String, MyFile> file in I) { writeMessage(file.Key + " = remove from server"); deleteOnServer(file.Key); // delete file or directory on server } // push changes to server processOutQueue(); writeMessage("enableing listener since sync is done"); enableDirListener(); // writeMessage("Sync finished " + DateTime.UtcNow); if (outQueue.Count == 0) { setStatus(ClientStatus.READY); } // TODO: set to READY once incomming finish Thread.Sleep(2000); checkSync(); }
/// <summary> /// Deal with incoming signal from server /// </summary> /// <param name="signal"></param> private void handleInput(Signal signal) { if (paused) { return; } writeMessage("Handling input for signal " + signal); // TODO: make sure these all update the index setStatus(ClientStatus.SYNCING); switch (signal) { case Signal.s2c: MyFile newFile = Common.ReceiveFile(socket, dataDir); if (newFile != null) { fileIndex.Update(newFile); incommingFiles.Remove(newFile.name); setOverlay(true); } break; case Signal.deleteOnClient: // catchup operation String relPath = Common.ReceiveString(socket); if (Common.DeleteLocal(dataDir + relPath)) { fileIndex.Remove(relPath); } break; case Signal.createDirectoryOnClient: // catchup operation relPath = Common.ReceiveString(socket); if (Common.CreateLocalDirectory(dataDir + relPath)) { fileIndex.Update(new MyFile(relPath, 'd', Common.GetModTime(dataDir + relPath), 0, "0")); } break; case Signal.requestServerFileList_response: String jsonStringFiles = Common.ReceiveString(socket); List <List <string> > fileDict = JsonConvert.DeserializeObject <List <List <string> > >(jsonStringFiles); S.Clear(); foreach (List <string> fileItem in fileDict) { // TODO: try, catch for parse errors etc S.Add(fileItem[0], new MyFile(fileItem[0], char.Parse(fileItem[1]), long.Parse(fileItem[2]), long.Parse(fileItem[3]), fileItem[4])); } break; case Signal.attachaccount_response: // TODO: replace JSON parser with simple text parsing so we dont have to lug around the dependency Dictionary <string, string> jsonMap = JsonConvert.DeserializeObject <Dictionary <string, string> >(Common.ReceiveString(socket)); if (jsonMap["status"] != "success")// TODO: change to signal { writeMessage("Unable to attach account. Server response: " + jsonMap["error"]); // TODO: catch these exceptions above somewhere //throw new Exception("Unable to attach account. Server response: " + jsonMap["error"]); //socket.Close(); Stop(); } else { //writeMessage("set account salt to: " + account.Salt); if (Common.AppVersion != jsonMap["serverMyboxVersion"]) { writeMessage("Client and Server Mybox versions do not match"); } } break; default: writeMessage("Unknown command from server: " + signal); break; } lastReceivedOperation = signal; if (incommingFiles.Count == 0 && outQueue.Count == 0) { setStatus(ClientStatus.READY); } }
/// <summary> /// Receive a file over a socket /// </summary> /// <param name="socket"></param> /// <param name="baseDir">the base directory the file will live in</param> /// <returns></returns> public static MyFile ReceiveFile(Socket socket, string baseDir) { byte[] buffer = new byte[buf_size]; MyFile myFile = null; try { // receive order: name length, name, timestamp, checksum, data length, data socket.Receive(buffer, 2, 0); Int16 nameLength = BitConverter.ToInt16(buffer, 0); socket.Receive(buffer, nameLength, 0); String relPath = System.Text.Encoding.UTF8.GetString(buffer, 0, nameLength); // timestamp socket.Receive(buffer, 8, 0); // TODO: make timestamps into int DateTime timestamp = UnixTimeStampToDateTime(BitConverter.ToInt64(buffer, 0)); // checksum socket.Receive(buffer, 16, 0); String checksumString = BitConverter.ToString(buffer, 0, 16).Replace("-", String.Empty).ToLower(); String tempLocation = TempDir + Path.DirectorySeparatorChar + "mb" + DateTimeToUnixTimestamp(DateTime.Now).ToString() + _random.Next(0, 26).ToString() + _random.Next(0, 26).ToString(); Console.WriteLine("Receiving file: " + relPath); Console.WriteLine(" timestmp:" + DateTimeToUnixTimestamp(timestamp) + " checksum: " + checksumString); Console.WriteLine(" temp: " + tempLocation); // data socket.Receive(buffer, 4, 0); // assumes filesize cannot be larger then int bytes, 4GB? Int32 fileLength = BitConverter.ToInt32(buffer, 0); int fileBytesRead = 0; MD5 md5 = MD5.Create(); using (FileStream fs = File.Create(tempLocation, buf_size)) { using (CryptoStream cs = new CryptoStream(fs, md5, CryptoStreamMode.Write)) { while (fileBytesRead + buf_size <= fileLength) { fileBytesRead += socket.Receive(buffer, buf_size, 0); cs.Write(buffer, 0, buf_size); } if (fileBytesRead < fileLength) { socket.Receive(buffer, fileLength - fileBytesRead, 0); // make sure this reads to the end cs.Write(buffer, 0, fileLength - fileBytesRead); } } } String calculatedChecksum = BitConverter.ToString(md5.Hash).Replace("-", String.Empty).ToLower(); // network fault tolerance, varify checksum before moving file from temp to dir System.Console.WriteLine(" calculated checksum: " + calculatedChecksum); if (calculatedChecksum == checksumString) { File.Move(tempLocation, baseDir + relPath); File.SetLastWriteTimeUtc(baseDir + relPath, timestamp); myFile = new MyFile(relPath, 'f', timestamp.Ticks, fileLength, checksumString); } else { throw new Exception("Received file checksum did not match"); } } catch (Exception e) { Console.WriteLine("Operation failed: " + e.Message); } return(myFile); }
/// <summary> /// Update or insert a new entry for the file into the database /// </summary> /// <returns> /// Flase if there was a problem during the update /// </returns> /// <param name='user'></param> /// <param name='thisFile'></param> public bool UpdateFile(ServerUser user, MyFile thisFile) { // TODO: use OwnCloud API calls if possible perhaps: http://owncloud.org/dev/apps/database/ string path = "/" + user.id + "/files/" + thisFile.name; string absPath = GetDataDir(user) + thisFile.name; //Server.baseDataDir + path; FileInfo f = new FileInfo (absPath); long mtime = Common.DateTimeToUnixTimestamp(f.LastWriteTimeUtc); DbCommand command_checkExists = dbConnection.CreateCommand(); command_checkExists.CommandText = "SELECT count(id) FROM oc_fscache WHERE path='"+ path +"'"; int checkFound = Convert.ToInt32(command_checkExists.ExecuteScalar()); DbCommand command = dbConnection.CreateCommand(); if (checkFound > 0) { command.CommandText = "UPDATE oc_fscache SET mtime='" + mtime + "' WHERE path='"+ path +"'"; } else { // if the entry does not exist, insert it instead of updating it long ctime = Common.DateTimeToUnixTimestamp(f.CreationTimeUtc); DbCommand command_getParent = dbConnection.CreateCommand (); command_getParent.CommandText = "SELECT id FROM oc_fscache WHERE path_hash='" + Common.Md5Hash(path.Substring(0, path.LastIndexOf('/'))) + "'"; int parentId = Convert.ToInt32 (command_getParent.ExecuteScalar ()); string mimetype = MIMEAssistant.GetMIMEType(f.Name); string mimepart = mimetype.Substring(0, mimetype.LastIndexOf('/')); bool writable = true; //!f.IsReadOnly; bool encrypted = false; // ? bool versioned = false; // ? command.CommandText = String.Format("INSERT INTO oc_fscache (parent, name, path, path_hash, size, mtime, ctime, mimetype, mimepart,`user`,writable,encrypted,versioned) " + "VALUES('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', '{9}', '{10}', '{11}', '{12}')", parentId, f.Name, path, Common.Md5Hash(path), f.Length, mtime, ctime, mimetype, mimepart, user.id, writable, encrypted, versioned); } return (command.ExecuteNonQuery() == 1); }
public static MyFile SendFile(String relPath, Socket socket, String baseDir) { MyFile myFile = null; try { String fullPath = baseDir + relPath; byte[] fileName = Encoding.UTF8.GetBytes(relPath); //file name byte[] fileNameLen = BitConverter.GetBytes((Int16)(fileName.Length)); //length of file name byte[] fileData = File.ReadAllBytes(fullPath); //file byte[] fileDataLen = BitConverter.GetBytes(fileData.Length); // file length // TODO: make sure "file length" matches actual file size long modtime = Common.GetModTime(fullPath); // TODO: make timestamps into int byte[] timestamp = BitConverter.GetBytes(modtime); // assume long = int64 = 8 bytes // temporarially calc checksum here, though should be done higher up byte[] checksum = FileChecksumToBytes(fullPath); String checksumString = BitConverter.ToString(checksum).Replace("-", String.Empty).ToLower(); Console.WriteLine("Sending file " + relPath + " " + modtime); socket.Send(fileNameLen);//2 socket.Send(fileName); socket.Send(timestamp);//8 socket.Send(checksum);//16 bytes, or 32 characters? socket.Send(fileDataLen);//4, TODO: set this to 8 bits for files larger then 4GB ? socket.Send(fileData); myFile = new MyFile(relPath, 'f', modtime, fileData.Length, checksumString); } catch (Exception e) { Console.WriteLine("Operation failed: " + e.Message); } return myFile; }
/// <summary> /// Receive a file over a socket /// </summary> /// <param name="socket"></param> /// <param name="baseDir">the base directory the file will live in</param> /// <returns></returns> public static MyFile ReceiveFile(Socket socket, string baseDir) { byte[] buffer = new byte[buf_size]; MyFile myFile = null; try { // receive order: name length, name, timestamp, checksum, data length, data socket.Receive(buffer, 2, 0); Int16 nameLength = BitConverter.ToInt16(buffer, 0); socket.Receive(buffer, nameLength, 0); String relPath = System.Text.Encoding.UTF8.GetString(buffer, 0, nameLength); // timestamp socket.Receive(buffer, 8, 0); // TODO: make timestamps into int DateTime timestamp = UnixTimeStampToDateTime(BitConverter.ToInt64(buffer, 0)); // checksum socket.Receive(buffer, 16, 0); String checksumString = BitConverter.ToString(buffer, 0, 16).Replace("-", String.Empty).ToLower(); String tempLocation = TempDir + Path.DirectorySeparatorChar + "mb" + DateTimeToUnixTimestamp(DateTime.Now).ToString() + _random.Next(0, 26).ToString() + _random.Next(0, 26).ToString(); Console.WriteLine("Receiving file: " + relPath); Console.WriteLine(" timestmp:" + DateTimeToUnixTimestamp(timestamp) + " checksum: " + checksumString); Console.WriteLine(" temp: " + tempLocation); // data socket.Receive(buffer, 4, 0); // assumes filesize cannot be larger then int bytes, 4GB? Int32 fileLength = BitConverter.ToInt32(buffer, 0); int fileBytesRead = 0; MD5 md5 = MD5.Create(); using (FileStream fs = File.Create(tempLocation, buf_size)) { using (CryptoStream cs = new CryptoStream(fs, md5, CryptoStreamMode.Write)) { while (fileBytesRead + buf_size <= fileLength) { fileBytesRead += socket.Receive(buffer, buf_size, 0); cs.Write(buffer, 0, buf_size); } if (fileBytesRead < fileLength) { socket.Receive(buffer, fileLength - fileBytesRead, 0); // make sure this reads to the end cs.Write(buffer, 0, fileLength - fileBytesRead); } } } String calculatedChecksum = BitConverter.ToString(md5.Hash).Replace("-", String.Empty).ToLower(); // network fault tolerance, varify checksum before moving file from temp to dir System.Console.WriteLine(" calculated checksum: " + calculatedChecksum); if (calculatedChecksum == checksumString) { File.Move(tempLocation, baseDir + relPath); File.SetLastWriteTimeUtc(baseDir + relPath, timestamp); myFile = new MyFile(relPath, 'f', timestamp.Ticks, fileLength, checksumString); } else throw new Exception("Received file checksum did not match"); } catch (Exception e) { Console.WriteLine("Operation failed: " + e.Message); } return myFile; }