Skip to content

Instantly share code, notes, and snippets.

@mob-sakai
Created July 28, 2017 10:04
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mob-sakai/c964b6f737898e23133f029e9fdaf514 to your computer and use it in GitHub Desktop.
Save mob-sakai/c964b6f737898e23133f029e9fdaf514 to your computer and use it in GitHub Desktop.
Etag-cacheable DownloadHandler for Unity.
using UnityEngine.Networking;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using System;
namespace Mobcast.Coffee.AssetSystem
{
public static class UnityWebRequestCachingExtensions
{
/// <summary>
/// Set UnityWebRequest to be cacheable(Etag).
/// </summary>
public static void SetCacheable(this UnityWebRequest www, CacheableDownloadHandler handler)
{
var etag = CacheableDownloadHandler.GetCacheEtag(www.url);
if (etag != null)
www.SetRequestHeader("If-None-Match", etag);
www.downloadHandler = handler;
}
}
/// <summary>
/// Cacheable download handler texture.
/// </summary>
public class CacheableDownloadHandlerTexture : CacheableDownloadHandler
{
Texture2D m_Texture;
public CacheableDownloadHandlerTexture(UnityWebRequest www, byte[] preallocateBuffer)
: base(www, preallocateBuffer)
{
}
/// <summary>
/// Returns the downloaded Texture, or null.
/// </summary>
public Texture2D texture
{
get
{
if (m_Texture == null)
{
m_Texture = new Texture2D(1, 1);
m_Texture.LoadImage(GetData(), true);
}
return m_Texture;
}
}
}
/// <summary>
/// Cacheable download handler.
/// </summary>
public abstract class CacheableDownloadHandler : DownloadHandlerScript
{
const string kDataSufix = "_d";
const string kEtagSufix = "_e";
static string s_WebCachePath;
static SHA1CryptoServiceProvider s_SHA1 = new SHA1CryptoServiceProvider();
/// <summary>
/// Is the download already finished?
/// </summary>
public bool isDone { get; private set; }
UnityWebRequest m_WebRequest;
MemoryStream m_Stream;
protected byte[] m_Buffer;
internal CacheableDownloadHandler(UnityWebRequest www, byte[] preallocateBuffer)
: base(preallocateBuffer)
{
this.m_WebRequest = www;
m_Stream = new MemoryStream(preallocateBuffer.Length);
}
/// <summary>
/// Get path for web-caching.
/// </summary>
public static string GetCachePath(string url)
{
if (s_WebCachePath == null)
s_WebCachePath = Application.temporaryCachePath + "/WebCache/";
if (!Directory.Exists(s_WebCachePath))
Directory.CreateDirectory(s_WebCachePath);
return s_WebCachePath + Convert.ToBase64String(s_SHA1.ComputeHash(UTF8Encoding.Default.GetBytes(url))).Replace('/', '_');
}
/// <summary>
/// Get cached Etag for url.
/// </summary>
public static string GetCacheEtag(string url)
{
var path = GetCachePath(url);
var infoPath = path + kEtagSufix;
var dataPath = path + kDataSufix;
return File.Exists(infoPath) && File.Exists(dataPath)
? File.ReadAllText(infoPath)
: null;
}
/// <summary>
/// Load cached data for url.
/// </summary>
public static byte[] LoadCache(string url)
{
return File.ReadAllBytes(GetCachePath(url) + kDataSufix);
}
/// <summary>
/// Save cache data for url.
/// </summary>
public static void SaveCache(string url, string etag, byte[] datas)
{
var path = GetCachePath(url);
File.WriteAllText(path + kEtagSufix, etag);
File.WriteAllBytes(path + kDataSufix, datas);
}
/// <summary>
/// Callback, invoked when the data property is accessed.
/// </summary>
protected override byte[] GetData()
{
if (!isDone)
{
throw new InvalidOperationException("Downloading is not completed. " + m_WebRequest.url);
}
else if (m_Buffer == null)
{
// Etag cache hit!
if (m_WebRequest.responseCode == 304)
{
m_Buffer = LoadCache(m_WebRequest.url);
}
// Download is completed successfully.
else if (m_WebRequest.responseCode == 200)
{
m_Buffer = m_Stream.GetBuffer();
SaveCache(m_WebRequest.url, m_WebRequest.GetResponseHeader("Etag"), m_Buffer);
}
}
if (m_Stream != null)
{
m_Stream.Dispose();
m_Stream = null;
}
return m_Buffer;
}
/// <summary>
/// Callback, invoked as data is received from the remote server.
/// </summary>
protected override bool ReceiveData(byte[] data, int dataLength)
{
m_Stream.Write(data, 0, dataLength);
return true;
}
/// <summary>
/// Callback, invoked when all data has been received from the remote server.
/// </summary>
protected override void CompleteContent()
{
base.CompleteContent();
isDone = true;
}
/// <summary>
/// Signals that this [DownloadHandler] is no longer being used, and should clean up any resources it is using.
/// </summary>
public new void Dispose()
{
base.Dispose();
if (m_Stream != null)
{
m_Stream.Dispose();
m_Stream = null;
}
}
}
}
@Kibsgaard
Copy link

Thanks for this.

I needed to replace line 145
m_Buffer = m_Stream.GetBuffer();
with
m_Buffer = m_Stream.ToArray();

The streams raw buffer seems to often be larger than the actual data and you end up with a lot of garbage data padded at the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment