LCOV - code coverage report
Current view: top level - source/ps - CacheLoader.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 38 51 74.5 %
Date: 2022-06-14 00:41:00 Functions: 5 5 100.0 %

          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         390 : CCacheLoader::CCacheLoader(PIVFS vfs, const std::wstring& fileExtension) :
      29         390 :     m_VFS(vfs), m_FileExtension(fileExtension)
      30             : {
      31         390 : }
      32             : 
      33         388 : Status CCacheLoader::TryLoadingCached(const VfsPath& sourcePath, const MD5& initialHash, u32 version, VfsPath& loadPath)
      34             : {
      35        1164 :     VfsPath archiveCachePath = ArchiveCachePath(sourcePath);
      36             : 
      37             :     // Try the archive cache file first
      38         388 :     if (CanUseArchiveCache(sourcePath, archiveCachePath))
      39             :     {
      40           0 :         loadPath = archiveCachePath;
      41             :         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         776 :     Status err = m_VFS->GetFileInfo(sourcePath, NULL);
      50         388 :     if (err < 0)
      51             :     {
      52             :         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         748 :     if (m_VFS->GetFileInfo(looseCachePath, NULL) >= 0)
      61             :     {
      62         316 :         loadPath = looseCachePath;
      63             :         return INFO::OK;
      64             :     }
      65             : 
      66             :     // No cache - we'll need to regenerate it
      67             : 
      68          58 :     loadPath = looseCachePath;
      69             :     return INFO::SKIPPED;
      70             : }
      71             : 
      72         388 : 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         388 :     size_t archiveCachePriority = 0;
      78         388 :     size_t sourcePriority = 0;
      79             : 
      80         776 :     bool archiveCacheExists = (m_VFS->GetFilePriority(archiveCachePath, &archiveCachePriority) >= 0);
      81             : 
      82             :     // Can't use it if there's no cache
      83         388 :     if (!archiveCacheExists)
      84             :         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             :         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             :         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             :     return true;
     111             : }
     112             : 
     113         388 : VfsPath CCacheLoader::ArchiveCachePath(const VfsPath& sourcePath) const
     114             : {
     115        1940 :     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        1137 :     CFileInfo fileInfo;
     121         758 :     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         379 :     u8 digest[MD5::DIGESTSIZE];
     139         379 :     hash.Final(digest);
     140             : 
     141             :     // Get the mod path
     142         379 :     OsPath path;
     143         758 :     m_VFS->GetOriginalPath(sourcePath, path);
     144             : 
     145        1516 :     return VfsPath("cache") /
     146        1516 :         path_name_only(path.BeforeCommon(sourcePath).Parent().string().c_str()) /
     147        1895 :             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        3032 :             wstring_from_utf8(Hexify(digest, 8)) +
     151         758 :             m_FileExtension);
     152             : }

Generated by: LCOV version 1.13