floydpink / CachedImage

A WPF control that wraps the Image control to enable file-system based caching
http://floydpink.github.io/CachedImage/
MIT License
69 stars 22 forks source link

A better way to cache image #3

Closed stackia closed 9 years ago

stackia commented 9 years ago

BitmapImage has a UriCachePolicy property. You can simply set it to RequestCacheLevel.Default to get an IE-like cache policy with all exceptions handled properly.

        private static void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var url = e.NewValue as string;

            if (string.IsNullOrEmpty(url))
                return;

            var cachedImage = (Image) obj;
            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.UriSource = new Uri(url);
            bitmapImage.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
            bitmapImage.EndInit();
            cachedImage.Source = bitmapImage;
        }

There are also some issues in your code:

I tried to rewrite FileCache.cs before I discovered the UriCachePolicy property of BitmapImage. This is what I have done and it worked correctly in my scenario where hundreds of CachedImage control request some random image simultaneously.

        // A private field to record if a file is being written.
        private static readonly Dictionary<string, bool> IsWritingFile = new Dictionary<string, bool>();

        public static async Task<MemoryStream> HitAsync(string url)
        {
            if (!Directory.Exists(AppCacheDirectory))
            {
                Directory.CreateDirectory(AppCacheDirectory);
            }
            var uri = new Uri(url);
            var fileNameBuilder = new StringBuilder();
            using (var sha1 = new SHA1Managed())
            {
                var canonicalUrl = uri.ToString();
                var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(canonicalUrl));
                fileNameBuilder.Append(BitConverter.ToString(hash).Replace("-", "").ToLower());
                if (Path.HasExtension(canonicalUrl))
                    fileNameBuilder.Append(Path.GetExtension(canonicalUrl));
            }

            var fileName = fileNameBuilder.ToString();
            var localFile = string.Format("{0}\\{1}", AppCacheDirectory, fileName);
            var memoryStream = new MemoryStream();

            FileStream fileStream = null;
            if (!IsWritingFile.ContainsKey(fileName) && File.Exists(localFile))
            {
                using (fileStream = new FileStream(localFile, FileMode.Open, FileAccess.Read))
                {
                    await fileStream.CopyToAsync(memoryStream);
                }
                memoryStream.Seek(0, SeekOrigin.Begin);
                return memoryStream;
            }

            var request = WebRequest.Create(uri);
            request.Timeout = 30;
            try
            {
                var response = await request.GetResponseAsync();
                var responseStream = response.GetResponseStream();
                if (responseStream == null)
                    return null;
                if (!IsWritingFile.ContainsKey(fileName))
                {
                    IsWritingFile[fileName] = true;
                    fileStream = new FileStream(localFile, FileMode.Create, FileAccess.Write);
                }

                using (responseStream)
                {
                    var bytebuffer = new byte[100];
                    int bytesRead;
                    do
                    {
                        bytesRead = await responseStream.ReadAsync(bytebuffer, 0, 100);
                        if (fileStream != null)
                            await fileStream.WriteAsync(bytebuffer, 0, bytesRead);
                        await memoryStream.WriteAsync(bytebuffer, 0, bytesRead);
                    } while (bytesRead > 0);
                    if (fileStream != null)
                    {
                        await fileStream.FlushAsync();
                        fileStream.Dispose();
                        IsWritingFile.Remove(fileName);
                    }
                }
                memoryStream.Seek(0, SeekOrigin.Begin);
                return memoryStream;
            }
            catch (WebException)
            {
                return null;
            }
        }

In Image.cs:

        private static async void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var url = e.NewValue as string;

            if (string.IsNullOrEmpty(url))
                return;

            var memoryStream = await FileCache.HitAsync(url);
            if (memoryStream == null)
                return;

            try
            {
                var cachedImage = (Image) obj;
                var bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.StreamSource = memoryStream;
                bitmapImage.EndInit();
                cachedImage.Source = bitmapImage;
            }
            catch (Exception)
            {
                // ignored, in case the downloaded file is a broken or not an image.
            }
        }
floydpink commented 9 years ago

Thank you. Could you please open a pull request for this?

stackia commented 9 years ago

Here you go ~

floydpink commented 9 years ago

Closed via the PR #5