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-2022 Made to Order Software Corp. All Rights Reserved 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 : { 67 16 : } 68 : 69 : 70 : /** \brief Initialize a DirectoryCollection object. 71 : * 72 : * This function initializes a directory which represents a collection 73 : * of files from disk. 74 : * 75 : * By default recursive is true meaning that the specified directory 76 : * and all of its children are read in the collection. 77 : * 78 : * \warning 79 : * The specified path must be a valid directory path and name. If the 80 : * name represents a file, then the DirectoryCollection is marked as 81 : * invalid. 82 : * 83 : * \note 84 : * The file content is not loaded so the collection is fairly lightweight. 85 : * 86 : * \param[in] path A directory path. If the name is not a valid 87 : * directory the created DirectoryCollection is 88 : * marked as being invalid. 89 : * \param[in] recursive Whether to load all the files found in 90 : * sub-directories. 91 : */ 92 190 : DirectoryCollection::DirectoryCollection( 93 : std::string const & path 94 190 : , bool recursive) 95 190 : : m_recursive(recursive) 96 190 : , m_filepath(path) 97 : { 98 190 : m_filename = m_filepath; 99 190 : m_valid = m_filepath.isDirectory() || m_filepath.isRegular(); 100 190 : } 101 : 102 : 103 : /** \brief Clean up a DirectoryCollection object. 104 : * 105 : * The destructor ensures that the object is properly cleaned up. 106 : */ 107 299 : DirectoryCollection::~DirectoryCollection() 108 : { 109 284 : close(); 110 299 : } 111 : 112 : 113 : /** \brief Close the directory collection. 114 : * 115 : * This function marks the collection as invalid in effect rendering 116 : * the collection unusable. 117 : */ 118 350 : void DirectoryCollection::close() 119 : { 120 350 : m_entries_loaded = false; 121 350 : m_filepath.clear(); 122 : 123 350 : FileCollection::close(); 124 350 : } 125 : 126 : 127 : /** \brief Retrieve a vector to the collection entries. 128 : * 129 : * This function makes sure that the directory collection is valid, if not 130 : * an exception is raised. If valid, then the function makes sure that 131 : * the entries were loaded and then it returns a copy of the vector 132 : * holding the entries. 133 : * 134 : * \note 135 : * The copy of the vector is required because of the implementation 136 : * of CollectionCollection which does not hold a vector of all the 137 : * entries defined in its children. It is also cleaner (albeit slower) 138 : * in case one wants to use the library in a thread environment. 139 : * 140 : * \return A copy of the internal FileEntry vector. 141 : */ 142 144278 : FileEntry::vector_t DirectoryCollection::entries() const 143 : { 144 144278 : loadEntries(); 145 : 146 144241 : return FileCollection::entries(); 147 : } 148 : 149 : 150 : /** \brief Get an entry from the collection. 151 : * 152 : * This function returns a shared pointer to a FileEntry object for 153 : * the entry with the specified name. To ignore the path part of the 154 : * filename while searching for a match, specify FileCollection::IGNORE 155 : * as the second argument. 156 : * 157 : * \note 158 : * The collection must be valid or the function raises an exception. 159 : * 160 : * \param[in] name A string containing the name of the entry to get. 161 : * \param[in] matchpath Specify MatchPath::MATCH, if the path should match 162 : * as well, specify MatchPath::IGNORE, if the path 163 : * should be ignored. 164 : * 165 : * \return A shared pointer to the found entry. The returned pointer 166 : * is null if no entry is found. 167 : * 168 : * \sa mustBeValid() 169 : */ 170 143352 : FileEntry::pointer_t DirectoryCollection::getEntry(std::string const & name, MatchPath matchpath) const 171 : { 172 143352 : loadEntries(); 173 : 174 143280 : return FileCollection::getEntry(name, matchpath); 175 : } 176 : 177 : 178 : /** \brief Retrieve pointer to an istream. 179 : * 180 : * This function returns a shared pointer to an istream defined from 181 : * the named entry, which is expected to be available in this collection. 182 : * 183 : * The function returns a null pointer if no FileEntry can be found from 184 : * the specified name or the FileEntry is marked as invalid. 185 : * 186 : * The returned istream represents a file on disk, although the filename 187 : * must exist in the collection or it will be ignored. A filename that 188 : * represents a directory cannot return an input stream and thus an error 189 : * is returned in that case. 190 : * 191 : * \note 192 : * The stream is always opened in binary mode. 193 : * 194 : * \param[in] entry_name The name of the file to search in the collection. 195 : * \param[in] matchpath Whether the full path or just the filename is matched. 196 : * 197 : * \return A shared pointer to an open istream for the specified entry. 198 : * 199 : * \sa CollectionCollection 200 : * \sa FileCollection 201 : * \sa ZipFile 202 : */ 203 126335 : DirectoryCollection::stream_pointer_t DirectoryCollection::getInputStream(std::string const & entry_name, MatchPath matchpath) 204 : { 205 126335 : FileEntry::pointer_t ent(getEntry(entry_name, matchpath)); 206 126299 : if(ent == nullptr || ent->isDirectory()) 207 : { 208 2249 : return DirectoryCollection::stream_pointer_t(); 209 : } 210 : 211 248100 : DirectoryCollection::stream_pointer_t p(std::make_shared<std::ifstream>(ent->getName(), std::ios::in | std::ios::binary)); 212 124050 : return p; 213 126299 : } 214 : 215 : 216 : /** \brief Create another DirectoryCollection. 217 : * 218 : * This function creates a clone of this DirectoryCollection. This is 219 : * a simple new DirectoryCollection of this collection. 220 : * 221 : * \return The function returns a shared pointer of the new collection. 222 : */ 223 64 : FileCollection::pointer_t DirectoryCollection::clone() const 224 : { 225 64 : return std::make_shared<DirectoryCollection>(*this); 226 : } 227 : 228 : 229 : /** \brief This is an internal function that loads the file entries. 230 : * 231 : * This function is the top level which starts the process of loading 232 : * all the files found in the specified directory and sub-directories 233 : * if the DirectoryCollection was created with the recursive flag 234 : * set to true (the default.) 235 : */ 236 287630 : void DirectoryCollection::loadEntries() const 237 : { 238 : // WARNING: this has to stay here because the collection could get close()'d... 239 287630 : mustBeValid(); 240 : 241 287522 : if(!m_entries_loaded) 242 : { 243 207 : m_entries_loaded = true; 244 : 245 : // if the read fails then the directory may have been deleted 246 : // in which case we want to invalidate this DirectoryCollection 247 : // object 248 : try 249 : { 250 : // include the root directory 251 207 : FileEntry::pointer_t entry(std::make_shared<DirectoryEntry>(m_filepath, "")); 252 207 : const_cast<DirectoryCollection *>(this)->m_entries.push_back(entry); 253 : 254 : // now read the data inside that directory 255 207 : if(m_filepath.isDirectory()) 256 : { 257 56 : const_cast<DirectoryCollection *>(this)->load(FilePath()); 258 : } 259 207 : } 260 1 : catch(...) 261 : { 262 1 : const_cast<DirectoryCollection *>(this)->close(); 263 1 : throw; 264 1 : } 265 : } 266 287521 : } 267 : 268 : 269 : /** \brief This is the function loading all the file entries. 270 : * 271 : * This function loads all the file entries found in the specified 272 : * directory. If the DirectoryCollection was created with the 273 : * recursive flag, then this function will load sub-directories 274 : * infinitum. 275 : * 276 : * \param[in] subdir The directory to read. 277 : */ 278 960 : void DirectoryCollection::load(FilePath const & subdir) 279 : { 280 : #ifdef ZIPIOS_WINDOWS 281 : struct read_dir_t 282 : { 283 : read_dir_t(FilePath const & path) 284 : { 285 : /** \todo 286 : * Make necessary changes to support 64 bit and Unicode 287 : * (require utf8 -> wchar_t, then use _wfindfirsti64().) 288 : * We'll have to update the next() function too, of course. 289 : */ 290 : m_handle = _findfirsti64(static_cast<std::string>(path).c_str(), &m_fileinfo); 291 : if(m_handle == 0) 292 : { 293 : if(errno == ENOENT) 294 : { 295 : // this can happen, the directory is empty and thus has 296 : // absolutely no information 297 : m_read_first = true; 298 : } 299 : else 300 : { 301 : throw IOException("an I/O error occurred while reading a directory"); 302 : } 303 : } 304 : } 305 : 306 : ~read_dir_t() 307 : { 308 : // a completely empty directory may give us a "null pointer" 309 : // when calling _[w]findfirst[i64]() 310 : if(m_handle != 0) 311 : { 312 : _findclose(m_handle); 313 : } 314 : } 315 : 316 : std::string next() 317 : { 318 : if(m_read_first) 319 : { 320 : __int64 const r(_findnexti64(m_handle, &m_fileinfo)); 321 : if(r != 0) 322 : { 323 : if(errno != ENOENT) 324 : { 325 : throw IOException("an I/O error occurred while reading a directory"); 326 : } 327 : return std::string(); 328 : } 329 : } 330 : else 331 : { 332 : // the _findfirst() includes a response, use it! 333 : m_read_first = true; 334 : } 335 : 336 : return m_fileinfo.name; 337 : } 338 : 339 : private: 340 : long m_handle = 0; 341 : struct _finddatai64_t m_fileinfo = {}; 342 : bool m_read_first = 0; 343 : }; 344 : #else 345 : struct read_dir_t 346 : { 347 960 : read_dir_t(FilePath const & path) 348 960 : : m_dir(opendir(static_cast<std::string>(path).c_str())) 349 : { 350 960 : if(m_dir == nullptr) 351 : { 352 1 : throw IOException("an I/O error occurred while trying to access directory"); 353 : } 354 959 : } 355 : 356 959 : ~read_dir_t() 357 : { 358 959 : closedir(m_dir); 359 959 : } 360 : 361 12595 : std::string next() 362 : { 363 : // we must reset errno because readdir() does not change it 364 : // when the end of the directory is reached 365 : // 366 : // Note: readdir() is expected to be thread safe as long as 367 : // each thread use a different m_dir parameter 368 : // 369 12595 : errno = 0; 370 12595 : struct dirent * entry(readdir(m_dir)); 371 12595 : if(entry == nullptr) 372 : { 373 959 : if(errno != 0) 374 : { 375 : throw IOException("an I/O error occurred while reading a directory"); // LCOV_EXCL_LINE 376 : } 377 959 : return std::string(); 378 : } 379 : 380 11636 : return entry->d_name; 381 : } 382 : 383 : private: 384 : DIR * m_dir = nullptr; 385 : }; 386 : #endif 387 : 388 1920 : read_dir_t dir(m_filepath + subdir); 389 : for(;;) 390 : { 391 12595 : std::string const & name(dir.next()); 392 12595 : if(name.empty()) 393 : { 394 959 : break; 395 : } 396 : 397 : // skip the "." and ".." directories, they are never added to 398 : // a Zip archive 399 11636 : if(name != "." && name != "..") 400 : { 401 19436 : FileEntry::pointer_t entry(std::make_shared<DirectoryEntry>(m_filepath + subdir + name, "")); 402 9718 : m_entries.push_back(entry); 403 : 404 9718 : if(m_recursive && entry->isDirectory()) 405 : { 406 906 : load(subdir + name); 407 : } 408 9718 : } 409 24231 : } 410 959 : } 411 : 412 : 413 : } // zipios namespace 414 : 415 : // Local Variables: 416 : // mode: cpp 417 : // indent-tabs-mode: nil 418 : // c-basic-offset: 4 419 : // tab-width: 4 420 : // End: 421 : 422 : // vim: ts=4 sw=4 et