- Developed by Dented Pixel
API Docs for: LeanLoader 0.2
Show:

File: LeanLoader.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;

public class LeanTest : object {
	private static int tests = 0;
	private static int passes = 0;

	public static void debug( string name, bool didPass){
		float len = printOutLength(name);
		string logName = "<b>"+ name + "</b> " + "".PadRight(40-(int)len,"_"[0]) + " [ "+ (didPass ? "<color=green>pass</color>" : "<color=red>FAIL</color>") +" ]";
		Debug.Log(logName);
		if(didPass)
			passes++;
		tests++;
	}

	public static float printOutLength( string str ){
		float len = 0.0f;
		for(int i = 0; i < str.Length; i++){
			if(str[i]=="I"[0]){
				len += 0.5f;
			}else{
				len += 1.0f;
			}
		}
		return len;
	}

	public static void overview(){

	}
}

/**
* JSON object for parsing a string into a useable JSON object
*
* @class LeanJSON
* @constructor
* @param {String} str:String String of representing the JSON object (will be parsed and made into an easily readable Dictionary<string,string> object)
*/
public class LeanJSON : Dictionary<string,string> {
	private static char begJson = "{"[0];
	private static char endJson = "}"[0];
	private static char begArr = "["[0];
	private static char endArr = "]"[0];
	private static char quoteDouble = "\""[0];
	private static char quoteSingle = "'"[0];
	private static char colon = ":"[0];
	private static char comma = ","[0];
	private static char escape = "\\"[0];

	public override string ToString(){
		return DictToString(this, null);
	}

	public static string DictToString<T, V>(IEnumerable<KeyValuePair<T, V>> items, string format)
	{
	    format = string.IsNullOrEmpty(format) ? "{0}='{1}', " : format; 

	    StringBuilder itemString = new StringBuilder();
	    itemString.Append("{");
	    foreach(var item in items)
	        itemString.AppendFormat(format, item.Key, item.Value);

	    itemString.Append("}");
	    return itemString.ToString(); 
	}

