Path: blob/master/Core/FileLoaders/DiskCachingFileLoader.cpp
5664 views
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "ppsspp_config.h"1819#include <algorithm>20#include <cstddef>21#include <set>22#include <mutex>23#include <cstring>2425#include "Common/Data/Encoding/Utf8.h"26#include "Common/File/DiskFree.h"27#include "Common/File/DirListing.h"28#include "Common/File/FileUtil.h"29#include "Common/File/Path.h"30#include "Common/Log.h"31#include "Common/CommonWindows.h"32#include "Core/FileLoaders/DiskCachingFileLoader.h"33#include "Core/System.h"3435#if PPSSPP_PLATFORM(UWP)36#include <fileapifromapp.h>37#endif3839static const char * const CACHEFILE_MAGIC = "ppssppDC";40static const s64 SAFETY_FREE_DISK_SPACE = 768 * 1024 * 1024; // 768 MB41// Aim to allow this many files cached at once.42static const u32 CACHE_SPACE_FLEX = 4;4344Path DiskCachingFileLoaderCache::cacheDir_;4546std::map<Path, DiskCachingFileLoaderCache *> DiskCachingFileLoader::caches_;47std::mutex DiskCachingFileLoader::cachesMutex_;4849// Takes ownership of backend.50DiskCachingFileLoader::DiskCachingFileLoader(FileLoader *backend)51: ProxiedFileLoader(backend) {52}5354void DiskCachingFileLoader::Prepare() {55std::call_once(preparedFlag_, [this]() {56filesize_ = ProxiedFileLoader::FileSize();57if (filesize_ > 0) {58InitCache();59}60});61}6263DiskCachingFileLoader::~DiskCachingFileLoader() {64if (filesize_ > 0) {65ShutdownCache();66}67}6869bool DiskCachingFileLoader::Exists() {70Prepare();71return ProxiedFileLoader::Exists();72}7374bool DiskCachingFileLoader::ExistsFast() {75// It may require a slow operation to check - if we have data, let's say yes.76// This helps initial load, since we check each recent file for existence.77return true;78}7980s64 DiskCachingFileLoader::FileSize() {81Prepare();82return filesize_;83}8485size_t DiskCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) {86Prepare();87size_t readSize;8889if (absolutePos >= filesize_) {90bytes = 0;91} else if (absolutePos + (s64)bytes >= filesize_) {92bytes = (size_t)(filesize_ - absolutePos);93}9495if (cache_ && cache_->IsValid() && (flags & Flags::HINT_UNCACHED) == 0) {96readSize = cache_->ReadFromCache(absolutePos, bytes, data);97// While in case the cache size is too small for the entire read.98while (readSize < bytes) {99readSize += cache_->SaveIntoCache(backend_, absolutePos + readSize, bytes - readSize, (u8 *)data + readSize, flags);100// We're done, nothing more to read.101if (readSize == bytes) {102break;103}104// If there are already-cached blocks afterward, we have to read them.105size_t bytesFromCache = cache_->ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize);106readSize += bytesFromCache;107if (bytesFromCache == 0) {108// We can't read any more.109break;110}111}112} else {113readSize = backend_->ReadAt(absolutePos, bytes, data, flags);114}115116return readSize;117}118119std::vector<Path> DiskCachingFileLoader::GetCachedPathsInUse() {120std::lock_guard<std::mutex> guard(cachesMutex_);121122// This is on the file loader so that it can manage the caches_.123std::vector<Path> files;124files.reserve(caches_.size());125126for (const auto &it : caches_) {127files.push_back(it.first);128}129130return files;131}132133void DiskCachingFileLoader::InitCache() {134std::lock_guard<std::mutex> guard(cachesMutex_);135136Path path = ProxiedFileLoader::GetPath();137auto &entry = caches_[path];138if (!entry) {139entry = new DiskCachingFileLoaderCache(path, filesize_);140}141142cache_ = entry;143cache_->AddRef();144}145146void DiskCachingFileLoader::ShutdownCache() {147std::lock_guard<std::mutex> guard(cachesMutex_);148149if (cache_->Release()) {150// If it ran out of counts, delete it.151delete cache_;152caches_.erase(ProxiedFileLoader::GetPath());153}154cache_ = nullptr;155}156157DiskCachingFileLoaderCache::DiskCachingFileLoaderCache(const Path &path, u64 filesize)158: filesize_(filesize), origPath_(path) {159InitCache(path);160}161162DiskCachingFileLoaderCache::~DiskCachingFileLoaderCache() {163ShutdownCache();164}165166void DiskCachingFileLoaderCache::InitCache(const Path &filename) {167cacheSize_ = 0;168indexCount_ = 0;169oldestGeneration_ = 0;170maxBlocks_ = MAX_BLOCKS_LOWER_BOUND;171flags_ = 0;172generation_ = 0;173174const Path cacheFilePath = MakeCacheFilePath(filename);175bool fileLoaded = LoadCacheFile(cacheFilePath);176177// We do some basic locking to protect against two things: crashes and concurrency.178// Concurrency will break the file. Crashes will probably leave it inconsistent.179if (fileLoaded && !LockCacheFile(true)) {180if (RemoveCacheFile(cacheFilePath)) {181// Create a new one.182fileLoaded = false;183} else {184// Couldn't remove, in use? Give up on caching.185CloseFileHandle();186}187}188if (!fileLoaded) {189CreateCacheFile(cacheFilePath);190191if (!LockCacheFile(true)) {192CloseFileHandle();193}194}195}196197void DiskCachingFileLoaderCache::ShutdownCache() {198if (f_) {199bool failed = false;200if (File::Fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {201failed = true;202} else if (fwrite(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {203failed = true;204} else if (fflush(f_) != 0) {205failed = true;206}207if (failed) {208// Leave it locked, it's broken.209ERROR_LOG(Log::Loader, "Unable to flush disk cache.");210} else {211LockCacheFile(false);212}213CloseFileHandle();214}215216index_.clear();217blockIndexLookup_.clear();218cacheSize_ = 0;219}220221size_t DiskCachingFileLoaderCache::ReadFromCache(s64 pos, size_t bytes, void *data) {222std::lock_guard<std::mutex> guard(lock_);223224if (!f_) {225return 0;226}227228size_t cacheStartPos = (size_t)(pos / blockSize_);229size_t cacheEndPos = (size_t)((pos + bytes - 1) / blockSize_);230size_t readSize = 0;231size_t offset = (size_t)(pos - (cacheStartPos * (u64)blockSize_));232u8 *p = (u8 *)data;233234for (size_t i = cacheStartPos; i <= cacheEndPos; ++i) {235auto &info = index_[i];236if (info.block == INVALID_BLOCK) {237return readSize;238}239info.generation = generation_;240if (info.hits < std::numeric_limits<u16>::max()) {241++info.hits;242}243244size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);245if (!ReadBlockData(p + readSize, info, offset, toRead)) {246return readSize;247}248readSize += toRead;249250// Don't need an offset after the first read.251offset = 0;252}253return readSize;254}255256size_t DiskCachingFileLoaderCache::SaveIntoCache(FileLoader *backend, s64 pos, size_t bytes, void *data, FileLoader::Flags flags) {257std::lock_guard<std::mutex> guard(lock_);258259if (!f_) {260// Just to keep things working.261return backend->ReadAt(pos, bytes, data, flags);262}263264size_t cacheStartPos = (size_t)(pos / blockSize_);265size_t cacheEndPos = (size_t)((pos + bytes - 1) / blockSize_);266size_t readSize = 0;267size_t offset = (size_t)(pos - (cacheStartPos * (u64)blockSize_));268u8 *p = (u8 *)data;269270size_t blocksToRead = 0;271for (size_t i = cacheStartPos; i <= cacheEndPos; ++i) {272auto &info = index_[i];273if (info.block != INVALID_BLOCK) {274break;275}276++blocksToRead;277if (blocksToRead >= MAX_BLOCKS_PER_READ) {278break;279}280}281282if (!MakeCacheSpaceFor(blocksToRead) || blocksToRead == 0) {283return 0;284}285286if (blocksToRead == 1) {287auto &info = index_[cacheStartPos];288289u8 *buf = new u8[blockSize_];290size_t readBytes = backend->ReadAt(cacheStartPos * (u64)blockSize_, blockSize_, buf, flags);291292// Check if it was written while we were busy. Might happen if we thread.293if (info.block == INVALID_BLOCK && readBytes != 0) {294info.block = AllocateBlock((u32)cacheStartPos);295WriteBlockData(info, buf);296WriteIndexData((u32)cacheStartPos, info);297}298299size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);300memcpy(p + readSize, buf + offset, toRead);301readSize += toRead;302303delete [] buf;304} else {305u8 *wholeRead = new u8[blocksToRead * blockSize_];306size_t readBytes = backend->ReadAt(cacheStartPos * (u64)blockSize_, blocksToRead * blockSize_, wholeRead, flags);307308for (size_t i = 0; i < blocksToRead; ++i) {309auto &info = index_[cacheStartPos + i];310// Check if it was written while we were busy. Might happen if we thread.311if (info.block == INVALID_BLOCK && readBytes != 0) {312info.block = AllocateBlock((u32)cacheStartPos + (u32)i);313WriteBlockData(info, wholeRead + (i * blockSize_));314// TODO: Doing each index together would probably be better.315WriteIndexData((u32)cacheStartPos + (u32)i, info);316}317318size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);319memcpy(p + readSize, wholeRead + (i * blockSize_) + offset, toRead);320readSize += toRead;321}322delete[] wholeRead;323}324325cacheSize_ += blocksToRead;326++generation_;327328if (generation_ == std::numeric_limits<u16>::max()) {329RebalanceGenerations();330}331332return readSize;333}334335bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {336size_t goal = (size_t)maxBlocks_ - blocks;337338while (cacheSize_ > goal) {339u16 minGeneration = generation_;340341// We increment the iterator inside because we delete things inside.342for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {343if (blockIndexLookup_[i] == INVALID_INDEX) {344continue;345}346auto &info = index_[blockIndexLookup_[i]];347348// Check for the minimum seen generation.349// TODO: Do this smarter?350if (info.generation != 0 && info.generation < minGeneration) {351minGeneration = info.generation;352}353354// 0 means it was never used yet or was the first read (e.g. block descriptor.)355if (info.generation == oldestGeneration_ || info.generation == 0) {356info.block = INVALID_BLOCK;357info.generation = 0;358info.hits = 0;359--cacheSize_;360361// TODO: Doing this in chunks might be a lot better.362WriteIndexData(blockIndexLookup_[i], info);363blockIndexLookup_[i] = INVALID_INDEX;364365// Keep going?366if (cacheSize_ <= goal) {367break;368}369}370}371372// If we didn't find any, update to the lowest we did find.373oldestGeneration_ = minGeneration;374}375376return true;377}378379void DiskCachingFileLoaderCache::RebalanceGenerations() {380// To make things easy, we will subtract oldestGeneration_ and cut in half.381// That should give us more space but not break anything.382383for (size_t i = 0; i < index_.size(); ++i) {384auto &info = index_[i];385if (info.block == INVALID_BLOCK) {386continue;387}388389if (info.generation > oldestGeneration_) {390info.generation = (info.generation - oldestGeneration_) / 2;391// TODO: Doing this all at once would be much better.392WriteIndexData((u32)i, info);393}394}395396oldestGeneration_ = 0;397}398399u32 DiskCachingFileLoaderCache::AllocateBlock(u32 indexPos) {400for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {401if (blockIndexLookup_[i] == INVALID_INDEX) {402blockIndexLookup_[i] = indexPos;403return (u32)i;404}405}406407_dbg_assert_msg_(false, "Not enough free blocks");408return INVALID_BLOCK;409}410411std::string DiskCachingFileLoaderCache::MakeCacheFilename(const Path &path) {412static const char *const invalidChars = "?*:/\\^|<>\"'";413std::string filename = path.ToString();414for (size_t i = 0; i < filename.size(); ++i) {415int c = filename[i];416if (strchr(invalidChars, c) != nullptr) {417filename[i] = '_';418}419}420return filename + ".ppdc";421}422423::Path DiskCachingFileLoaderCache::MakeCacheFilePath(const Path &filename) {424Path dir = cacheDir_;425if (dir.empty()) {426dir = GetSysDirectory(DIRECTORY_CACHE);427}428429if (!File::Exists(dir)) {430File::CreateFullPath(dir);431}432433return dir / MakeCacheFilename(filename);434}435436s64 DiskCachingFileLoaderCache::GetBlockOffset(u32 block) {437// This is where the blocks start.438s64 blockOffset = (s64)sizeof(FileHeader) + (s64)indexCount_ * (s64)sizeof(BlockInfo);439// Now to the actual block.440return blockOffset + (s64)block * (s64)blockSize_;441}442443bool DiskCachingFileLoaderCache::ReadBlockData(u8 *dest, BlockInfo &info, size_t offset, size_t size) {444if (!f_) {445return false;446}447if (size == 0) {448return true;449}450s64 blockOffset = GetBlockOffset(info.block);451452// Before we read, make sure the buffers are flushed.453// We might be trying to read an area we've recently written.454fflush(f_);455456bool failed = false;457if (File::Fseek(f_, blockOffset, SEEK_SET) != 0) {458failed = true;459} else if (fread(dest + offset, size, 1, f_) != 1) {460failed = true;461}462463if (failed) {464ERROR_LOG(Log::Loader, "Unable to read disk cache data entry.");465CloseFileHandle();466}467return !failed;468}469470void DiskCachingFileLoaderCache::WriteBlockData(BlockInfo &info, const u8 *src) {471if (!f_) {472return;473}474s64 blockOffset = GetBlockOffset(info.block);475476bool failed = false;477if (File::Fseek(f_, blockOffset, SEEK_SET) != 0) {478failed = true;479} else if (fwrite(src, blockSize_, 1, f_) != 1) {480failed = true;481}482483if (failed) {484ERROR_LOG(Log::Loader, "Unable to write disk cache data entry.");485CloseFileHandle();486}487}488489void DiskCachingFileLoaderCache::WriteIndexData(u32 indexPos, BlockInfo &info) {490if (!f_) {491return;492}493494u32 offset = (u32)sizeof(FileHeader) + indexPos * (u32)sizeof(BlockInfo);495496bool failed = false;497if (File::Fseek(f_, offset, SEEK_SET) != 0) {498failed = true;499} else if (fwrite(&info, sizeof(BlockInfo), 1, f_) != 1) {500failed = true;501}502503if (failed) {504ERROR_LOG(Log::Loader, "Unable to write disk cache index entry.");505CloseFileHandle();506}507}508509bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) {510FILE *fp = File::OpenCFile(path, "rb+");511if (!fp) {512return false;513}514515FileHeader header;516bool valid = true;517if (fread(&header, sizeof(FileHeader), 1, fp) != 1) {518valid = false;519} else if (memcmp(header.magic, CACHEFILE_MAGIC, sizeof(header.magic)) != 0) {520valid = false;521} else if (header.version != CACHE_VERSION) {522valid = false;523} else if (header.filesize != filesize_) {524valid = false;525} else if (header.maxBlocks < MAX_BLOCKS_LOWER_BOUND || header.maxBlocks > MAX_BLOCKS_UPPER_BOUND) {526// This means it's not in our safety bounds, reject.527valid = false;528}529530// If it's valid, retain the file pointer.531if (valid) {532f_ = fp;533534// Now let's load the index.535blockSize_ = header.blockSize;536maxBlocks_ = header.maxBlocks;537flags_ = header.flags;538LoadCacheIndex();539} else {540ERROR_LOG(Log::Loader, "Disk cache file header did not match, recreating cache file");541fclose(fp);542}543544return valid;545}546547void DiskCachingFileLoaderCache::LoadCacheIndex() {548if (File::Fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {549CloseFileHandle();550return;551}552553indexCount_ = (size_t)((filesize_ + blockSize_ - 1) / blockSize_);554index_.resize(indexCount_);555blockIndexLookup_.resize(maxBlocks_);556memset(&blockIndexLookup_[0], INVALID_INDEX, maxBlocks_ * sizeof(blockIndexLookup_[0]));557558if (fread(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {559CloseFileHandle();560return;561}562563// Now let's set some values we need.564oldestGeneration_ = std::numeric_limits<u16>::max();565generation_ = 0;566cacheSize_ = 0;567568for (size_t i = 0; i < index_.size(); ++i) {569if (index_[i].block > maxBlocks_) {570index_[i].block = INVALID_BLOCK;571}572if (index_[i].block == INVALID_BLOCK) {573continue;574}575576if (index_[i].generation < oldestGeneration_) {577oldestGeneration_ = index_[i].generation;578}579if (index_[i].generation > generation_) {580generation_ = index_[i].generation;581}582++cacheSize_;583584blockIndexLookup_[index_[i].block] = (u32)i;585}586}587588void DiskCachingFileLoaderCache::CreateCacheFile(const Path &path) {589maxBlocks_ = DetermineMaxBlocks();590if (maxBlocks_ < MAX_BLOCKS_LOWER_BOUND) {591GarbageCollectCacheFiles(MAX_BLOCKS_LOWER_BOUND * DEFAULT_BLOCK_SIZE);592maxBlocks_ = DetermineMaxBlocks();593}594if (maxBlocks_ < MAX_BLOCKS_LOWER_BOUND) {595// There's not enough free space to cache, disable.596f_ = nullptr;597ERROR_LOG(Log::Loader, "Not enough free space; disabling disk cache");598return;599}600flags_ = 0;601602f_ = File::OpenCFile(path, "wb+");603if (!f_) {604ERROR_LOG(Log::Loader, "Could not create disk cache file");605return;606}607608blockSize_ = DEFAULT_BLOCK_SIZE;609610FileHeader header;611memcpy(header.magic, CACHEFILE_MAGIC, sizeof(header.magic));612header.version = CACHE_VERSION;613header.blockSize = blockSize_;614header.filesize = filesize_;615header.maxBlocks = maxBlocks_;616header.flags = flags_;617618if (fwrite(&header, sizeof(header), 1, f_) != 1) {619CloseFileHandle();620return;621}622623indexCount_ = (size_t)((filesize_ + blockSize_ - 1) / blockSize_);624index_.clear();625index_.resize(indexCount_);626blockIndexLookup_.resize(maxBlocks_);627memset(&blockIndexLookup_[0], INVALID_INDEX, maxBlocks_ * sizeof(blockIndexLookup_[0]));628629if (fwrite(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {630CloseFileHandle();631return;632}633if (fflush(f_) != 0) {634CloseFileHandle();635return;636}637638INFO_LOG(Log::Loader, "Created new disk cache file for %s", origPath_.c_str());639}640641bool DiskCachingFileLoaderCache::LockCacheFile(bool lockStatus) {642if (!f_) {643return false;644}645646u32 offset = (u32)offsetof(FileHeader, flags);647648bool failed = false;649if (File::Fseek(f_, offset, SEEK_SET) != 0) {650failed = true;651} else if (fread(&flags_, sizeof(u32), 1, f_) != 1) {652failed = true;653}654655if (failed) {656ERROR_LOG(Log::Loader, "Unable to read current flags during disk cache locking");657CloseFileHandle();658return false;659}660661// TODO: Also use flock where supported?662if (lockStatus) {663if ((flags_ & FLAG_LOCKED) != 0) {664ERROR_LOG(Log::Loader, "Could not lock disk cache file for %s (already locked)", origPath_.c_str());665return false;666}667flags_ |= FLAG_LOCKED;668} else {669if ((flags_ & FLAG_LOCKED) == 0) {670ERROR_LOG(Log::Loader, "Could not unlock disk cache file for %s", origPath_.c_str());671return false;672}673flags_ &= ~FLAG_LOCKED;674}675676if (File::Fseek(f_, offset, SEEK_SET) != 0) {677failed = true;678} else if (fwrite(&flags_, sizeof(u32), 1, f_) != 1) {679failed = true;680} else if (fflush(f_) != 0) {681failed = true;682}683684if (failed) {685ERROR_LOG(Log::Loader, "Unable to write updated flags during disk cache locking");686CloseFileHandle();687return false;688}689690if (lockStatus) {691INFO_LOG(Log::Loader, "Locked disk cache file for %s", origPath_.c_str());692} else {693INFO_LOG(Log::Loader, "Unlocked disk cache file for %s", origPath_.c_str());694}695return true;696}697698bool DiskCachingFileLoaderCache::RemoveCacheFile(const Path &path) {699// Note that some platforms, you can't delete open files. So we check.700CloseFileHandle();701return File::Delete(path);702}703704void DiskCachingFileLoaderCache::CloseFileHandle() {705if (f_) {706fclose(f_);707}708f_ = nullptr;709fd_ = 0;710}711712bool DiskCachingFileLoaderCache::HasData() const {713if (!f_) {714return false;715}716717for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {718if (blockIndexLookup_[i] != INVALID_INDEX) {719return true;720}721}722return false;723}724725u64 DiskCachingFileLoaderCache::FreeDiskSpace() {726Path dir = cacheDir_;727if (dir.empty()) {728dir = GetSysDirectory(DIRECTORY_CACHE);729}730731int64_t result = 0;732if (free_disk_space(dir, result)) {733return (u64)result;734}735736// We can't know for sure how much is free, so we have to assume none.737return 0;738}739740u32 DiskCachingFileLoaderCache::DetermineMaxBlocks() {741const s64 freeBytes = FreeDiskSpace();742// We want to leave them some room for other stuff.743const u64 availBytes = std::max(0LL, freeBytes - SAFETY_FREE_DISK_SPACE);744const u64 freeBlocks = availBytes / (u64)DEFAULT_BLOCK_SIZE;745746const u32 alreadyCachedCount = CountCachedFiles();747// This is how many more files of free space we will aim for.748const u32 flex = CACHE_SPACE_FLEX > alreadyCachedCount ? CACHE_SPACE_FLEX - alreadyCachedCount : 1;749750const u64 freeBlocksWithFlex = freeBlocks / flex;751if (freeBlocksWithFlex > MAX_BLOCKS_LOWER_BOUND) {752if (freeBlocksWithFlex > MAX_BLOCKS_UPPER_BOUND) {753return MAX_BLOCKS_UPPER_BOUND;754}755// This might be smaller than what's free, but if they try to launch a second game,756// they'll be happier when it can be cached too.757return (u32)freeBlocksWithFlex;758}759760// Might be lower than LOWER_BOUND, but that's okay. That means not enough space.761// We abandon the idea of flex since there's not enough space free anyway.762return (u32)freeBlocks;763}764765u32 DiskCachingFileLoaderCache::CountCachedFiles() {766Path dir = cacheDir_;767if (dir.empty()) {768dir = GetSysDirectory(DIRECTORY_CACHE);769}770771std::vector<File::FileInfo> files;772return (u32)GetFilesInDir(dir, &files, "ppdc:");773}774775void DiskCachingFileLoaderCache::GarbageCollectCacheFiles(u64 goalBytes) {776// We attempt to free up at least enough files from the cache to get goalBytes more space.777const std::vector<Path> usedPaths = DiskCachingFileLoader::GetCachedPathsInUse();778std::set<std::string> used;779for (const Path &path : usedPaths) {780used.insert(MakeCacheFilename(path));781}782783Path dir = cacheDir_;784if (dir.empty()) {785dir = GetSysDirectory(DIRECTORY_CACHE);786}787788std::vector<File::FileInfo> files;789File::GetFilesInDir(dir, &files, "ppdc:");790791u64 remaining = goalBytes;792// TODO: Could order by LRU or etc.793for (File::FileInfo &file : files) {794if (file.isDirectory) {795continue;796}797if (used.find(file.name) != used.end()) {798// In use, must leave alone.799continue;800}801802#ifdef _WIN32803const std::wstring w32path = file.fullName.ToWString();804#if PPSSPP_PLATFORM(UWP)805bool success = DeleteFileFromAppW(w32path.c_str()) != 0;806#else807bool success = DeleteFileW(w32path.c_str()) != 0;808#endif809#else810bool success = unlink(file.fullName.c_str()) == 0;811#endif812813if (success) {814if (file.size > remaining) {815// We're done, huzzah.816break;817}818819// A little bit more.820remaining -= file.size;821}822}823824// At this point, we've done all we can.825}826827828