/// <summary> /// Sends contents of a file to a client. /// </summary> /// <param name="reader">StreamReader used to ask the client for acknowledgement.</param> /// <param name="writer">StreamWriter used to send the data.</param> /// <param name="name">Path to the file.</param> /// <returns></returns> private bool SendFileContents(StreamReader reader, StreamWriter writer, string name) { try { using (var fileReader = new StreamReader(System.IO.File.OpenRead(name), encoding)) { string line; writer.WriteLine(name); if (ProtocolHelper.ExtractProtocol(reader.ReadLine()) != Protocol.SUCCESS) { return(true); } while ((line = fileReader.ReadLine()) != null) { writer.WriteLine(line); } writer.WriteLine(Protocol.TRANSMISSION_END); } return(true); } catch (IOException) { try { writer.WriteLine(Protocol.FAIL); return(true); } catch (IOException) { return(false); } } }
/// <summary> /// Requests (and reads if authorized) contents of a given file from /// the server. /// </summary> /// <param name="reader">StreamReader used to read authorization result and the contents of the file.</param> /// <param name="writer">StreamWriter used to send the request.</param> /// <param name="file">Path to the file.</param> private void RequestFileContents(StreamReader reader, StreamWriter writer, string file) { writer.WriteLine(Protocol.REQUEST_FILE_CONTENTS); writer.WriteLine(subTreePrefix + file); string line = reader.ReadLine(); string tmpFile = file + ".tmp"; if (ProtocolHelper.ExtractProtocol(line) == Protocol.SUCCESS) { DB.File tmp = null; if (!db.GetFiles().TryGet(file, out tmp)) { tmp = new DB.File(file); db.GetFiles().Add(tmp); } try { reader.ReadLine(); // File name resent, here we can discard it. writer.WriteLine(Protocol.SUCCESS); Directory.CreateDirectory(Path.GetDirectoryName(file)); using (var stream = System.IO.File.OpenWrite(tmpFile)) using (var fileWriter = new StreamWriter(stream, encoding)) { while ((line = reader.ReadLine()) != null && line != "TRANSMISSION_END" && line != "FAIL") { fileWriter.WriteLine(line); } } FileHelper.Move(tmpFile, file); tmp.DateModified = System.IO.File.GetLastWriteTime(file); } catch (SocketException) { // This should be connection interruption or time out. FileHelper.Delete(tmpFile); } catch (IOException) { // Target file in use or nonexistent. FileHelper.Delete(tmpFile); } } }
/// <summary> /// Asks the server for information (modification time) about all files /// this client has read access to and requests contents if necessary. /// </summary> private void PerformInitialRequest() { var filesToRequest = new List <string>(); var filesOnServer = new List <string>(); TcpClient client = null; try { client = ConnectToServer(); if (client == null) { return; } using (var stream = client.GetStream()) using (var reader = new StreamReader(stream, encoding)) using (var writer = new StreamWriter(stream, encoding)) { writer.AutoFlush = true; reader.BaseStream.ReadTimeout = 10000; string line; string fileName; DB.File file; Protocol message; writer.WriteLine(Protocol.AUTHENTICATE); writer.WriteLine(name); writer.WriteLine(passwordHash); line = reader.ReadLine(); message = ProtocolHelper.ExtractProtocol(line); if (message != Protocol.SUCCESS) { return; } writer.WriteLine(Protocol.REQUEST_INITIAL_INFO); line = reader.ReadLine(); message = ProtocolHelper.ExtractProtocol(line); if (message == Protocol.SUCCESS) { var files = db.GetFiles(); while (true) { fileName = reader.ReadLine(); if (fileName.StartsWith(subTreePrefix + TrackedDirectory + @"\")) { fileName = fileName.Substring(subTreePrefix.Length); } else if (fileName == "TRANSMISSION_END") { break; } else { reader.ReadLine(); // Skip modification time. continue; } filesOnServer.Add(fileName); line = reader.ReadLine(); if (!files.TryGet(fileName, out file) || file.OlderThan(DateTime.Parse(line))) { filesToRequest.Add(fileName); } } foreach (var f in filesToRequest) { RequestFileContents(reader, writer, f); } } writer.WriteLine(Protocol.TRANSMISSION_END); client.Close(); var filesToDelete = db.GetFiles().GetDictionary().Keys.ToList().Except(filesOnServer); foreach (var f in filesToDelete) { db.GetFiles().Remove(f); FileHelper.Delete(f); } } } catch (SocketException) { /* Time out. */ } catch (IOException) { /* File probably already deleted as RequestFileContents does not throw. */ } finally { db.Save(); if (client != null) { client.Close(); } } }
/// <summary> /// Informs server about all changes found during the last /// directory checks. /// </summary> private void InformServer() { if ((filesCreated.Count == 0 || !canCreateFiles) && filesChecked.Count == db.GetFiles().Count&& filesChanged.Count == 0) { // If checked == db files, we still need to clear the checked files otherwise deletes would be ignored // next time we check. lock (filesChecked) { if (filesChecked.Count > 0) { filesChecked.Clear(); } } return; } try { var client = ConnectToServer(); if (client == null) { return; } var files = db.GetFiles(); using (var stream = client.GetStream()) using (var writer = new StreamWriter(stream, encoding)) using (var reader = new StreamReader(stream, encoding)) { writer.AutoFlush = true; // Authentication of this session. writer.WriteLine(Protocol.AUTHENTICATE); writer.WriteLine(name); writer.WriteLine(passwordHash); if (ProtocolHelper.ExtractProtocol(reader.ReadLine()) != Protocol.SUCCESS) { writer.WriteLine(Protocol.TRANSMISSION_END); Console.WriteLine("[ERROR] Refused because of wrong credentials."); ClearLists(); return; } lock (filesCreated) { bool removeCreatedFiles = false; foreach (var file in filesCreated) { writer.WriteLine(Protocol.FILE_CREATED); writer.WriteLine(subTreePrefix + file); if (ProtocolHelper.ExtractProtocol(reader.ReadLine()) == Protocol.SUCCESS) { SendFileContents(writer, file); } else { writer.WriteLine(Protocol.TRANSMISSION_END); canCreateFiles = false; removeCreatedFiles = true; break; } } if (removeCreatedFiles) { // This removes those recently tracked and canCreateFiles == false will prevent new tracking. foreach (var file in filesCreated) { files.Remove(file); } } filesCreated.Clear(); } lock (filesChanged) { foreach (var file in filesChanged) { writer.WriteLine(Protocol.FILE_CHANGED); writer.WriteLine(subTreePrefix + file); if (ProtocolHelper.ExtractProtocol(reader.ReadLine()) == Protocol.SUCCESS) { SendFileContents(writer, file); } else { files.Remove(file); // Leave it physically on the disk, but do not track it. } } filesChanged.Clear(); } lock (filesDeleted) { filesDeleted = db.GetFiles().GetDictionary().Keys.ToList().Except(filesChecked).ToList(); lock (filesChecked) { filesChecked.Clear(); } foreach (var file in filesDeleted) { writer.WriteLine(Protocol.FILE_DELETED); writer.WriteLine(subTreePrefix + file); db.GetFiles().Remove(file); /* * Note: This is either SUCCESS or FAIL (depending of authorization). * Currently is ignored, assuming the user just wants to get rid of the file, * but it may be useful to add a config option to request the data of deleted * files to recover them if the deletion fails on the server. */ reader.ReadLine(); } filesDeleted.Clear(); } writer.WriteLine(Protocol.TRANSMISSION_END); } } catch (IOException e) { Console.WriteLine("[ERROR] Cannot inform the server: {0}", e.Message); ClearLists(); } finally { db.Save(); } }
/// <summary> /// Performs initial authentication either from config file, or /// (if it's missing or manual authentication is forced) from /// the console (and saves info on success). /// </summary> /// <returns>True if authentication succeeded, false otherwise.</returns> private bool Authenticate() { bool register = false; bool consoleAuthentication = false; if (forceManualAuthentization || !System.IO.File.Exists(userData)) { // Console authentication. consoleAuthentication = true; register = GetCredentialsFromConsole(out name, out passwordHash); } else { // Saved authentication. using (var stream = System.IO.File.OpenRead(userData)) using (var reader = new StreamReader(stream)) { name = reader.ReadLine(); passwordHash = reader.ReadLine(); } } bool authenticated = false; using (var client = ConnectToServer()) { if (client == null) { return(false); } using (var stream = client.GetStream()) using (var writer = new StreamWriter(stream, encoding)) using (var reader = new StreamReader(stream, encoding)) { // Actual authentication communication. writer.AutoFlush = true; writer.WriteLine(register ? Protocol.REGISTER : Protocol.AUTHENTICATE); writer.WriteLine(name); writer.WriteLine(passwordHash); Protocol response = ProtocolHelper.ExtractProtocol(reader.ReadLine()); authenticated = (response == Protocol.SUCCESS); if (authenticated) { writer.WriteLine(Protocol.NEW_CONNECTION); writer.WriteLine(listenPort); writer.WriteLine(Protocol.TRANSMISSION_END); } } } if (consoleAuthentication && authenticated) { // Save the credentials for later use. using (var stream = System.IO.File.Open(userData, FileMode.Create)) using (var writer = new StreamWriter(stream, encoding)) { writer.WriteLine(name); writer.WriteLine(passwordHash); writer.WriteLine(listenPort); } } return(authenticated); }
/// <summary> /// Handles a notification from the server (mostly /// regardin file changes). /// </summary> /// <param name="s">Socket used for the communication.</param> private void HandleServerNotice(Socket s) { try { using (var stream = new NetworkStream(s)) using (var reader = new StreamReader(stream, encoding)) using (var writer = new StreamWriter(stream, encoding)) { writer.AutoFlush = true; //reader.BaseStream.ReadTimeout = 10000; var message = ProtocolHelper.ExtractProtocol(reader.ReadLine()); string name = reader.ReadLine(); if (name.StartsWith(subTreePrefix + TrackedDirectory + @"\")) { name = name.Substring(subTreePrefix.Length); if (message != Protocol.FILE_DELETED) { writer.WriteLine(Protocol.SUCCESS); } else { reader.ReadLine(); // T_E token, can be ignored here. } } else { if (message != Protocol.FILE_DELETED) { writer.WriteLine(Protocol.FAIL); } else { reader.ReadLine(); // T_E token, can be ignored here. } writer.WriteLine(Protocol.TRANSMISSION_END); return; } if (message == Protocol.NONE || name == null) { return; } DB.File file = null; if (message == Protocol.FILE_CREATED || (name != null && !db.GetFiles().TryGet(name, out file) && message != Protocol.FILE_DELETED)) { file = new DB.File(name); file.DateModified = FileHelper.Create(file.Name); if (file.DateModified != DateTime.MaxValue) { db.GetFiles().Add(file); } } if (message == Protocol.FILE_DELETED) { FileHelper.Delete(name); if (file != null) { db.GetFiles().Remove(file); } reader.ReadLine(); // T_E token, can be ignored here. } else { lock (file) { string tmpName = name + ".tmp"; try { string line; using (var fstream = System.IO.File.OpenWrite(tmpName)) using (var fileWriter = new StreamWriter(fstream, encoding)) { while ((line = reader.ReadLine()) != null && line != "TRANSMISSION_END") { fileWriter.WriteLine(line); } fileWriter.BaseStream.SetLength(fileWriter.BaseStream.Position); } FileHelper.Move(tmpName, file.Name); file.DateModified = System.IO.File.GetLastWriteTime(file.Name); } catch (IOException e) { Console.WriteLine(e); // TODO: FileHelper.Delete(tmpName); } } } } } catch (IOException e) { // TODO: Console.WriteLine("HUH? {0}", e.Message); } finally { db.Save(); } }
/// <summary> /// Handles communication with a single client. /// </summary> /// <param name="s">Socket used for communication with the client.</param> private void HandleConnection(Socket s) { int newPort = 0; try { // Resend the client to another port. using (var writer = new StreamWriter(new NetworkStream(s), encoding)) { writer.AutoFlush = true; newPort = GetNewPort(); writer.WriteLine(newPort); var tmpListener = new TcpListener(IPAddress.Parse(serverAddress), newPort); tmpListener.Start(); // Just to be sure, close the original after establishing a new connection. var tmpSocket = tmpListener.AcceptSocket(); tmpListener.Stop(); s.Close(); s = tmpSocket; } using (var stream = new NetworkStream(s)) using (var reader = new StreamReader(stream, encoding)) using (var writer = new StreamWriter(stream, encoding)) { writer.AutoFlush = true; reader.BaseStream.ReadTimeout = 10000; string msg; User user = null; bool running = true; while (running) { msg = reader.ReadLine(); Protocol protocol = ProtocolHelper.ExtractProtocol(msg); if (protocol == Protocol.NONE) // Client crash/hang. { return; } switch (protocol) { case Protocol.AUTHENTICATE: user = HandleUserAuthentication(s, reader, writer); if (user == null) { running = false; } break; case Protocol.REGISTER: user = HandleUserRegistration(s, reader, writer); if (user == null) { running = false; } break; case Protocol.FILE_CHANGED: case Protocol.FILE_CREATED: case Protocol.FILE_DELETED: HandleFileUpdate(s, reader, writer, protocol, user); break; case Protocol.REQUEST_INITIAL_INFO: if (!SendInitialInfo(writer, user)) { running = false; } break; case Protocol.REQUEST_FILE_CONTENTS: HandleFileRequest(reader, writer, user); break; case Protocol.TRANSMISSION_END: running = false; break; case Protocol.NEW_CONNECTION: GetClientListenPort(reader, user); break; } } } } catch (IOException) { /* This should be timeout or connection close. */ } finally { ReleasePort(newPort); s.Close(); } }