LCOV - code coverage report
Current view: top level - src - directorycollection.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 70 70 100.0 %
Date: 2019-04-24 14:10:30 Functions: 16 16 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-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

Generated by: LCOV version 1.12