LCOV - code coverage report
Current view: top level - src - directorycollection.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 74 74 100.0 %
Date: 2024-06-15 08:26:09 Functions: 14 14 100.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14

Snap C++ | List of projects | List of versions