Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/FileLoaders/DiskCachingFileLoader.cpp
5664 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
20
#include <algorithm>
21
#include <cstddef>
22
#include <set>
23
#include <mutex>
24
#include <cstring>
25
26
#include "Common/Data/Encoding/Utf8.h"
27
#include "Common/File/DiskFree.h"
28
#include "Common/File/DirListing.h"
29
#include "Common/File/FileUtil.h"
30
#include "Common/File/Path.h"
31
#include "Common/Log.h"
32
#include "Common/CommonWindows.h"
33
#include "Core/FileLoaders/DiskCachingFileLoader.h"
34
#include "Core/System.h"
35
36
#if PPSSPP_PLATFORM(UWP)
37
#include <fileapifromapp.h>
38
#endif
39
40
static const char * const CACHEFILE_MAGIC = "ppssppDC";
41
static const s64 SAFETY_FREE_DISK_SPACE = 768 * 1024 * 1024; // 768 MB
42
// Aim to allow this many files cached at once.
43
static const u32 CACHE_SPACE_FLEX = 4;
44
45
Path DiskCachingFileLoaderCache::cacheDir_;
46
47
std::map<Path, DiskCachingFileLoaderCache *> DiskCachingFileLoader::caches_;
48
std::mutex DiskCachingFileLoader::cachesMutex_;
49
50
// Takes ownership of backend.
51
DiskCachingFileLoader::DiskCachingFileLoader(FileLoader *backend)
52
: ProxiedFileLoader(backend) {
53
}
54
55
void DiskCachingFileLoader::Prepare() {
56
std::call_once(preparedFlag_, [this]() {
57
filesize_ = ProxiedFileLoader::FileSize();
58
if (filesize_ > 0) {
59
InitCache();
60
}
61
});
62
}
63
64
DiskCachingFileLoader::~DiskCachingFileLoader() {
65
if (filesize_ > 0) {
66
ShutdownCache();
67
}
68
}
69
70
bool DiskCachingFileLoader::Exists() {
71
Prepare();
72
return ProxiedFileLoader::Exists();
73
}
74
75
bool DiskCachingFileLoader::ExistsFast() {
76
// It may require a slow operation to check - if we have data, let's say yes.
77
// This helps initial load, since we check each recent file for existence.
78
return true;
79
}
80
81
s64 DiskCachingFileLoader::FileSize() {
82
Prepare();
83
return filesize_;
84
}
85
86
size_t DiskCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) {
87
Prepare();
88
size_t readSize;
89
90
if (absolutePos >= filesize_) {
91
bytes = 0;
92
} else if (absolutePos + (s64)bytes >= filesize_) {
93
bytes = (size_t)(filesize_ - absolutePos);
94
}
95
96
if (cache_ && cache_->IsValid() && (flags & Flags::HINT_UNCACHED) == 0) {
97
readSize = cache_->ReadFromCache(absolutePos, bytes, data);
98
// While in case the cache size is too small for the entire read.
99
while (readSize < bytes) {
100
readSize += cache_->SaveIntoCache(backend_, absolutePos + readSize, bytes - readSize, (u8 *)data + readSize, flags);
101
// We're done, nothing more to read.
102
if (readSize == bytes) {
103
break;
104
}
105
// If there are already-cached blocks afterward, we have to read them.
106
size_t bytesFromCache = cache_->ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize);
107
readSize += bytesFromCache;
108
if (bytesFromCache == 0) {
109
// We can't read any more.
110
break;
111
}
112
}
113
} else {
114
readSize = backend_->ReadAt(absolutePos, bytes, data, flags);
115
}
116
117
return readSize;
118
}
119
120
std::vector<Path> DiskCachingFileLoader::GetCachedPathsInUse() {
121
std::lock_guard<std::mutex> guard(cachesMutex_);
122
123
// This is on the file loader so that it can manage the caches_.
124
std::vector<Path> files;
125
files.reserve(caches_.size());
126
127
for (const auto &it : caches_) {
128
files.push_back(it.first);
129
}
130
131
return files;
132
}
133
134
void DiskCachingFileLoader::InitCache() {
135
std::lock_guard<std::mutex> guard(cachesMutex_);
136
137
Path path = ProxiedFileLoader::GetPath();
138
auto &entry = caches_[path];
139
if (!entry) {
140
entry = new DiskCachingFileLoaderCache(path, filesize_);
141
}
142
143
cache_ = entry;
144
cache_->AddRef();
145
}
146
147
void DiskCachingFileLoader::ShutdownCache() {
148
std::lock_guard<std::mutex> guard(cachesMutex_);
149
150
if (cache_->Release()) {
151
// If it ran out of counts, delete it.
152
delete cache_;
153
caches_.erase(ProxiedFileLoader::GetPath());
154
}
155
cache_ = nullptr;
156
}
157
158
DiskCachingFileLoaderCache::DiskCachingFileLoaderCache(const Path &path, u64 filesize)
159
: filesize_(filesize), origPath_(path) {
160
InitCache(path);
161
}
162
163
DiskCachingFileLoaderCache::~DiskCachingFileLoaderCache() {
164
ShutdownCache();
165
}
166
167
void DiskCachingFileLoaderCache::InitCache(const Path &filename) {
168
cacheSize_ = 0;
169
indexCount_ = 0;
170
oldestGeneration_ = 0;
171
maxBlocks_ = MAX_BLOCKS_LOWER_BOUND;
172
flags_ = 0;
173
generation_ = 0;
174
175
const Path cacheFilePath = MakeCacheFilePath(filename);
176
bool fileLoaded = LoadCacheFile(cacheFilePath);
177
178
// We do some basic locking to protect against two things: crashes and concurrency.
179
// Concurrency will break the file. Crashes will probably leave it inconsistent.
180
if (fileLoaded && !LockCacheFile(true)) {
181
if (RemoveCacheFile(cacheFilePath)) {
182
// Create a new one.
183
fileLoaded = false;
184
} else {
185
// Couldn't remove, in use? Give up on caching.
186
CloseFileHandle();
187
}
188
}
189
if (!fileLoaded) {
190
CreateCacheFile(cacheFilePath);
191
192
if (!LockCacheFile(true)) {
193
CloseFileHandle();
194
}
195
}
196
}
197
198
void DiskCachingFileLoaderCache::ShutdownCache() {
199
if (f_) {
200
bool failed = false;
201
if (File::Fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {
202
failed = true;
203
} else if (fwrite(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {
204
failed = true;
205
} else if (fflush(f_) != 0) {
206
failed = true;
207
}
208
if (failed) {
209
// Leave it locked, it's broken.
210
ERROR_LOG(Log::Loader, "Unable to flush disk cache.");
211
} else {
212
LockCacheFile(false);
213
}
214
CloseFileHandle();
215
}
216
217
index_.clear();
218
blockIndexLookup_.clear();
219
cacheSize_ = 0;
220
}
221
222
size_t DiskCachingFileLoaderCache::ReadFromCache(s64 pos, size_t bytes, void *data) {
223
std::lock_guard<std::mutex> guard(lock_);
224
225
if (!f_) {
226
return 0;
227
}
228
229
size_t cacheStartPos = (size_t)(pos / blockSize_);
230
size_t cacheEndPos = (size_t)((pos + bytes - 1) / blockSize_);
231
size_t readSize = 0;
232
size_t offset = (size_t)(pos - (cacheStartPos * (u64)blockSize_));
233
u8 *p = (u8 *)data;
234
235
for (size_t i = cacheStartPos; i <= cacheEndPos; ++i) {
236
auto &info = index_[i];
237
if (info.block == INVALID_BLOCK) {
238
return readSize;
239
}
240
info.generation = generation_;
241
if (info.hits < std::numeric_limits<u16>::max()) {
242
++info.hits;
243
}
244
245
size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);
246
if (!ReadBlockData(p + readSize, info, offset, toRead)) {
247
return readSize;
248
}
249
readSize += toRead;
250
251
// Don't need an offset after the first read.
252
offset = 0;
253
}
254
return readSize;
255
}
256
257
size_t DiskCachingFileLoaderCache::SaveIntoCache(FileLoader *backend, s64 pos, size_t bytes, void *data, FileLoader::Flags flags) {
258
std::lock_guard<std::mutex> guard(lock_);
259
260
if (!f_) {
261
// Just to keep things working.
262
return backend->ReadAt(pos, bytes, data, flags);
263
}
264
265
size_t cacheStartPos = (size_t)(pos / blockSize_);
266
size_t cacheEndPos = (size_t)((pos + bytes - 1) / blockSize_);
267
size_t readSize = 0;
268
size_t offset = (size_t)(pos - (cacheStartPos * (u64)blockSize_));
269
u8 *p = (u8 *)data;
270
271
size_t blocksToRead = 0;
272
for (size_t i = cacheStartPos; i <= cacheEndPos; ++i) {
273
auto &info = index_[i];
274
if (info.block != INVALID_BLOCK) {
275
break;
276
}
277
++blocksToRead;
278
if (blocksToRead >= MAX_BLOCKS_PER_READ) {
279
break;
280
}
281
}
282
283
if (!MakeCacheSpaceFor(blocksToRead) || blocksToRead == 0) {
284
return 0;
285
}
286
287
if (blocksToRead == 1) {
288
auto &info = index_[cacheStartPos];
289
290
u8 *buf = new u8[blockSize_];
291
size_t readBytes = backend->ReadAt(cacheStartPos * (u64)blockSize_, blockSize_, buf, flags);
292
293
// Check if it was written while we were busy. Might happen if we thread.
294
if (info.block == INVALID_BLOCK && readBytes != 0) {
295
info.block = AllocateBlock((u32)cacheStartPos);
296
WriteBlockData(info, buf);
297
WriteIndexData((u32)cacheStartPos, info);
298
}
299
300
size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);
301
memcpy(p + readSize, buf + offset, toRead);
302
readSize += toRead;
303
304
delete [] buf;
305
} else {
306
u8 *wholeRead = new u8[blocksToRead * blockSize_];
307
size_t readBytes = backend->ReadAt(cacheStartPos * (u64)blockSize_, blocksToRead * blockSize_, wholeRead, flags);
308
309
for (size_t i = 0; i < blocksToRead; ++i) {
310
auto &info = index_[cacheStartPos + i];
311
// Check if it was written while we were busy. Might happen if we thread.
312
if (info.block == INVALID_BLOCK && readBytes != 0) {
313
info.block = AllocateBlock((u32)cacheStartPos + (u32)i);
314
WriteBlockData(info, wholeRead + (i * blockSize_));
315
// TODO: Doing each index together would probably be better.
316
WriteIndexData((u32)cacheStartPos + (u32)i, info);
317
}
318
319
size_t toRead = std::min(bytes - readSize, (size_t)blockSize_ - offset);
320
memcpy(p + readSize, wholeRead + (i * blockSize_) + offset, toRead);
321
readSize += toRead;
322
}
323
delete[] wholeRead;
324
}
325
326
cacheSize_ += blocksToRead;
327
++generation_;
328
329
if (generation_ == std::numeric_limits<u16>::max()) {
330
RebalanceGenerations();
331
}
332
333
return readSize;
334
}
335
336
bool DiskCachingFileLoaderCache::MakeCacheSpaceFor(size_t blocks) {
337
size_t goal = (size_t)maxBlocks_ - blocks;
338
339
while (cacheSize_ > goal) {
340
u16 minGeneration = generation_;
341
342
// We increment the iterator inside because we delete things inside.
343
for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {
344
if (blockIndexLookup_[i] == INVALID_INDEX) {
345
continue;
346
}
347
auto &info = index_[blockIndexLookup_[i]];
348
349
// Check for the minimum seen generation.
350
// TODO: Do this smarter?
351
if (info.generation != 0 && info.generation < minGeneration) {
352
minGeneration = info.generation;
353
}
354
355
// 0 means it was never used yet or was the first read (e.g. block descriptor.)
356
if (info.generation == oldestGeneration_ || info.generation == 0) {
357
info.block = INVALID_BLOCK;
358
info.generation = 0;
359
info.hits = 0;
360
--cacheSize_;
361
362
// TODO: Doing this in chunks might be a lot better.
363
WriteIndexData(blockIndexLookup_[i], info);
364
blockIndexLookup_[i] = INVALID_INDEX;
365
366
// Keep going?
367
if (cacheSize_ <= goal) {
368
break;
369
}
370
}
371
}
372
373
// If we didn't find any, update to the lowest we did find.
374
oldestGeneration_ = minGeneration;
375
}
376
377
return true;
378
}
379
380
void DiskCachingFileLoaderCache::RebalanceGenerations() {
381
// To make things easy, we will subtract oldestGeneration_ and cut in half.
382
// That should give us more space but not break anything.
383
384
for (size_t i = 0; i < index_.size(); ++i) {
385
auto &info = index_[i];
386
if (info.block == INVALID_BLOCK) {
387
continue;
388
}
389
390
if (info.generation > oldestGeneration_) {
391
info.generation = (info.generation - oldestGeneration_) / 2;
392
// TODO: Doing this all at once would be much better.
393
WriteIndexData((u32)i, info);
394
}
395
}
396
397
oldestGeneration_ = 0;
398
}
399
400
u32 DiskCachingFileLoaderCache::AllocateBlock(u32 indexPos) {
401
for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {
402
if (blockIndexLookup_[i] == INVALID_INDEX) {
403
blockIndexLookup_[i] = indexPos;
404
return (u32)i;
405
}
406
}
407
408
_dbg_assert_msg_(false, "Not enough free blocks");
409
return INVALID_BLOCK;
410
}
411
412
std::string DiskCachingFileLoaderCache::MakeCacheFilename(const Path &path) {
413
static const char *const invalidChars = "?*:/\\^|<>\"'";
414
std::string filename = path.ToString();
415
for (size_t i = 0; i < filename.size(); ++i) {
416
int c = filename[i];
417
if (strchr(invalidChars, c) != nullptr) {
418
filename[i] = '_';
419
}
420
}
421
return filename + ".ppdc";
422
}
423
424
::Path DiskCachingFileLoaderCache::MakeCacheFilePath(const Path &filename) {
425
Path dir = cacheDir_;
426
if (dir.empty()) {
427
dir = GetSysDirectory(DIRECTORY_CACHE);
428
}
429
430
if (!File::Exists(dir)) {
431
File::CreateFullPath(dir);
432
}
433
434
return dir / MakeCacheFilename(filename);
435
}
436
437
s64 DiskCachingFileLoaderCache::GetBlockOffset(u32 block) {
438
// This is where the blocks start.
439
s64 blockOffset = (s64)sizeof(FileHeader) + (s64)indexCount_ * (s64)sizeof(BlockInfo);
440
// Now to the actual block.
441
return blockOffset + (s64)block * (s64)blockSize_;
442
}
443
444
bool DiskCachingFileLoaderCache::ReadBlockData(u8 *dest, BlockInfo &info, size_t offset, size_t size) {
445
if (!f_) {
446
return false;
447
}
448
if (size == 0) {
449
return true;
450
}
451
s64 blockOffset = GetBlockOffset(info.block);
452
453
// Before we read, make sure the buffers are flushed.
454
// We might be trying to read an area we've recently written.
455
fflush(f_);
456
457
bool failed = false;
458
if (File::Fseek(f_, blockOffset, SEEK_SET) != 0) {
459
failed = true;
460
} else if (fread(dest + offset, size, 1, f_) != 1) {
461
failed = true;
462
}
463
464
if (failed) {
465
ERROR_LOG(Log::Loader, "Unable to read disk cache data entry.");
466
CloseFileHandle();
467
}
468
return !failed;
469
}
470
471
void DiskCachingFileLoaderCache::WriteBlockData(BlockInfo &info, const u8 *src) {
472
if (!f_) {
473
return;
474
}
475
s64 blockOffset = GetBlockOffset(info.block);
476
477
bool failed = false;
478
if (File::Fseek(f_, blockOffset, SEEK_SET) != 0) {
479
failed = true;
480
} else if (fwrite(src, blockSize_, 1, f_) != 1) {
481
failed = true;
482
}
483
484
if (failed) {
485
ERROR_LOG(Log::Loader, "Unable to write disk cache data entry.");
486
CloseFileHandle();
487
}
488
}
489
490
void DiskCachingFileLoaderCache::WriteIndexData(u32 indexPos, BlockInfo &info) {
491
if (!f_) {
492
return;
493
}
494
495
u32 offset = (u32)sizeof(FileHeader) + indexPos * (u32)sizeof(BlockInfo);
496
497
bool failed = false;
498
if (File::Fseek(f_, offset, SEEK_SET) != 0) {
499
failed = true;
500
} else if (fwrite(&info, sizeof(BlockInfo), 1, f_) != 1) {
501
failed = true;
502
}
503
504
if (failed) {
505
ERROR_LOG(Log::Loader, "Unable to write disk cache index entry.");
506
CloseFileHandle();
507
}
508
}
509
510
bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) {
511
FILE *fp = File::OpenCFile(path, "rb+");
512
if (!fp) {
513
return false;
514
}
515
516
FileHeader header;
517
bool valid = true;
518
if (fread(&header, sizeof(FileHeader), 1, fp) != 1) {
519
valid = false;
520
} else if (memcmp(header.magic, CACHEFILE_MAGIC, sizeof(header.magic)) != 0) {
521
valid = false;
522
} else if (header.version != CACHE_VERSION) {
523
valid = false;
524
} else if (header.filesize != filesize_) {
525
valid = false;
526
} else if (header.maxBlocks < MAX_BLOCKS_LOWER_BOUND || header.maxBlocks > MAX_BLOCKS_UPPER_BOUND) {
527
// This means it's not in our safety bounds, reject.
528
valid = false;
529
}
530
531
// If it's valid, retain the file pointer.
532
if (valid) {
533
f_ = fp;
534
535
// Now let's load the index.
536
blockSize_ = header.blockSize;
537
maxBlocks_ = header.maxBlocks;
538
flags_ = header.flags;
539
LoadCacheIndex();
540
} else {
541
ERROR_LOG(Log::Loader, "Disk cache file header did not match, recreating cache file");
542
fclose(fp);
543
}
544
545
return valid;
546
}
547
548
void DiskCachingFileLoaderCache::LoadCacheIndex() {
549
if (File::Fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {
550
CloseFileHandle();
551
return;
552
}
553
554
indexCount_ = (size_t)((filesize_ + blockSize_ - 1) / blockSize_);
555
index_.resize(indexCount_);
556
blockIndexLookup_.resize(maxBlocks_);
557
memset(&blockIndexLookup_[0], INVALID_INDEX, maxBlocks_ * sizeof(blockIndexLookup_[0]));
558
559
if (fread(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {
560
CloseFileHandle();
561
return;
562
}
563
564
// Now let's set some values we need.
565
oldestGeneration_ = std::numeric_limits<u16>::max();
566
generation_ = 0;
567
cacheSize_ = 0;
568
569
for (size_t i = 0; i < index_.size(); ++i) {
570
if (index_[i].block > maxBlocks_) {
571
index_[i].block = INVALID_BLOCK;
572
}
573
if (index_[i].block == INVALID_BLOCK) {
574
continue;
575
}
576
577
if (index_[i].generation < oldestGeneration_) {
578
oldestGeneration_ = index_[i].generation;
579
}
580
if (index_[i].generation > generation_) {
581
generation_ = index_[i].generation;
582
}
583
++cacheSize_;
584
585
blockIndexLookup_[index_[i].block] = (u32)i;
586
}
587
}
588
589
void DiskCachingFileLoaderCache::CreateCacheFile(const Path &path) {
590
maxBlocks_ = DetermineMaxBlocks();
591
if (maxBlocks_ < MAX_BLOCKS_LOWER_BOUND) {
592
GarbageCollectCacheFiles(MAX_BLOCKS_LOWER_BOUND * DEFAULT_BLOCK_SIZE);
593
maxBlocks_ = DetermineMaxBlocks();
594
}
595
if (maxBlocks_ < MAX_BLOCKS_LOWER_BOUND) {
596
// There's not enough free space to cache, disable.
597
f_ = nullptr;
598
ERROR_LOG(Log::Loader, "Not enough free space; disabling disk cache");
599
return;
600
}
601
flags_ = 0;
602
603
f_ = File::OpenCFile(path, "wb+");
604
if (!f_) {
605
ERROR_LOG(Log::Loader, "Could not create disk cache file");
606
return;
607
}
608
609
blockSize_ = DEFAULT_BLOCK_SIZE;
610
611
FileHeader header;
612
memcpy(header.magic, CACHEFILE_MAGIC, sizeof(header.magic));
613
header.version = CACHE_VERSION;
614
header.blockSize = blockSize_;
615
header.filesize = filesize_;
616
header.maxBlocks = maxBlocks_;
617
header.flags = flags_;
618
619
if (fwrite(&header, sizeof(header), 1, f_) != 1) {
620
CloseFileHandle();
621
return;
622
}
623
624
indexCount_ = (size_t)((filesize_ + blockSize_ - 1) / blockSize_);
625
index_.clear();
626
index_.resize(indexCount_);
627
blockIndexLookup_.resize(maxBlocks_);
628
memset(&blockIndexLookup_[0], INVALID_INDEX, maxBlocks_ * sizeof(blockIndexLookup_[0]));
629
630
if (fwrite(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {
631
CloseFileHandle();
632
return;
633
}
634
if (fflush(f_) != 0) {
635
CloseFileHandle();
636
return;
637
}
638
639
INFO_LOG(Log::Loader, "Created new disk cache file for %s", origPath_.c_str());
640
}
641
642
bool DiskCachingFileLoaderCache::LockCacheFile(bool lockStatus) {
643
if (!f_) {
644
return false;
645
}
646
647
u32 offset = (u32)offsetof(FileHeader, flags);
648
649
bool failed = false;
650
if (File::Fseek(f_, offset, SEEK_SET) != 0) {
651
failed = true;
652
} else if (fread(&flags_, sizeof(u32), 1, f_) != 1) {
653
failed = true;
654
}
655
656
if (failed) {
657
ERROR_LOG(Log::Loader, "Unable to read current flags during disk cache locking");
658
CloseFileHandle();
659
return false;
660
}
661
662
// TODO: Also use flock where supported?
663
if (lockStatus) {
664
if ((flags_ & FLAG_LOCKED) != 0) {
665
ERROR_LOG(Log::Loader, "Could not lock disk cache file for %s (already locked)", origPath_.c_str());
666
return false;
667
}
668
flags_ |= FLAG_LOCKED;
669
} else {
670
if ((flags_ & FLAG_LOCKED) == 0) {
671
ERROR_LOG(Log::Loader, "Could not unlock disk cache file for %s", origPath_.c_str());
672
return false;
673
}
674
flags_ &= ~FLAG_LOCKED;
675
}
676
677
if (File::Fseek(f_, offset, SEEK_SET) != 0) {
678
failed = true;
679
} else if (fwrite(&flags_, sizeof(u32), 1, f_) != 1) {
680
failed = true;
681
} else if (fflush(f_) != 0) {
682
failed = true;
683
}
684
685
if (failed) {
686
ERROR_LOG(Log::Loader, "Unable to write updated flags during disk cache locking");
687
CloseFileHandle();
688
return false;
689
}
690
691
if (lockStatus) {
692
INFO_LOG(Log::Loader, "Locked disk cache file for %s", origPath_.c_str());
693
} else {
694
INFO_LOG(Log::Loader, "Unlocked disk cache file for %s", origPath_.c_str());
695
}
696
return true;
697
}
698
699
bool DiskCachingFileLoaderCache::RemoveCacheFile(const Path &path) {
700
// Note that some platforms, you can't delete open files. So we check.
701
CloseFileHandle();
702
return File::Delete(path);
703
}
704
705
void DiskCachingFileLoaderCache::CloseFileHandle() {
706
if (f_) {
707
fclose(f_);
708
}
709
f_ = nullptr;
710
fd_ = 0;
711
}
712
713
bool DiskCachingFileLoaderCache::HasData() const {
714
if (!f_) {
715
return false;
716
}
717
718
for (size_t i = 0; i < blockIndexLookup_.size(); ++i) {
719
if (blockIndexLookup_[i] != INVALID_INDEX) {
720
return true;
721
}
722
}
723
return false;
724
}
725
726
u64 DiskCachingFileLoaderCache::FreeDiskSpace() {
727
Path dir = cacheDir_;
728
if (dir.empty()) {
729
dir = GetSysDirectory(DIRECTORY_CACHE);
730
}
731
732
int64_t result = 0;
733
if (free_disk_space(dir, result)) {
734
return (u64)result;
735
}
736
737
// We can't know for sure how much is free, so we have to assume none.
738
return 0;
739
}
740
741
u32 DiskCachingFileLoaderCache::DetermineMaxBlocks() {
742
const s64 freeBytes = FreeDiskSpace();
743
// We want to leave them some room for other stuff.
744
const u64 availBytes = std::max(0LL, freeBytes - SAFETY_FREE_DISK_SPACE);
745
const u64 freeBlocks = availBytes / (u64)DEFAULT_BLOCK_SIZE;
746
747
const u32 alreadyCachedCount = CountCachedFiles();
748
// This is how many more files of free space we will aim for.
749
const u32 flex = CACHE_SPACE_FLEX > alreadyCachedCount ? CACHE_SPACE_FLEX - alreadyCachedCount : 1;
750
751
const u64 freeBlocksWithFlex = freeBlocks / flex;
752
if (freeBlocksWithFlex > MAX_BLOCKS_LOWER_BOUND) {
753
if (freeBlocksWithFlex > MAX_BLOCKS_UPPER_BOUND) {
754
return MAX_BLOCKS_UPPER_BOUND;
755
}
756
// This might be smaller than what's free, but if they try to launch a second game,
757
// they'll be happier when it can be cached too.
758
return (u32)freeBlocksWithFlex;
759
}
760
761
// Might be lower than LOWER_BOUND, but that's okay. That means not enough space.
762
// We abandon the idea of flex since there's not enough space free anyway.
763
return (u32)freeBlocks;
764
}
765
766
u32 DiskCachingFileLoaderCache::CountCachedFiles() {
767
Path dir = cacheDir_;
768
if (dir.empty()) {
769
dir = GetSysDirectory(DIRECTORY_CACHE);
770
}
771
772
std::vector<File::FileInfo> files;
773
return (u32)GetFilesInDir(dir, &files, "ppdc:");
774
}
775
776
void DiskCachingFileLoaderCache::GarbageCollectCacheFiles(u64 goalBytes) {
777
// We attempt to free up at least enough files from the cache to get goalBytes more space.
778
const std::vector<Path> usedPaths = DiskCachingFileLoader::GetCachedPathsInUse();
779
std::set<std::string> used;
780
for (const Path &path : usedPaths) {
781
used.insert(MakeCacheFilename(path));
782
}
783
784
Path dir = cacheDir_;
785
if (dir.empty()) {
786
dir = GetSysDirectory(DIRECTORY_CACHE);
787
}
788
789
std::vector<File::FileInfo> files;
790
File::GetFilesInDir(dir, &files, "ppdc:");
791
792
u64 remaining = goalBytes;
793
// TODO: Could order by LRU or etc.
794
for (File::FileInfo &file : files) {
795
if (file.isDirectory) {
796
continue;
797
}
798
if (used.find(file.name) != used.end()) {
799
// In use, must leave alone.
800
continue;
801
}
802
803
#ifdef _WIN32
804
const std::wstring w32path = file.fullName.ToWString();
805
#if PPSSPP_PLATFORM(UWP)
806
bool success = DeleteFileFromAppW(w32path.c_str()) != 0;
807
#else
808
bool success = DeleteFileW(w32path.c_str()) != 0;
809
#endif
810
#else
811
bool success = unlink(file.fullName.c_str()) == 0;
812
#endif
813
814
if (success) {
815
if (file.size > remaining) {
816
// We're done, huzzah.
817
break;
818
}
819
820
// A little bit more.
821
remaining -= file.size;
822
}
823
}
824
825
// At this point, we've done all we can.
826
}
827
828