From 8bbb1d86c74694001d1433b924cedd4ebd295ee1 Mon Sep 17 00:00:00 2001 From: Daniel Luberda Date: Sat, 24 Sep 2016 14:26:12 +0200 Subject: [PATCH] Added DownloadStarted callbacks --- .../Cache/IDownloadCache.cs | 4 +- .../Extensions/StringExtensions.cs | 15 +++++++ .../FFImageLoading.csproj | 6 ++- .../Work/DownloadInformation.cs | 25 ++++++++++++ .../Work/TaskParameter.cs | 38 +++++++++++------- .../Work/StreamResolver/UrlStreamResolver.cs | 2 +- .../CachedImageRenderer.cs | 3 ++ .../CachedImageRenderer.cs | 3 ++ .../CachedImageRenderer.cs | 3 ++ .../Args/CachedImageEvents.cs | 10 +++++ source/FFImageLoading.Forms/CachedImage.cs | 39 +++++++++++++++++++ .../Cache/DownloadCache.cs | 22 +++++++---- .../Cache/SimpleDiskCache.cs | 4 +- .../Work/ImageLoaderTaskBase.cs | 2 +- .../Work/DataResolver/UrlDataResolver.cs | 2 +- .../Cache/SimpleDiskCache.cs | 4 +- .../DataResolver/UrlDataResolver.cs | 2 +- 17 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 source/FFImageLoading.Common/Extensions/StringExtensions.cs create mode 100644 source/FFImageLoading.Common/Work/DownloadInformation.cs diff --git a/source/FFImageLoading.Common/Cache/IDownloadCache.cs b/source/FFImageLoading.Common/Cache/IDownloadCache.cs index 75da0439b..2d55dab78 100644 --- a/source/FFImageLoading.Common/Cache/IDownloadCache.cs +++ b/source/FFImageLoading.Common/Cache/IDownloadCache.cs @@ -12,9 +12,9 @@ public interface IDownloadCache HttpClient DownloadHttpClient { get; set; } - Task GetAsync (string url, CancellationToken token, TimeSpan? duration = null, string key = null, CacheType? cacheType = null); + Task GetAsync (string url, CancellationToken token, Action onDownloadStarted, TimeSpan? duration = null, string key = null, CacheType? cacheType = null); - Task GetStreamAsync (string url, CancellationToken token, TimeSpan? duration = null, string key = null, CacheType? cacheType = null); + Task GetStreamAsync (string url, CancellationToken token, Action onDownloadStarted, TimeSpan? duration = null, string key = null, CacheType? cacheType = null); } } diff --git a/source/FFImageLoading.Common/Extensions/StringExtensions.cs b/source/FFImageLoading.Common/Extensions/StringExtensions.cs new file mode 100644 index 000000000..f883151d0 --- /dev/null +++ b/source/FFImageLoading.Common/Extensions/StringExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Linq; + +namespace FFImageLoading +{ + public static class StringExtensions + { + public static string ToSanitizedKey(this string key) + { + return new string(key.ToCharArray() + .Where(c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) + .ToArray()); + } + } +} diff --git a/source/FFImageLoading.Common/FFImageLoading.csproj b/source/FFImageLoading.Common/FFImageLoading.csproj index 31c768be8..5086a2e6c 100644 --- a/source/FFImageLoading.Common/FFImageLoading.csproj +++ b/source/FFImageLoading.Common/FFImageLoading.csproj @@ -88,6 +88,8 @@ + + @@ -95,8 +97,8 @@ - - + + diff --git a/source/FFImageLoading.Common/Work/DownloadInformation.cs b/source/FFImageLoading.Common/Work/DownloadInformation.cs new file mode 100644 index 000000000..14772bd10 --- /dev/null +++ b/source/FFImageLoading.Common/Work/DownloadInformation.cs @@ -0,0 +1,25 @@ +using System; +namespace FFImageLoading +{ + public class DownloadInformation + { + public DownloadInformation(string url, string customCacheKey, string fileName, bool allowDiskCaching, TimeSpan? cacheValidity) + { + Url = url; + CustomCacheKey = customCacheKey; + FileName = fileName?.ToSanitizedKey(); + AllowDiskCaching = allowDiskCaching; + CacheValidity = cacheValidity; + } + + public string Url { get; private set; } + + public string CustomCacheKey { get; private set; } + + public string FileName { get; private set; } + + public bool AllowDiskCaching { get; private set; } + + public TimeSpan? CacheValidity { get; private set; } + } +} diff --git a/source/FFImageLoading.Common/Work/TaskParameter.cs b/source/FFImageLoading.Common/Work/TaskParameter.cs index fa5b435e5..47acb7a88 100644 --- a/source/FFImageLoading.Common/Work/TaskParameter.cs +++ b/source/FFImageLoading.Common/Work/TaskParameter.cs @@ -77,17 +77,10 @@ private TaskParameter() Transformations = new List(); // default values so we don't have a null value - OnSuccess = (s,r) => - { - }; - - OnError = ex => - { - }; - - OnFinish = scheduledWork => - { - }; + OnSuccess = (s,r) => {}; + OnError = ex => { }; + OnFinish = scheduledWork => { }; + OnDownloadStarted = downloadInformation => { }; } public void Dispose() @@ -95,9 +88,10 @@ public void Dispose() if (!_disposed) { // remove reference to callbacks - OnSuccess = (s, r) => {}; - OnError = (e) => {}; - OnFinish = (sw) => {}; + OnSuccess = (s, r) => { }; + OnError = ex => { }; + OnFinish = scheduledWork => { }; + OnDownloadStarted = downloadInformation => { }; Transformations = null; Stream = null; @@ -138,6 +132,8 @@ public void Dispose() public Action OnFinish { get; private set; } + public Action OnDownloadStarted { get; private set; } + public List Transformations { get; private set; } public bool? LoadTransparencyChannel { get; private set; } @@ -403,6 +399,20 @@ public TaskParameter Finish(Action action) OnFinish = action; return this; } + + /// + /// If image starts downloading from Internet this callback is called + /// + /// The TaskParameter instance for chaining the call. + /// Action. + public TaskParameter DownloadStarted(Action action) + { + if (action == null) + throw new Exception("Given lambda should not be null."); + + OnDownloadStarted = action; + return this; + } } } diff --git a/source/FFImageLoading.Droid/Work/StreamResolver/UrlStreamResolver.cs b/source/FFImageLoading.Droid/Work/StreamResolver/UrlStreamResolver.cs index 0709a04a8..f5f7c548b 100644 --- a/source/FFImageLoading.Droid/Work/StreamResolver/UrlStreamResolver.cs +++ b/source/FFImageLoading.Droid/Work/StreamResolver/UrlStreamResolver.cs @@ -21,7 +21,7 @@ public UrlStreamResolver(TaskParameter parameter, IDownloadCache downloadCache) public async Task> GetStream(string identifier, CancellationToken token) { - var cachedStream = await DownloadCache.GetStreamAsync(identifier, token, Parameters.CacheDuration, Parameters.CustomCacheKey, Parameters.CacheType).ConfigureAwait(false); + var cachedStream = await DownloadCache.GetStreamAsync(identifier, token, Parameters.OnDownloadStarted, Parameters.CacheDuration, Parameters.CustomCacheKey, Parameters.CacheType).ConfigureAwait(false); var imageInformation = new ImageInformation(); imageInformation.SetPath(identifier); diff --git a/source/FFImageLoading.Forms.Droid/CachedImageRenderer.cs b/source/FFImageLoading.Forms.Droid/CachedImageRenderer.cs index 97dad23b5..e9de17c26 100644 --- a/source/FFImageLoading.Forms.Droid/CachedImageRenderer.cs +++ b/source/FFImageLoading.Forms.Droid/CachedImageRenderer.cs @@ -269,6 +269,9 @@ private void UpdateBitmap(CachedImage previous = null) imageLoader.Error((exception) => element.OnError(new CachedImageEvents.ErrorEventArgs(exception))); + imageLoader.DownloadStarted((downloadInformation) => + element.OnDownloadStarted(new CachedImageEvents.DownloadStartedEventArgs(downloadInformation))); + _currentTask = imageLoader.Into(imageView); } } diff --git a/source/FFImageLoading.Forms.Touch/CachedImageRenderer.cs b/source/FFImageLoading.Forms.Touch/CachedImageRenderer.cs index c3f2a7f03..fc1a11f12 100644 --- a/source/FFImageLoading.Forms.Touch/CachedImageRenderer.cs +++ b/source/FFImageLoading.Forms.Touch/CachedImageRenderer.cs @@ -265,6 +265,9 @@ private void SetImage(CachedImage oldElement = null) imageLoader.Error((exception) => element.OnError(new CachedImageEvents.ErrorEventArgs(exception))); + + imageLoader.DownloadStarted((downloadInformation) => + element.OnDownloadStarted(new CachedImageEvents.DownloadStartedEventArgs(downloadInformation))); _currentTask = imageLoader.Into(Control); } diff --git a/source/FFImageLoading.Forms.WinRT/CachedImageRenderer.cs b/source/FFImageLoading.Forms.WinRT/CachedImageRenderer.cs index 029b73a05..7ade187a5 100644 --- a/source/FFImageLoading.Forms.WinRT/CachedImageRenderer.cs +++ b/source/FFImageLoading.Forms.WinRT/CachedImageRenderer.cs @@ -352,6 +352,9 @@ private async void UpdateSource() imageLoader.Error((exception) => element.OnError(new CachedImageEvents.ErrorEventArgs(exception))); + imageLoader.DownloadStarted((downloadInformation) => + element.OnDownloadStarted(new CachedImageEvents.DownloadStartedEventArgs(downloadInformation))); + _currentTask = imageLoader.Into(Control); } } diff --git a/source/FFImageLoading.Forms/Args/CachedImageEvents.cs b/source/FFImageLoading.Forms/Args/CachedImageEvents.cs index cecee5331..87e8b576c 100644 --- a/source/FFImageLoading.Forms/Args/CachedImageEvents.cs +++ b/source/FFImageLoading.Forms/Args/CachedImageEvents.cs @@ -37,6 +37,16 @@ public FinishEventArgs(IScheduledWork scheduledWork) public IScheduledWork ScheduledWork { get; private set; } } + + public class DownloadStartedEventArgs : EventArgs + { + public DownloadStartedEventArgs(DownloadInformation downloadInformation) + { + DownloadInformation = downloadInformation; + } + + public DownloadInformation DownloadInformation { get; private set; } + } } } diff --git a/source/FFImageLoading.Forms/CachedImage.cs b/source/FFImageLoading.Forms/CachedImage.cs index 540ddb049..b54426004 100644 --- a/source/FFImageLoading.Forms/CachedImage.cs +++ b/source/FFImageLoading.Forms/CachedImage.cs @@ -719,6 +719,45 @@ internal void OnFinish(CachedImageEvents.FinishEventArgs e) var finishCommand = FinishCommand; if (finishCommand != null && finishCommand.CanExecute(e)) finishCommand.Execute(e); + } + + + /// + /// Occurs when an image starts downloading from web. + /// + public event EventHandler DownloadStarted; + + /// + /// The DownloadStartedCommandProperty. + /// + public static readonly BindableProperty DownloadStartedCommandProperty = BindableProperty.Create(nameof(DownloadStartedCommand), typeof(ICommand), typeof(CachedImage)); + + /// + /// Gets or sets the DownloadStartedCommand. + /// Occurs when an image starts downloading from web. + /// Command parameter: EventArgs + /// + /// The download started command. + public ICommand DownloadStartedCommand + { + get + { + return (ICommand)GetValue(DownloadStartedCommandProperty); + } + set + { + SetValue(DownloadStartedCommandProperty, value); + } + } + + internal void OnDownloadStarted(EventArgs e) + { + var handler = DownloadStarted; + if (handler != null) handler(this, e); + + var downloadStartedCommand = DownloadStartedCommand; + if (downloadStartedCommand != null && downloadStartedCommand.CanExecute(e)) + downloadStartedCommand.Execute(e); } /// diff --git a/source/FFImageLoading.Shared/Cache/DownloadCache.cs b/source/FFImageLoading.Shared/Cache/DownloadCache.cs index f4d6c3972..4d900bfce 100644 --- a/source/FFImageLoading.Shared/Cache/DownloadCache.cs +++ b/source/FFImageLoading.Shared/Cache/DownloadCache.cs @@ -28,11 +28,12 @@ public async Task GetDiskCacheFilePathAsync(string url, string key = nul return await _diskCache.GetFilePathAsync(filename); } - public async Task GetAsync(string url, CancellationToken token, TimeSpan? duration = null, string key = null, CacheType? cacheType = null) + public async Task GetAsync(string url, CancellationToken token, Action onDownloadStarted, TimeSpan? duration = null, string key = null, CacheType? cacheType = null) { string filename = string.IsNullOrWhiteSpace(key) ? _md5Helper.MD5(url) : _md5Helper.MD5(key); var allowDiskCaching = AllowDiskCaching(cacheType); string filepath = allowDiskCaching == false ? null : await _diskCache.GetFilePathAsync(filename); + if (allowDiskCaching) { byte[] data = await _diskCache.TryGetAsync(filename, token).ConfigureAwait(false); @@ -40,15 +41,19 @@ public async Task GetAsync(string url, CancellationToken token, return new DownloadedData(filepath, data) { RetrievedFromDiskCache = true }; } - var bytes = await DownloadBytesAndCacheAsync(url, filename, filepath, token, duration, cacheType).ConfigureAwait(false); + var downloadInformation = new DownloadInformation(url, key, filename, allowDiskCaching, duration); + onDownloadStarted?.Invoke(downloadInformation); + + var bytes = await DownloadBytesAndCacheAsync(url, filename, token, duration, cacheType).ConfigureAwait(false); return new DownloadedData(filepath, bytes); } - public async Task GetStreamAsync(string url, CancellationToken token, TimeSpan? duration = null, string key = null, CacheType? cacheType = null) + public async Task GetStreamAsync(string url, CancellationToken token, Action onDownloadStarted, TimeSpan? duration = null, string key = null, CacheType? cacheType = null) { string filename = string.IsNullOrWhiteSpace(key) ? _md5Helper.MD5(url) : _md5Helper.MD5(key); var allowDiskCaching = AllowDiskCaching(cacheType); - string filepath = allowDiskCaching == false ? null : await _diskCache.GetFilePathAsync(filename); + // string filepath = allowDiskCaching == false ? null : await _diskCache.GetFilePathAsync(filename); + if (allowDiskCaching) { var diskStream = await _diskCache.TryGetStreamAsync(filename).ConfigureAwait(false); @@ -56,11 +61,14 @@ public async Task GetStreamAsync(string url, CancellationToken toke return new CacheStream(diskStream, true); } - var memoryStream = await DownloadStreamAndCacheAsync(url, filename, filepath, token, duration, cacheType).ConfigureAwait(false); + var downloadInformation = new DownloadInformation(url, key, filename, allowDiskCaching, duration); + onDownloadStarted?.Invoke(downloadInformation); + + var memoryStream = await DownloadStreamAndCacheAsync(url, filename, token, duration, cacheType).ConfigureAwait(false); return new CacheStream(memoryStream, false); } - private async Task DownloadStreamAndCacheAsync(string url, string filename, string filepath, CancellationToken token, TimeSpan? duration, CacheType? cacheType) + private async Task DownloadStreamAndCacheAsync(string url, string filename, CancellationToken token, TimeSpan? duration, CacheType? cacheType) { var responseBytes = await DownloadAsync(url, filename, token).ConfigureAwait(false); if (responseBytes == null) @@ -78,7 +86,7 @@ private async Task DownloadStreamAndCacheAsync(string url, string return memoryStream; } - private async Task DownloadBytesAndCacheAsync(string url, string filename, string filepath, CancellationToken token, TimeSpan? duration, CacheType? cacheType) + private async Task DownloadBytesAndCacheAsync(string url, string filename, CancellationToken token, TimeSpan? duration, CacheType? cacheType) { var responseBytes = await DownloadAsync(url, filename, token).ConfigureAwait(false); if (responseBytes == null) diff --git a/source/FFImageLoading.Shared/Cache/SimpleDiskCache.cs b/source/FFImageLoading.Shared/Cache/SimpleDiskCache.cs index 73109dbff..888c2de2c 100644 --- a/source/FFImageLoading.Shared/Cache/SimpleDiskCache.cs +++ b/source/FFImageLoading.Shared/Cache/SimpleDiskCache.cs @@ -281,9 +281,7 @@ void CleanCallback(object state) private string SanitizeKey(string key) { - return new string (key - .Where (c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) - .ToArray ()); + return key.ToSanitizedKey(); } } } diff --git a/source/FFImageLoading.Shared/Work/ImageLoaderTaskBase.cs b/source/FFImageLoading.Shared/Work/ImageLoaderTaskBase.cs index fbae780ad..f6607d828 100644 --- a/source/FFImageLoading.Shared/Work/ImageLoaderTaskBase.cs +++ b/source/FFImageLoading.Shared/Work/ImageLoaderTaskBase.cs @@ -321,7 +321,7 @@ protected async virtual Task TryDownloadImageAsync() if (Parameters.Source != ImageSource.Url) throw new InvalidOperationException("DownloadOnly: Only Url ImageSource is supported."); - var data = await DownloadCache.GetStreamAsync(Parameters.Path, CancellationToken.Token, Parameters.CacheDuration, Parameters.CustomCacheKey, Parameters.CacheType).ConfigureAwait(false); + var data = await DownloadCache.GetStreamAsync(Parameters.Path, CancellationToken.Token, Parameters.OnDownloadStarted, Parameters.CacheDuration, Parameters.CustomCacheKey, Parameters.CacheType).ConfigureAwait(false); using (var imageStream = data.ImageStream) { if (!data.RetrievedFromDiskCache) diff --git a/source/FFImageLoading.Touch/Work/DataResolver/UrlDataResolver.cs b/source/FFImageLoading.Touch/Work/DataResolver/UrlDataResolver.cs index 75a665ab7..584f17586 100644 --- a/source/FFImageLoading.Touch/Work/DataResolver/UrlDataResolver.cs +++ b/source/FFImageLoading.Touch/Work/DataResolver/UrlDataResolver.cs @@ -20,7 +20,7 @@ public UrlDataResolver(TaskParameter parameter, IDownloadCache downloadCache) { public async Task GetData(string identifier, CancellationToken token) { - var downloadedData = await DownloadCache.GetAsync(identifier, token, Parameters.CacheDuration, Parameters.CustomCacheKey, Parameters.CacheType).ConfigureAwait(false); + var downloadedData = await DownloadCache.GetAsync(identifier, token, Parameters.OnDownloadStarted, Parameters.CacheDuration, Parameters.CustomCacheKey, Parameters.CacheType).ConfigureAwait(false); var bytes = downloadedData.Bytes; var path = downloadedData.CachedPath; var result = downloadedData.RetrievedFromDiskCache ? LoadingResult.DiskCache : LoadingResult.Internet; diff --git a/source/FFImageLoading.Windows/Cache/SimpleDiskCache.cs b/source/FFImageLoading.Windows/Cache/SimpleDiskCache.cs index 141f6a104..70e66c951 100644 --- a/source/FFImageLoading.Windows/Cache/SimpleDiskCache.cs +++ b/source/FFImageLoading.Windows/Cache/SimpleDiskCache.cs @@ -367,9 +367,7 @@ async Task WaitForPendingWriteIfExists(string key) string SanitizeKey(string key) { - return new string(key - .Where(c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) - .ToArray()); + return key.ToSanitizedKey(); } } } diff --git a/source/FFImageLoading.Windows/DataResolver/UrlDataResolver.cs b/source/FFImageLoading.Windows/DataResolver/UrlDataResolver.cs index f3e56beb4..b144f6db2 100644 --- a/source/FFImageLoading.Windows/DataResolver/UrlDataResolver.cs +++ b/source/FFImageLoading.Windows/DataResolver/UrlDataResolver.cs @@ -20,7 +20,7 @@ public UrlDataResolver(TaskParameter parameter, IDownloadCache downloadCache) public async Task> GetStream(string identifier, CancellationToken token) { - var cachedStream = await DownloadCache.GetStreamAsync(identifier, token, Parameters.CacheDuration, Parameters.CustomCacheKey, Parameters.CacheType).ConfigureAwait(false); + var cachedStream = await DownloadCache.GetStreamAsync(identifier, token, Parameters.OnDownloadStarted, Parameters.CacheDuration, Parameters.CustomCacheKey, Parameters.CacheType).ConfigureAwait(false); var imageInformation = new ImageInformation(); imageInformation.SetPath(identifier);