123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727 |
- #if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Threading;
- #if NETFX_CORE
- using FileStream = BestHTTP.PlatformSupport.IO.FileStream;
- using Directory = BestHTTP.PlatformSupport.IO.Directory;
- using File = BestHTTP.PlatformSupport.IO.File;
- using BestHTTP.PlatformSupport.IO;
-
- #pragma warning disable 4014
- #else
- using FileStream = System.IO.FileStream;
- using Directory = System.IO.Directory;
- using System.IO;
- #endif
- namespace BestHTTP.Caching
- {
- using BestHTTP.Extensions;
- public sealed class UriComparer : IEqualityComparer<Uri>
- {
- public bool Equals(Uri x, Uri y)
- {
- return Uri.Compare(x, y, UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped, StringComparison.Ordinal) == 0;
- }
- public int GetHashCode(Uri uri)
- {
- return uri.ToString().GetHashCode();
- }
- }
- public static class HTTPCacheService
- {
- #region Properties & Fields
-
-
-
- private const int LibraryVersion = 2;
- public static bool IsSupported
- {
- get
- {
- if (IsSupportCheckDone)
- return isSupported;
- try
- {
- File.Exists(HTTPManager.GetRootCacheFolder());
- isSupported = true;
- }
- catch
- {
- isSupported = false;
- HTTPManager.Logger.Warning("HTTPCacheService", "Cache Service Disabled!");
- }
- finally
- {
- IsSupportCheckDone = true;
- }
- return isSupported;
- }
- }
- private static bool isSupported;
- private static bool IsSupportCheckDone;
- private static Dictionary<Uri, HTTPCacheFileInfo> library;
- private static Dictionary<Uri, HTTPCacheFileInfo> Library { get { LoadLibrary(); return library; } }
- private static Dictionary<UInt64, HTTPCacheFileInfo> UsedIndexes = new Dictionary<ulong, HTTPCacheFileInfo>();
- internal static string CacheFolder { get; private set; }
- private static string LibraryPath { get; set; }
- private static bool InClearThread;
- private static bool InMaintainenceThread;
-
-
-
- private static UInt64 NextNameIDX;
- #endregion
- static HTTPCacheService()
- {
- NextNameIDX = 0x0001;
- }
- #region Common Functions
- internal static void CheckSetup()
- {
- if (!HTTPCacheService.IsSupported)
- return;
- try
- {
- SetupCacheFolder();
- LoadLibrary();
- }
- catch
- { }
- }
- internal static void SetupCacheFolder()
- {
- if (!HTTPCacheService.IsSupported)
- return;
- try
- {
- if (string.IsNullOrEmpty(CacheFolder) || string.IsNullOrEmpty(LibraryPath))
- {
- CacheFolder = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "HTTPCache");
- if (!Directory.Exists(CacheFolder))
- Directory.CreateDirectory(CacheFolder);
- LibraryPath = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "Library");
- }
- }
- catch
- {
- isSupported = false;
- HTTPManager.Logger.Warning("HTTPCacheService", "Cache Service Disabled!");
- }
- }
- internal static UInt64 GetNameIdx()
- {
- lock(Library)
- {
- UInt64 result = NextNameIDX;
- do
- {
- NextNameIDX = ++NextNameIDX % UInt64.MaxValue;
- } while (UsedIndexes.ContainsKey(NextNameIDX));
- return result;
- }
- }
- internal static bool HasEntity(Uri uri)
- {
- if (!IsSupported)
- return false;
- lock (Library)
- return Library.ContainsKey(uri);
- }
- internal static bool DeleteEntity(Uri uri, bool removeFromLibrary = true)
- {
- if (!IsSupported)
- return false;
- object uriLocker = HTTPCacheFileLock.Acquire(uri);
-
-
-
-
- lock(uriLocker)
- {
- try
- {
- lock (Library)
- {
- HTTPCacheFileInfo info;
- bool inStats = Library.TryGetValue(uri, out info);
- if (inStats)
- info.Delete();
- if (inStats && removeFromLibrary)
- {
- Library.Remove(uri);
- UsedIndexes.Remove(info.MappedNameIDX);
- }
- return true;
- }
- }
- finally
- {
-
- }
- }
-
- }
- internal static bool IsCachedEntityExpiresInTheFuture(HTTPRequest request)
- {
- if (!IsSupported)
- return false;
- HTTPCacheFileInfo info;
- lock (Library)
- if (Library.TryGetValue(request.CurrentUri, out info))
- return info.WillExpireInTheFuture();
- return false;
- }
-
-
-
-
- internal static void SetHeaders(HTTPRequest request)
- {
- if (!IsSupported)
- return;
- HTTPCacheFileInfo info;
- lock (Library)
- if (Library.TryGetValue(request.CurrentUri, out info))
- info.SetUpRevalidationHeaders(request);
- }
- #endregion
- #region Get Functions
- internal static HTTPCacheFileInfo GetEntity(Uri uri)
- {
- if (!IsSupported)
- return null;
- HTTPCacheFileInfo info = null;
- lock (Library)
- Library.TryGetValue(uri, out info);
- return info;
- }
- internal static HTTPResponse GetFullResponse(HTTPRequest request)
- {
- if (!IsSupported)
- return null;
- HTTPCacheFileInfo info;
- lock (Library)
- if (Library.TryGetValue(request.CurrentUri, out info))
- return info.ReadResponseTo(request);
- return null;
- }
- #endregion
- #region Storing
-
-
-
-
- internal static bool IsCacheble(Uri uri, HTTPMethods method, HTTPResponse response)
- {
- if (!IsSupported)
- return false;
- if (method != HTTPMethods.Get)
- return false;
- if (response == null)
- return false;
-
-
-
-
- if (response.StatusCode < 200 || response.StatusCode >= 400)
- return false;
-
- var cacheControls = response.GetHeaderValues("cache-control");
- if (cacheControls != null)
- {
- if (cacheControls.Exists(headerValue => {
- string value = headerValue.ToLower();
- return value.Contains("no-store") || value.Contains("no-cache");
- }))
- return false;
- }
- var pragmas = response.GetHeaderValues("pragma");
- if (pragmas != null)
- {
- if (pragmas.Exists(headerValue => {
- string value = headerValue.ToLower();
- return value.Contains("no-store") || value.Contains("no-cache");
- }))
- return false;
- }
-
- var byteRanges = response.GetHeaderValues("content-range");
- if (byteRanges != null)
- return false;
- return true;
- }
- internal static HTTPCacheFileInfo Store(Uri uri, HTTPMethods method, HTTPResponse response)
- {
- if (response == null || response.Data == null || response.Data.Length == 0)
- return null;
- if (!IsSupported)
- return null;
- HTTPCacheFileInfo info = null;
- lock (Library)
- {
- if (!Library.TryGetValue(uri, out info))
- {
- Library.Add(uri, info = new HTTPCacheFileInfo(uri));
- UsedIndexes.Add(info.MappedNameIDX, info);
- }
- try
- {
- info.Store(response);
- if (HTTPManager.Logger.Level == Logger.Loglevels.All)
- HTTPManager.Logger.Verbose("HTTPCacheService", string.Format("{0} - Saved to cache", uri.ToString()));
- }
- catch
- {
-
- DeleteEntity(uri);
- throw;
- }
- }
- return info;
- }
- internal static System.IO.Stream PrepareStreamed(Uri uri, HTTPResponse response)
- {
- if (!IsSupported)
- return null;
- HTTPCacheFileInfo info;
- lock (Library)
- {
- if (!Library.TryGetValue(uri, out info))
- {
- Library.Add(uri, info = new HTTPCacheFileInfo(uri));
- UsedIndexes.Add(info.MappedNameIDX, info);
- }
- try
- {
- return info.GetSaveStream(response);
- }
- catch
- {
-
- DeleteEntity(uri);
- throw;
- }
- }
- }
- #endregion
- #region Public Maintenance Functions
-
-
-
-
- public static void BeginClear()
- {
- if (!IsSupported)
- return;
- if (InClearThread)
- return;
- InClearThread = true;
- SetupCacheFolder();
- #if !NETFX_CORE
- ThreadPool.QueueUserWorkItem(new WaitCallback((param) => ClearImpl(param)));
-
- #else
- #pragma warning disable 4014
- Windows.System.Threading.ThreadPool.RunAsync(ClearImpl);
- #pragma warning restore 4014
- #endif
- }
- private static void ClearImpl(object param)
- {
- if (!IsSupported)
- return;
- try
- {
-
- string[] cacheEntries = Directory.GetFiles(CacheFolder);
- for (int i = 0; i < cacheEntries.Length; ++i)
- {
-
-
- try
- {
- File.Delete(cacheEntries[i]);
- }
- catch
- { }
- }
- }
- finally
- {
- UsedIndexes.Clear();
- library.Clear();
- NextNameIDX = 0x0001;
- SaveLibrary();
- InClearThread = false;
- }
- }
-
-
-
-
- public static void BeginMaintainence(HTTPCacheMaintananceParams maintananceParam)
- {
- if (maintananceParam == null)
- throw new ArgumentNullException("maintananceParams == null");
- if (!HTTPCacheService.IsSupported)
- return;
- if (InMaintainenceThread)
- return;
- InMaintainenceThread = true;
- SetupCacheFolder();
- #if !NETFX_CORE
- ThreadPool.QueueUserWorkItem(new WaitCallback((param) =>
-
- #else
- #pragma warning disable 4014
- Windows.System.Threading.ThreadPool.RunAsync((param) =>
- #pragma warning restore 4014
- #endif
- {
- try
- {
- lock (Library)
- {
-
- DateTime deleteOlderAccessed = DateTime.UtcNow - maintananceParam.DeleteOlder;
- List<HTTPCacheFileInfo> removedEntities = new List<HTTPCacheFileInfo>();
- foreach (var kvp in Library)
- if (kvp.Value.LastAccess < deleteOlderAccessed)
- {
- if (DeleteEntity(kvp.Key, false))
- removedEntities.Add(kvp.Value);
- }
- for (int i = 0; i < removedEntities.Count; ++i)
- {
- Library.Remove(removedEntities[i].Uri);
- UsedIndexes.Remove(removedEntities[i].MappedNameIDX);
- }
- removedEntities.Clear();
- ulong cacheSize = GetCacheSize();
-
- if (cacheSize > maintananceParam.MaxCacheSize)
- {
- List<HTTPCacheFileInfo> fileInfos = new List<HTTPCacheFileInfo>(library.Count);
- foreach(var kvp in library)
- fileInfos.Add(kvp.Value);
- fileInfos.Sort();
- int idx = 0;
- while (cacheSize >= maintananceParam.MaxCacheSize && idx < fileInfos.Count)
- {
- try
- {
- var fi = fileInfos[idx];
- ulong length = (ulong)fi.BodyLength;
- DeleteEntity(fi.Uri);
- cacheSize -= length;
- }
- catch
- {}
- finally
- {
- ++idx;
- }
- }
- }
- }
- }
- finally
- {
- SaveLibrary();
- InMaintainenceThread = false;
- }
- }
- #if !NETFX_CORE
- ));
- #else
- );
- #endif
- }
- public static int GetCacheEntityCount()
- {
- if (!HTTPCacheService.IsSupported)
- return 0;
- CheckSetup();
- lock(Library)
- return Library.Count;
- }
- public static ulong GetCacheSize()
- {
- ulong size = 0;
- if (!IsSupported)
- return size;
- CheckSetup();
- lock (Library)
- foreach (var kvp in Library)
- if (kvp.Value.BodyLength > 0)
- size += (ulong)kvp.Value.BodyLength;
- return size;
- }
- #endregion
- #region Cache Library Management
- private static void LoadLibrary()
- {
-
- if (library != null)
- return;
- if (!IsSupported)
- return;
- library = new Dictionary<Uri, HTTPCacheFileInfo>(new UriComparer());
- if (!File.Exists(LibraryPath))
- {
- DeleteUnusedFiles();
- return;
- }
- try
- {
- int version;
- lock (library)
- {
- using (var fs = new FileStream(LibraryPath, FileMode.Open))
- using (var br = new System.IO.BinaryReader(fs))
- {
- version = br.ReadInt32();
- if (version > 1)
- NextNameIDX = br.ReadUInt64();
- int statCount = br.ReadInt32();
- for (int i = 0; i < statCount; ++i)
- {
- Uri uri = new Uri(br.ReadString());
- var entity = new HTTPCacheFileInfo(uri, br, version);
- if (entity.IsExists())
- {
- library.Add(uri, entity);
- if (version > 1)
- UsedIndexes.Add(entity.MappedNameIDX, entity);
- }
- }
- }
- }
- if (version == 1)
- BeginClear();
- else
- DeleteUnusedFiles();
- }
- catch
- {}
- }
- internal static void SaveLibrary()
- {
- if (library == null)
- return;
- if (!IsSupported)
- return;
- try
- {
- lock (Library)
- {
- using (var fs = new FileStream(LibraryPath, FileMode.Create))
- using (var bw = new System.IO.BinaryWriter(fs))
- {
- bw.Write(LibraryVersion);
- bw.Write(NextNameIDX);
- bw.Write(Library.Count);
- foreach (var kvp in Library)
- {
- bw.Write(kvp.Key.ToString());
- kvp.Value.SaveTo(bw);
- }
- }
- }
- }
- catch
- {}
- }
- internal static void SetBodyLength(Uri uri, int bodyLength)
- {
- if (!IsSupported)
- return;
- lock (Library)
- {
- HTTPCacheFileInfo fileInfo;
- if (Library.TryGetValue(uri, out fileInfo))
- fileInfo.BodyLength = bodyLength;
- else
- {
- Library.Add(uri, fileInfo = new HTTPCacheFileInfo(uri, DateTime.UtcNow, bodyLength));
- UsedIndexes.Add(fileInfo.MappedNameIDX, fileInfo);
- }
- }
- }
-
-
-
- private static void DeleteUnusedFiles()
- {
- if (!IsSupported)
- return;
- CheckSetup();
-
- string[] cacheEntries = Directory.GetFiles(CacheFolder);
- for (int i = 0; i < cacheEntries.Length; ++i)
- {
-
-
- try
- {
- string filename = System.IO.Path.GetFileName(cacheEntries[i]);
- UInt64 idx = 0;
- bool deleteFile = false;
- if (UInt64.TryParse(filename, System.Globalization.NumberStyles.AllowHexSpecifier, null, out idx))
- lock (Library)
- deleteFile = !UsedIndexes.ContainsKey(idx);
- else
- deleteFile = true;
- if (deleteFile)
- File.Delete(cacheEntries[i]);
- }
- catch
- {}
- }
- }
- #endregion
- }
- }
- #endif
|