Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CacheLoader.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 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 "ps/CLogger.h"
23 #include "maths/MD5.h"
24 
25 #include <iomanip>
26 
27 CCacheLoader::CCacheLoader(PIVFS vfs, const std::wstring& fileExtension) :
28  m_VFS(vfs), m_FileExtension(fileExtension)
29 {
30 }
31 
32 Status CCacheLoader::TryLoadingCached(const VfsPath& sourcePath, const MD5& initialHash, u32 version, VfsPath& loadPath)
33 {
34  VfsPath archiveCachePath = ArchiveCachePath(sourcePath);
35 
36  // Try the archive cache file first
37  if (CanUseArchiveCache(sourcePath, archiveCachePath))
38  {
39  loadPath = archiveCachePath;
40  return INFO::OK;
41  }
42 
43  // Fail if no source or archive cache
44  // Note: this is not always an error case, because for instance there
45  // are some uncached .pmd/psa files in the game with no source .dae.
46  // This test fails (correctly) in that valid situation, so it seems
47  // best to leave the error handling to the caller.
48  Status err = m_VFS->GetFileInfo(sourcePath, NULL);
49  if (err < 0)
50  {
51  return err;
52  }
53 
54  // Look for loose cache of source file
55 
56  VfsPath looseCachePath = LooseCachePath(sourcePath, initialHash, version);
57 
58  // If the loose cache file exists, use it
59  if (m_VFS->GetFileInfo(looseCachePath, NULL) >= 0)
60  {
61  loadPath = looseCachePath;
62  return INFO::OK;
63  }
64 
65  // No cache - we'll need to regenerate it
66 
67  loadPath = looseCachePath;
68  return INFO::SKIPPED;
69 }
70 
71 bool CCacheLoader::CanUseArchiveCache(const VfsPath& sourcePath, const VfsPath& archiveCachePath)
72 {
73  // We want to use the archive cache whenever possible,
74  // unless it's superseded by a source file that the user has edited
75 
76  size_t archiveCachePriority = 0;
77  size_t sourcePriority = 0;
78 
79  bool archiveCacheExists = (m_VFS->GetFilePriority(archiveCachePath, &archiveCachePriority) >= 0);
80 
81  // Can't use it if there's no cache
82  if (!archiveCacheExists)
83  return false;
84 
85  bool sourceExists = (m_VFS->GetFilePriority(sourcePath, &sourcePriority) >= 0);
86 
87  // Must use the cache if there's no source
88  if (!sourceExists)
89  return true;
90 
91  // If source file is from a higher-priority mod than archive cache,
92  // don't use the old cache
93  if (archiveCachePriority < sourcePriority)
94  return false;
95 
96  // If source file is more recent than the archive cache (i.e. the user has edited it),
97  // don't use the old cache
98  CFileInfo sourceInfo, archiveCacheInfo;
99  if (m_VFS->GetFileInfo(sourcePath, &sourceInfo) >= 0 &&
100  m_VFS->GetFileInfo(archiveCachePath, &archiveCacheInfo) >= 0)
101  {
102  const double howMuchNewer = difftime(sourceInfo.MTime(), archiveCacheInfo.MTime());
103  const double threshold = 2.0; // FAT timestamp resolution [seconds]
104  if (howMuchNewer > threshold)
105  return false;
106  }
107 
108  // Otherwise we can use the cache
109  return true;
110 }
111 
113 {
114  return sourcePath.ChangeExtension(sourcePath.Extension().string() + L".cached" + m_FileExtension);
115 }
116 
117 VfsPath CCacheLoader::LooseCachePath(const VfsPath& sourcePath, const MD5& initialHash, u32 version)
118 {
119  CFileInfo fileInfo;
120  if (m_VFS->GetFileInfo(sourcePath, &fileInfo) < 0)
121  {
122  debug_warn(L"source file disappeared"); // this should never happen
123  return VfsPath();
124  }
125 
126  u64 mtime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it
127  u64 size = (u64)fileInfo.Size();
128 
129  // Construct a hash of the file data and settings.
130 
131  MD5 hash = initialHash;
132  hash.Update((const u8*)&mtime, sizeof(mtime));
133  hash.Update((const u8*)&size, sizeof(size));
134  hash.Update((const u8*)&version, sizeof(version));
135  // these are local cached files, so we don't care about endianness etc
136 
137  // Use a short prefix of the full hash (we don't need high collision-resistance),
138  // converted to hex
139  u8 digest[MD5::DIGESTSIZE];
140  hash.Final(digest);
141  std::wstringstream digestPrefix;
142  digestPrefix << std::hex;
143  for (size_t i = 0; i < 8; ++i)
144  digestPrefix << std::setfill(L'0') << std::setw(2) << (int)digest[i];
145 
146  // Get the mod path
147  OsPath path;
148  m_VFS->GetRealPath(sourcePath, path);
149 
150  // Construct the final path
151  return VfsPath("cache") / path_name_only(path.BeforeCommon(sourcePath).Parent().string().c_str()) / sourcePath.ChangeExtension(sourcePath.Extension().string() + L"." + digestPrefix.str() + m_FileExtension);
152 }
#define u8
Definition: types.h:39
Path VfsPath
VFS path of the form &quot;(dir/)*file?&quot;.
Definition: vfs_path.h:40
std::wstring m_FileExtension
Definition: CacheLoader.h:71
const Status OK
Definition: status.h:386
static const size_t DIGESTSIZE
Definition: MD5.h:30
VfsPath LooseCachePath(const VfsPath &sourcePath, const MD5 &initialHash, u32 version)
Return the path of the loose cache for the given source file.
shared_ptr< IVFS > PIVFS
Definition: vfs.h:226
Path Parent() const
Definition: path.h:150
Path BeforeCommon(Path other) const
Return the path before the common part of both paths.
Definition: path.h:213
void Update(const u8 *data, size_t len)
Definition: MD5.h:34
MD5 hashing algorithm.
Definition: MD5.h:27
Definition: path.h:75
VfsPath ArchiveCachePath(const VfsPath &sourcePath)
Return the path of the archive cache for the given source file.
const String & string() const
Definition: path.h:123
off_t Size() const
Definition: file_system.h:58
i64 Status
Error handling system.
Definition: status.h:171
CCacheLoader(PIVFS vfs, const std::wstring &fileExtension)
Definition: CacheLoader.cpp:27
#define u64
Definition: types.h:42
const Status SKIPPED
Definition: status.h:392
#define u32
Definition: types.h:41
Path ChangeExtension(Path extension) const
Definition: path.h:185
time_t MTime() const
Definition: file_system.h:63
Path Extension() const
Definition: path.h:176
const wchar_t * path_name_only(const wchar_t *path)
Get the path component of a path.
Definition: path.cpp:85
void Final(u8 *digest)
Definition: MD5.cpp:69
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
bool CanUseArchiveCache(const VfsPath &sourcePath, const VfsPath &archiveCachePath)
Determines whether we can safely use the archived cache file, or need to re-convert the source file...
Definition: CacheLoader.cpp:71
Status TryLoadingCached(const VfsPath &sourcePath, const MD5 &initialHash, u32 version, VfsPath &loadPath)
Attempts to find a valid cached which can be loaded.
Definition: CacheLoader.cpp:32