diff --git a/src/Client.cs b/src/Client.cs index b39974f..9942a44 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -31,10 +31,15 @@ namespace WTelegram public Config TLConfig { get; private set; } /// Number of automatic reconnections on connection/reactor failure public int MaxAutoReconnects { get; set; } = 5; + /// Number of seconds under which an error 420 FLOOD_WAIT_X will not be raised and your request will instead be auto-retried after the delay + public int FloodRetryThreshold { get; set; } = 60; /// Is this Client instance the main or a secondary DC session public bool IsMainDC => (_dcSession?.DataCenter?.id ?? 0) == _session.MainDC; /// Is this Client currently disconnected? public bool Disconnected => _tcpClient != null && !(_tcpClient.Client?.Connected ?? false); + /// Used to indicate progression of file download/upload + /// total size of file in bytes, or 0 if unknown + public delegate void ProgressCallback(long transmitted, long totalSize); private readonly Func _config; private readonly int _apiId; @@ -421,8 +426,11 @@ namespace WTelegram _bareRequest = 0; } // TODO: implement an Updates gaps handling system? https://core.telegram.org/api/updates - var udpatesState = await this.Updates_GetState(); // this call reenables incoming Updates - OnUpdate(udpatesState); + if (IsMainDC) + { + var udpatesState = await this.Updates_GetState(); // this call reenables incoming Updates + OnUpdate(udpatesState); + } } else throw; @@ -772,7 +780,7 @@ namespace WTelegram else if (rpcError.error_code == 420 && ((number = rpcError.error_message.IndexOf("_WAIT_")) > 0)) { number = int.Parse(rpcError.error_message[(number + 6)..]); - if (number <= 60) + if (number <= FloodRetryThreshold) { await Task.Delay(number * 1000); goto retry; @@ -1019,23 +1027,25 @@ namespace WTelegram return user; } -#region TL-Helpers + #region TL-Helpers /// Helper function to upload a file to Telegram /// Path to the file to upload + /// (optional) Callback for tracking the progression of the transfer /// an or than can be used in various requests - public Task UploadFileAsync(string pathname) - => UploadFileAsync(File.OpenRead(pathname), Path.GetFileName(pathname)); + public Task UploadFileAsync(string pathname, ProgressCallback progress = null) + => UploadFileAsync(File.OpenRead(pathname), Path.GetFileName(pathname), progress); /// Helper function to upload a file to Telegram /// Content of the file to upload /// Name of the file + /// (optional) Callback for tracking the progression of the transfer /// an or than can be used in various requests - public async Task UploadFileAsync(Stream stream, string filename) + public async Task UploadFileAsync(Stream stream, string filename, ProgressCallback progress = null) { using var md5 = MD5.Create(); using (stream) { - long length = stream.Length; + long transmitted = 0, length = stream.Length; var isBig = length >= 10 * 1024 * 1024; int file_total_parts = (int)((length - 1) / FilePartSize) + 1; long file_id = Helpers.RandomLong(); @@ -1047,11 +1057,11 @@ namespace WTelegram var bytes = new byte[Math.Min(FilePartSize, bytesLeft)]; read = await FullReadAsync(stream, bytes, bytes.Length); await _parallelTransfers.WaitAsync(); + bytesLeft -= read; var task = SavePart(file_part, bytes); lock (tasks) tasks[file_part] = task; if (!isBig) md5.TransformBlock(bytes, 0, read, null, 0); - bytesLeft -= read; if (read < FilePartSize && bytesLeft != 0) throw new ApplicationException($"Failed to fully read stream ({read},{bytesLeft})"); async Task SavePart(int file_part, byte[] bytes) @@ -1062,7 +1072,8 @@ namespace WTelegram await this.Upload_SaveBigFilePart(file_id, file_part, file_total_parts, bytes); else await this.Upload_SaveFilePart(file_id, file_part, bytes); - lock (tasks) tasks.Remove(file_part); + lock (tasks) { transmitted += bytes.Length; tasks.Remove(file_part); } + progress?.Invoke(transmitted, length); } catch (Exception) { @@ -1175,23 +1186,25 @@ namespace WTelegram /// The photo to download /// Stream to write the file content to. This method does not close/dispose the stream /// A specific size/version of the photo, or to download the largest version of the photo + /// (optional) Callback for tracking the progression of the transfer /// The file type of the photo - public async Task DownloadFileAsync(Photo photo, Stream outputStream, PhotoSizeBase photoSize = null) + public async Task DownloadFileAsync(Photo photo, Stream outputStream, PhotoSizeBase photoSize = null, ProgressCallback progress = null) { photoSize ??= photo.LargestPhotoSize; var fileLocation = photo.ToFileLocation(photoSize); - return await DownloadFileAsync(fileLocation, outputStream, photo.dc_id, photoSize.FileSize); + return await DownloadFileAsync(fileLocation, outputStream, photo.dc_id, photoSize.FileSize, progress); } /// Download a document from Telegram into the outputStream /// The document to download /// Stream to write the file content to. This method does not close/dispose the stream /// A specific size/version of the document thumbnail to download, or to download the document itself + /// (optional) Callback for tracking the progression of the transfer /// MIME type of the document/thumbnail - public async Task DownloadFileAsync(Document document, Stream outputStream, PhotoSizeBase thumbSize = null) + public async Task DownloadFileAsync(Document document, Stream outputStream, PhotoSizeBase thumbSize = null, ProgressCallback progress = null) { var fileLocation = document.ToFileLocation(thumbSize); - var fileType = await DownloadFileAsync(fileLocation, outputStream, document.dc_id, thumbSize?.FileSize ?? document.size); + var fileType = await DownloadFileAsync(fileLocation, outputStream, document.dc_id, thumbSize?.FileSize ?? document.size, progress); return thumbSize == null ? document.mime_type : "image/" + fileType; } @@ -1200,14 +1213,16 @@ namespace WTelegram /// Stream to write file content to. This method does not close/dispose the stream /// (optional) DC on which the file is stored /// (optional) Expected file size + /// (optional) Callback for tracking the progression of the transfer /// The file type - public async Task DownloadFileAsync(InputFileLocationBase fileLocation, Stream outputStream, int fileDC = 0, int fileSize = 0) + public async Task DownloadFileAsync(InputFileLocationBase fileLocation, Stream outputStream, int fileDC = 0, int fileSize = 0, ProgressCallback progress = null) { Storage_FileType fileType = Storage_FileType.unknown; var client = fileDC == 0 ? this : await GetClientForDC(fileDC, true); using var writeSem = new SemaphoreSlim(1); long streamStartPos = outputStream.Position; int fileOffset = 0, maxOffsetSeen = 0; + long transmitted = 0; var tasks = new Dictionary(); bool abort = false; while (!abort) @@ -1267,6 +1282,7 @@ namespace WTelegram } await outputStream.WriteAsync(fileData.bytes, 0, fileData.bytes.Length); maxOffsetSeen = Math.Max(maxOffsetSeen, offset + fileData.bytes.Length); + transmitted += fileData.bytes.Length; } catch (Exception) { @@ -1276,6 +1292,7 @@ namespace WTelegram finally { writeSem.Release(); + progress?.Invoke(transmitted, fileSize); } } lock (tasks) tasks.Remove(offset);