Skip to content

Instantly share code, notes, and snippets.

@NVentimiglia
Last active August 29, 2015 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NVentimiglia/2285c8ea452dbe2d4714 to your computer and use it in GitHub Desktop.
Save NVentimiglia/2285c8ea452dbe2d4714 to your computer and use it in GitHub Desktop.
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Android.Graphics;
using Xamarin.Forms;
using Color = Xamarin.Forms.Color;
using Path = System.IO.Path;
namespace Ventimiglia.Controls
{
/// <summary>
/// An image control that supports asyncranous loading. Requires the ImageDownloader Helper
/// </summary>
public class AsyncImage : ContentView
{
public Grid Root = new Grid
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
};
public ActivityIndicator Indicator = new ActivityIndicator
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
IsRunning = true
};
public Image Image = new Image
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
IsVisible = false
};
protected CachedHttpClient Client;
protected int Attempts = 0;
protected bool DidComplete;
protected bool IsLoading;
protected ImageSource ISource;
public static readonly BindableProperty RetryProperty = BindableProperty.Create<AsyncImage, int>(p => p.Retry, 3);
public static readonly BindableProperty OneTimeProperty =
BindableProperty.Create<AsyncImage, bool>(p => p.OneTime, true);
public static readonly BindableProperty SourceProperty =
BindableProperty.Create<AsyncImage, string>(p => p.Source, string.Empty,
propertyChanged: OnSourcePropertyChanged);
public int Retry
{
get { return (int) GetValue(RetryProperty); }
set { SetValue(RetryProperty, Retry); }
}
public bool OneTime
{
get { return (bool) GetValue(OneTimeProperty); }
set { SetValue(OneTimeProperty, value); }
}
public string Source
{
get { return (string) GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public Color SpinnerColor
{
get { return Indicator.Color; }
set { Indicator.Color = value; }
}
public AsyncImage()
{
Client = new CachedHttpClient();
Root.Children.Add(Image);
Root.Children.Add(Indicator);
Content = Root;
}
/// <summary>
/// Called when [items source property changed].
/// </summary>
/// <param name="bindable">The bindable.</param>
/// <param name="value">The value.</param>
/// <param name="newValue">The new value.</param>
private static void OnSourcePropertyChanged(BindableObject bindable, string value, string newValue)
{
((AsyncImage) (bindable)).DoLoad();
}
private async void DoLoad()
{
if (IsLoading)
return;
Image.IsVisible = false;
Indicator.IsVisible = true;
if (string.IsNullOrEmpty(Source))
return;
Attempts++;
if (!Source.Contains("http"))
{
Complete(ImageSource.FromFile(Source));
return;
}
try
{
IsLoading = true;
var bits = await Client.GetAsync(new Uri(Source));
var nbits = Resize(bits);
Complete(ImageSource.FromStream(() => new MemoryStream(nbits)));
IsLoading = false;
}
catch (Exception)
{
IsLoading = false;
if (Retry > Attempts)
{
DoRetry();
}
}
}
private async void DoRetry()
{
await Task.Delay(500);
DoLoad();
}
private byte[] Resize(byte[] bits)
{
return ResizeImage(bits, (int) Width, (int) Height);
}
private void Complete(ImageSource source)
{
ISource = source;
DidComplete = true;
Image.Source = source;
Image.IsVisible = true;
Indicator.IsVisible = false;
}
public byte[] ResizeImage(byte[] imageData, int width, int height)
{
// Load the bitmap
using (Bitmap originalImage = BitmapFactory.DecodeByteArray(imageData, 0, imageData.Length))
{
using (Bitmap resizedImage = Bitmap.CreateScaledBitmap(originalImage, (int)width, (int)height, false))
{
using (MemoryStream ms = new MemoryStream())
{
resizedImage.Compress(Bitmap.CompressFormat.Png, 95, ms);
return ms.ToArray();
}
}
}
}
}
#region HttpCache
/// <summary>
/// Utility service for async image downloads
/// </summary>
public class CachedHttpClient
{
private readonly FileCache store;
private readonly HttpClient http;
private readonly TimeSpan cacheDuration;
public string CacheName { get; protected set; }
public CachedHttpClient() : this(TimeSpan.FromDays(5))
{
}
public CachedHttpClient(TimeSpan cacheDuration, string cacheName = "Images")
{
this.cacheDuration = cacheDuration;
CacheName = cacheName;
http = new HttpClient();
http.Timeout = new TimeSpan(0, 0, 30);
store = new FileCache(cacheDuration, cacheName);
}
public bool HasLocallyCachedCopy(Uri uri)
{
var filename = Uri.EscapeDataString(uri.AbsoluteUri);
var lastWriteTime = store.LastWrite(filename);
if (lastWriteTime == null)
return false;
return lastWriteTime.Value + cacheDuration >= DateTime.UtcNow;
}
public async Task<byte[]> GetAsync(Uri uri)
{
var filename = Uri.EscapeDataString(uri.AbsoluteUri);
if (HasLocallyCachedCopy(uri))
{
return store.Read(filename);
}
using (var d = await http.GetAsync(uri))
{
var bits = await d.Content.ReadAsByteArrayAsync();
store.Write(filename, bits);
return bits;
}
}
}
#endregion
#region Cache
/// <summary>
/// Reusable File Cache. I/O with expiration
/// </summary>
public class FileCache
{
//TODO COMPRESSION AND ASYNC
public string Group { get; protected set; }
public object _syncLock = new object();
public FileCache(TimeSpan duration, string cacheGroup = "Images")
{
Group = cacheGroup;
//RemoveExpired(duration);
}
/// <summary>
/// Removes an item from the cache
/// </summary>
/// <param name="fname"></param>
public void Remove(string fname)
{
if (Exists(fname))
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Group);
var filePath = Path.Combine(path, fname);
lock (_syncLock)
{
File.Delete(filePath);
}
}
}
/// <summary>
/// Deletes all items in this cache group
/// </summary>
public void Clear()
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Group);
lock (_syncLock)
{
Directory.Delete(path);
}
}
/// <summary>
/// Removes all expires items from this cache group
/// </summary>
/// <param name="cacheTime"></param>
public void RemoveExpired(TimeSpan cacheTime)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Group);
if (!Directory.Exists(path))
return;
string[] files;
lock (_syncLock)
{
files = Directory.GetFiles(path);
}
foreach (var file in files)
{
var fInfo = new FileInfo(file);
if (fInfo.LastWriteTimeUtc + cacheTime < DateTime.UtcNow)
{
Remove(file);
}
}
}
/// <summary>
/// Does the file exist ?
/// </summary>
/// <param name="fname"></param>
/// <returns></returns>
public bool Exists(string fname)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Group);
if (!Directory.Exists(path))
return false;
var filePath = Path.Combine(path, fname);
if (!File.Exists(filePath))
return false;
return true;
}
/// <summary>
/// Last write time of the cache item
/// </summary>
/// <param name="fname"></param>
/// <returns></returns>
public DateTime? LastWrite(string fname)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Group);
if (!Directory.Exists(path))
return null;
var filePath = Path.Combine(path, fname);
if (!File.Exists(filePath))
return null;
//NotWorking, WTF
return DateTime.UtcNow;
return File.GetLastWriteTimeUtc(fname);
}
/// <summary>
/// Write File
/// </summary>
/// <param name="fname"></param>
/// <param name="data"></param>
public void Write(string fname, byte[] data)
{
lock (_syncLock)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Group);
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
var filePath = Path.Combine(path, fname);
File.WriteAllBytes(filePath, data);
//Not working
//var base64String = Convert.ToBase64String(data);
//File.WriteAllText(filePath, base64String);
}
}
/// <summary>
/// Read File
/// </summary>
/// <param name="fname"></param>
/// <returns></returns>
public byte[] Read(string fname)
{
lock (_syncLock)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Group);
if (!Directory.Exists(path))
return null;
var filePath = Path.Combine(path, fname);
if (!File.Exists(filePath))
return null;
//Not working
// var data = File.ReadAllText(filePath);
//return Convert.FromBase64String(data);
//Not working
return File.ReadAllBytes(filePath);
}
}
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment