private async Task <bool> SendFiles(NetworkStream networkStream, List <SyncFileInfo> dataToUpload, SyncDatabase syncDb) { foreach (var fileInfo in dataToUpload) { var filePath = Path.Combine(_baseDir, fileInfo.RelativePath); var fileLength = new FileInfo(filePath).Length; var data = new SendFileCommandData { FileLength = fileLength, SessionId = _sessionId, RelativeFilePath = fileInfo.RelativePath, HashStr = syncDb.Files.First(x => x.RelativePath == fileInfo.RelativePath).HashStr, }; var dataBytes = Serializer.Serialize(data); await NetworkHelperSequential.WriteCommandHeader(networkStream, Commands.SendFileCmd, dataBytes.Length); await NetworkHelperSequential.WriteBytes(networkStream, dataBytes); await NetworkHelperSequential.WriteFromFileAndHashAsync(networkStream, filePath, (int)fileLength); } return(true); }
private async Task ProcessGetFileCmd(CommandHeader cmdHeader) { var data = await NetworkHelperSequential.Read <GetFileCommandData>(_networkStream, cmdHeader.PayloadLength); var ret = new ServerResponse(); var session = SessionStorage.Instance.GetSession(data.SessionId); if (session == null) { ret.ErrorMsg = "Session does not exist"; } else if (session.Expired) { ret.ErrorMsg = "Session has expired"; //Log?.Invoke("Session has expired"); //return ret; } else { data.RelativeFilePath = PathHelpers.NormalizeRelative(data.RelativeFilePath); Msg?.Invoke($"Sending '{data.RelativeFilePath}'"); var filePath = Path.Combine(session.BaseDir, data.RelativeFilePath); var fileLength = new FileInfo(filePath).Length; var fileLengthBytes = BitConverter.GetBytes(fileLength); await NetworkHelperSequential.WriteCommandHeader(_networkStream, Commands.GetFileCmd, sizeof(long)); await NetworkHelperSequential.WriteBytes(_networkStream, fileLengthBytes); await NetworkHelperSequential.WriteFromFileAndHashAsync(_networkStream, filePath, (int)fileLength); } }
private async Task ProcessFinishSessionCmd(CommandHeader cmdHeader) { var sessionId = await NetworkHelperSequential.Read <Guid>(_networkStream, cmdHeader.PayloadLength); var response = new ServerResponseWithData <SyncInfo>(); var session = SessionStorage.Instance.GetSession(sessionId); if (session == null) { response.ErrorMsg = "Session does not exist"; } else if (session.Expired) { response.ErrorMsg = "Session has expired"; //Log?.Invoke("Session has expired"); //return ret; } else { try { FinishSession(session); } catch (Exception e) { response.ErrorMsg = e.ToString(); } //session.SyncDb = SyncDatabase.Initialize(session.BaseDir, session.SyncDbDir); session.SyncDb.Store(session.SyncDbDir); } await CommandHelper.WriteCommandResponse(_networkStream, Commands.FinishSessionCmd, response); }
public static SyncDatabase Initialize(string baseDir, string syncDbDir) { var localFiles = Directory.GetFiles(baseDir, "*", SearchOption.AllDirectories); var inside = syncDbDir.StartsWith(baseDir); var localInfos = localFiles.Select(i => { if (inside && i.StartsWith(syncDbDir)) { return(null); } { var hash = NetworkHelperSequential.HashFileAsync(new FileInfo(i)).Result; return(new SyncFileInfo { HashStr = hash.ToHashString(), RelativePath = i.Replace(baseDir, string.Empty).TrimStart(Path.DirectorySeparatorChar), AbsolutePath = i, State = SyncFileState.New, }); } }).Where(i => i != null).ToList(); return(new SyncDatabase { Files = localInfos, }); }
private async Task <ServerResponse> FinishSession(Stream networkStream, Guid sessionId) { var cmdDataBytes = Serializer.Serialize(sessionId); await NetworkHelperSequential.WriteCommandHeader(networkStream, Commands.FinishSessionCmd, cmdDataBytes.Length); await NetworkHelperSequential.WriteBytes(networkStream, cmdDataBytes); var cmdHeader = await NetworkHelperSequential.ReadCommandHeader(networkStream); if (cmdHeader.Command != Commands.FinishSessionCmd) { return new ServerResponseWithData <SyncInfo> { ErrorMsg = "Wrong command received" } } ; if (cmdHeader.PayloadLength == 0) { return new ServerResponseWithData <SyncInfo> { ErrorMsg = "No data received" } } ; var responseBytes = await NetworkHelperSequential.ReadBytes(networkStream, cmdHeader.PayloadLength); var response = Serializer.Deserialize <ServerResponse>(responseBytes); return(response); }
private async Task <ServerResponseWithData <Guid> > GetSession(Stream networkStream) { await NetworkHelperSequential.WriteCommandHeader(networkStream, Commands.GetSessionCmd); var cmdHeader = await NetworkHelperSequential.ReadCommandHeader(networkStream); if (cmdHeader.Command != Commands.GetSessionCmd) { return(new ServerResponseWithData <Guid> { ErrorMsg = "Wrong command received" }); } if (cmdHeader.PayloadLength == 0) { return(new ServerResponseWithData <Guid> { ErrorMsg = "No data received" }); } var responseBytes = await NetworkHelperSequential.ReadBytes(networkStream, cmdHeader.PayloadLength); var response = Serializer.Deserialize <ServerResponseWithData <Guid> >(responseBytes); return(response); }
private async Task ProcessCommands() { try { var cmdHeader = await NetworkHelperSequential.ReadCommandHeader(_networkStream); switch (cmdHeader.Command) { case Commands.GetSessionCmd: await ProcessGetSessionCmd(); break; case Commands.GetSyncListCmd: await ProcessGetSyncListCmd(cmdHeader); break; case Commands.GetFileCmd: await ProcessGetFileCmd(cmdHeader); break; case Commands.SendFileCmd: await ProcessSendFileCmd(cmdHeader); break; case Commands.FinishSessionCmd: await ProcessFinishSessionCmd(cmdHeader); break; case Commands.DisconnectCmd: _connected = false; break; default: _connected = false; break; } } catch (Exception e) { if (_connected) { Debugger.Break(); _connected = false; Console.WriteLine($"Unexpected error:\r\n{e}"); } else { // client disconnected Console.WriteLine("Unexpected error but client already disconnected, ignoring..."); } } }
public static async Task WriteCommandResponse <T>(NetworkStream stream, byte command, T data) { var responseBytes = Serializer.Serialize(data); var length = responseBytes.Length; await NetworkHelperSequential.WriteCommandHeader(stream, command, length); await NetworkHelperSequential.WriteBytes(stream, responseBytes); }
private async Task <bool> ReceiveFiles(Stream networkStream, IEnumerable <SyncFileInfo> dataToDownload, SyncDatabase syncDb) { foreach (var fileInfo in dataToDownload) { var data = new GetFileCommandData { SessionId = _sessionId, RelativeFilePath = fileInfo.RelativePath, }; var dataBytes = Serializer.Serialize(data); await NetworkHelperSequential.WriteCommandHeader(networkStream, Commands.GetFileCmd, dataBytes.Length); await NetworkHelperSequential.WriteBytes(networkStream, dataBytes); var cmdHeader = await NetworkHelperSequential.ReadCommandHeader(networkStream); if (cmdHeader.Command != Commands.GetFileCmd) { return(false); } if (cmdHeader.PayloadLength == 0) { return(false); } var fileLengthBytes = await NetworkHelperSequential.ReadBytes(networkStream, cmdHeader.PayloadLength); var fileLength = BitConverter.ToInt64(fileLengthBytes, 0); var tmpFilePath = Path.Combine(_newDir, fileInfo.RelativePath); var newHash = await NetworkHelperSequential.ReadToFileAndHashAsync(networkStream, tmpFilePath, (int)fileLength); if (!string.Equals(newHash.ToHashString(), fileInfo.HashStr, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("File copy error: hash mismatch"); } _sessionFileHelper.AddNew(fileInfo.RelativePath); var fi = syncDb.Files.FirstOrDefault(x => x.RelativePath == fileInfo.RelativePath); if (fi == null) { fi = new SyncFileInfo { RelativePath = fileInfo.RelativePath, }; syncDb.Files.Add(fi); } fi.HashStr = newHash.ToHashString(); fi.State = SyncFileState.NotChanged; } return(true); }
private static void CheckState(string baseDir, string syncDbDir, SyncDatabase syncDb) { var localFiles = Directory.GetFiles(baseDir, "*", SearchOption.AllDirectories).ToList(); var dbDirInBase = syncDbDir.StartsWith(baseDir); foreach (var stored in syncDb.Files) { var localFilePath = Path.Combine(baseDir, stored.RelativePath); var localFileIdx = localFiles.IndexOf(localFilePath); if (localFileIdx < 0) { stored.State = SyncFileState.Deleted; } else { var localFile = localFiles[localFileIdx]; localFiles.RemoveAt(localFileIdx); { var hash = NetworkHelperSequential.HashFileAsync(new FileInfo(localFile)).Result; var localFileHash = hash.ToHashString(); if (localFileHash != stored.HashStr) { stored.State = SyncFileState.Modified; stored.HashStr = localFileHash; } } } } var localInfos = localFiles.Select(localFile => { if (dbDirInBase && localFile.StartsWith(syncDbDir)) { return(null); } var localFileRelativePath = localFile.Replace(baseDir, string.Empty); { var hash = NetworkHelperSequential.HashFileAsync(new FileInfo(localFile)).Result; return(new SyncFileInfo { HashStr = hash.ToHashString(), RelativePath = localFileRelativePath.TrimStart(Path.DirectorySeparatorChar), AbsolutePath = localFile, State = SyncFileState.New, }); } }).Where(i => i != null).ToList(); syncDb.Files.AddRange(localInfos); }
private async Task ProcessSendFileCmd(CommandHeader cmdHeader) { var data = await NetworkHelperSequential.Read <SendFileCommandData>(_networkStream, cmdHeader.PayloadLength); var ret = new ServerResponse(); var session = SessionStorage.Instance.GetSession(data.SessionId); if (session == null) { ret.ErrorMsg = "Session does not exist"; } else if (session.Expired) { ret.ErrorMsg = "Session has expired"; Msg?.Invoke("Session has expired"); } else { data.RelativeFilePath = PathHelpers.NormalizeRelative(data.RelativeFilePath); var filePath = Path.Combine(session.NewDir, data.RelativeFilePath); var fileDir = Path.GetDirectoryName(filePath); PathHelpers.EnsureDirExists(fileDir); Msg?.Invoke($"Receiving file '{data.RelativeFilePath}'"); var newHash = await NetworkHelperSequential.ReadToFileAndHashAsync(_networkStream, filePath, data.FileLength); var fileInfo = session.SyncDb.Files.FirstOrDefault(i => i.RelativePath == data.RelativeFilePath); if (fileInfo != null) { fileInfo.HashStr = newHash.ToHashString(); fileInfo.State = SyncFileState.NotChanged; } else { session.SyncDb.AddFile(session.BaseDir, data.RelativeFilePath, newHash.ToHashString()); } } }
private async Task <ServerResponseWithData <SyncInfo> > GetSyncList(Stream networkStream, Guid sessionId, List <SyncFileInfo> syncDbFiles) { var cmdData = new GetSyncListCommandData { SessionId = sessionId, Files = syncDbFiles, }; var cmdDataBytes = Serializer.Serialize(cmdData); await NetworkHelperSequential.WriteCommandHeader(networkStream, Commands.GetSyncListCmd, cmdDataBytes.Length); await NetworkHelperSequential.WriteBytes(networkStream, cmdDataBytes); var cmdHeader = await NetworkHelperSequential.ReadCommandHeader(networkStream); if (cmdHeader.Command != Commands.GetSyncListCmd) { return new ServerResponseWithData <SyncInfo> { ErrorMsg = "Wrong command received" } } ; if (cmdHeader.PayloadLength == 0) { return new ServerResponseWithData <SyncInfo> { ErrorMsg = "No data received" } } ; var responseBytes = await NetworkHelperSequential.ReadBytes(networkStream, cmdHeader.PayloadLength); var response = Serializer.Deserialize <ServerResponseWithData <SyncInfo> >(responseBytes); return(response); }
private async Task ProcessGetSyncListCmd(CommandHeader cmdHeader) { var data = await NetworkHelperSequential.Read <GetSyncListCommandData>(_networkStream, cmdHeader.PayloadLength); var ret = new ServerResponseWithData <SyncInfo>(); var session = SessionStorage.Instance.GetSession(data.SessionId); if (session == null) { ret.ErrorMsg = "Session does not exist"; } else if (session.Expired) { ret.ErrorMsg = "Session has expired"; //Log?.Invoke("Session has expired"); //return ret; } else { Msg?.Invoke("Scanning local folder..."); var syncDb = GetSyncDb(session.BaseDir, session.SyncDbDir, out var error); if (syncDb == null) { ret.ErrorMsg = error; Msg?.Invoke($"Failed to get sync db: {error}"); } else { var syncInfo = new SyncInfo(); PathHelpers.NormalizeRelative(data.Files); Msg?.Invoke("Preparing sync list..."); foreach (var localFileInfo in syncDb.Files) { var remoteFileInfo = data.Files.FirstOrDefault(remoteFile => remoteFile.RelativePath == localFileInfo.RelativePath); if (remoteFileInfo == null) { if (localFileInfo.State != SyncFileState.Deleted) { syncInfo.ToDownload.Add(localFileInfo); } } else { data.Files.Remove(remoteFileInfo); switch (remoteFileInfo.State) { case SyncFileState.Deleted: if (localFileInfo.State == SyncFileState.NotChanged || localFileInfo.State == SyncFileState.Deleted) { var filePath = Path.Combine(session.BaseDir, localFileInfo.RelativePath); if (File.Exists(filePath)) { var movedFilePath = Path.Combine(session.RemovedDir, localFileInfo.RelativePath); var movedFileDir = Path.GetDirectoryName(movedFilePath); if (movedFileDir == null) { throw new InvalidOperationException($"Unable to get '{movedFilePath}'s dir"); } if (!Directory.Exists(movedFileDir)) { Directory.CreateDirectory(movedFileDir); } File.Move(filePath, movedFilePath); } } else if (localFileInfo.State == SyncFileState.New) { syncInfo.ToDownload.Add(localFileInfo); } else { syncInfo.Conflicts.Add(localFileInfo); } break; case SyncFileState.New: syncInfo.Conflicts.Add(localFileInfo); break; case SyncFileState.Modified: if (localFileInfo.State == SyncFileState.NotChanged) { syncInfo.ToUpload.Add(localFileInfo); } else { syncInfo.Conflicts.Add(remoteFileInfo); } break; case SyncFileState.NotChanged: if (localFileInfo.State == SyncFileState.Modified) { syncInfo.ToDownload.Add(localFileInfo); } else if (localFileInfo.State == SyncFileState.Deleted) { syncInfo.ToRemove.Add(remoteFileInfo); } else if (localFileInfo.State == SyncFileState.New) { Debugger.Break(); // not possible } break; } } } foreach (var remoteFileInfo in data.Files.Where(x => x.State != SyncFileState.Deleted)) { syncInfo.ToUpload.Add(remoteFileInfo); } ret.Data = syncInfo; session.SyncDb = syncDb; } } await CommandHelper.WriteCommandResponse(_networkStream, Commands.GetSyncListCmd, ret); }
public async Task Sync() { try { using (var client = new TcpClient()) { await client.ConnectAsync(IPAddress.Parse(_serverAddress), _serverPort); using (var networkStream = client.GetStream()) { var sessionId = await GetSession(networkStream); if (sessionId.HasError) { Log?.Invoke($"Unable to create sync session. Server response was '{sessionId.ErrorMsg}'"); return; } _sessionId = sessionId.Data; if (!Directory.Exists(_syncDbDir)) { var dirInfo = Directory.CreateDirectory(_syncDbDir); dirInfo.Attributes = dirInfo.Attributes | FileAttributes.Hidden; } var syncDb = GetLocalSyncDb(out var error); if (syncDb == null) { Log?.Invoke(error); return; } var syncList = await GetSyncList(networkStream, _sessionId, syncDb.Files); if (syncList.HasError) { Log?.Invoke($"Unable to get sync list. Server response was '{syncList.ErrorMsg}'"); return; } PathHelpers.NormalizeRelative(syncList.Data.ToDownload, syncList.Data.ToUpload, syncList.Data.ToRemove); PathHelpers.EnsureDirExists(_toRemoveDir); PathHelpers.EnsureDirExists(_newDir); foreach (var fileInfo in syncList.Data.ToRemove) { _sessionFileHelper.PrepareForRemove(fileInfo.RelativePath); var fi = syncDb.Files.First(x => x.RelativePath == fileInfo.RelativePath); syncDb.Files.Remove(fi); } if (syncList.Data.Conflicts.Count > 0) { Debugger.Break(); } if (!await ReceiveFiles(networkStream, syncList.Data.ToDownload, syncDb)) { return; } if (!await SendFiles(networkStream, syncList.Data.ToUpload, syncDb)) { return; } var response = await FinishSession(networkStream, _sessionId); if (response.HasError) { Log?.Invoke($"Error finishing session. Server response was '{response.ErrorMsg}'"); return; } _sessionFileHelper.FinishSession(); syncDb.Files.RemoveAll(x => x.State == SyncFileState.Deleted); syncDb.Store(_syncDbDir); File.WriteAllText(Path.Combine(_syncDbDir, $"sync-{DateTime.Now:dd-MM-yyyy_hh-mm-ss}.log"), _log.ToString()); if (new DirectoryInfo(_newDir).EnumerateFiles("*", SearchOption.AllDirectories).Any()) { Debugger.Break(); // all files should be removed by now } if (new DirectoryInfo(_toRemoveDir).EnumerateFiles("*", SearchOption.AllDirectories).Any()) { Debugger.Break(); // all files should be removed by now } Directory.Delete(_newDir, true); Directory.Delete(_toRemoveDir, true); await NetworkHelperSequential.WriteCommandHeader(networkStream, Commands.DisconnectCmd); } } } catch (Exception e) { Log?.Invoke($"Error during sync {e}"); } }