提交帧同步案例

This commit is contained in:
PC-20230316NUNE\Administrator
2024-01-26 19:15:07 +08:00
parent 3a345ab966
commit 68c4d5e811
3928 changed files with 463020 additions and 1 deletions

View File

@@ -0,0 +1,394 @@
#if !BESTHTTP_DISABLE_CACHING
using System;
using System.Collections.Generic;
using System.IO;
namespace BestHTTP.Caching
{
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.FileSystem;
/// <summary>
/// Holds all metadata that need for efficient caching, so we don't need to touch the disk to load headers.
/// </summary>
public class HTTPCacheFileInfo : IComparable<HTTPCacheFileInfo>
{
#region Properties
/// <summary>
/// The uri that this HTTPCacheFileInfo belongs to.
/// </summary>
internal Uri Uri { get; set; }
/// <summary>
/// The last access time to this cache entity. The date is in UTC.
/// </summary>
internal DateTime LastAccess { get; set; }
/// <summary>
/// The length of the cache entity's body.
/// </summary>
public int BodyLength { get; set; }
/// <summary>
/// ETag of the entity.
/// </summary>
private string ETag { get; set; }
/// <summary>
/// LastModified date of the entity.
/// </summary>
private string LastModified { get; set; }
/// <summary>
/// When the cache will expire.
/// </summary>
private DateTime Expires { get; set; }
/// <summary>
/// The age that came with the response
/// </summary>
private long Age { get; set; }
/// <summary>
/// Maximum how long the entry should served from the cache without revalidation.
/// </summary>
private long MaxAge { get; set; }
/// <summary>
/// The Date that came with the response.
/// </summary>
private DateTime Date { get; set; }
/// <summary>
/// Indicates whether the entity must be revalidated with the server or can be serverd directly from the cache without touching the server.
/// </summary>
private bool MustRevalidate { get; set; }
/// <summary>
/// The date and time when the HTTPResponse received.
/// </summary>
private DateTime Received { get; set; }
/// <summary>
/// Cached path.
/// </summary>
private string ConstructedPath { get; set; }
/// <summary>
/// This is the index of the entity. Filenames are generated from this value.
/// </summary>
internal UInt64 MappedNameIDX { get; set; }
#endregion
#region Constructors
internal HTTPCacheFileInfo(Uri uri)
:this(uri, DateTime.UtcNow, -1)
{
}
internal HTTPCacheFileInfo(Uri uri, DateTime lastAcces, int bodyLength)
{
this.Uri = uri;
this.LastAccess = lastAcces;
this.BodyLength = bodyLength;
this.MaxAge = -1;
this.MappedNameIDX = HTTPCacheService.GetNameIdx();
}
internal HTTPCacheFileInfo(Uri uri, System.IO.BinaryReader reader, int version)
{
this.Uri = uri;
this.LastAccess = DateTime.FromBinary(reader.ReadInt64());
this.BodyLength = reader.ReadInt32();
switch(version)
{
case 2:
this.MappedNameIDX = reader.ReadUInt64();
goto case 1;
case 1:
{
this.ETag = reader.ReadString();
this.LastModified = reader.ReadString();
this.Expires = DateTime.FromBinary(reader.ReadInt64());
this.Age = reader.ReadInt64();
this.MaxAge = reader.ReadInt64();
this.Date = DateTime.FromBinary(reader.ReadInt64());
this.MustRevalidate = reader.ReadBoolean();
this.Received = DateTime.FromBinary(reader.ReadInt64());
break;
}
}
}
#endregion
#region Helper Functions
internal void SaveTo(System.IO.BinaryWriter writer)
{
writer.Write(LastAccess.ToBinary());
writer.Write(BodyLength);
writer.Write(MappedNameIDX);
writer.Write(ETag);
writer.Write(LastModified);
writer.Write(Expires.ToBinary());
writer.Write(Age);
writer.Write(MaxAge);
writer.Write(Date.ToBinary());
writer.Write(MustRevalidate);
writer.Write(Received.ToBinary());
}
public string GetPath()
{
if (ConstructedPath != null)
return ConstructedPath;
return ConstructedPath = System.IO.Path.Combine(HTTPCacheService.CacheFolder, MappedNameIDX.ToString("X"));
}
public bool IsExists()
{
if (!HTTPCacheService.IsSupported)
return false;
return HTTPManager.IOService.FileExists(GetPath());
}
internal void Delete()
{
if (!HTTPCacheService.IsSupported)
return;
string path = GetPath();
try
{
HTTPManager.IOService.FileDelete(path);
}
catch
{ }
finally
{
Reset();
}
}
private void Reset()
{
// MappedNameIDX will remain the same. When we re-save an entity, it will not reset the MappedNameIDX.
this.BodyLength = -1;
this.ETag = string.Empty;
this.Expires = DateTime.FromBinary(0);
this.LastModified = string.Empty;
this.Age = 0;
this.MaxAge = -1;
this.Date = DateTime.FromBinary(0);
this.MustRevalidate = false;
this.Received = DateTime.FromBinary(0);
}
#endregion
#region Caching
private void SetUpCachingValues(HTTPResponse response)
{
response.CacheFileInfo = this;
this.ETag = response.GetFirstHeaderValue("ETag").ToStrOrEmpty();
this.Expires = response.GetFirstHeaderValue("Expires").ToDateTime(DateTime.FromBinary(0));
this.LastModified = response.GetFirstHeaderValue("Last-Modified").ToStrOrEmpty();
this.Age = response.GetFirstHeaderValue("Age").ToInt64(0);
this.Date = response.GetFirstHeaderValue("Date").ToDateTime(DateTime.FromBinary(0));
string cacheControl = response.GetFirstHeaderValue("cache-control");
if (!string.IsNullOrEmpty(cacheControl))
{
string[] kvp = cacheControl.FindOption("max-age");
if (kvp != null)
{
// Some cache proxies will return float values
double maxAge;
if (double.TryParse(kvp[1], out maxAge))
this.MaxAge = (int)maxAge;
}
this.MustRevalidate = cacheControl.ToLower().Contains("must-revalidate");
}
this.Received = DateTime.UtcNow;
}
internal bool WillExpireInTheFuture()
{
if (!IsExists())
return false;
if (MustRevalidate)
return false;
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4 :
// The max-age directive takes priority over Expires
if (MaxAge != -1)
{
// Age calculation:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.3
long apparent_age = Math.Max(0, (long)(Received - Date).TotalSeconds);
long corrected_received_age = Math.Max(apparent_age, Age);
long resident_time = (long)(DateTime.UtcNow - Date).TotalSeconds;
long current_age = corrected_received_age + resident_time;
return current_age < MaxAge;
}
return Expires > DateTime.UtcNow;
}
internal void SetUpRevalidationHeaders(HTTPRequest request)
{
if (!IsExists())
return;
// -If an entity tag has been provided by the origin server, MUST use that entity tag in any cache-conditional request (using If-Match or If-None-Match).
// -If only a Last-Modified value has been provided by the origin server, SHOULD use that value in non-subrange cache-conditional requests (using If-Modified-Since).
// -If both an entity tag and a Last-Modified value have been provided by the origin server, SHOULD use both validators in cache-conditional requests. This allows both HTTP/1.0 and HTTP/1.1 caches to respond appropriately.
if (!string.IsNullOrEmpty(ETag))
request.SetHeader("If-None-Match", ETag);
if (!string.IsNullOrEmpty(LastModified))
request.SetHeader("If-Modified-Since", LastModified);
}
public System.IO.Stream GetBodyStream(out int length)
{
if (!IsExists())
{
length = 0;
return null;
}
length = BodyLength;
LastAccess = DateTime.UtcNow;
//FileStream stream = new FileStream(GetPath(), FileMode.Open, FileAccess.Read, FileShare.Read);
Stream stream = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Open);
stream.Seek(-length, System.IO.SeekOrigin.End);
return stream;
}
internal HTTPResponse ReadResponseTo(HTTPRequest request)
{
if (!IsExists())
return null;
LastAccess = DateTime.UtcNow;
using (Stream stream = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Open)/*new FileStream(GetPath(), FileMode.Open, FileAccess.Read, FileShare.Read)*/)
{
var response = new HTTPResponse(request, stream, request.UseStreaming, true);
response.CacheFileInfo = this;
response.Receive(BodyLength);
return response;
}
}
internal void Store(HTTPResponse response)
{
if (!HTTPCacheService.IsSupported)
return;
string path = GetPath();
// Path name too long, we don't want to get exceptions
if (path.Length > HTTPManager.MaxPathLength)
return;
if (HTTPManager.IOService.FileExists(path))
Delete();
using (Stream writer = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Create) /*new FileStream(path, FileMode.Create)*/)
{
writer.WriteLine("HTTP/1.1 {0} {1}", response.StatusCode, response.Message);
foreach (var kvp in response.Headers)
{
for (int i = 0; i < kvp.Value.Count; ++i)
writer.WriteLine("{0}: {1}", kvp.Key, kvp.Value[i]);
}
writer.WriteLine();
writer.Write(response.Data, 0, response.Data.Length);
}
BodyLength = response.Data.Length;
LastAccess = DateTime.UtcNow;
SetUpCachingValues(response);
}
internal System.IO.Stream GetSaveStream(HTTPResponse response)
{
if (!HTTPCacheService.IsSupported)
return null;
LastAccess = DateTime.UtcNow;
string path = GetPath();
if (HTTPManager.IOService.FileExists(path))
Delete();
// Path name too long, we don't want to get exceptions
if (path.Length > HTTPManager.MaxPathLength)
return null;
// First write out the headers
using (Stream writer = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Create) /*new FileStream(path, FileMode.Create)*/)
{
writer.WriteLine("HTTP/1.1 {0} {1}", response.StatusCode, response.Message);
foreach (var kvp in response.Headers)
{
for (int i = 0; i < kvp.Value.Count; ++i)
writer.WriteLine("{0}: {1}", kvp.Key, kvp.Value[i]);
}
writer.WriteLine();
}
// If caching is enabled and the response is from cache, and no content-length header set, then we set one to the response.
if (response.IsFromCache && !response.Headers.ContainsKey("content-length"))
response.Headers.Add("content-length", new List<string> { BodyLength.ToString() });
SetUpCachingValues(response);
// then create the stream with Append FileMode
return HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Append); //new FileStream(GetPath(), FileMode.Append);
}
#endregion
#region IComparable<HTTPCacheFileInfo>
public int CompareTo(HTTPCacheFileInfo other)
{
return this.LastAccess.CompareTo(other.LastAccess);
}
#endregion
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1f50da5c22225384fbe271e0453fe545
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
#if !BESTHTTP_DISABLE_CACHING
using System;
using System.Collections.Generic;
namespace BestHTTP.Caching
{
sealed class HTTPCacheFileLock
{
private static Dictionary<Uri, object> FileLocks = new Dictionary<Uri, object>();
private static object SyncRoot = new object();
internal static object Acquire(Uri uri)
{
lock (SyncRoot)
{
object fileLock;
if (!FileLocks.TryGetValue(uri, out fileLock))
FileLocks.Add(uri, fileLock = new object());
return fileLock;
}
}
internal static void Remove(Uri uri)
{
lock (SyncRoot)
{
if (FileLocks.ContainsKey(uri))
FileLocks.Remove(uri);
}
}
internal static void Clear()
{
lock (SyncRoot)
{
FileLocks.Clear();
}
}
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7b141dab023cd9d438e0585b93cf2826
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
#if !BESTHTTP_DISABLE_CACHING
using System;
namespace BestHTTP.Caching
{
public sealed class HTTPCacheMaintananceParams
{
/// <summary>
/// Delete cache entries that accessed older then this value. If TimeSpan.FromSeconds(0) is used then all cache entries will be deleted. With TimeSpan.FromDays(2) entries that older then two days will be deleted.
/// </summary>
public TimeSpan DeleteOlder { get; private set; }
/// <summary>
/// If the cache is larger then the MaxCacheSize after the first maintanance step, then the maintanance job will forcedelete cache entries starting with the oldest last accessed one.
/// </summary>
public ulong MaxCacheSize { get; private set; }
public HTTPCacheMaintananceParams(TimeSpan deleteOlder, ulong maxCacheSize)
{
this.DeleteOlder = deleteOlder;
this.MaxCacheSize = maxCacheSize;
}
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3ce5552e539e38647a8f37ab36951bf7
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,716 @@
#if !BESTHTTP_DISABLE_CACHING
using System;
using System.Collections.Generic;
using System.Threading;
//
// Version 1: Initial release
// Version 2: Filenames are generated from an index.
//
namespace BestHTTP.Caching
{
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.FileSystem;
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
/// <summary>
/// Library file-format versioning support
/// </summary>
private const int LibraryVersion = 2;
public static bool IsSupported
{
get
{
if (IsSupportCheckDone)
return isSupported;
try
{
// If DirectoryExists throws an exception we will set IsSupprted to false
HTTPManager.IOService.DirectoryExists(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;
/// <summary>
/// Stores the index of the next stored entity. The entity's file name is generated from this index.
/// </summary>
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 (!HTTPManager.IOService.DirectoryExists(CacheFolder))
HTTPManager.IOService.DirectoryCreate(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);
}
public static bool DeleteEntity(Uri uri, bool removeFromLibrary = true)
{
if (!IsSupported)
return false;
object uriLocker = HTTPCacheFileLock.Acquire(uri);
// Just use lock now: http://forum.unity3d.com/threads/4-6-ios-64-bit-beta.290551/page-6#post-1937033
// To avoid a dead-lock we try acquire the lock on this uri only for a little time.
// If we can't acquire it, its better to just return without risking a deadlock.
//if (Monitor.TryEnter(uriLocker, TimeSpan.FromSeconds(0.5f)))
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
{
//Monitor.Exit(uriLocker);
}
}
//return false;
}
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;
}
/// <summary>
/// Utility function to set the cache control headers according to the spec.: http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
/// </summary>
/// <param name="request"></param>
internal static void SetHeaders(HTTPRequest request)
{
if (!IsSupported)
return;
request.RemoveHeader("If-None-Match");
request.RemoveHeader("If-Modified-Since");
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
/// <summary>
/// Checks if the given response can be cached. http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
/// </summary>
/// <returns>Returns true if cacheable, false otherwise.</returns>
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;
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.12 - Cache Replacement
// It MAY insert it into cache storage and MAY, if it meets all other requirements, use it to respond to any future requests that would previously have caused the old response to be returned.
//if (response.StatusCode == 304)
// return false;
if (response.StatusCode < 200 || response.StatusCode >= 400)
return false;
//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.2
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;
}
// Responses with byte ranges not supported yet.
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
{
// If something happens while we write out the response, than we will delete it because it might be in an invalid state.
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
{
// If something happens while we write out the response, than we will delete it because it might be in an invalid state.
DeleteEntity(uri);
throw;
}
}
}
#endregion
#region Public Maintenance Functions
/// <summary>
/// Deletes all cache entity. Non blocking.
/// <remarks>Call it only if there no requests currently processed, because cache entries can be deleted while a server sends back a 304 result, so there will be no data to read from the cache!</remarks>
/// </summary>
public static void BeginClear()
{
if (!IsSupported)
return;
if (InClearThread)
return;
InClearThread = true;
SetupCacheFolder();
#if !NETFX_CORE
ThreadPool.QueueUserWorkItem(new WaitCallback((param) => ClearImpl(param)));
//new Thread(ClearImpl).Start();
#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
{
// GetFiles will return a string array that contains the files in the folder with the full path
string[] cacheEntries = HTTPManager.IOService.GetFiles(CacheFolder);
for (int i = 0; i < cacheEntries.Length; ++i)
{
// We need a try-catch block because between the Directory.GetFiles call and the File.Delete calls a maintenance job, or other file operations can delete any file from the cache folder.
// So while there might be some problem with any file, we don't want to abort the whole for loop
try
{
HTTPManager.IOService.FileDelete(cacheEntries[i]);
}
catch
{ }
}
}
finally
{
UsedIndexes.Clear();
library.Clear();
NextNameIDX = 0x0001;
SaveLibrary();
InClearThread = false;
}
}
/// <summary>
/// Deletes all expired cache entity.
/// <remarks>Call it only if there no requests currently processed, because cache entries can be deleted while a server sends back a 304 result, so there will be no data to read from the cache!</remarks>
/// </summary>
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) =>
//new Thread((param) =>
#else
#pragma warning disable 4014
Windows.System.Threading.ThreadPool.RunAsync((param) =>
#pragma warning restore 4014
#endif
{
try
{
lock (Library)
{
// Delete cache entries older than the given time.
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();
// This step will delete all entries starting with the oldest LastAccess property while the cache size greater then the MaxCacheSize in the given param.
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()
{
// Already loaded?
if (library != null)
return;
if (!IsSupported)
return;
library = new Dictionary<Uri, HTTPCacheFileInfo>(new UriComparer());
if (!HTTPManager.IOService.FileExists(LibraryPath))
{
DeleteUnusedFiles();
return;
}
try
{
int version;
lock (library)
{
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.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 = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.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);
}
}
}
/// <summary>
/// Deletes all files from the cache folder that isn't in the Library.
/// </summary>
private static void DeleteUnusedFiles()
{
if (!IsSupported)
return;
CheckSetup();
// GetFiles will return a string array that contains the files in the folder with the full path
string[] cacheEntries = HTTPManager.IOService.GetFiles(CacheFolder);
for (int i = 0; i < cacheEntries.Length; ++i)
{
// We need a try-catch block because between the Directory.GetFiles call and the File.Delete calls a maintenance job, or other file operations can delete any file from the cache folder.
// So while there might be some problem with any file, we don't want to abort the whole for loop
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)
HTTPManager.IOService.FileDelete(cacheEntries[i]);
}
catch
{}
}
}
#endregion
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d29e8551599393f4193bc4daa6968607
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: