gewentao
6 年前
当前提交
bda4734a
共有 14 个文件被更改,包括 2597 次插入 和 1 次删除
-
13Assets/UIWidgets/painting/image_provider.cs
-
3Assets/UIWidgets/lib.meta
-
1001Assets/Plugins/Mono.Data.Sqlite.dll
-
30Assets/Plugins/Mono.Data.Sqlite.dll.meta
-
1001Assets/Plugins/System.Data.dll
-
30Assets/Plugins/System.Data.dll.meta
-
3Assets/UIWidgets/lib/cache_manager.meta
-
347Assets/UIWidgets/lib/cache_manager/cache_manager.cs
-
3Assets/UIWidgets/lib/cache_manager/cache_manager.cs.meta
-
156Assets/UIWidgets/lib/cache_manager/cache_meta.cs
-
11Assets/UIWidgets/lib/cache_manager/cache_meta.cs.meta
|
|||
fileFormatVersion: 2 |
|||
guid: d5b7bf0751a64454ba399aad57ab71fc |
|||
timeCreated: 1535510135 |
1001
Assets/Plugins/Mono.Data.Sqlite.dll
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
fileFormatVersion: 2 |
|||
guid: 3e54fe1804cc94b16a015d1a2d114b55 |
|||
PluginImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
isPreloaded: 0 |
|||
isOverridable: 0 |
|||
platformData: |
|||
- first: |
|||
Any: |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
- first: |
|||
Editor: Editor |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
DefaultValueInitialized: true |
|||
- first: |
|||
Windows Store Apps: WindowsStoreApps |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
1001
Assets/Plugins/System.Data.dll
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
fileFormatVersion: 2 |
|||
guid: acabc02e8ca194aae8d9d3c268ffe747 |
|||
PluginImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
isPreloaded: 0 |
|||
isOverridable: 0 |
|||
platformData: |
|||
- first: |
|||
Any: |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
- first: |
|||
Editor: Editor |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
DefaultValueInitialized: true |
|||
- first: |
|||
Windows Store Apps: WindowsStoreApps |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 46a1a2081f1e4842a8ce89f2f0e15234 |
|||
timeCreated: 1535510181 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Security.Cryptography; |
|||
using RSG; |
|||
using System.Text; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using UnityEngine; |
|||
using System.Net; |
|||
using Mono.Data.Sqlite; |
|||
using UIWidgets.painting; |
|||
|
|||
namespace UIWidgets.lib.cache_manager { |
|||
public class CacheManager { |
|||
private readonly string _keyCacheData = "lib_cached_image_data"; |
|||
private readonly string _keyCacheCleanDate = "lib_cached_image_data_last_clean"; |
|||
|
|||
private string _dbUri; |
|||
|
|||
private const string DbFileName = @"ui_widgets_cache.db"; |
|||
|
|||
private static TimeSpan inBetweenCleans = new TimeSpan(7, 0, 0, 0); |
|||
private static TimeSpan maxAgeCacheObject = new TimeSpan(30, 0, 0, 0); |
|||
|
|||
private static int maxNrOfCacheObjects = 2; // configurable ?
|
|||
|
|||
private static CacheManager _instance; |
|||
|
|||
// public DateTime lastCacheClean;
|
|||
private bool _isStoringData = false; |
|||
private bool _shouldStoreDataAgain = false; |
|||
|
|||
public static CacheManager getInstance() { |
|||
if (_instance == null) { |
|||
_instance = new CacheManager(); |
|||
_instance._init(); |
|||
} |
|||
|
|||
return _instance; |
|||
} |
|||
|
|||
private void _init() { |
|||
_setupDatabase(); |
|||
// _getLastCleanTimestampFromPreferences();
|
|||
} |
|||
|
|||
private void _setupDatabase() { |
|||
var directoryPath = Application.persistentDataPath; |
|||
var _dbFilePath = Path.Combine(directoryPath, DbFileName); |
|||
_dbUri = "URI=file:" + _dbFilePath; |
|||
|
|||
if (!Directory.Exists(directoryPath)) { |
|||
Directory.CreateDirectory(directoryPath); |
|||
} |
|||
|
|||
if (!File.Exists(_dbFilePath)) { |
|||
SqliteConnection.CreateFile(_dbFilePath); |
|||
} |
|||
|
|||
using (var connection = new SqliteConnection(_dbUri)) { |
|||
connection.Open(); |
|||
const string createCacheTable = @"CREATE TABLE IF NOT EXISTS Cache (
|
|||
Key TEXT NOT NULL PRIMARY KEY, |
|||
FilePath TEXT NOT NULL, |
|||
ETag TEXT, |
|||
Url TEXT NOT NULL, |
|||
Touched REAL, |
|||
ValidTill REAL |
|||
)";
|
|||
|
|||
using (var command = new SqliteCommand(createCacheTable, connection)) { |
|||
command.ExecuteNonQuery(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IPromise<CacheMeta> getMeta(string url) { |
|||
var key = generateHashKey(url); |
|||
|
|||
CacheMeta meta = null; |
|||
|
|||
using (var connection = new SqliteConnection(_dbUri)) { |
|||
connection.Open(); |
|||
const string metaQuery = @"SELECT Key, FilePath, ETag, Url, Touched, ValidTill FROM Cache
|
|||
WHERE Key = @Key";
|
|||
|
|||
using (var command = new SqliteCommand(metaQuery, connection)) { |
|||
command.Parameters.AddWithValue("@Key", key); |
|||
|
|||
using (var reader = command.ExecuteReader()) { |
|||
if (reader.HasRows && reader.Read()) { |
|||
meta = new CacheMeta.Builder(reader.GetString(0)) |
|||
.RelativePath(reader.GetString(1)) |
|||
.ETag(reader.IsDBNull(2) ? string.Empty : reader.GetString(2)) |
|||
.Url(reader.GetString(3)) |
|||
.Touched(reader.GetDouble(4)) |
|||
.ValidTill(reader.GetDouble(5)) |
|||
.Build(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
var promise = new Promise<CacheMeta>(); |
|||
if (meta == null) { |
|||
meta = new CacheMeta(url); |
|||
} |
|||
|
|||
meta.touch(); |
|||
promise.Resolve(meta); |
|||
return promise; |
|||
} |
|||
|
|||
public IPromise<CacheMeta> downloadFileIfNeeded(CacheMeta meta) { |
|||
var promise = new Promise<CacheMeta>(); // Create promise.
|
|||
var filepath = meta.getFilePath(); |
|||
var fileExpire = meta.validTill == 0.0 || |
|||
CacheMeta.fromMillisecondsSinceEpoch(meta.validTill) < DateTime.Now; |
|||
if (filepath == null || |
|||
fileExpire || |
|||
!File.Exists(filepath)) { |
|||
// download from url
|
|||
WebRequest webRequest = WebRequest.Create(new Uri(meta.url)); |
|||
if (fileExpire && meta.eTag != null) { |
|||
webRequest.Headers.Set("If-None-Match", meta.eTag); |
|||
} |
|||
|
|||
webRequest.BeginGetResponse(result => { |
|||
const int BufferSize = 1024; |
|||
|
|||
var bytes = new byte[BufferSize]; |
|||
var response = webRequest.EndGetResponse(result); |
|||
|
|||
var statusCode = (int) ((HttpWebResponse) response).StatusCode; |
|||
var respHeaders = response.Headers; |
|||
var headerDict = new Dictionary<string, string>(); |
|||
for (int i = 0; i < respHeaders.Count; i++) { |
|||
string header = respHeaders.GetKey(i); |
|||
string value = respHeaders.Get(header); |
|||
headerDict[header] = respHeaders[value]; |
|||
} |
|||
|
|||
if (statusCode == 200) { |
|||
meta.setDataFromHeaders(headerDict); |
|||
|
|||
var stream = response.GetResponseStream(); |
|||
if (stream != null) { |
|||
var localStream = File.Create(meta.getFilePath()); |
|||
int bytesRead; |
|||
while ((bytesRead = stream.Read(bytes, 0, BufferSize)) > 0) { |
|||
localStream.Write(bytes, 0, bytesRead); |
|||
} |
|||
|
|||
stream.Close(); |
|||
localStream.Close(); |
|||
promise.Resolve(meta); |
|||
} |
|||
} else if (statusCode == 304) { |
|||
meta.setDataFromHeaders(headerDict); |
|||
promise.Resolve(meta); |
|||
} |
|||
}, null); |
|||
} |
|||
else { |
|||
promise.Resolve(meta); |
|||
} |
|||
|
|||
return promise; |
|||
} |
|||
|
|||
public IPromise<string> updateMeta(CacheMeta newMeta) { |
|||
var key = generateHashKey(newMeta.url); |
|||
|
|||
const string checkMetaQuery = @"SELECT COUNT(*) FROM Cache WHERE
|
|||
Key = @Key";
|
|||
|
|||
bool recordFound = false; |
|||
|
|||
using (var connection = new SqliteConnection(_dbUri)) { |
|||
connection.Open(); |
|||
using (var command = new SqliteCommand(checkMetaQuery, connection)) { |
|||
command.Parameters.AddWithValue("@Key", key); |
|||
|
|||
using (var reader = command.ExecuteReader()) { |
|||
if (reader.Read()) { |
|||
recordFound = reader.GetInt32(0) > 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (recordFound) { |
|||
const string updateCacheQuery = |
|||
@"UPDATE Cache SET FilePath = @FilePath, ETag = @ETag,
|
|||
Url = @Url, Touched = @Touched, ValidTill = @ValidTill |
|||
WHERE Key = @Key";
|
|||
|
|||
using (var command = new SqliteCommand(updateCacheQuery, connection)) { |
|||
command.Parameters.AddWithValue("@FilePath", newMeta.relativePath); |
|||
command.Parameters.AddWithValue("@ETag", newMeta.eTag); |
|||
command.Parameters.AddWithValue("@Url", newMeta.url); |
|||
command.Parameters.AddWithValue("@Touched", newMeta.touched); |
|||
command.Parameters.AddWithValue("@ValidTill", newMeta.validTill); |
|||
command.Parameters.AddWithValue("@key", key); |
|||
command.ExecuteNonQuery(); |
|||
} |
|||
} |
|||
else { |
|||
const string insertQuery = |
|||
@"INSERT INTO Cache (Key, FilePath, ETag, Url, Touched, ValidTill)
|
|||
VALUES (@Key, @FilePath, @ETag, @Url, @Touched, @ValidTill)";
|
|||
|
|||
using (var command = new SqliteCommand(insertQuery, connection)) { |
|||
command.Parameters.AddWithValue("@Key", key); |
|||
command.Parameters.AddWithValue("@FilePath", newMeta.relativePath); |
|||
command.Parameters.AddWithValue("@ETag", newMeta.eTag); |
|||
command.Parameters.AddWithValue("@Url", newMeta.url); |
|||
command.Parameters.AddWithValue("@Touched", newMeta.touched); |
|||
command.Parameters.AddWithValue("@ValidTill", newMeta.validTill); |
|||
command.ExecuteNonQuery(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// _removeOldObjectsFromCache(); // do we need this?
|
|||
_shrinkLargeCache(); |
|||
|
|||
var promise = new Promise<string>(); |
|||
promise.Resolve(newMeta.getFilePath()); |
|||
return promise; |
|||
} |
|||
|
|||
public IPromise<ImageInfo> loadCacheFile(string path) { |
|||
var promise = new Promise<ImageInfo>(); |
|||
var bytes = File.ReadAllBytes(path); |
|||
var imageInfo = new ImageInfo(new ui.Image( |
|||
bytes |
|||
)); |
|||
promise.Resolve(imageInfo); |
|||
return promise; |
|||
} |
|||
|
|||
private void _removeOldObjectsFromCache() { |
|||
var oldestDataAllowed = DateTime.Now - maxAgeCacheObject; |
|||
var metas = new List<CacheMeta>(); |
|||
|
|||
const string query = @"SELECT Key, FilePath, ETag, Url, Touched, ValidTill
|
|||
FROM Cache |
|||
WHERE Touched < @OldestTouchedAllowed";
|
|||
|
|||
const string deleteQuery = @"DELETE FROM Cache WHERE Key in (@KeyList)"; |
|||
using (var connection = new SqliteConnection(_dbUri)) { |
|||
connection.Open(); |
|||
using (var command = new SqliteCommand(query, connection)) { |
|||
command.Parameters.AddWithValue("@OldestTouchedAllowed", |
|||
CacheMeta.millisecondsSinceEpoch(oldestDataAllowed)); |
|||
using (var reader = command.ExecuteReader()) { |
|||
while (reader.HasRows && reader.Read()) { |
|||
metas.Add( |
|||
new CacheMeta.Builder(reader.GetString(0)) |
|||
.RelativePath(reader.GetString(1)) |
|||
.ETag(reader.IsDBNull(2) ? string.Empty : reader.GetString(2)) |
|||
.Url(reader.GetString(3)) |
|||
.Touched(reader.GetDouble(4)) |
|||
.ValidTill(reader.GetDouble(5)) |
|||
.Build() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
foreach (var meta in metas) { |
|||
File.Delete(meta.getFilePath()); |
|||
} |
|||
|
|||
using (var command = new SqliteCommand(deleteQuery, connection)) { |
|||
command.Parameters.AddWithValue("@KeyList", |
|||
string.Join(",", metas.Select(m => m.key.ToString()).ToArray())); |
|||
command.ExecuteNonQuery(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void _shrinkLargeCache() { |
|||
const string countQuery = @"SELECT COUNT(*) FROM Cache"; |
|||
var totalRecord = 0; |
|||
using (var connection = new SqliteConnection(_dbUri)) { |
|||
connection.Open(); |
|||
using (var command = new SqliteCommand(countQuery, connection)) { |
|||
using (var reader = command.ExecuteReader()) { |
|||
if (reader.Read()) { |
|||
totalRecord = reader.GetInt32(0); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (totalRecord > maxNrOfCacheObjects) { |
|||
var metas = new List<CacheMeta>(); |
|||
var overflow = totalRecord - maxNrOfCacheObjects; |
|||
const string overflowQuery = @"SELECT Key, FilePath, ETag, Url, Touched, ValidTill
|
|||
FROM Cache ORDER BY Touched LIMIT @Overflow";
|
|||
using (var command = new SqliteCommand(overflowQuery, connection)) { |
|||
command.Parameters.AddWithValue("@Overflow", overflow); |
|||
using (var reader = command.ExecuteReader()) { |
|||
while (reader.HasRows && reader.Read()) { |
|||
metas.Add( |
|||
new CacheMeta.Builder(reader.GetString(0)) |
|||
.RelativePath(reader.GetString(1)) |
|||
.ETag(reader.IsDBNull(2) ? string.Empty : reader.GetString(2)) |
|||
.Url(reader.GetString(3)) |
|||
.Touched(reader.GetDouble(4)) |
|||
.ValidTill(reader.GetDouble(5)) |
|||
.Build() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
foreach (var meta in metas) { |
|||
File.Delete(meta.getFilePath()); |
|||
} |
|||
|
|||
const string deleteQuery = @"DELETE FROM Cache WHERE Key in (@KeyList)"; |
|||
using (var command = new SqliteCommand(deleteQuery, connection)) { |
|||
command.Parameters.AddWithValue("@KeyList", |
|||
string.Join(",", metas.Select(m => m.key.ToString()).ToArray())); |
|||
command.ExecuteNonQuery(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static string generateHashKey(string url) { |
|||
using (var md5 = MD5.Create()) { |
|||
byte[] inputBytes = Encoding.ASCII.GetBytes(url); |
|||
byte[] hashBytes = md5.ComputeHash(inputBytes); |
|||
|
|||
// Convert the byte array to hexadecimal string
|
|||
StringBuilder sb = new StringBuilder(); |
|||
for (int i = 0; i < hashBytes.Length; i++) { |
|||
sb.Append(hashBytes[i].ToString("X2")); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 32cd17d74783499d97a953247420a1b3 |
|||
timeCreated: 1535510219 |
|
|||
using System.Collections.Generic; |
|||
using System; |
|||
using System.IO; |
|||
using UnityEngine; |
|||
|
|||
namespace UIWidgets.lib.cache_manager { |
|||
public class CacheMeta { |
|||
private static readonly string _directory = Application.persistentDataPath; |
|||
public string relativePath = null; |
|||
public string eTag = null; |
|||
public double touched; |
|||
public double validTill; |
|||
public string url; |
|||
public string key; |
|||
|
|||
public CacheMeta(string url) { |
|||
this.url = url; |
|||
touch(); |
|||
} |
|||
|
|||
private CacheMeta(Builder builder) { |
|||
key = builder.key; |
|||
relativePath = builder.relativePath; |
|||
eTag = builder.eTag; |
|||
touched = builder.touched; |
|||
validTill = builder.validTill; |
|||
url = builder.url; |
|||
} |
|||
|
|||
public string getFilePath() { |
|||
if (this.relativePath == null) { |
|||
return null; |
|||
} |
|||
return _directory + this.relativePath; |
|||
} |
|||
|
|||
public void setRelativePath(string path) { |
|||
this.relativePath = path; |
|||
} |
|||
|
|||
public void touch() { |
|||
this.touched = millisecondsSinceEpoch(DateTime.Now); |
|||
} |
|||
|
|||
public void setDataFromHeaders(Dictionary<string, string> headers) { |
|||
var ageDuration = new TimeSpan(7, 0, 0, 0); // 7 days
|
|||
|
|||
if (headers.ContainsKey("cache-control")) { |
|||
var cacheControl = headers["cache-control"]; |
|||
string[] stringSeparators = {", "}; |
|||
var controlSettings = cacheControl.Split(stringSeparators, StringSplitOptions.None); |
|||
foreach (var controlSetting in controlSettings) { |
|||
if (controlSetting.StartsWith("max-age=")) { |
|||
int validSeconds = 0; |
|||
if (int.TryParse(controlSetting.Split('=')[1], out validSeconds)) { |
|||
if (validSeconds > 0) { |
|||
ageDuration = new TimeSpan(0, 0, validSeconds); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
validTill = millisecondsSinceEpoch(DateTime.Now + ageDuration); |
|||
|
|||
if (headers.ContainsKey("etag")) { |
|||
eTag = headers["etag"]; |
|||
} |
|||
|
|||
var fileExtension = ""; |
|||
if (headers.ContainsKey("content-type")) { |
|||
var type = headers["content-type"].Split('/'); |
|||
if (type.Length == 2) { |
|||
fileExtension = string.Format(".{0}", type[1]); |
|||
} |
|||
} |
|||
|
|||
var oldPath = getFilePath(); |
|||
if (oldPath != null && !oldPath.EndsWith(fileExtension)) { |
|||
removeOldFile(oldPath); |
|||
relativePath = null; |
|||
} |
|||
|
|||
if (relativePath == null) { |
|||
var fileName = string.Format("/cache_{0}{1}", Guid.NewGuid(), fileExtension); |
|||
relativePath = fileName; |
|||
} |
|||
} |
|||
|
|||
private static void removeOldFile(string filePath) { |
|||
if (File.Exists(filePath)) { |
|||
File.Delete(filePath); |
|||
} |
|||
} |
|||
|
|||
public static double millisecondsSinceEpoch(DateTime time) { |
|||
return (time - new DateTime(1970, 1, 1)).TotalMilliseconds; |
|||
} |
|||
|
|||
public static DateTime fromMillisecondsSinceEpoch(double ms) { |
|||
return new DateTime(1970, 1, 1).AddMilliseconds(ms); |
|||
} |
|||
|
|||
public sealed class Builder { |
|||
internal string key { get; private set; } |
|||
|
|||
internal string relativePath { get; private set; } |
|||
|
|||
internal string eTag { get; private set; } |
|||
|
|||
internal double touched { get; private set; } |
|||
|
|||
internal double validTill { get; private set; } |
|||
|
|||
internal string url { get; private set; } |
|||
|
|||
public Builder(string key) { |
|||
if (string.IsNullOrEmpty(key)) { |
|||
throw new ArgumentException("key can't be empty", "key"); |
|||
} |
|||
|
|||
this.key = key; |
|||
} |
|||
|
|||
public Builder RelativePath(string relativePath) { |
|||
this.relativePath = relativePath; |
|||
return this; |
|||
} |
|||
|
|||
public Builder ETag(string eTag) { |
|||
this.eTag = eTag; |
|||
return this; |
|||
} |
|||
|
|||
public Builder Touched(double touched) { |
|||
this.touched = touched; |
|||
return this; |
|||
} |
|||
|
|||
public Builder ValidTill(double validTill) { |
|||
this.validTill = validTill; |
|||
return this; |
|||
} |
|||
|
|||
public Builder Url(string url) { |
|||
this.url = url; |
|||
return this; |
|||
} |
|||
|
|||
public CacheMeta Build() |
|||
{ |
|||
return new CacheMeta(this); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: d54df7319b8ca49cf9aba74db3698508 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue