Flurl icon indicating copy to clipboard operation
Flurl copied to clipboard

Feature Request: Download / Upload Progress Reporting

Open yzpopulation opened this issue 8 years ago • 10 comments

     public static class Ext
    {
        private static async Task<bool> _DownloadFileAsync(this IFlurlClient client, string downloadDirectory,
            IProgress<DownloadFileProgressInfo> progress, string downloadedFileName = null, int bufferSize = 4096)
        {
            bool err = false;
            bool autoDispose = client.Settings.AutoDispose;
            client.Settings.AutoDispose = false;
            try
            {
                HttpResponseMessage res = await client.SendAsync(HttpMethod.Get, null, new CancellationToken?(),
                    HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
                if (downloadedFileName == null)
                    if (res.Content.Headers.ContentDisposition != null)
                        downloadedFileName = res.Content.Headers.ContentDisposition.FileName;
                    else
                        downloadedFileName = Path.GetFileName(client.Url.Path);
                if (!Directory.Exists(downloadDirectory))
                    Directory.CreateDirectory(downloadDirectory);
                DownloadFileProgressInfo info = new DownloadFileProgressInfo();
                info.FinalDataSize = res.Content.Headers.ContentLength;
                ConfiguredTaskAwaitable<Stream> configuredTaskAwaitable =
                    res.Content.ReadAsStreamAsync().ConfigureAwait(false);
                Stream httpStream = await configuredTaskAwaitable;
                try
                {
                    var assembly =
                        AppDomain.CurrentDomain.GetAssemblies()
                            .First(
                                s => s.FullName == "Flurl.Http, Version=1.1.1.0, Culture=neutral, PublicKeyToken=null");
                    Type[] types = assembly.GetTypes();
                    Type tFileUtil = types.First(s => s.Name == "FileUtil");
                    MethodInfo mFileUtil = tFileUtil.GetMethod("OpenWriteAsync",
                        BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Static);
                    Task<Stream> ts =
                        mFileUtil.Invoke(null, new object[] {downloadDirectory, downloadedFileName, bufferSize}) as
                            Task<Stream>;
                    configuredTaskAwaitable = ts.ConfigureAwait(false);
                    Stream filestream = await configuredTaskAwaitable;
                    try
                    {
                        Stopwatch sw = new Stopwatch();
                        byte[] buffer = new byte[bufferSize];
                        while (true)
                        {
                            sw.Reset();
                            sw.Start();
                            int num = await httpStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
                            sw.Stop();
                            if (sw.ElapsedMilliseconds > 0)
                            {
                                info.TimeTaken = sw.ElapsedMilliseconds;
                                info.DataSize = num;
                                progress.Report(info);
                            }
                            int bytesRead;
                            if ((bytesRead = num) != 0)
                                await filestream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
                            else
                                break;
                        }
                    }
                    catch (Exception)
                    {
                        err = true;
                    }
                    finally
                    {
                        if (filestream != null)
                        {
                        }
                        filestream?.Dispose();
                    }
                }
                catch (Exception)
                {
                    err = true;
                }
                finally
                {
                    httpStream?.Dispose();
                }
            }
            catch (Exception)
            {
                err = true;
            }
            finally
            {
                client.Settings.AutoDispose = autoDispose;
                if (client.Settings.AutoDispose)
                    client.Dispose();
            }
            return err;
        }

        public static async Task<bool> DownloadFileAsync(this IFlurlClient client, string localFolderPath,
            IProgress<DownloadFileProgressInfo> progress, string localFileName = null, int bufferSize = 4096)
        {
            return await _DownloadFileAsync(client, localFolderPath, progress, localFileName, bufferSize);
        }

        public static async Task<bool> DownloadFileAsync(this string url, string localFolderPath,
            IProgress<DownloadFileProgressInfo> progress, string localFileName = null, int bufferSize = 4096)
        {
            return await new FlurlClient(url, true)._DownloadFileAsync(localFolderPath, progress, localFileName,
                bufferSize);
        }

        public static async Task<bool> DownloadFileAsync(this Url url, string localFolderPath,
            IProgress<DownloadFileProgressInfo> progress, string localFileName = null, int bufferSize = 4096)
        {
            return await new FlurlClient(url, true)._DownloadFileAsync(localFolderPath, progress, localFileName,
                bufferSize);
        }
    }

    public class DownloadFileProgressInfo
    {
        private long _dataSize;
        private long _timeTaken;

        public long TimeTaken
        {
            get { return _timeTaken; }
            set
            {
                _timeTaken = value;
                TotalTimeTaken += value;
            }
        }

        public long TotalTimeTaken { get; private set; }

        public long DataSize
        {
            get { return _dataSize; }
            set
            {
                _dataSize = value;
                TotalDataSize += value;
            }
        }

        public long TotalDataSize { get; private set; }
        public long? FinalDataSize { get; set; }

        public long Speed
        {
            get
            {
                if (TimeTaken == 0) return 0;
                return DataSize * 8 / TimeTaken;
            }
        }

        public long TotalSpeed
        {
            get
            {
                if (TotalDataSize == 0 || TotalTimeTaken == 0) return 0;
                return TotalDataSize * 8 / TotalTimeTaken;
            }
        }

        public void Clear()
        {
            TimeTaken = 0;
            TotalTimeTaken = 0;
            DataSize = 0;
            TotalDataSize = 0;
            FinalDataSize = 0;
        }
    }

improve from http://stackoverflow.com/questions/22795399/what-is-a-valid-way-to-track-readasstreamasync-download-speed-rate

yzpopulation avatar Dec 03 '16 14:12 yzpopulation

Thanks for the suggestion. I won't make any promises but I'll keep this issue open and consider progress reporting as a possible future enhancement.

As a side note (to self?) here's a simpler implementation that purports to have at least some cross-platform support: http://stackoverflow.com/questions/21169573/how-to-implement-progress-reporting-for-portable-httpclient

tmenier avatar Dec 07 '16 03:12 tmenier

Any thoughts to consider this yet?

Im-PJ avatar Aug 03 '17 20:08 Im-PJ

It won't make the cut for the next major version. I don't have a timeframe for it.

tmenier avatar Aug 04 '17 17:08 tmenier

Would love to see this

lunim avatar Aug 07 '18 16:08 lunim

Would like to see this as well. It it currently even possible to access the ReadAsStreamAsync() object?

JBtje avatar Aug 24 '18 09:08 JBtje

Pushed to backlog. So I guess its not part of 3.0

Im-PJ avatar Mar 26 '19 11:03 Im-PJ

@Im-PJ Don't read too much in to that. Nothing has been decided about 3.0 yet, I'm just working on getting issues better organized.

tmenier avatar Apr 06 '19 12:04 tmenier

I'm actively gathering feedback to help prioritize issues for 3.0. If this one is important to you, please vote for it here!

tmenier avatar Apr 06 '19 14:04 tmenier

up

azhe403 avatar May 31 '20 05:05 azhe403

I was looking for something like this, I found this issue and that there was not a built-in way, so I decided to implement this helper, hopefully can help you out guys :)

public static async Task<Stream> DownloadStreamWithProgressAsync(string url, CancellationToken cancellationToken, IProgress<DownloadProgressArgs> progessReporter)
{
    try
    {
        using(IFlurlResponse response = await url.GetAsync(cancellationToken, HttpCompletionOption.ResponseHeadersRead))
        using (var stream = await response.GetStreamAsync())
        {
            var receivedBytes = 0;
            var buffer = new byte[4096];
            var totalBytes = Convert.ToDouble(response.ResponseMessage.Content.Headers.ContentLength);

            var memStream = new MemoryStream();

            while (true)
            {
                cancellationToken.ThrowIfCancellationRequested();

                int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);

                await memStream.WriteAsync(buffer, 0, bytesRead);

                if (bytesRead == 0)
                {
                    break;
                }
                receivedBytes += bytesRead;

                var args = new DownloadProgressArgs(receivedBytes, totalBytes);
                progessReporter.Report(args);
            }

            memStream.Position = 0;
            return memStream;
        }
    }
    catch(Exception e)
    {
        throw e;
    }
}

public class DownloadProgressArgs : EventArgs
{
    public DownloadProgressArgs(int bytesReceived, double totalBytes)
    {
        BytesReceived = bytesReceived;
        TotalBytes = totalBytes;
    }

    public double TotalBytes { get; }

    public double BytesReceived { get; }

    public double PercentComplete => 100 * (BytesReceived / TotalBytes);
}

Panda-Sharp avatar Mar 31 '21 08:03 Panda-Sharp