CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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