Line data Source code
1 : /*
2 : Zipios -- a small C++ library that provides easy access to .zip files.
3 :
4 : Copyright (C) 2000-2007 Thomas Sondergaard
5 : Copyright (C) 2015-2019 Made to Order Software Corporation
6 :
7 : This library is free software; you can redistribute it and/or
8 : modify it under the terms of the GNU Lesser General Public
9 : License as published by the Free Software Foundation; either
10 : version 2.1 of the License, or (at your option) any later version.
11 :
12 : This library is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : Lesser General Public License for more details.
16 :
17 : You should have received a copy of the GNU Lesser General Public
18 : License along with this library; if not, write to the Free Software
19 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 : */
21 :
22 : /** \file
23 : * \brief Implementation of zipios::DirectoryCollection.
24 : *
25 : * This file includes the implementation of the zipios::DirectoryCollection
26 : * class, which is used to read a directory from disk and create
27 : * a set of zipios::DirectoryEntry objects.
28 : */
29 :
30 : #if !defined(ZIPIOS_WINDOWS) && (defined(_WINDOWS) || defined(WIN32) || defined(_WIN32) || defined(__WIN32))
31 : #define ZIPIOS_WINDOWS
32 : #endif
33 :
34 : #include "zipios/directorycollection.hpp"
35 :
36 : #include "zipios/zipiosexceptions.hpp"
37 :
38 : #include <fstream>
39 :
40 : #ifdef ZIPIOS_WINDOWS
41 : #include <io.h>
42 : #else
43 : #include <dirent.h>
44 : #include <errno.h>
45 : #endif
46 :
47 :
48 : namespace zipios
49 : {
50 :
51 : /** \class DirectoryCollection
52 : * \brief A collection generated from reading a directory.
53 : *
54 : * The DirectoryCollection class is a FileCollection that obtains
55 : * its entries from a directory on disk.
56 : */
57 :
58 :
59 : /** \brief Initialize a DirectoryCollection object.
60 : *
61 : * The default constructor initializes an empty directory collection.
62 : * Note that an empty collection is invalid by default so there is
63 : * probably not much you will be able to do with such an object.
64 : */
65 16 : DirectoryCollection::DirectoryCollection()
66 : //: m_entries_loaded(false) -- auto-init
67 : //, m_recursive(true) -- auto-init
68 : //, m_filepath("") -- auto-init
69 : {
70 16 : }
71 :
72 :
73 : /** \brief Initialize a DirectoryCollection object.
74 : *
75 : * This function initializes a directory which represents a collection
76 : * of files from disk.
77 : *
78 : * By default recursive is true meaning that the specified directory
79 : * and all of its children are read in the collection.
80 : *
81 : * \warning
82 : * The specified path must be a valid directory path and name. If the
83 : * name represents a file, then the DirectoryCollection is marked as
84 : * invalid.
85 : *
86 : * \note
87 : * The file content is not loaded so the collection is fairly lightweight.
88 : *
89 : * \param[in] path A directory path. If the name is not a valid
90 : * directory the created DirectoryCollection is
91 : * marked as being invalid.
92 : * \param[in] recursive Whether to load all the files found in
93 : * sub-direcotries.
94 : */
95 176 : DirectoryCollection::DirectoryCollection(std::string const & path, bool recursive)
96 : //: m_entries_loaded(false) -- auto-init
97 : : m_recursive(recursive)
98 176 : , m_filepath(path)
99 : {
100 176 : m_filename = m_filepath;
101 176 : m_valid = m_filepath.isDirectory() | m_filepath.isRegular();
102 176 : }
103 :
104 :
105 : /** \brief Clean up a DirectoryCollection object.
106 : *
107 : * The destructor ensures that the object is properly cleaned up.
108 : */
109 619 : DirectoryCollection::~DirectoryCollection()
110 : {
111 270 : close();
112 349 : }
113 :
114 :
115 : /** \brief Close the directory collection.
116 : *
117 : * This function marks the collection as invalid in effect rendering
118 : * the collection unusable.
119 : */
120 336 : void DirectoryCollection::close()
121 : {
122 336 : m_entries_loaded = false;
123 336 : m_filepath = "";
124 :
125 336 : FileCollection::close();
126 336 : }
127 :
128 :
129 : /** \brief Retrieve a vector to the collection entries.
130 : *
131 : * This function makes sure that the directory collection is valid, if not
132 : * an exception is raised. If valid, then the function makes sure that
133 : * the entries were loaded and then it returns a copy of the vector
134 : * holding the entries.
135 : *
136 : * \note
137 : * The copy of the vector is required because of the implementation
138 : * of CollectionCollection which does not hold a vector of all the
139 : * entries defined in its children. It is also cleaner (albeit slower)
140 : * in case one wants to use the library in a thread environment.
141 : *
142 : * \return A copy of the internal FileEntry vector.
143 : */
144 184195 : FileEntry::vector_t DirectoryCollection::entries() const
145 : {
146 184195 : loadEntries();
147 :
148 184158 : return FileCollection::entries();
149 : }
150 :
151 :
152 : /** \brief Get an entry from the collection.
153 : *
154 : * This function returns a shared pointer to a FileEntry object for
155 : * the entry with the specified name. To ignore the path part of the
156 : * filename while searching for a match, specify FileCollection::IGNORE
157 : * as the second argument.
158 : *
159 : * \note
160 : * The collection must be valid or the function raises an exception.
161 : *
162 : * \param[in] name A string containing the name of the entry to get.
163 : * \param[in] matchpath Speficy MatchPath::MATCH, if the path should match
164 : * as well, specify MatchPath::IGNORE, if the path
165 : * should be ignored.
166 : *
167 : * \return A shared pointer to the found entry. The returned pointer
168 : * is null if no entry is found.
169 : *
170 : * \sa mustBeValid()
171 : */
172 183295 : FileEntry::pointer_t DirectoryCollection::getEntry(std::string const & name, MatchPath matchpath) const
173 : {
174 183295 : loadEntries();
175 :
176 183223 : return FileCollection::getEntry(name, matchpath);
177 : }
178 :
179 :
180 : /** \brief Retrieve pointer to an istream.
181 : *
182 : * This function returns a shared pointer to an istream defined from
183 : * the named entry, which is expected to be available in this collection.
184 : *
185 : * The function returns a null pointer if no FileEntry can be found from
186 : * the specified name or the FileEntry is marked as invalid.
187 : *
188 : * The returned istream represents a file on disk, although the filename
189 : * must exist in the collection or it will be ignored. A filename that
190 : * represents a directory cannot return an input stream and thus an error
191 : * is returned in that case.
192 : *
193 : * \note
194 : * The stream is always opened in binary mode.
195 : *
196 : * \param[in] entry_name The name of the file to search in the collection.
197 : * \param[in] matchpath Whether the full path or just the filename is matched.
198 : *
199 : * \return A shared pointer to an open istream for the specified entry.
200 : *
201 : * \sa CollectionCollection
202 : * \sa FileCollection
203 : * \sa ZipFile
204 : */
205 169010 : DirectoryCollection::stream_pointer_t DirectoryCollection::getInputStream(std::string const & entry_name, MatchPath matchpath)
206 : {
207 337984 : FileEntry::pointer_t ent(getEntry(entry_name, matchpath));
208 168974 : if(ent == nullptr || ent->isDirectory())
209 : {
210 1898 : return DirectoryCollection::stream_pointer_t();
211 : }
212 :
213 334152 : DirectoryCollection::stream_pointer_t p(new std::ifstream(ent->getName(), std::ios::in | std::ios::binary));
214 167076 : return p;
215 : }
216 :
217 :
218 : /** \brief Create another DirectoryCollection.
219 : *
220 : * This function creates a clone of this DirectoryCollection. This is
221 : * a simple new DirectoryCollection of this collection.
222 : *
223 : * \return The function returns a shared pointer of the new collection.
224 : */
225 64 : FileCollection::pointer_t DirectoryCollection::clone() const
226 : {
227 64 : return FileCollection::pointer_t(new DirectoryCollection(*this));
228 : }
229 :
230 :
231 : /** \brief This is an internal function that loads the file entries.
232 : *
233 : * This function is the top level which starts the process of loading
234 : * all the files found in the specified directory and sub-directories
235 : * if the DirectoryCollection was created with the recursive flag
236 : * set to true (the default.)
237 : */
238 367490 : void DirectoryCollection::loadEntries() const
239 : {
240 : // WARNING: this has to stay here because the collection could get close()'s...
241 367490 : mustBeValid();
242 :
243 367382 : if(!m_entries_loaded)
244 : {
245 193 : m_entries_loaded = true;
246 :
247 : // if the read fails then the directory may have been deleted
248 : // in which case we want to invalidate this DirectoryCollection
249 : // object
250 : try
251 : {
252 : // include the root directory
253 386 : FileEntry::pointer_t entry(new DirectoryEntry(m_filepath, ""));
254 193 : const_cast<DirectoryCollection *>(this)->m_entries.push_back(entry);
255 :
256 : // now read the data inside that directory
257 193 : if(m_filepath.isDirectory())
258 : {
259 53 : const_cast<DirectoryCollection *>(this)->load(FilePath());
260 : }
261 : }
262 2 : catch(...)
263 : {
264 1 : const_cast<DirectoryCollection *>(this)->close();
265 1 : throw;
266 : }
267 : }
268 367381 : }
269 :
270 :
271 : /** \brief This is the function loading all the file entries.
272 : *
273 : * This function loads all the file entries found in the specified
274 : * directory. If the DirectoryCollection was created with the
275 : * recursive flag, then this function will load sub-directories
276 : * infinitum.
277 : *
278 : * \param[in] subdir The directory to read.
279 : */
280 953 : void DirectoryCollection::load(FilePath const& subdir)
281 : {
282 : #ifdef ZIPIOS_WINDOWS
283 : struct read_dir_t
284 : {
285 : read_dir_t(FilePath const& path)
286 : //: m_handle(0) -- auto-init
287 : //, m_fileinfo() -- initialized below
288 : //, m_read_first(false) -- auto-init
289 : {
290 : /** \todo
291 : * Make necessary changes to support 64 bit and Unicode
292 : * (require utf8 -> wchar_t, then use _wfindfirsti64().)
293 : * We'll have to update the next() function too, of course.
294 : */
295 : m_handle = _findfirsti64(path.getName().c_str(), &m_findinfo);
296 : if(m_handle == 0)
297 : {
298 : if(errno == ENOENT)
299 : {
300 : // this can happen, the directory is empty and thus has
301 : // absolutely no information
302 : f_read_first = true;
303 : }
304 : else
305 : {
306 : throw IOException("an I/O error occurred while reading a directory");
307 : }
308 : }
309 : }
310 :
311 : ~read_dir_t()
312 : {
313 : // a completely empty directory may give us a "null pointer"
314 : // when calling _[w]findfirst[i64]()
315 : if(m_handle != 0)
316 : {
317 : _findclose(m_handle);
318 : }
319 : }
320 :
321 : std::string next()
322 : {
323 : if(m_read_first)
324 : {
325 : __int64 const r(_findnexti64(m_handle, &m_fileinfo));
326 : if(r != 0)
327 : {
328 : if(errno != ENOENT)
329 : {
330 : throw IOException("an I/O error occurred while reading a directory");
331 : }
332 : return std::string();
333 : }
334 : }
335 : else
336 : {
337 : // the _findfirst() includes a response, use it!
338 : m_read_first = true;
339 : }
340 :
341 : return m_fileinfo.name;
342 : }
343 :
344 : private:
345 : long m_handle = 0;
346 : struct _finddata_t m_fileinfo;
347 : bool m_read_first = 0;
348 : };
349 : #else
350 : struct read_dir_t
351 : {
352 953 : read_dir_t(FilePath const& path)
353 953 : : m_dir(opendir(static_cast<std::string>(path).c_str()))
354 : {
355 953 : if(m_dir == nullptr)
356 : {
357 1 : throw IOException("an I/O error occurred while trying to access directory");
358 : }
359 952 : }
360 :
361 952 : ~read_dir_t()
362 952 : {
363 952 : closedir(m_dir);
364 952 : }
365 :
366 12559 : std::string next()
367 : {
368 : // we must reset errno because readdir() does not change it
369 : // when the end of the directory is reached
370 : //
371 : // Note: readdir() is expected to be thread safe as long as
372 : // each thread use a different m_dir parameter
373 : //
374 12559 : errno = 0;
375 12559 : struct dirent * entry(readdir(m_dir));
376 12559 : if(entry == nullptr)
377 : {
378 952 : if(errno != 0)
379 : {
380 : throw IOException("an I/O error occurred while reading a directory"); // LCOV_EXCL_LINE
381 : }
382 952 : return std::string();
383 : }
384 :
385 11607 : return entry->d_name;
386 : }
387 :
388 : private:
389 : DIR * m_dir;
390 : };
391 : #endif
392 :
393 1906 : read_dir_t dir(m_filepath + subdir);
394 11607 : for(;;)
395 : {
396 24166 : std::string const& name(dir.next());
397 12559 : if(name.empty())
398 : {
399 952 : break;
400 : }
401 :
402 : // skip the "." and ".." directories, they are never added to
403 : // a Zip archive
404 11607 : if(name != "." && name != "..")
405 : {
406 19406 : FileEntry::pointer_t entry(new DirectoryEntry(m_filepath + subdir + name, ""));
407 9703 : m_entries.push_back(entry);
408 :
409 9703 : if(m_recursive && entry->isDirectory())
410 : {
411 901 : load(subdir + name);
412 : }
413 : }
414 : }
415 952 : }
416 :
417 :
418 3 : } // zipios namespace
419 :
420 : // Local Variables:
421 : // mode: cpp
422 : // indent-tabs-mode: nil
423 : // c-basic-offset: 4
424 : // tab-width: 4
425 : // End:
426 :
427 : // vim: ts=4 sw=4 et
|