Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "CacheLoader.h"
21 :
22 : #include "maths/MD5.h"
23 : #include "ps/CLogger.h"
24 : #include "ps/Util.h"
25 :
26 : #include <iomanip>
27 :
28 451 : CCacheLoader::CCacheLoader(PIVFS vfs, const std::wstring& fileExtension) :
29 451 : m_VFS(vfs), m_FileExtension(fileExtension)
30 : {
31 451 : }
32 :
33 448 : Status CCacheLoader::TryLoadingCached(const VfsPath& sourcePath, const MD5& initialHash, u32 version, VfsPath& loadPath)
34 : {
35 896 : VfsPath archiveCachePath = ArchiveCachePath(sourcePath);
36 :
37 : // Try the archive cache file first
38 448 : if (CanUseArchiveCache(sourcePath, archiveCachePath))
39 : {
40 0 : loadPath = archiveCachePath;
41 0 : return INFO::OK;
42 : }
43 :
44 : // Fail if no source or archive cache
45 : // Note: this is not always an error case, because for instance there
46 : // are some uncached .pmd/psa files in the game with no source .dae.
47 : // This test fails (correctly) in that valid situation, so it seems
48 : // best to leave the error handling to the caller.
49 448 : Status err = m_VFS->GetFileInfo(sourcePath, NULL);
50 448 : if (err < 0)
51 : {
52 74 : return err;
53 : }
54 :
55 : // Look for loose cache of source file
56 :
57 748 : VfsPath looseCachePath = LooseCachePath(sourcePath, initialHash, version);
58 :
59 : // If the loose cache file exists, use it
60 374 : if (m_VFS->GetFileInfo(looseCachePath, NULL) >= 0)
61 : {
62 316 : loadPath = looseCachePath;
63 316 : return INFO::OK;
64 : }
65 :
66 : // No cache - we'll need to regenerate it
67 :
68 58 : loadPath = looseCachePath;
69 58 : return INFO::SKIPPED;
70 : }
71 :
72 448 : bool CCacheLoader::CanUseArchiveCache(const VfsPath& sourcePath, const VfsPath& archiveCachePath)
73 : {
74 : // We want to use the archive cache whenever possible,
75 : // unless it's superseded by a source file that the user has edited
76 :
77 448 : size_t archiveCachePriority = 0;
78 448 : size_t sourcePriority = 0;
79 :
80 448 : bool archiveCacheExists = (m_VFS->GetFilePriority(archiveCachePath, &archiveCachePriority) >= 0);
81 :
82 : // Can't use it if there's no cache
83 448 : if (!archiveCacheExists)
84 448 : return false;
85 :
86 0 : bool sourceExists = (m_VFS->GetFilePriority(sourcePath, &sourcePriority) >= 0);
87 :
88 : // Must use the cache if there's no source
89 0 : if (!sourceExists)
90 0 : return true;
91 :
92 : // If source file is from a higher-priority mod than archive cache,
93 : // don't use the old cache
94 0 : if (archiveCachePriority < sourcePriority)
95 0 : return false;
96 :
97 : // If source file is more recent than the archive cache (i.e. the user has edited it),
98 : // don't use the old cache
99 0 : CFileInfo sourceInfo, archiveCacheInfo;
100 0 : if (m_VFS->GetFileInfo(sourcePath, &sourceInfo) >= 0 &&
101 0 : m_VFS->GetFileInfo(archiveCachePath, &archiveCacheInfo) >= 0)
102 : {
103 0 : const double howMuchNewer = difftime(sourceInfo.MTime(), archiveCacheInfo.MTime());
104 0 : const double threshold = 2.0; // FAT timestamp resolution [seconds]
105 0 : if (howMuchNewer > threshold)
106 0 : return false;
107 : }
108 :
109 : // Otherwise we can use the cache
110 0 : return true;
111 : }
112 :
113 448 : VfsPath CCacheLoader::ArchiveCachePath(const VfsPath& sourcePath) const
114 : {
115 448 : return sourcePath.ChangeExtension(sourcePath.Extension().string() + L".cached" + m_FileExtension);
116 : }
117 :
118 379 : VfsPath CCacheLoader::LooseCachePath(const VfsPath& sourcePath, const MD5& initialHash, u32 version)
119 : {
120 758 : CFileInfo fileInfo;
121 379 : if (m_VFS->GetFileInfo(sourcePath, &fileInfo) < 0)
122 : {
123 0 : debug_warn(L"source file disappeared"); // this should never happen
124 0 : return VfsPath();
125 : }
126 :
127 379 : u64 mtime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it
128 379 : u64 size = (u64)fileInfo.Size();
129 :
130 : // Construct a hash of the file data and settings.
131 :
132 379 : MD5 hash = initialHash;
133 379 : hash.Update((const u8*)&mtime, sizeof(mtime));
134 379 : hash.Update((const u8*)&size, sizeof(size));
135 379 : hash.Update((const u8*)&version, sizeof(version));
136 : // these are local cached files, so we don't care about endianness etc
137 :
138 : u8 digest[MD5::DIGESTSIZE];
139 379 : hash.Final(digest);
140 :
141 : // Get the mod path
142 758 : OsPath path;
143 379 : m_VFS->GetOriginalPath(sourcePath, path);
144 :
145 758 : return VfsPath("cache") /
146 758 : path_name_only(path.BeforeCommon(sourcePath).Parent().string().c_str()) /
147 1516 : sourcePath.ChangeExtension(sourcePath.Extension().string() +
148 758 : L"." +
149 : // Use a short prefix of the full hash (we don't need high collision-resistance)
150 1516 : wstring_from_utf8(Hexify(digest, 8)) +
151 379 : m_FileExtension);
152 3 : }
|