LCOV - code coverage report
Current view: top level - source/lib/file/vfs - vfs_tree.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 84 111 75.7 %
Date: 2023-01-19 00:18:29 Functions: 18 20 90.0 %

          Line data    Source code
       1             : /* Copyright (C) 2020 Wildfire Games.
       2             :  *
       3             :  * Permission is hereby granted, free of charge, to any person obtaining
       4             :  * a copy of this software and associated documentation files (the
       5             :  * "Software"), to deal in the Software without restriction, including
       6             :  * without limitation the rights to use, copy, modify, merge, publish,
       7             :  * distribute, sublicense, and/or sell copies of the Software, and to
       8             :  * permit persons to whom the Software is furnished to do so, subject to
       9             :  * the following conditions:
      10             :  *
      11             :  * The above copyright notice and this permission notice shall be included
      12             :  * in all copies or substantial portions of the Software.
      13             :  *
      14             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
      15             :  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
      16             :  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
      17             :  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
      18             :  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
      19             :  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
      20             :  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      21             :  */
      22             : 
      23             : /*
      24             :  * 'tree' of VFS directories and files
      25             :  */
      26             : 
      27             : #include "precompiled.h"
      28             : #include "lib/file/vfs/vfs_tree.h"
      29             : 
      30             : #include <cstdio>
      31             : #include <ctime>
      32             : 
      33             : #include "lib/file/common/file_stats.h"
      34             : #include "lib/sysdep/cpu.h"
      35             : 
      36             : 
      37             : //-----------------------------------------------------------------------------
      38             : 
      39        2451 : VfsFile::VfsFile(const VfsPath& name, size_t size, time_t mtime, size_t priority, const PIFileLoader& loader)
      40        2451 :     : m_name(name), m_size(size), m_mtime(mtime), m_priority(priority), m_loader(loader)
      41             : {
      42        2451 : }
      43             : 
      44             : 
      45             : 
      46             : //-----------------------------------------------------------------------------
      47             : 
      48         813 : VfsDirectory::VfsDirectory()
      49         813 :     : m_shouldPopulate(0)
      50             : {
      51         813 : }
      52             : 
      53          12 : static bool ShouldDelete(const VfsFile& file, const VfsFile& deletedFile)
      54             : {
      55             :     // We only check priority here, a .DELETED file in a mod should not
      56             :     // delete files in that mod. For the same reason we ignore loose
      57             :     // .DELETED files next to an archive.
      58          12 :     return file.Priority() < deletedFile.Priority();
      59             : }
      60             : 
      61          12 : static bool ShouldReplaceWith(const VfsFile& previousFile, const VfsFile& newFile)
      62             : {
      63             :     // 1) priority (override mods)
      64          12 :     if(newFile.Priority() < previousFile.Priority())
      65           1 :         return false;
      66          11 :     if(newFile.Priority() > previousFile.Priority())
      67           6 :         return true;
      68             : 
      69             :     // 2) timestamp
      70             :     {
      71           5 :         const double howMuchNewer = difftime(newFile.MTime(), previousFile.MTime());
      72           5 :         const double threshold = 2.0;   // FAT timestamp resolution [seconds]
      73           5 :         if(howMuchNewer > threshold) // newer
      74           1 :             return true;
      75           4 :         if(howMuchNewer < -threshold)    // older
      76           1 :             return false;
      77             :         // else: "equal" (tolerating small differences due to FAT's low
      78             :         // mtime resolution)
      79             :     }
      80             : 
      81             :     // 3) precedence (efficiency of file provider)
      82           3 :     if(newFile.Loader()->Precedence() < previousFile.Loader()->Precedence())
      83           0 :         return false;
      84             : 
      85           3 :     return true;
      86             : }
      87             : 
      88             : 
      89           9 : void VfsDirectory::DeleteSubtree(const VfsFile& file)
      90             : {
      91           9 :     ENSURE(file.Name().Extension() == L".DELETED");
      92             : 
      93          18 :     const VfsPath basename = file.Name().Basename();
      94           9 :     std::map<VfsPath, VfsFile>::iterator fit = m_files.find(basename);
      95           9 :     if(fit != m_files.end() && ShouldDelete(fit->second, file))
      96           2 :         m_files.erase(basename);
      97             : 
      98           9 :     std::map<VfsPath, VfsDirectory>::iterator dit = m_subdirectories.find(basename);
      99           9 :     if(dit != m_subdirectories.end() && dit->second.DeleteTree(file))
     100           5 :         m_subdirectories.erase(dit);
     101           9 : }
     102             : 
     103           6 : bool VfsDirectory::DeleteTree(const VfsFile& file)
     104             : {
     105          15 :     for(std::map<VfsPath, VfsFile>::iterator it = m_files.begin(); it != m_files.end();)
     106           9 :         if(ShouldDelete(it->second, file))
     107           8 :             it = m_files.erase(it);
     108             :         else
     109           1 :             ++it;
     110             : 
     111           6 :     for(std::map<VfsPath, VfsDirectory>::iterator it = m_subdirectories.begin(); it != m_subdirectories.end();)
     112           0 :         if(it->second.DeleteTree(file))
     113           0 :             it = m_subdirectories.erase(it);
     114             :         else
     115           0 :             ++it;
     116             : 
     117           6 :     return m_files.empty() && m_subdirectories.empty();
     118             : }
     119             : 
     120             : 
     121        2444 : VfsFile* VfsDirectory::AddFile(const VfsFile& file)
     122             : {
     123        4888 :     std::pair<VfsPath, VfsFile> value = std::make_pair(file.Name(), file);
     124        2444 :     std::pair<VfsFiles::iterator, bool> ret = m_files.insert(value);
     125        2444 :     if(!ret.second) // already existed
     126             :     {
     127          12 :         VfsFile& previousFile = ret.first->second;
     128          12 :         const VfsFile& newFile = value.second;
     129          12 :         if(ShouldReplaceWith(previousFile, newFile))
     130          10 :             previousFile = newFile;
     131             :     }
     132             :     else
     133             :     {
     134             :         stats_vfs_file_add(file.Size());
     135             :     }
     136             : 
     137        4888 :     return &(*ret.first).second;
     138             : }
     139             : 
     140             : 
     141             : // rationale: passing in a pre-constructed VfsDirectory and copying that into
     142             : // our map would be slower and less convenient for the caller.
     143         728 : VfsDirectory* VfsDirectory::AddSubdirectory(const VfsPath& name)
     144             : {
     145        1456 :     std::pair<VfsPath, VfsDirectory> value = std::make_pair(name.string(), VfsDirectory());
     146         728 :     std::pair<VfsSubdirectories::iterator, bool> ret = m_subdirectories.insert(value);
     147        1456 :     return &(*ret.first).second;
     148             : }
     149             : 
     150             : 
     151           3 : void VfsDirectory::RemoveFile(const VfsPath& name)
     152             : {
     153           3 :     m_files.erase(name.string());
     154           3 : }
     155             : 
     156             : 
     157        6900 : VfsFile* VfsDirectory::GetFile(const VfsPath& name)
     158             : {
     159        6900 :     VfsFiles::iterator it = m_files.find(name.string());
     160        6900 :     if(it == m_files.end())
     161         790 :         return 0;
     162        6110 :     return &it->second;
     163             : }
     164             : 
     165       16411 : VfsDirectory* VfsDirectory::GetSubdirectory(const VfsPath& name)
     166             : {
     167       16411 :     VfsSubdirectories::iterator it = m_subdirectories.find(name.string());
     168       16411 :     if(it == m_subdirectories.end())
     169         671 :         return 0;
     170       15740 :     return &it->second;
     171             : }
     172             : 
     173             : 
     174         878 : void VfsDirectory::SetAssociatedDirectory(const PRealDirectory& realDirectory)
     175             : {
     176         878 :     if(!cpu_CAS(&m_shouldPopulate, 0, 1))
     177           0 :         DEBUG_WARN_ERR(ERR::LOGIC); // caller didn't check ShouldPopulate
     178         878 :     m_realDirectory = realDirectory;
     179         878 : }
     180             : 
     181             : 
     182       24472 : bool VfsDirectory::ShouldPopulate()
     183             : {
     184       24472 :     return cpu_CAS(&m_shouldPopulate, 1, 0);    // test and reset
     185             : }
     186             : 
     187             : 
     188           3 : void VfsDirectory::RequestRepopulate()
     189             : {
     190           3 :     m_shouldPopulate = 1;
     191           3 : }
     192             : 
     193             : 
     194           3 : void VfsDirectory::Clear()
     195             : {
     196           3 :     m_files.clear();
     197           3 :     m_subdirectories.clear();
     198           3 :     m_realDirectory.reset();
     199           3 :     m_shouldPopulate = 0;
     200           3 : }
     201             : 
     202             : 
     203             : //-----------------------------------------------------------------------------
     204             : 
     205           0 : std::wstring FileDescription(const VfsFile& file)
     206             : {
     207             :     wchar_t timestamp[25];
     208           0 :     const time_t mtime = file.MTime();
     209           0 :     wcsftime(timestamp, ARRAY_SIZE(timestamp), L"%a %b %d %H:%M:%S %Y", localtime(&mtime));
     210             : 
     211             :     wchar_t buf[200];
     212           0 :     swprintf_s(buf, ARRAY_SIZE(buf), L"(%c; %6lu; %ls) %ls", file.Loader()->LocationCode(), (unsigned long)file.Size(), timestamp, file.Name().string().c_str());
     213           0 :     return buf;
     214             : }
     215             : 
     216             : 
     217           0 : std::wstring FileDescriptions(const VfsDirectory& directory, size_t indentLevel)
     218             : {
     219           0 :     VfsDirectory::VfsFiles files = directory.Files();
     220             : 
     221           0 :     std::wstring descriptions;
     222           0 :     descriptions.reserve(100*files.size());
     223             : 
     224           0 :     const std::wstring indentation(4*indentLevel, ' ');
     225           0 :     for(VfsDirectory::VfsFiles::const_iterator it = files.begin(); it != files.end(); ++it)
     226             :     {
     227           0 :         const VfsFile& file = it->second;
     228           0 :         descriptions += indentation;
     229           0 :         descriptions += FileDescription(file);
     230           0 :         descriptions += L"\n";
     231             :     }
     232             : 
     233           0 :     return descriptions;
     234             : }
     235             : 
     236             : 
     237           1 : void DirectoryDescriptionR(std::wstring& descriptions, const VfsDirectory& directory, size_t indentLevel)
     238             : {
     239           2 :     const std::wstring indentation(4*indentLevel, ' ');
     240             : 
     241           1 :     const VfsDirectory::VfsSubdirectories& subdirectories = directory.Subdirectories();
     242           1 :     for(VfsDirectory::VfsSubdirectories::const_iterator it = subdirectories.begin(); it != subdirectories.end(); ++it)
     243             :     {
     244           0 :         const VfsPath& name = it->first;
     245           0 :         const VfsDirectory& subdirectory = it->second;
     246           0 :         descriptions += indentation;
     247           0 :         descriptions += std::wstring(L"[") + name.string() + L"]\n";
     248           0 :         descriptions += FileDescriptions(subdirectory, indentLevel+1);
     249             : 
     250           0 :         DirectoryDescriptionR(descriptions, subdirectory, indentLevel+1);
     251             :     }
     252           4 : }

Generated by: LCOV version 1.13