Line data Source code
1 : /* Copyright (C) 2021 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 : * populate VFS directories with files
25 : */
26 :
27 : #include "precompiled.h"
28 : #include "lib/file/vfs/vfs_populate.h"
29 :
30 : #include "lib/file/archive/archive_zip.h"
31 : #include "lib/file/vfs/vfs_tree.h"
32 : #include "lib/file/vfs/vfs_lookup.h"
33 : #include "lib/file/vfs/vfs.h" // error codes
34 :
35 : struct CompareFileInfoByName
36 : {
37 : bool operator()(const CFileInfo& a, const CFileInfo& b)
38 : {
39 : return a.Name() < b.Name();
40 : }
41 : };
42 :
43 : // helper class that allows breaking up the logic into sub-functions without
44 : // always having to pass directory/realDirectory as parameters.
45 528 : class PopulateHelper
46 : {
47 : NONCOPYABLE(PopulateHelper);
48 : public:
49 528 : PopulateHelper(VfsDirectory* directory, const PRealDirectory& realDirectory)
50 528 : : m_directory(directory), m_realDirectory(realDirectory)
51 : {
52 528 : }
53 :
54 528 : Status AddEntries() const
55 : {
56 1056 : CFileInfos files; files.reserve(500);
57 1056 : DirectoryNames subdirectoryNames; subdirectoryNames.reserve(50);
58 528 : RETURN_STATUS_IF_ERR(GetDirectoryEntries(m_realDirectory->Path(), &files, &subdirectoryNames));
59 :
60 : // Since .DELETED files only remove files in lower priority mods
61 : // loose files and archive files have no conflicts so we do not need
62 : // to sort them.
63 : // We add directories after they might have been removed by .DELETED
64 : // files (as they did not contain any files at that point). The order
65 : // of GetDirectoryEntries is undefined, but that does not really matter (TODO really?)
66 : // so we do not need to sort its output.
67 528 : RETURN_STATUS_IF_ERR(AddFiles(files));
68 528 : AddSubdirectories(subdirectoryNames);
69 :
70 528 : return INFO::OK;
71 : }
72 :
73 : private:
74 2315 : void AddFile(const CFileInfo& fileInfo) const
75 : {
76 4630 : const VfsPath name = fileInfo.Name();
77 4630 : const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), m_realDirectory->Priority(), m_realDirectory);
78 2315 : if(name.Extension() == L".DELETED")
79 : {
80 0 : m_directory->DeleteSubtree(file);
81 0 : if(!(m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED))
82 0 : return;
83 : }
84 :
85 2315 : m_directory->AddFile(file);
86 : }
87 :
88 0 : static void AddArchiveFile(const VfsPath& pathname, const CFileInfo& fileInfo, PIArchiveFile archiveFile, uintptr_t cbData)
89 : {
90 0 : PopulateHelper* this_ = (PopulateHelper*)cbData;
91 :
92 : // (we have to create missing subdirectoryNames because archivers
93 : // don't always place directory entries before their files)
94 0 : const size_t flags = VFS_LOOKUP_ADD|VFS_LOOKUP_SKIP_POPULATE;
95 : VfsDirectory* directory;
96 0 : WARN_IF_ERR(vfs_Lookup(pathname, this_->m_directory, directory, 0, flags));
97 :
98 0 : const VfsPath name = fileInfo.Name();
99 0 : const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), this_->m_realDirectory->Priority(), archiveFile);
100 0 : if(name.Extension() == L".DELETED")
101 : {
102 0 : directory->DeleteSubtree(file);
103 0 : if(!(this_->m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED))
104 0 : return;
105 : }
106 :
107 0 : directory->AddFile(file);
108 : }
109 :
110 528 : Status AddFiles(const CFileInfos& files) const
111 : {
112 1056 : const OsPath path(m_realDirectory->Path());
113 :
114 2843 : for(size_t i = 0; i < files.size(); i++)
115 : {
116 4630 : const OsPath pathname = path / files[i].Name();
117 2315 : if(pathname.Extension() == L".zip")
118 : {
119 0 : PIArchiveReader archiveReader = CreateArchiveReader_Zip(pathname);
120 : // archiveReader == nullptr if file could not be opened (e.g. because
121 : // archive is currently open in another program)
122 0 : if(archiveReader)
123 0 : RETURN_STATUS_IF_ERR(archiveReader->ReadEntries(AddArchiveFile, (uintptr_t)this));
124 : }
125 : else // regular (non-archive) file
126 2315 : AddFile(files[i]);
127 : }
128 :
129 528 : return INFO::OK;
130 : }
131 :
132 528 : void AddSubdirectories(const DirectoryNames& subdirectoryNames) const
133 : {
134 1096 : for(size_t i = 0; i < subdirectoryNames.size(); i++)
135 : {
136 : // skip version control directories - this avoids cluttering the
137 : // VFS with hundreds of irrelevant files.
138 568 : if(subdirectoryNames[i] == L".svn" || subdirectoryNames[i] == L".git")
139 0 : continue;
140 :
141 568 : VfsDirectory* subdirectory = m_directory->AddSubdirectory(subdirectoryNames[i]);
142 1136 : PRealDirectory realDirectory = CreateRealSubdirectory(m_realDirectory, subdirectoryNames[i]);
143 568 : vfs_Attach(subdirectory, realDirectory);
144 : }
145 528 : }
146 :
147 : VfsDirectory* const m_directory;
148 : PRealDirectory m_realDirectory;
149 : };
150 :
151 :
152 24450 : Status vfs_Populate(VfsDirectory* directory)
153 : {
154 24450 : if(!directory->ShouldPopulate())
155 23922 : return INFO::OK;
156 :
157 528 : const PRealDirectory& realDirectory = directory->AssociatedDirectory();
158 :
159 528 : if(realDirectory->Flags() & VFS_MOUNT_WATCH)
160 0 : realDirectory->Watch();
161 :
162 1056 : PopulateHelper helper(directory, realDirectory);
163 528 : RETURN_STATUS_IF_ERR(helper.AddEntries());
164 :
165 528 : return INFO::OK;
166 : }
167 :
168 :
169 859 : Status vfs_Attach(VfsDirectory* directory, const PRealDirectory& realDirectory)
170 : {
171 1718 : PRealDirectory existingRealDir = directory->AssociatedDirectory();
172 :
173 : // Don't allow replacing the real directory by a lower-priority one.
174 859 : if (!existingRealDir || existingRealDir->Priority() < realDirectory->Priority())
175 : {
176 : // This ordering is peculiar but useful, as it "defers" the population call.
177 : // If there is already a real directory, we will replace it (and lose track of it),
178 : // so we'll populate it right away, but the 'new' real directory can wait until we access it.
179 840 : RETURN_STATUS_IF_ERR(vfs_Populate(directory));
180 840 : directory->SetAssociatedDirectory(realDirectory);
181 840 : return INFO::OK;
182 : }
183 : // We are attaching a lower-priority real directory.
184 : // Because of deferred population, we need to immediately populate this new directory.
185 19 : bool shouldPop = directory->ShouldPopulate();
186 : // This sets "should populate" to true, so the vfs_Populate call below immediately populates.
187 19 : directory->SetAssociatedDirectory(realDirectory);
188 19 : RETURN_STATUS_IF_ERR(vfs_Populate(directory));
189 : // Reset to the higher priority realDirectory, which resets ShouldPopulate to true.
190 19 : directory->SetAssociatedDirectory(existingRealDir);
191 : // Avoid un-necessary repopulation by clearing the flag.
192 19 : if (!shouldPop)
193 3 : directory->ShouldPopulate();
194 :
195 19 : return INFO::OK;
196 3 : }
|