/// <summary>Gets information about the current transfer status.</summary> /// <param name="token">The transfer to check.</param> /// <returns>Returns an information object or <code>null</code> when not available.</returns> public async Task <R <FileTransfer, CommandError> > GetStats(FileTransferToken token) { if (token.Status != TransferStatus.Transfering) { return(CommandError.Custom("No transfer found")); } var result = await FileTransferList(); if (result.Ok) { return(result.Value.Where(x => x.ServerFileTransferId == token.ServerTransferId).MapToSingle()); } return(result.Error); }
/// <summary>Stops an active transfer.</summary> /// <param name="token">The token to abort.</param> /// <param name="delete">True to delete the file. /// False to only temporarily stop the transfer (can be resumed again with <see cref="Resume"/>).</param> public void Abort(FileTransferToken token, bool delete = false) { lock (token) { if (token.Status != TransferStatus.Transfering && token.Status != TransferStatus.Waiting) { return; } parent.FileTransferStop(token.ServerTransferId, delete); token.Status = TransferStatus.Cancelled; if (delete && token.CloseStreamWhenDone) { token.LocalStream.Close(); } } }
/// <summary>Gets information about the current transfer status.</summary> /// <param name="token">The transfer to check.</param> /// <returns>Returns an information object or <code>null</code> when not available.</returns> public R <FileTransfer, CommandError> GetStats(FileTransferToken token) { lock (token) { if (token.Status != TransferStatus.Transfering) { return(CommandError.Custom("No transfer found")); } } var result = parent.FileTransferList(); if (result.Ok) { return(result.Value.Where(x => x.ServerFileTransferId == token.ServerTransferId).WrapSingle()); } return(R <FileTransfer, CommandError> .Err(result.Error)); }
private void StartWorker(FileTransferToken token) { lock (transferQueue) { transferQueue.Enqueue(token); if (threadEnd || workerThread is null || !workerThread.IsAlive) { threadEnd = false; workerThread = new Thread(() => { Tools.SetLogId(parent.ConnectionData.LogId); TransferLoop(); }) { Name = $"FileTransfer[{parent.ConnectionData.LogId}]" }; workerThread.Start(); } } }
/// <summary>Stops an active transfer.</summary> /// <param name="token">The token to abort.</param> /// <param name="delete">True to delete the file. /// False to only temporarily stop the transfer (can be resumed again with <see cref="Resume"/>).</param> public async Task Abort(FileTransferToken token, bool delete = false) { if (token.Status != TransferStatus.Transfering && token.Status != TransferStatus.Waiting) { return; } await FileTransferStop(token.ServerTransferId, delete); token.Status = TransferStatus.Cancelled; if (delete && token.CloseStreamWhenDone) { #if NETSTANDARD2_0 token.LocalStream.Dispose(); #else await token.LocalStream.DisposeAsync(); #endif } }
/// <summary>Initiate a file download from the server.</summary> /// <param name="stream">Data stream to write to.</param> /// <param name="channel">The channel id to download from.</param> /// <param name="path">The download path within the channel. Eg: "file.txt", "path/file.png"</param> /// <param name="channelPassword">The password for the channel.</param> /// <param name="closeStream">True will <see cref="IDisposable.Dispose"/> the stream after the download is finished.</param> /// <returns>A token to track the file transfer.</returns> public R <FileTransferToken, CommandError> DownloadFile(Stream stream, ChannelId channel, string path, string channelPassword = "", bool closeStream = true) { ushort cftid = GetFreeTransferId(); var request = parent.FileTransferInitDownload(channel, path, channelPassword, cftid, 0); if (!request.Ok) { if (closeStream) { stream.Close(); } return(request.Error); } var token = new FileTransferToken(stream, request.Value, channel, path, channelPassword, 0) { CloseStreamWhenDone = closeStream }; StartWorker(token); return(token); }
/// <summary>Resumes a download from a previously stopped position.</summary> /// <param name="token">The aborted token.</param> public async Task <E <CommandError> > Resume(FileTransferToken token) { if (token.Status != TransferStatus.Cancelled) { return(CommandError.Custom("Only cancelled transfers can be resumed")); } if (token.Direction == TransferDirection.Upload) { var result = await FileTransferInitUpload(token.ChannelId, token.Path, token.ChannelPassword, token.ClientTransferId, token.Size, false, true); if (!result.Ok) { return(result.Error); } var request = result.Value; token.ServerTransferId = request.ServerFileTransferId; token.SeekPosition = (long)request.SeekPosition; token.Port = request.Port; token.TransferKey = request.FileTransferKey; } else // Download { var result = await FileTransferInitDownload(token.ChannelId, token.Path, token.ChannelPassword, token.ClientTransferId, token.LocalStream.Position); if (!result.Ok) { return(result.Error); } var request = result.Value; token.ServerTransferId = request.ServerFileTransferId; token.SeekPosition = -1; token.Port = request.Port; token.TransferKey = request.FileTransferKey; } token.Status = TransferStatus.Waiting; return(await Transfer(token)); }
/// <summary>Initiate a file download from the server.</summary> /// <param name="stream">Data stream to write to.</param> /// <param name="channel">The channel id to download from.</param> /// <param name="path">The download path within the channel. Eg: "file.txt", "path/file.png"</param> /// <param name="channelPassword">The password for the channel.</param> /// <param name="closeStream">True will <see cref="IDisposable.Dispose"/> the stream after the download is finished.</param> /// <returns>A token to track the file transfer.</returns> public async Task <R <FileTransferToken, CommandError> > DownloadFile(Stream stream, ChannelId channel, string path, string channelPassword = "", bool closeStream = true) { ushort cftid = GetFreeTransferId(); var request = await FileTransferInitDownload(channel, path, channelPassword, cftid, 0); if (!request.Ok) { if (closeStream) { #if NETSTANDARD2_0 stream.Dispose(); #else await stream.DisposeAsync(); #endif } return(request.Error); } var token = new FileTransferToken(stream, request.Value, channel, path, channelPassword, 0) { CloseStreamWhenDone = closeStream }; return(await Transfer(token)); }
private async Task <R <FileTransferToken, CommandError> > Transfer(FileTransferToken token) { try { if (remoteAddress is null) { token.Status = TransferStatus.Failed; Log.Trace("Client is not connected. Transfer failed {@token}", token); return(CommandError.ConnectionClosed); } if (token.Status != TransferStatus.Waiting) { return(CommandError.Custom("Token is not open")); } token.Status = TransferStatus.Transfering; Log.Trace("Creating new file transfer connection to {0}", remoteAddress); using var client = new TcpClient(remoteAddress.AddressFamily); try { await client.ConnectAsync(remoteAddress.Address, token.Port); } catch (SocketException ex) { Log.Warn(ex, "SocketException trying to connect to filetransfer port"); token.Status = TransferStatus.Failed; return(CommandError.ConnectionClosed); } using var md5Dig = token.CreateMd5 ? MD5.Create() : null; using var stream = client.GetStream(); byte[] keyBytes = Encoding.ASCII.GetBytes(token.TransferKey); await stream.WriteAsync(keyBytes, 0, keyBytes.Length); if (token.SeekPosition >= 0 && token.LocalStream.Position != token.SeekPosition) { token.LocalStream.Seek(token.SeekPosition, SeekOrigin.Begin); } if (token.Direction == TransferDirection.Upload) { // https://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,2a0f078c2e0c0aa8,references const int bufferSize = 81920; var buffer = new byte[bufferSize]; int read; md5Dig?.Initialize(); while ((read = await token.LocalStream.ReadAsync(buffer, 0, buffer.Length)) != 0) { await stream.WriteAsync(buffer, 0, read); md5Dig?.TransformBlock(buffer, 0, read, buffer, 0); } md5Dig?.TransformFinalBlock(Array.Empty <byte>(), 0, 0); token.Md5Sum = md5Dig?.Hash; } else // Download { // try to preallocate space try { token.LocalStream.SetLength(token.Size); } catch (NotSupportedException) { } await stream.CopyToAsync(token.LocalStream); } if (token.Status == TransferStatus.Transfering && token.LocalStream.Position == token.Size) { token.Status = TransferStatus.Done; if (token.CloseStreamWhenDone) { #if NETSTANDARD2_0 token.LocalStream.Dispose(); #else await token.LocalStream.DisposeAsync(); #endif } } } catch (IOException ex) { Log.Debug(ex, "IOException during filetransfer"); } catch (Exception ex) { Log.Error(ex, "Exception during filetransfer"); } finally { if (token.Status != TransferStatus.Done && token.Status != TransferStatus.Cancelled) { token.Status = TransferStatus.Failed; } } if (token.Status == TransferStatus.Failed) { return(CommandError.Custom("Upload didn't finish")); } return(token); }