	public LeanJSON( string str ){
		int beg = str.IndexOf(begJson);
		int end = str.LastIndexOf(endJson);
		str = str.Substring(beg+1,end-beg-1);
		// Debug.Log("short:"+str);

		bool isDoubleQuoted = false;
		bool hasStartedQuote = false;
		int hasStartedEscape = -1;
		int closureLevel = 0;
		int lastComma = 0;
		int endPoint = str.Length-1;
		List<string> commaSplit = new List<string>();
		for(int i = 0; i <= endPoint; i++){
			/*if(str[i]==comma){
				Debug.Log("char["+i+"]:"+str[i] + " closureLevel:"+closureLevel+" hasStartedEscape:"+hasStartedEscape+" hasStartedQuote:"+hasStartedQuote+" lastComma:"+lastComma+" part:"+str.Substring(lastComma,i-lastComma));
			}*/
			// split on comma
			if(hasStartedEscape<=0){
				if(closureLevel==0){ // only parse if it is on the first level
					if(hasStartedQuote){ // Check if the quote area has ended
						if(isDoubleQuoted){
							if(str[i]==quoteDouble){
								hasStartedQuote = false;
							}
						}else{
							if(str[i]==quoteSingle){
								hasStartedQuote = false;
							}
						}
					}else{
						if(str[i]==comma){ // Push to array
							string split = str.Substring(lastComma,i-lastComma);
							// Debug.Log("split:"+split);
							commaSplit.Add( split );
							lastComma = i + 1;
						}else if(str[i]==escape){
							hasStartedEscape = 2;
						}else if(hasStartedEscape<=0){
							if(str[i]==quoteDouble){
								isDoubleQuoted = true;
								hasStartedQuote = true;
							}else if(str[i]==quoteSingle){
								isDoubleQuoted = false;
								hasStartedQuote = true;
							}	
						}
					}
				}
				if(i==endPoint){ // at end of string, push remaining
					string split = str.Substring(lastComma,i-lastComma+1);
					split = split.Replace(@"\", string.Empty); // strip off any escape characters from the strings
					commaSplit.Add( split );
					// Debug.Log("split:"+split);
				}

				if(hasStartedQuote==false){ // increment and decrement closure level
					if(str[i]==begJson){
						closureLevel++;
					}else if(str[i]==endJson){
						// Debug.Log("decrementing part:"+str.Substring(lastComma,i-lastComma));
						closureLevel--;
					}else if(str[i]==begArr){
						closureLevel++;
					}else if(str[i]==endArr){
						// Debug.Log("decrementing part:"+str.Substring(lastComma,i-lastComma));
						closureLevel--;
					}
				}
			}

			if(hasStartedEscape>=0){
				// Debug.LogError("Escapes exist! part:"+str.Substring(lastComma,i));
				hasStartedEscape--;
			}
		}
		
		for(int i = 0; i < commaSplit.Count; i++){
			// NEED to fix, colons can be hidden inside strings
			if(commaSplit[i].Length>1){
				int colonPoint = commaSplit[i].IndexOf(colon);
				string[] colonSplit = new string[]{ commaSplit[i].Substring(0,colonPoint), commaSplit[i].Substring(colonPoint+1) } ;
				// Debug.Log("colon 0:"+colonSplit[0] + " 1:"+ colonSplit[1]);
				// Trim quotes
				for(int j = 0; j < colonSplit.Length; j++){
					// Trim double quotes
					char firstChar = colonSplit[j][0];
					
					if(firstChar==quoteDouble){
						end = colonSplit[j].LastIndexOf(quoteDouble);
						// Debug.Log("colonSplit[j]:"+colonSplit[j]+" beg:"+beg+" end:"+end);
						colonSplit[j] = colonSplit[j].Substring(1,end-1);
					}else{
						// Trim single quotes
						if(firstChar==quoteSingle){
							end = colonSplit[j].LastIndexOf(quoteSingle);
							colonSplit[j] = colonSplit[j].Substring(1,end-1);
						}
					}
				}
				// Debug.Log("trimmed colon 0:"+colonSplit[0] + " 1:"+ colonSplit[1]);
				// Debug.Log("key:"+colonSplit[0]+" val:"+colonSplit[1]);
				this[ colonSplit[0] ] = colonSplit[1];
			}
		}
	}

	/**
	* Retrieve the object on the associated key (this is used when the JSON has nested JSON objects)
	* 
	* @method Object
	* @param {String} key:String String The key of the associated object you wish to retrieve
	* @return {LeanJSON} Value for key
	*/
	public LeanJSON Object( string key ){
		return new LeanJSON( this[key] );
	}

	/**
	* Retrieve the an array for the associated key (returns a LeanJSON[] Array). 
	* 
	* @method Array
	* @param {String} key:String String The key of the associated array you wish to retrieve
	* @return {Array} LeanJSON[] Array for key
	*/
	public LeanJSON[] Array( string key){
		string str = this[key];
		int beg = str.IndexOf(begArr);
		int end = str.LastIndexOf(endArr);
		str = str.Substring(beg+1,end-beg-1);

		bool isDoubleQuoted = false;
		bool hasStartedQuote = false;
		int hasStartedEscape = -1;
		int closureLevel = 0;
		int lastComma = 0;
		int endPoint = str.Length-1;
		List<string> commaSplit = new List<string>();
		for(int i = 0; i <= endPoint; i++){
			/*if(str[i]==comma){
				Debug.Log("char["+i+"]:"+str[i] + " closureLevel:"+closureLevel+" hasStartedEscape:"+hasStartedEscape+" hasStartedQuote:"+hasStartedQuote+" lastComma:"+lastComma+" part:"+str.Substring(lastComma,i-lastComma));
			}*/
			// split on comma
			if(hasStartedEscape<=0){
				if(closureLevel==0){ // only parse if it is on the first level
					if(hasStartedQuote){ // Check if the quote area has ended
						if(isDoubleQuoted){
							if(str[i]==quoteDouble){
								hasStartedQuote = false;
							}
						}else{
							if(str[i]==quoteSingle){
								hasStartedQuote = false;
							}
						}
					}else{
						if(str[i]==comma){ // Push to array
							string split = str.Substring(lastComma,i-lastComma);
							// Debug.Log("split:"+split);
							commaSplit.Add( split );
							lastComma = i + 1;
						}else if(str[i]==escape){
							hasStartedEscape = 2;
						}else if(hasStartedEscape<=0){
							if(str[i]==quoteDouble){
								isDoubleQuoted = true;
								hasStartedQuote = true;
							}else if(str[i]==quoteSingle){
								isDoubleQuoted = false;
								hasStartedQuote = true;
							}	
						}
					}
				}
				if(i==endPoint){ // at end of string, push remaining
					string split = str.Substring(lastComma,i-lastComma+1);
					split = split.Replace(@"\", string.Empty); // strip off any escape characters from the strings
					commaSplit.Add( split );
					// Debug.Log("split:"+split);
				}

				if(hasStartedQuote==false){ // increment and decrement closure level
					if(str[i]==begJson){
						closureLevel++;
					}else if(str[i]==endJson){
						// Debug.Log("decrementing part:"+str.Substring(lastComma,i-lastComma));
						closureLevel--;
					}
				}
			}

			if(hasStartedEscape>=0){
				// Debug.LogError("Escapes exist! part:"+str.Substring(lastComma,i));
				hasStartedEscape--;
			}
		}
		LeanJSON[] jsonArr = new LeanJSON[ commaSplit.Count ];
		for(int i = 0; i < jsonArr.Length; i++){
			if(commaSplit[i].Length>2)
				jsonArr[i] = new LeanJSON( commaSplit[i] );
		}

		return jsonArr;
	}

	/**
	* Retrieve the string the associated key 
	* 
	* @method String
	* @param {String} key:String String The key of the associated string you wish to retrieve
	* @return {String} Value for key
	*/
	public string String( string key ){
		return this[key];
	}

	/**
	* Retrieve the double value for the associated key 
	* 
	* @method Double
	* @param {String} key:String String The key of the associated string you wish to retrieve
	* @return {double} Value for key
	*/
	public float Double( string key ){
		return double.Parse( this[key] );
	}

	/**
	* Retrieve the float value for the associated key 
	* 
	* @method Float
	* @param {String} key:String String The key of the associated string you wish to retrieve
	* @return {float} Value for key
	*/
	public float Float( string key ){
		return float.Parse( this[key] );
	}

	/**
	* Retrieve the int value for the associated key 
	* 
	* @method Int
	* @param {String} key:String String The key of the associated string you wish to retrieve
	* @return {int} Value for key
	*/
	public int Int( string key ){
		return int.Parse( this[key] );
	}

	/**
	* Retrieve the boolean value for the associated key 
	* 
	* @method Boolean
	* @param {String} key:String String The key of the associated string you wish to retrieve
	* @return {boolean} Value for key
	*/
	public bool Boolean( string key ){
		return bool.Parse( this[key] );
	}

	public bool Bool( string key ){
		return bool.Parse( this[key] );
	}

}

public class LLOptions : object {

	public System.Action<Texture2D> onImageLoad;
	public System.Action<Texture2D,Dictionary<string,object>> onImageLoadP;
	public System.Action<string> onTextLoad;
	public System.Action<LeanJSON> onJSONLoad;
	public System.Action<string,Dictionary<string,object>> onTextLoadP;
	public System.Action<LeanJSON,Dictionary<string,object>> onJSONLoadP;
	public System.Action<AudioClip> onAudioLoad;
	public System.Action<AudioClip,Dictionary<string,object>> onAudioLoadP;
	public System.Action<string> onError;
	public Dictionary<string, object> postParams;
	public bool useCache;
	public bool useCacheAsBackup;
	public bool isImage;
	public bool isText;
	public bool isJSON;
	public bool isAudio;
	public int cacheLife;
	public string cacheName;
	public LeanLoading ll;
	public Dictionary<string,object> onLoadParam;

	/**
	* Set options for the LeanLoader
	*
	* @class LLOptions
	* @constructor
	*/
	public LLOptions(){
		cacheLife = System.Int32.MaxValue;
	}

	/**
	* Set the callback for the asset once it is loaded (image loading)
	* 
	* @method LLOptions().setOnLoad
	* @param {System.Action<Texture2D>} onLoad:System.Action<Texture2D> System.Action<Texture2D> The method that is called once the image is loaded
	*/
	public LLOptions setOnLoad( System.Action<Texture2D> onLoad ){
		this.onImageLoad = onLoad;
		return this;
	}
	public LLOptions setOnLoad( System.Action<Texture2D,Dictionary<string,object>> onLoad ){
		this.onImageLoadP = onLoad;
		return this;
	}

	/**
	* Set the callback for the asset once it is loaded (data loading)
	* 
	* @method LLOptions().setOnLoad
	* @param {System.Action<string>} onLoad:System.Action<string> System.Action<string> The method that is called once the data is loaded
	*/
	public LLOptions setOnLoad( System.Action<string> onLoad ){
		this.onTextLoad = onLoad;
		return this;
	}
	public LLOptions setOnLoad( System.Action<string,Dictionary<string,object>> onLoad ){
		this.onTextLoadP = onLoad;
		return this;
	}

	/**
	* Set the callback for the asset once it is loaded (data loading JSON specific)
	* 
	* @method LLOptions().setOnLoad
	* @param {System.Action<LeanJSON>} onLoad:System.Action<LeanJSON> System.Action<LeanJSON> The method that is called once the data is loaded
	*/
	public LLOptions setOnLoad( System.Action<LeanJSON> onLoad ){
		this.onJSONLoad = onLoad;
		return this;
	}
	public LLOptions setOnLoad( System.Action<LeanJSON,Dictionary<string,object>> onLoad ){
		this.onJSONLoadP = onLoad;
		return this;
	}

	/**
	* Set the callback for the asset once it is loaded (audio loading)
	* 
	* @method LLOptions().setOnLoad
	* @param {System.Action<AudioClip>} onLoad:System.Action<AudioClip> System.Action<AudioClip> The method that is called once the audio is loaded
	*/
	public LLOptions setOnLoad( System.Action<AudioClip> onLoad ){
		this.onAudioLoad = onLoad;
		return this;
	}
	public LLOptions setOnLoad( System.Action<AudioClip,Dictionary<string,object>> onLoad ){
		this.onAudioLoadP = onLoad;
		return this;
	}

	/**
	* Set the callback for error method that is called if it encounters any issues while loading
	* 
	* @method LLOptions().setOnError
	* @param {System.Action<string>} onError:System.Action<string> System.Action<string> The method that is called when if it encounters an error
	*/
	public LLOptions setOnError( System.Action<string> onError ){
		this.onError = onError;
		return this;
	}

	/**
	* Set Post parameters in the server call
	* 
	* @method LLOptions().setPostParams
	* @param {Dictionary<string, string>} dictionary:Dictionary<string, string> Dictionary<string, string> Add a dictionary of string values that will be posted to the server call
	*/
	public LLOptions setPostParams( Dictionary<string, object> dict ){
		postParams = dict;
		return this;
	}

	/**
	* Set Post parameters in the server call
	* 
	* @method LLOptions().setPostParams
	* @param {Hashtable} hashtable:Hashtable Hashtable Add a hashtable of values that will be posted to the server call
	*/
	#if !UNITY_METRO
	public LLOptions setPostParams( Hashtable hash ){
		postParams = new Dictionary<string, object>();
		foreach (DictionaryEntry pair in hash){
			Debug.Log("pair.Value:"+pair.Value);
			string valS;
			var val = pair.Value;
			if(val.GetType()==typeof(float)){
				valS = ""+val;
			}else{
				valS = val as string;
			}
			postParams.Add((string)pair.Key, valS);
		}
		return this;
	}

	public LLOptions setOnLoadParam( Hashtable hash ){
		this.onLoadParam = new Dictionary<string, object>();
		foreach (DictionaryEntry pair in hash){
			this.onLoadParam.Add((string)pair.Key, (object)pair.Value);
		}
		return this;
	}
	#endif

	/**
	* Set the use of caching. On subsequent calls if the cache-life is still valid, it will retrieve it from the cache first
	* 
	* @method LLOptions().setUseCache
	* @param {boolean} useCache:boolean boolean determining whether or not to cache this item (and retrieve from cache in subsequent calls)
	*/
	public LLOptions setUseCache( bool useCache ){
		this.useCache = useCache;
		return this;
	}

	/**
	* This mode of caching only pulls up the cache as a backup (if internet is not available, or it has an issue reaching the asset)
	* 
	* @method LLOptions().setUseCacheAsBackup
	* @param {boolean} useCacheAsBackup:boolean boolean determining whether or not to retrieve a cached version as a backup to getting the most up-to-date version
	*/
	public LLOptions setUseCacheAsBackup( bool useCacheAsBackup ){
		this.useCacheAsBackup = useCacheAsBackup;
		return this;
	}

	/**
	* Set the cache life of the asset (in seconds)
	* 
	* @method LLOptions().setCacheLife
	* @param {int} cacheLife:int int value in seconds specifying the life-time of the cached asset. Once this value is exceded a new version will be retrieved.
	*/
	public LLOptions setCacheLife( int cacheLife ){
		this.cacheLife = cacheLife;
		return this;
	}

	public LLOptions setOnLoadParam( Dictionary<string,object> hash ){
		this.onLoadParam = hash;
		return this;
	}

	public void savePostValues(){
		if(postParams!=null){
			StringBuilder strBuild = new StringBuilder(100);

			foreach (var key in postParams.Keys){
				strBuild.Append("&");
				strBuild.Append((string)key);
				strBuild.Append("=");
				strBuild.Append((string)postParams[key]);
			}

			cacheName = strBuild.ToString();
		}
	}

	public static Dictionary<string,object> dictionary( Dictionary<string,object> table ){
		Dictionary<string, object> d = new Dictionary<string, object>();
		foreach (var key in table.Keys){
			string valS;
			var val = table[key];
			if(val.GetType()==typeof(float)){
				valS = ""+val;
			}else{
				valS = val as string;
			}
			// Debug.Log("key:"+key+" value:"+valS);
			d.Add((string)key, valS);
		}
		return d;
	}
}

/**
* Helper class for saving large files over a period of time, to avoid frame-rate hiccups
*
* @class LLStreamingSave
* @constructor
*/

public class LLStreamingSave : MonoBehaviour{

	private static char strSplitChar = "|"[0];
	private StringBuilder strBuild;

	/**
	* The amount of loops that are spent on every update decoding audio data (decrease if you are having performance issues)
	* 
	* @property AUDIO_DECODE_EVERY
	* @type {int}
	* @default 40000
	*/
	public static int AUDIO_DECODE_EVERY = 40000;
	/**
	* The amount of loops that are spent on every update encoding audio date (decrease if you are having performance issues)
	* 
	* @property AUDIO_ENCODE_EVERY
	* @type {int}
	* @default 5000
	*/
	public static int AUDIO_ENCODE_EVERY = 5000;

	public void save( AudioClip audio, LLOptions options ){
		StartCoroutine( serializeAudio( audio, options ) );
	}

	public void retrieve( string str, LLOptions options ){
		StartCoroutine( deSerializeAudio( str, options ) );
	}

	IEnumerator serializeAudio( AudioClip audio, LLOptions options ) {
		int lengthSamples = audio.samples * audio.channels;

		strBuild = new StringBuilder(lengthSamples*10+100);
		string str;
		float[] samples = new float[lengthSamples];
		audio.GetData(samples, 0);
		int i = 0;
		int j = 1;
		while (i < samples.Length) {
        	if(j%AUDIO_ENCODE_EVERY==0){
        		// Debug.Log("processing");
        		yield return true;
        	}
			if(samples[i]<0.0f){
				str = samples[i].ToString("F7");
			}else{
				str = samples[i].ToString("F8");
			}
			strBuild.Append( str );
			++i;
			j++;
		}
		
		strBuild.Append("|" + audio.name);
		strBuild.Append("|" + lengthSamples);
		strBuild.Append("|" + audio.channels);
		strBuild.Append("|" + audio.frequency);
		strBuild.Append("|" + samples.Length);

		try {
			PlayerPrefs.SetString(options.cacheName, strBuild.ToString());	
		}catch (PlayerPrefsException err) {
			Debug.Log("Exceded Storage Limit (only usually issue on the Web Player) error: " + err);
			if(options.onError!=null)
		        options.onError( err.ToString() );
		}

		Destroy( this );
	}

	IEnumerator deSerializeAudio( string str, LLOptions options ){
		string[] splitGroup = str.Split( strSplitChar );

		string aName = splitGroup[1];
		int lengthSamples = int.Parse( splitGroup[2] );
		int channels = int.Parse( splitGroup[3] );
		int frequency = int.Parse( splitGroup[4] );
		//Debug.Log("name:"+aName+" len:"+lengthSamples+" channels:"+channels+" freq:"+frequency+" samplesLength:"+samplesLength);
		AudioClip audio = AudioClip.Create(aName, lengthSamples/channels, channels, frequency, true, false);

		string audioStr = splitGroup[0];
		
		float[] data = new float[lengthSamples];
		
		int i = 0;
		int k = 0;
		int j = 1;
        while (i < audioStr.Length) {
        	if(j%AUDIO_DECODE_EVERY==0){
        		// Debug.Log("processing");
        		options.ll.fakeProgress = i*1.0f/audioStr.Length;
        		yield return true;
        	}
            data[k] = float.Parse( audioStr.Substring(i, 10) );
            i+=10;
            k++;
            j++;
        }
        audio.SetData( data, 0 );

        if(options.onAudioLoadP!=null){
			options.onAudioLoadP( audio, options.onLoadParam );
		}else{
			options.onAudioLoad( audio );
		}
		options.ll.fakeProgress = 1.0f;
		
		Destroy( this );
	}
}

/**
* Keep track of the loading progress of the asset, as well as other attributes
*
* @class LeanLoading
* @constructor
*/
public class LeanLoading : object {

	/**
	* The name the asset is stored as (useful for deleting later)
	* 
	* @property cacheName
	* @type {string}
	*/
	public string cacheName;
	/**
	* Whether the cache being used for this asset 
	* 
	* @property cacheUsed
	* @default false
	* @type {boolean}
	*/
	public bool cacheUsed;
	/**
	* The WWW object that is used to load the object (in case you need to access anything specific) 
	* 
	* @property www
	* @type {WWW}
	*/
	public WWW www;
	public float fakeProgress;

	public LeanLoading(){
		fakeProgress = -1.0f;
	}

	/**
	* How much of the asset has loaded (from 0.0f to 1.0f)
	* 
	* @property progress
	* @type {float}
	* @default 0.0f
	*/
	public float progress{
		get{
			if(www!=null){
				return www.progress;
			}else if(fakeProgress>=0.0f){
				return fakeProgress;
			}else{
				return 0.0f;
			}
		}
	}

}

/**
* Load assets from the web and cache them for faster performance and offline use
*
* @class LeanLoader
* @constructor
*/
public class LeanLoader : MonoBehaviour {

	private const string CACHE_KEYS = "LLCacheKeys";

	private static Dictionary<string, int> allKeys;
	private static char strSplitChar = "|"[0];

	public LeanLoader(){
		if(allKeys==null){
			allKeys = retrieveHashtable( CACHE_KEYS );
		}
	}

	private static LeanLoader reference;
	private static GameObject staticGo;
	private const string uniqueClassName = "~LeanLoader";

	private static void init(){
		if(staticGo==null){
			staticGo = GameObject.Find(uniqueClassName);
			if(staticGo==null){
				staticGo = new GameObject( uniqueClassName );
				reference = staticGo.AddComponent(typeof(LeanLoader)) as LeanLoader;
				staticGo.isStatic = true;
				DontDestroyOnLoad( staticGo );
				//staticGo.hideFlags = HideFlags.HideInHierarchy;
			}else{
				reference = staticGo.GetComponent(typeof(LeanLoader)) as LeanLoader;
			}
		}
	}

	private static void saveHashtable( Dictionary<string, int> dict, string name ){
		int count = dict.Keys.Count;
		StringBuilder strBuild = new StringBuilder(count*20);
		int i = 0;
		foreach(KeyValuePair<string, int> entry in dict){
	        strBuild.Append( entry.Key );
	        if(i<count-1)
				strBuild.Append( strSplitChar );

			i++;
		}
		PlayerPrefs.SetString(name, strBuild.ToString()); 
	}

	private static Dictionary<string, int> retrieveHashtable( string name ){
		string str = PlayerPrefs.GetString(name);
		// Debug.Log("saved keys:"+str);
		if(str==null)
			return new Dictionary<string, int>();

		string[] splitGroup = str.Split( strSplitChar );
		Dictionary<string, int> dict = new Dictionary<string, int>();
		for(int i = 0; i < splitGroup.Length; i++){
			dict[ splitGroup[i] ] = 1;
		}

		return dict;
	}

	private static System.DateTime getLastSavedTime( string dataURL ){
		string keyDate = dataURL+"_d";
		if(PlayerPrefs.HasKey( keyDate )){
			string date = PlayerPrefs.GetString(keyDate);
			string[] dateSplit = date.Split(":"[0]);
			if(dateSplit.Length>6){
				System.DateTime dateTime = new System.DateTime(System.Int32.Parse(dateSplit[0]), System.Int32.Parse(dateSplit[1]), System.Int32.Parse(dateSplit[2]), System.Int32.Parse(dateSplit[3]), System.Int32.Parse(dateSplit[4]), System.Int32.Parse(dateSplit[5]), System.Int32.Parse(dateSplit[6]) );
				return dateTime;
			}
		}
		return System.DateTime.Now;
	}

	/**
	* Remove a specific cached item
	* 
	* @method LeanLoader.deleteCache
	* @param {string} dataURL:String The path of the asset you want to delete from the cache
	*/
	public static void deleteCache( string dataURL ){
		PlayerPrefs.DeleteKey(dataURL); 
		PlayerPrefs.DeleteKey(dataURL+"_w");
		PlayerPrefs.DeleteKey(dataURL+"_h");
		PlayerPrefs.DeleteKey(dataURL+"_d");
	}

	/**
	* Removes all of the LeanLoader cached items from the PlayerPrefs
	* 
	* @method LeanLoader.deleteCacheAll
	*/
	public static void deleteCacheAll(){
		init();
		foreach(KeyValuePair<string, int> entry in allKeys){
	        deleteCache( entry.Key );
		}

		PlayerPrefs.DeleteKey( CACHE_KEYS ); 
	}

	/**
	* Load an asset from the internet
	* 
	* @method LeanLoader.load
	* @param {String} dataURL:String URL of the data you wish to load
	* @param {LLOptions} options:LLOptions The optional parameters you wish to specify for the loading of the asset (use caching, onComplete handlers, etc...)
	* @return {LeanLoading} Returns a LeanLoading object where you can track the assets progress, and other attributes
	* @example <i>Javascript:</i><br>
	* LeanLoader.load("http://dentedpixel.com/assets/Monkeyshines.png", LLOptions().setOnComplete( imageLoaded ).setUseCache( true ));
	* function imageLoaded( tex:Texture2D ){<br>
	* 	Debug.Log("image loaded:"+tex);<br>
	* }<br>
	* <br><br>
	* <i>C#: </i> <br>
	* LeanLoader.load("http://dentedpixel.com/assets/Monkeyshines.png", new LLOptions().setOnComplete( imageLoaded ).setUseCache( true ));
	* void imageLoaded( Texture2D tex ){<br>
	* 	Debug.Log("image loaded:"+tex);<br>
	* }<br>
	* <br><br>
	*/
	public static LeanLoading load( string dataURL, LLOptions options ){ 
		init();

		if(options.useCache || options.useCacheAsBackup){
			options.savePostValues();
		}
		if(!string.IsNullOrEmpty(options.cacheName) ){
			options.cacheName = dataURL+options.cacheName;
		}else{
			options.cacheName = dataURL;
		}
		// Debug.Log("options.cacheName:"+options.cacheName);
		
		bool cacheValid = options.useCache;
		if(options.useCacheAsBackup==false && options.useCache && PlayerPrefs.HasKey(options.cacheName+"_d")){
			System.DateTime dateWhenExpired = getLastSavedTime( options.cacheName );
			int hasExpired = System.DateTime.Now.CompareTo( dateWhenExpired );
			// Debug.Log("hasExpired:"+hasExpired + " dateWhenExpired:"+dateWhenExpired);
			cacheValid = hasExpired < 0;
		}
		options.isImage = options.onImageLoad!=null || options.onImageLoadP!=null;
		options.isText = options.onTextLoad!=null || options.onTextLoadP!=null;
		options.isJSON = options.onJSONLoad!=null || options.onJSONLoadP!=null;
		options.isAudio = options.onAudioLoad!=null || options.onAudioLoadP!=null;

		LeanLoading ll = new LeanLoading();
		ll.cacheUsed = cacheValid;
		ll.cacheName = options.cacheName;
		options.ll = ll;

		string temp = PlayerPrefs.GetString(options.cacheName);
		if(options.useCacheAsBackup==false && cacheValid && !string.IsNullOrEmpty(temp)){
			loadFromCache( options);
		}else{
			reference.startSendData( dataURL, options );
		}

		return ll;
	}

	public static void loadFromCache( string dataURL, LLOptions options ){
		options.cacheName = dataURL;
		loadFromCache( options );
	}

	private static void loadFromCache( LLOptions options ){
		string temp = PlayerPrefs.GetString(options.cacheName);
		if(!string.IsNullOrEmpty(temp)){
			if(options.isImage){
				int width = PlayerPrefs.GetInt(options.cacheName+"_w");
				int height = PlayerPrefs.GetInt(options.cacheName+"_h");
				byte[] byteArray = System.Convert.FromBase64String(temp);
				Texture2D tex = new Texture2D(width,height,TextureFormat.RGB24, false);
				tex.LoadImage(byteArray);

				if(options.onImageLoadP!=null){
					options.onImageLoadP( tex, options.onLoadParam );
				}else{
					options.onImageLoad( tex );
				}
				options.ll.fakeProgress = 1.0f;
		   }else if(options.isText){
			   	if(options.onTextLoadP!=null){
					options.onTextLoadP( temp, options.onLoadParam );
				}else{
					options.onTextLoad( temp );
				}
				options.ll.fakeProgress = 1.0f;
		   }else if(options.isJSON){
			   	if(options.onJSONLoadP!=null){
					options.onJSONLoadP( new LeanJSON( temp ), options.onLoadParam );
				}else{
					options.onJSONLoad( new LeanJSON( temp ) );
				}
				options.ll.fakeProgress = 1.0f;
		   }else if(options.isAudio){
		   		LLStreamingSave save = staticGo.AddComponent( typeof(LLStreamingSave) ) as LLStreamingSave;
				save.retrieve( temp, options );
		   }
		}
	}

	private void startSendData( string dataURL, LLOptions options ){
		StartCoroutine( sendData( dataURL, options ) );
	}

	IEnumerator sendData( string dataURL, LLOptions options ){
		WWWForm sendForm = null;

		if(options.postParams!=null){
			sendForm = new WWWForm();
		    foreach(KeyValuePair<string, object> entry in options.postParams){
		    	sendForm.AddField((string)entry.Key, ""+entry.Value );
			}
		}

		WWW www;
		if(sendForm!=null){
			www = new WWW(dataURL, sendForm); 
		}else{
			www = new WWW(dataURL);
		}
		
		options.ll.www = www;
		yield return www;

		if(!string.IsNullOrEmpty(www.error)) {
	        Debug.Log("Error: " + www.error + " while loading:"+dataURL);
	        if(options.onError!=null)
		        options.onError( www.error );

		    if(options.useCacheAsBackup || options.useCache)
		    	loadFromCache( options );
	    } else {
	        // Debug.Log("Receive response: " + www.text + " isImage:"+options.isImage);
	        if(options.isImage){
	        	if(www.text.IndexOf("{\"data\"")>=0){ // Work around for Facebook profile images, that provide a redirect
	        		LeanJSON j = new LeanJSON( www.text );
	        		LeanJSON d = j.Object("data");
					string picUrl = d["url"];
					startSendData(picUrl, options);
	        	}else{
					Texture2D tex = www.texture;
					byte[] byteArray = tex.EncodeToPNG();
					string imgRepStr = System.Convert.ToBase64String(byteArray);

					if(options.useCache||options.useCacheAsBackup){
						try {
							PlayerPrefs.SetString(options.cacheName, imgRepStr); 
							PlayerPrefs.SetInt(options.cacheName+"_w", tex.width);
							PlayerPrefs.SetInt(options.cacheName+"_h", tex.height);
							saveTime( options.cacheName, options.cacheLife );

							allKeys[ options.cacheName ] = 1;
							saveHashtable( allKeys, CACHE_KEYS );
						}catch (PlayerPrefsException err) {
							Debug.Log("Exceded Storage Limit (only usually issue on the Web Player) error: " + err);
							if(options.onError!=null)
						        options.onError( err.ToString() );
						}
					}
					if(options.onImageLoadP!=null){
						options.onImageLoadP( tex, options.onLoadParam );
					}else{
						options.onImageLoad( tex );
					}
				}
	        }else if(options.isText || options.isJSON){
	        	string response = www.text;

	        	if(options.useCache||options.useCacheAsBackup){
		        	try {
						PlayerPrefs.SetString(options.cacheName, response);
						saveTime( options.cacheName, options.cacheLife ); 

						allKeys[ options.cacheName ] = 1;
						saveHashtable( allKeys, CACHE_KEYS );
					}catch (PlayerPrefsException err) {
						Debug.Log("Exceded Storage Limit (only usually issue on the Web Player) error: " + err);
						if(options.onError!=null)
					        options.onError( err.ToString() );
					}
				}
				if(options.isText){
					if(options.onTextLoadP!=null){
						options.onTextLoadP( response, options.onLoadParam );
					}else{
						options.onTextLoad( response );
					}
				}else{
					if(options.onJSONLoadP!=null){
						options.onJSONLoadP( new LeanJSON( response ), options.onLoadParam );
					}else{
						options.onJSONLoad( new LeanJSON( response ) );
					}
				}
	        }else if(options.isAudio){
	        	AudioClip response = www.audioClip;

	        	if(options.useCache||options.useCacheAsBackup){
		        	try {
						//PlayerPrefs.SetString(options.cacheName, serializeAudio(response));
						LLStreamingSave save = gameObject.AddComponent( typeof(LLStreamingSave) ) as LLStreamingSave;
						save.save( response, options );

						saveTime( options.cacheName, options.cacheLife ); 

						allKeys[ options.cacheName ] = 1;
						saveHashtable( allKeys, CACHE_KEYS );
					}catch (PlayerPrefsException err) {
						Debug.Log("Exceded Storage Limit (only usually issue on the Web Player) error: " + err);
						if(options.onError!=null)
					        options.onError( err.ToString() );
					}
				}

				if(options.onAudioLoadP!=null){
					options.onAudioLoadP( response, options.onLoadParam );
				}else{
					options.onAudioLoad( response );
				}
	        }
	    }
	}

	private void saveTime( string dataURL, int lifeTime ){
		System.DateTime future = System.DateTime.Now.Add( new System.TimeSpan(0, 0, lifeTime ) );

		PlayerPrefs.SetString(dataURL+"_d", ""+future.Year +":"+ future.Month +":"+ future.Day +":"+ future.Hour +":"+ future.Minute +":"+ future.Second +":"+ future.Millisecond); 
		PlayerPrefs.Save();
	}
}