Last active
August 29, 2015 14:27
-
-
Save NVentimiglia/2285c8ea452dbe2d4714 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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