CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/FileLoaders/DiskCachingFileLoader.cpp
Views: 1401
// 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#endif3839#if PPSSPP_PLATFORM(SWITCH)40// Far from optimal, but I guess it works...41#define fseeko fseek42#endif4344static const char * const CACHEFILE_MAGIC = "ppssppDC";45static const s64 SAFETY_FREE_DISK_SPACE = 768 * 1024 * 1024; // 768 MB46// Aim to allow this many files cached at once.47static const u32 CACHE_SPACE_FLEX = 4;4849Path DiskCachingFileLoaderCache::cacheDir_;5051std::map<Path, DiskCachingFileLoaderCache *> DiskCachingFileLoader::caches_;52std::mutex DiskCachingFileLoader::cachesMutex_;5354// Takes ownership of backend.55DiskCachingFileLoader::DiskCachingFileLoader(FileLoader *backend)56: ProxiedFileLoader(backend) {57}5859void DiskCachingFileLoader::Prepare() {60std::call_once(preparedFlag_, [this]() {61filesize_ = ProxiedFileLoader::FileSize();62if (filesize_ > 0) {63InitCache();64}65});66}6768DiskCachingFileLoader::~DiskCachingFileLoader() {69if (filesize_ > 0) {70ShutdownCache();71}72}7374bool DiskCachingFileLoader::Exists() {75Prepare();76return ProxiedFileLoader::Exists();77}7879bool DiskCachingFileLoader::ExistsFast() {80// It may require a slow operation to check - if we have data, let's say yes.81// This helps initial load, since we check each recent file for existence.82return true;83}8485s64 DiskCachingFileLoader::FileSize() {86Prepare();87return filesize_;88}8990size_t DiskCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) {91Prepare();92size_t readSize;9394if (absolutePos >= filesize_) {95bytes = 0;96} else if (absolutePos + (s64)bytes >= filesize_) {97bytes = (size_t)(filesize_ - absolutePos);98}99100if (cache_ && cache_->IsValid() && (flags & Flags::HINT_UNCACHED) == 0) {101readSize = cache_->ReadFromCache(absolutePos, bytes, data);102// While in case the cache size is too small for the entire read.103while (readSize < bytes) {104readSize += cache_->SaveIntoCache(backend_, absolutePos + readSize, bytes - readSize, (u8 *)data + readSize, flags);105// We're done, nothing more to read.106if (readSize == bytes) {107break;108}109// If there are already-cached blocks afterward, we have to read them.110size_t bytesFromCache = cache_->ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize);111readSize += bytesFromCache;112if (bytesFromCache == 0) {113// We can't read any more.114break;115}116}117} else {118readSize = backend_->ReadAt(absolutePos, bytes, data, flags);119}120121return readSize;122}123124std::vector<Path> DiskCachingFileLoader::GetCachedPathsInUse() {125std::lock_guard<std::mutex> guard(cachesMutex_);126127// This is on the file loader so that it can manage the caches_.128std::vector<Path> files;129files.reserve(caches_.size());130131for (auto it : caches_) {132files.push_back(it.first);133}134135return files;136}137138void DiskCachingFileLoader::InitCache() {139std::lock_guard<std::mutex> guard(cachesMutex_);140141Path path = ProxiedFileLoader::GetPath();142auto &entry = caches_[path];143if (!entry) {144entry = new DiskCachingFileLoaderCache(path, filesize_);145}146147cache_ = entry;148cache_->AddRef();149}150151void DiskCachingFileLoader::ShutdownCache() {152std::lock_guard<std::mutex> guard(cachesMutex_);153154if (cache_->Release()) {155// If it ran out of counts, delete it.156delete cache_;157caches_.erase(ProxiedFileLoader::GetPath());158}159cache_ = nullptr;160}161162DiskCachingFileLoaderCache::DiskCachingFileLoaderCache(const Path &path, u64 filesize)163: filesize_(filesize), origPath_(path) {164InitCache(path);165}166167DiskCachingFileLoaderCache::~DiskCachingFileLoaderCache() {168ShutdownCache();169}170171void DiskCachingFileLoaderCache::InitCache(const Path &filename) {172cacheSize_ = 0;173indexCount_ = 0;174oldestGeneration_ = 0;175maxBlocks_ = MAX_BLOCKS_LOWER_BOUND;176flags_ = 0;177generation_ = 0;178179const Path cacheFilePath = MakeCacheFilePath(filename);180bool fileLoaded = LoadCacheFile(cacheFilePath);181182// We do some basic locking to protect against two things: crashes and concurrency.183// Concurrency will break the file. Crashes will probably leave it inconsistent.184if (fileLoaded && !LockCacheFile(true)) {185if (RemoveCacheFile(cacheFilePath)) {186// Create a new one.187fileLoaded = false;188} else {189// Couldn't remove, in use? Give up on caching.190CloseFileHandle();191}192}193if (!fileLoaded) {194CreateCacheFile(cacheFilePath);195196if (!LockCacheFile(true)) {197CloseFileHandle();198}199}200}201202void DiskCachingFileLoaderCache::ShutdownCache() {203if (f_) {204bool failed = false;205if (fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {206failed = true;207} else if (fwrite(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {208failed = true;209} else if (fflush(f_) != 0) {210failed = true;211}212if (failed) {213// Leave it locked, it's broken.214ERROR_LOG(Log::Loader, "Unable to flush disk cache.");215} else {216LockCacheFile(false);217}218CloseFileHandle();219}220221index_.clear();222blockIndexLookup_.clear();223cacheSize_ = 0;224}225226size_t DiskCachingFileLoaderCache::ReadFromCache(s64 pos, size_t bytes, void *data) {227std::lock_guard<std::mutex> guard(lock_);228229if (!f_) {230return 0;231}232233size_t cacheStartPos = (size_t)(pos / blockSize_);234size_t cacheEndPos = (size_t)((pos + bytes - 1) / blockSize_);235size_t readSize = 0;236size_t offset = (size_t)(pos - (cacheStartPos * (u64)blockSize_));237u8 *p = (u8 *)data;238239for (size_t i = cacheStartPos; i <= cacheEndPos; ++i) {240auto &info = index_[i];241if (info.block == INVALID_BLOCK) {242return readSize;243}244info.generation = generation_;245if (info.hits < std::numeric_limits<u16>::max()) {246++info.hits;247}248249size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);250if (!ReadBlockData(p + readSize, info, offset, toRead)) {251return readSize;252}253readSize += toRead;254255// Don't need an offset after the first read.256offset = 0;257}258return readSize;259}260261size_t DiskCachingFileLoaderCache::SaveIntoCache(FileLoader *backend, s64 pos, size_t bytes, void *data, FileLoader::Flags flags) {262std::lock_guard<std::mutex> guard(lock_);263264if (!f_) {265// Just to keep things working.266return backend->ReadAt(pos, bytes, data, flags);267}268269size_t cacheStartPos = (size_t)(pos / blockSize_);270size_t cacheEndPos = (size_t)((pos + bytes - 1) / blockSize_);271size_t readSize = 0;272size_t offset = (size_t)(pos - (cacheStartPos * (u64)blockSize_));273u8 *p = (u8 *)data;274275size_t blocksToRead = 0;276for (size_t i = cacheStartPos; i <= cacheEndPos; ++i) {277auto &info = index_[i];278if (info.block != INVALID_BLOCK) {279break;280}281++blocksToRead;282if (blocksToRead >= MAX_BLOCKS_PER_READ) {283break;284}285}286287if (!MakeCacheSpaceFor(blocksToRead) || blocksToRead == 0) {288return 0;289}290291if (blocksToRead == 1) {292auto &info = index_[cacheStartPos];293294u8 *buf = new u8[blockSize_];295size_t readBytes = backend->ReadAt(cacheStartPos * (u64)blockSize_, blockSize_, buf, flags);296297// Check if it was written while we were busy. Might happen if we thread.298if (info.block == INVALID_BLOCK && readBytes != 0) {299info.block = AllocateBlock((u32)cacheStartPos);300WriteBlockData(info, buf);301WriteIndexData((u32)cacheStartPos, info);302}303304size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);305memcpy(p + readSize, buf + offset, toRead);306readSize += toRead;307308delete [] buf;309} else {310u8 *wholeRead = new u8[blocksToRead * blockSize_];311size_t readBytes = backend->ReadAt(cacheStartPos * (u64)blockSize_, blocksToRead * blockSize_, wholeRead, flags);312313for (size_t i = 0; i < blocksToRead; ++i) {314auto &info = index_[cacheStartPos + i];315// Check if it was written while we were busy. Might happen if we thread.316if (info.block == INVALID_BLOCK && readBytes != 0) {317info.block = AllocateBlock((u32)cacheStartPos + (u32)i);318WriteBlockData(info, wholeRead + (i * blockSize_));319// TODO: Doing each index together would probably be better.320WriteIndexData((u32)cacheStartPos + (u32)i, info);321}322323size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);324memcpy(p + readSize, wholeRead + (i * blockSize_) + offset, toRead);325readSize += toRead;326}327delete[] wholeRead;328}329330cacheSize_ += blocksToRead;331++generation_;332333if (generation_ == std::numeric_limits<u16>::max()) {334RebalanceGenerations();335}336337return readSize;338}339340bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {341size_t goal = (size_t)maxBlocks_ - blocks;342343while (cacheSize_ > goal) {344u16 minGeneration = generation_;345346// We increment the iterator inside because we delete things inside.347for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {348if (blockIndexLookup_[i] == INVALID_INDEX) {349continue;350}351auto &info = index_[blockIndexLookup_[i]];352353// Check for the minimum seen generation.354// TODO: Do this smarter?355if (info.generation != 0 && info.generation < minGeneration) {356minGeneration = info.generation;357}358359// 0 means it was never used yet or was the first read (e.g. block descriptor.)360if (info.generation == oldestGeneration_ || info.generation == 0) {361info.block = INVALID_BLOCK;362info.generation = 0;363info.hits = 0;364--cacheSize_;365366// TODO: Doing this in chunks might be a lot better.367WriteIndexData(blockIndexLookup_[i], info);368blockIndexLookup_[i] = INVALID_INDEX;369370// Keep going?371if (cacheSize_ <= goal) {372break;373}374}375}376377// If we didn't find any, update to the lowest we did find.378oldestGeneration_ = minGeneration;379}380381return true;382}383384void DiskCachingFileLoaderCache::RebalanceGenerations() {385// To make things easy, we will subtract oldestGeneration_ and cut in half.386// That should give us more space but not break anything.387388for (size_t i = 0; i < index_.size(); ++i) {389auto &info = index_[i];390if (info.block == INVALID_BLOCK) {391continue;392}393394if (info.generation > oldestGeneration_) {395info.generation = (info.generation - oldestGeneration_) / 2;396// TODO: Doing this all at once would be much better.397WriteIndexData((u32)i, info);398}399}400401oldestGeneration_ = 0;402}403404u32 DiskCachingFileLoaderCache::AllocateBlock(u32 indexPos) {405for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {406if (blockIndexLookup_[i] == INVALID_INDEX) {407blockIndexLookup_[i] = indexPos;408return (u32)i;409}410}411412_dbg_assert_msg_(false, "Not enough free blocks");413return INVALID_BLOCK;414}415416std::string DiskCachingFileLoaderCache::MakeCacheFilename(const Path &path) {417static const char *const invalidChars = "?*:/\\^|<>\"'";418std::string filename = path.ToString();419for (size_t i = 0; i < filename.size(); ++i) {420int c = filename[i];421if (strchr(invalidChars, c) != nullptr) {422filename[i] = '_';423}424}425return filename + ".ppdc";426}427428::Path DiskCachingFileLoaderCache::MakeCacheFilePath(const Path &filename) {429Path dir = cacheDir_;430if (dir.empty()) {431dir = GetSysDirectory(DIRECTORY_CACHE);432}433434if (!File::Exists(dir)) {435File::CreateFullPath(dir);436}437438return dir / MakeCacheFilename(filename);439}440441s64 DiskCachingFileLoaderCache::GetBlockOffset(u32 block) {442// This is where the blocks start.443s64 blockOffset = (s64)sizeof(FileHeader) + (s64)indexCount_ * (s64)sizeof(BlockInfo);444// Now to the actual block.445return blockOffset + (s64)block * (s64)blockSize_;446}447448bool DiskCachingFileLoaderCache::ReadBlockData(u8 *dest, BlockInfo &info, size_t offset, size_t size) {449if (!f_) {450return false;451}452if (size == 0) {453return true;454}455s64 blockOffset = GetBlockOffset(info.block);456457// Before we read, make sure the buffers are flushed.458// We might be trying to read an area we've recently written.459fflush(f_);460461bool failed = false;462#ifdef __ANDROID__463if (lseek64(fd_, blockOffset, SEEK_SET) != blockOffset) {464failed = true;465} else if (read(fd_, dest + offset, size) != (ssize_t)size) {466failed = true;467}468#else469if (fseeko(f_, blockOffset, SEEK_SET) != 0) {470failed = true;471} else if (fread(dest + offset, size, 1, f_) != 1) {472failed = true;473}474#endif475476if (failed) {477ERROR_LOG(Log::Loader, "Unable to read disk cache data entry.");478CloseFileHandle();479}480return !failed;481}482483void DiskCachingFileLoaderCache::WriteBlockData(BlockInfo &info, const u8 *src) {484if (!f_) {485return;486}487s64 blockOffset = GetBlockOffset(info.block);488489bool failed = false;490#ifdef __ANDROID__491if (lseek64(fd_, blockOffset, SEEK_SET) != blockOffset) {492failed = true;493} else if (write(fd_, src, blockSize_) != (ssize_t)blockSize_) {494failed = true;495}496#else497if (fseeko(f_, blockOffset, SEEK_SET) != 0) {498failed = true;499} else if (fwrite(src, blockSize_, 1, f_) != 1) {500failed = true;501}502#endif503504if (failed) {505ERROR_LOG(Log::Loader, "Unable to write disk cache data entry.");506CloseFileHandle();507}508}509510void DiskCachingFileLoaderCache::WriteIndexData(u32 indexPos, BlockInfo &info) {511if (!f_) {512return;513}514515u32 offset = (u32)sizeof(FileHeader) + indexPos * (u32)sizeof(BlockInfo);516517bool failed = false;518if (fseek(f_, offset, SEEK_SET) != 0) {519failed = true;520} else if (fwrite(&info, sizeof(BlockInfo), 1, f_) != 1) {521failed = true;522}523524if (failed) {525ERROR_LOG(Log::Loader, "Unable to write disk cache index entry.");526CloseFileHandle();527}528}529530bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) {531FILE *fp = File::OpenCFile(path, "rb+");532if (!fp) {533return false;534}535536FileHeader header;537bool valid = true;538if (fread(&header, sizeof(FileHeader), 1, fp) != 1) {539valid = false;540} else if (memcmp(header.magic, CACHEFILE_MAGIC, sizeof(header.magic)) != 0) {541valid = false;542} else if (header.version != CACHE_VERSION) {543valid = false;544} else if (header.filesize != filesize_) {545valid = false;546} else if (header.maxBlocks < MAX_BLOCKS_LOWER_BOUND || header.maxBlocks > MAX_BLOCKS_UPPER_BOUND) {547// This means it's not in our safety bounds, reject.548valid = false;549}550551// If it's valid, retain the file pointer.552if (valid) {553f_ = fp;554555#ifdef __ANDROID__556// Android NDK does not support 64-bit file I/O using C streams557fd_ = fileno(f_);558#endif559560// Now let's load the index.561blockSize_ = header.blockSize;562maxBlocks_ = header.maxBlocks;563flags_ = header.flags;564LoadCacheIndex();565} else {566ERROR_LOG(Log::Loader, "Disk cache file header did not match, recreating cache file");567fclose(fp);568}569570return valid;571}572573void DiskCachingFileLoaderCache::LoadCacheIndex() {574if (fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {575CloseFileHandle();576return;577}578579indexCount_ = (size_t)((filesize_ + blockSize_ - 1) / blockSize_);580index_.resize(indexCount_);581blockIndexLookup_.resize(maxBlocks_);582memset(&blockIndexLookup_[0], INVALID_INDEX, maxBlocks_ * sizeof(blockIndexLookup_[0]));583584if (fread(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {585CloseFileHandle();586return;587}588589// Now let's set some values we need.590oldestGeneration_ = std::numeric_limits<u16>::max();591generation_ = 0;592cacheSize_ = 0;593594for (size_t i = 0; i < index_.size(); ++i) {595if (index_[i].block > maxBlocks_) {596index_[i].block = INVALID_BLOCK;597}598if (index_[i].block == INVALID_BLOCK) {599continue;600}601602if (index_[i].generation < oldestGeneration_) {603oldestGeneration_ = index_[i].generation;604}605if (index_[i].generation > generation_) {606generation_ = index_[i].generation;607}608++cacheSize_;609610blockIndexLookup_[index_[i].block] = (u32)i;611}612}613614void DiskCachingFileLoaderCache::CreateCacheFile(const Path &path) {615maxBlocks_ = DetermineMaxBlocks();616if (maxBlocks_ < MAX_BLOCKS_LOWER_BOUND) {617GarbageCollectCacheFiles(MAX_BLOCKS_LOWER_BOUND * DEFAULT_BLOCK_SIZE);618maxBlocks_ = DetermineMaxBlocks();619}620if (maxBlocks_ < MAX_BLOCKS_LOWER_BOUND) {621// There's not enough free space to cache, disable.622f_ = nullptr;623ERROR_LOG(Log::Loader, "Not enough free space; disabling disk cache");624return;625}626flags_ = 0;627628f_ = File::OpenCFile(path, "wb+");629if (!f_) {630ERROR_LOG(Log::Loader, "Could not create disk cache file");631return;632}633#ifdef __ANDROID__634// Android NDK does not support 64-bit file I/O using C streams635fd_ = fileno(f_);636#endif637638blockSize_ = DEFAULT_BLOCK_SIZE;639640FileHeader header;641memcpy(header.magic, CACHEFILE_MAGIC, sizeof(header.magic));642header.version = CACHE_VERSION;643header.blockSize = blockSize_;644header.filesize = filesize_;645header.maxBlocks = maxBlocks_;646header.flags = flags_;647648if (fwrite(&header, sizeof(header), 1, f_) != 1) {649CloseFileHandle();650return;651}652653indexCount_ = (size_t)((filesize_ + blockSize_ - 1) / blockSize_);654index_.clear();655index_.resize(indexCount_);656blockIndexLookup_.resize(maxBlocks_);657memset(&blockIndexLookup_[0], INVALID_INDEX, maxBlocks_ * sizeof(blockIndexLookup_[0]));658659if (fwrite(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {660CloseFileHandle();661return;662}663if (fflush(f_) != 0) {664CloseFileHandle();665return;666}667668INFO_LOG(Log::Loader, "Created new disk cache file for %s", origPath_.c_str());669}670671bool DiskCachingFileLoaderCache::LockCacheFile(bool lockStatus) {672if (!f_) {673return false;674}675676u32 offset = (u32)offsetof(FileHeader, flags);677678bool failed = false;679if (fseek(f_, offset, SEEK_SET) != 0) {680failed = true;681} else if (fread(&flags_, sizeof(u32), 1, f_) != 1) {682failed = true;683}684685if (failed) {686ERROR_LOG(Log::Loader, "Unable to read current flags during disk cache locking");687CloseFileHandle();688return false;689}690691// TODO: Also use flock where supported?692if (lockStatus) {693if ((flags_ & FLAG_LOCKED) != 0) {694ERROR_LOG(Log::Loader, "Could not lock disk cache file for %s (already locked)", origPath_.c_str());695return false;696}697flags_ |= FLAG_LOCKED;698} else {699if ((flags_ & FLAG_LOCKED) == 0) {700ERROR_LOG(Log::Loader, "Could not unlock disk cache file for %s", origPath_.c_str());701return false;702}703flags_ &= ~FLAG_LOCKED;704}705706if (fseek(f_, offset, SEEK_SET) != 0) {707failed = true;708} else if (fwrite(&flags_, sizeof(u32), 1, f_) != 1) {709failed = true;710} else if (fflush(f_) != 0) {711failed = true;712}713714if (failed) {715ERROR_LOG(Log::Loader, "Unable to write updated flags during disk cache locking");716CloseFileHandle();717return false;718}719720if (lockStatus) {721INFO_LOG(Log::Loader, "Locked disk cache file for %s", origPath_.c_str());722} else {723INFO_LOG(Log::Loader, "Unlocked disk cache file for %s", origPath_.c_str());724}725return true;726}727728bool DiskCachingFileLoaderCache::RemoveCacheFile(const Path &path) {729// Note that some platforms, you can't delete open files. So we check.730CloseFileHandle();731return File::Delete(path);732}733734void DiskCachingFileLoaderCache::CloseFileHandle() {735if (f_) {736fclose(f_);737}738f_ = nullptr;739fd_ = 0;740}741742bool DiskCachingFileLoaderCache::HasData() const {743if (!f_) {744return false;745}746747for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {748if (blockIndexLookup_[i] != INVALID_INDEX) {749return true;750}751}752return false;753}754755u64 DiskCachingFileLoaderCache::FreeDiskSpace() {756Path dir = cacheDir_;757if (dir.empty()) {758dir = GetSysDirectory(DIRECTORY_CACHE);759}760761int64_t result = 0;762if (free_disk_space(dir, result)) {763return (u64)result;764}765766// We can't know for sure how much is free, so we have to assume none.767return 0;768}769770u32 DiskCachingFileLoaderCache::DetermineMaxBlocks() {771const s64 freeBytes = FreeDiskSpace();772// We want to leave them some room for other stuff.773const u64 availBytes = std::max(0LL, freeBytes - SAFETY_FREE_DISK_SPACE);774const u64 freeBlocks = availBytes / (u64)DEFAULT_BLOCK_SIZE;775776const u32 alreadyCachedCount = CountCachedFiles();777// This is how many more files of free space we will aim for.778const u32 flex = CACHE_SPACE_FLEX > alreadyCachedCount ? CACHE_SPACE_FLEX - alreadyCachedCount : 1;779780const u64 freeBlocksWithFlex = freeBlocks / flex;781if (freeBlocksWithFlex > MAX_BLOCKS_LOWER_BOUND) {782if (freeBlocksWithFlex > MAX_BLOCKS_UPPER_BOUND) {783return MAX_BLOCKS_UPPER_BOUND;784}785// This might be smaller than what's free, but if they try to launch a second game,786// they'll be happier when it can be cached too.787return (u32)freeBlocksWithFlex;788}789790// Might be lower than LOWER_BOUND, but that's okay. That means not enough space.791// We abandon the idea of flex since there's not enough space free anyway.792return (u32)freeBlocks;793}794795u32 DiskCachingFileLoaderCache::CountCachedFiles() {796Path dir = cacheDir_;797if (dir.empty()) {798dir = GetSysDirectory(DIRECTORY_CACHE);799}800801std::vector<File::FileInfo> files;802return (u32)GetFilesInDir(dir, &files, "ppdc:");803}804805void DiskCachingFileLoaderCache::GarbageCollectCacheFiles(u64 goalBytes) {806// We attempt to free up at least enough files from the cache to get goalBytes more space.807const std::vector<Path> usedPaths = DiskCachingFileLoader::GetCachedPathsInUse();808std::set<std::string> used;809for (const Path &path : usedPaths) {810used.insert(MakeCacheFilename(path));811}812813Path dir = cacheDir_;814if (dir.empty()) {815dir = GetSysDirectory(DIRECTORY_CACHE);816}817818std::vector<File::FileInfo> files;819File::GetFilesInDir(dir, &files, "ppdc:");820821u64 remaining = goalBytes;822// TODO: Could order by LRU or etc.823for (File::FileInfo &file : files) {824if (file.isDirectory) {825continue;826}827if (used.find(file.name) != used.end()) {828// In use, must leave alone.829continue;830}831832#ifdef _WIN32833const std::wstring w32path = file.fullName.ToWString();834#if PPSSPP_PLATFORM(UWP)835bool success = DeleteFileFromAppW(w32path.c_str()) != 0;836#else837bool success = DeleteFileW(w32path.c_str()) != 0;838#endif839#else840bool success = unlink(file.fullName.c_str()) == 0;841#endif842843if (success) {844if (file.size > remaining) {845// We're done, huzzah.846break;847}848849// A little bit more.850remaining -= file.size;851}852}853854// At this point, we've done all we can.855}856857858