private IEnumerator Load <T>(string url, string pathWithoutHash, string hash, string storePath, Func <byte[], T> bytesToTConverter, Action <T> onLoaded, Action <int, string> onLoadFailed, Dictionary <string, string> requestHeader = null, double timeout = BackyardSettings.HTTP_TIMEOUT_SEC) where T : UnityEngine.Object { // ファイルパス、ファイル名を生成する var targetFolderNameAndHash = GenerateFolderAndFilePath(pathWithoutHash, hash, storePath); var targetFolderName = targetFolderNameAndHash.url; var targetFileName = targetFolderNameAndHash.url + "_" + targetFolderNameAndHash.hash; // フォルダ、ファイルがあるかどうかチェックする var folderPath = Path.Combine(storePath, targetFolderName); var existFolderPaths = filePersist.FileNamesInDomain(folderPath); var fileUniquePath = Path.Combine(folderPath, targetFileName); // キャッシュに対象が存在するかどうか if (pathObjectCache.ContainsKey(fileUniquePath)) { var cached = pathObjectCache[fileUniquePath] as T; onLoaded(cached); yield break; } // もしすでにロード中だったら待機する if (cachingLock.ContainsKey(targetFolderName)) { while (cachingLock.ContainsKey(targetFolderName)) { yield return(null); } // 待機完了、オンメモリのキャッシュにあるかどうかチェックし、あれば返す。なければダウンロードに移行する。 UnityEngine.Object _object; if (pathObjectCache.TryGetValue(fileUniquePath, out _object)) { onLoaded(_object as T); yield break; } // ダウンロードに向かう。 } // 処理中のロックをかける lock (writeLock) { cachingLock.Add(targetFolderName, CONST_VALUE); } /* * ロック後に、オンメモリにロードする必要 or DLする必要のチェックを行う。 */ // 既にDL済みのファイルが一つ以上存在していて、hashもヒットした -> ファイルはまだオンメモリにはないので、ここでロードして返す。 if (0 < existFolderPaths.Length && filePersist.IsExist(folderPath, targetFileName)) { // byte列を非同期で読み出す filePersist.Load( folderPath, targetFileName, bytes => { // 読み出し成功したのでオンメモリに載せる。 var tObj = bytesToTConverter(bytes); pathObjectCache[fileUniquePath] = tObj; lock (writeLock) { cachingLock.Remove(targetFolderName); } onLoaded(tObj); }, error => { lock (writeLock) { cachingLock.Remove(targetFolderName); } onLoadFailed(0, error); } ); yield break; } // オンメモリ、ファイルとしても手元に存在しないので、DLして取得を行う。 // 求めるhashに合致しない古いキャッシュファイルがある場合、消す。 foreach (var path in existFolderPaths) { var fileName = Path.GetFileName(path); filePersist.Delete(folderPath, fileName); } // ダウンロードを行う。 using (var request = UnityWebRequest.Get(url)) { filePersist.CreateDirectory(Path.Combine(filePersist.basePath, folderPath)); var fileSavePath = Path.Combine(filePersist.basePath, folderPath, targetFileName); if (requestHeader == null) { requestHeader = new Dictionary <string, string>(); } foreach (var item in requestHeader) { request.SetRequestHeader(item.Key, item.Value); } var handler = new DownloadHandlerFile(fileSavePath); // 失敗時に中途半端なファイルを消す handler.removeFileOnAbort = true; request.downloadHandler = handler; request.timeout = (int)timeout; var p = request.SendWebRequest(); while (!p.isDone) { yield return(null); } var responseCode = (int)request.responseCode; if (request.isNetworkError) { filePersist.Delete(folderPath, targetFileName); lock (writeLock) { cachingLock.Remove(targetFolderName); } onLoadFailed(responseCode, request.error); yield break; } if (request.isHttpError) { filePersist.Delete(folderPath, targetFileName); lock (writeLock) { cachingLock.Remove(targetFolderName); } onLoadFailed(responseCode, request.error); yield break; } } // ダウンロードが成功、保存も完了 // 保存したデータのbyte列を非同期で読み出す filePersist.Load( folderPath, targetFileName, bytes => { // 型に対しての返還式を取り出し、byteからT型を生成する。 var obj = bytesToTConverter(bytes); // キャッシュを行う pathObjectCache[fileUniquePath] = obj; lock (writeLock) { cachingLock.Remove(targetFolderName); } // 取得したT型オブジェクトを返す onLoaded(obj); }, error => { lock (writeLock) { cachingLock.Remove(targetFolderName); } onLoadFailed(0, error); } ); }
public IEnumerator CacheHashes( string domain, string[] items, Action onSucceeded, Action <int, string> onFailed ) { var domainPath = Path.Combine(filePersist.basePath, domain); if (!filePersist.IsDirectoryExists(domainPath)) { filePersist.CreateDirectory(domainPath); // 新規作成なのでオンメモリキャッシュも空 onMemoryHeadCountCache = new Dictionary <string, Dictionary <Char, int> >(); onMemoryHeadCountCache[domain] = new Dictionary <Char, int>(); } // フォルダがもうあるので、オンメモリキャッシュが作れる GenerateCacheIfNeed(domain); // キャッシュがない = ドメイン内のフォルダがない場合、キャッシュが空なので全て生成する。 if (onMemoryHeadCountCache[domain].Count == 0) { // これがもっと綺麗に回せればそれでいい気がする。事前にキャッシュがあれば変えられるはず。 var newHeadCountDict = new Dictionary <Char, List <string> >(); var distincted = items.Distinct(); foreach (var item in distincted) { // アイテムの長さが0ならキャッシュしない if (item.Length == 0) { continue; } if (!newHeadCountDict.ContainsKey(item[0])) { newHeadCountDict[item[0]] = new List <string>(); } newHeadCountDict[item[0]].Add(item); } // 初回なので全て生成する foreach (var key in newHeadCountDict.Keys) { // オンメモリキャッシュの更新 if (!onMemoryHeadCountCache[domain].ContainsKey(key)) { onMemoryHeadCountCache[domain][key] = 0; } var charFolderPath = Path.Combine(domainPath, key.ToString()); filePersist.CreateDirectory(charFolderPath); var itemNames = newHeadCountDict[key]; foreach (var item in itemNames) { var result = hmacFuncCache[domain](item); filePersist.Update(charFolderPath, result, string.Empty); onMemoryHeadCountCache[domain][key]++; } // おんなじアルファベット始まりの人がそんなに偏らないでしょという油断 yield return(null); } onSucceeded(); yield break; } // 最低一つはイニシャルフォルダが存在する。その情報はキャッシュに乗っかっている。 // イニシャルごとに並べる var currentHeadCountDict = new Dictionary <Char, HashSet <string> >(); { // この部分を、並び替えだけで軽量化できないかな〜とは思う。毎回全部これ作るの、一瞬とはいえしんどいはず。 var cmp = StringComparer.OrdinalIgnoreCase; Array.Sort(items, cmp); foreach (var item in items) { // アイテムの長さが0ならキャッシュしない if (item.Length == 0) { continue; } var initial = item[0]; if (!currentHeadCountDict.ContainsKey(initial)) { currentHeadCountDict[initial] = new HashSet <string>(); } currentHeadCountDict[initial].Add(item); } } // イニシャル数自体の比較 if (currentHeadCountDict.Keys.Count != onMemoryHeadCountCache[domain].Keys.Count) { // 差がある if (currentHeadCountDict.Keys.Count < onMemoryHeadCountCache[domain].Keys.Count) { // 消去されたものがある var removedInitial = onMemoryHeadCountCache[domain].Keys.Except(currentHeadCountDict.Keys).FirstOrDefault(); // オンメモリキャッシュへからのエントリ削除 onMemoryHeadCountCache[domain].Remove(removedInitial); // フォルダー削除 var charFolderPath = Path.Combine(domainPath, removedInitial.ToString()); filePersist.DeleteDirectory(charFolderPath); } if (currentHeadCountDict.Keys.Count > onMemoryHeadCountCache[domain].Keys.Count) { // 追加されたものがある var addedInitial = currentHeadCountDict.Keys.Except(onMemoryHeadCountCache[domain].Keys).FirstOrDefault(); // オンメモリキャッシュへのエントリ追加 onMemoryHeadCountCache[domain][addedInitial] = 0; // ファイル生成 var charFolderPath = Path.Combine(domainPath, addedInitial.ToString()); filePersist.CreateDirectory(charFolderPath); foreach (var item in currentHeadCountDict[addedInitial]) { var result = hmacFuncCache[domain](item); filePersist.Update(charFolderPath, result, string.Empty); // オンメモリキャッシュの更新 onMemoryHeadCountCache[domain][addedInitial]++; } } onSucceeded(); yield break; } // イニシャル単位での比較 foreach (var key in currentHeadCountDict.Keys) { var arrivedLength = currentHeadCountDict[key].Count; if (!onMemoryHeadCountCache[domain].ContainsKey(key)) { // 新しいinitialを持つ集合を発見した。 var newItems = currentHeadCountDict[key]; var charFolderPath = Path.Combine(domainPath, key.ToString()); filePersist.CreateDirectory(charFolderPath); var itemNames = currentHeadCountDict[key]; foreach (var item in itemNames) { var result = hmacFuncCache[domain](item); filePersist.Update(charFolderPath, result, string.Empty); } // キャッシュの更新 onMemoryHeadCountCache[domain][key] = arrivedLength; // 生成が完了したのでこのブロックを抜ける break; } // onMemoryHeadCountCache[domain][key]は存在していて、 // 既知のinitialへのアクセス // Debug.Log("k:" + key + " オンメモリにはある、なので変化が発生している"); // initial内の件数が変わっていない = 変化がない。 if (arrivedLength == onMemoryHeadCountCache[domain][key]) { // Debug.Log("key:" + key + " arrivedLength:" + arrivedLength + " onMemoryHeadCountCache[domain][key]:" + onMemoryHeadCountCache[domain][key]); continue; } // 件数が増えているか減っているか。 // 減っている -> キャッシュ対象が減った。 if (arrivedLength < onMemoryHeadCountCache[domain][key]) { var domainCharPath = Path.Combine(domain, key.ToString()); var cachedItems = filePersist.FileNamesInDomain(domainCharPath).Select(p => Path.GetFileName(p)).ToArray(); var currentItems = currentHeadCountDict[key].Select(i => hmacFuncCache[domain](i)).ToArray(); // 差分ファイル名 = item名を出す var deletedItemName = cachedItems.Except(currentItems).First(); // ファイルを削除する filePersist.Delete(domainCharPath, deletedItemName); onMemoryHeadCountCache[domain][key]--; break; } // 増えている -> キャッシュ対象が増えた。 if (arrivedLength > onMemoryHeadCountCache[domain][key]) { var fileNames = currentHeadCountDict[key]; var domainCharPath = Path.Combine(domain, key.ToString()); var cachedItems = filePersist.FileNamesInDomain(domainCharPath).Select(p => Path.GetFileName(p)).ToArray(); var currentItems = currentHeadCountDict[key].Select(i => hmacFuncCache[domain](i)).ToArray(); // 差分ファイル名 = item名を出す var newItemName = currentItems.Except(cachedItems).First(); // ファイルを出力する filePersist.Update(domainCharPath, newItemName, string.Empty); onMemoryHeadCountCache[domain][key]++; break; } } onSucceeded(); yield break; }