summaryrefslogtreecommitdiff
path: root/src/repository.cpp
blob: e911095675c8d9b0351b06446264a83e3f4500f4 (plain)
    1 ////////////////////////////////////////////////////////////////////////
    2 // FILE:        repository.cpp
    3 // AUTHOR:      Johannes Winkelmann, jw@tks6.net
    4 // COPYRIGHT:   (c) 2002 by Johannes Winkelmann
    5 // ---------------------------------------------------------------------
    6 //  This program is free software; you can redistribute it and/or modify
    7 //  it under the terms of the GNU General Public License as published by
    8 //  the Free Software Foundation; either version 2 of the License, or
    9 //  (at your option) any later version.
   10 ////////////////////////////////////////////////////////////////////////
   11 
   12 #include <cstdio>
   13 #include <cstring>
   14 #include <iostream>
   15 #include <algorithm>
   16 #include <vector>
   17 using namespace std;
   18 
   19 
   20 #include <sys/types.h>
   21 #include <sys/stat.h>
   22 #include <dirent.h>
   23 #include <unistd.h>
   24 #include <fnmatch.h>
   25 
   26 #include "datafileparser.h"
   27 #include "repository.h"
   28 #include "stringhelper.h"
   29 #include "pg_regex.h"
   30 using namespace StringHelper;
   31 
   32 
   33 const string Repository::CACHE_VERSION = "V5";
   34 
   35 /*!
   36   Create a repository
   37 */
   38 Repository::Repository(bool useRegex)
   39     : m_useRegex(useRegex)
   40 {
   41 }
   42 
   43 /*!
   44   Destroy a repository
   45 */
   46 Repository::~Repository()
   47 {
   48     map<string, Package*>::const_iterator it = m_packageMap.begin();
   49     for ( ; it != m_packageMap.end(); ++it ) {
   50         delete it->second;
   51     }
   52 }
   53 
   54 
   55 /*!
   56   \return a map of available packages
   57 */
   58 const map<string, Package*>& Repository::packages() const
   59 {
   60     return m_packageMap;
   61 }
   62 
   63 
   64 /*!
   65   Returns a sorted list of duplicate packages in the repository.
   66   In the pairs \a first is the shadowed port and
   67   \a second is the port which preceeds over \a first
   68   \return a list of duplicate packages in the repository
   69 */
   70 const list< pair<Package*, Package*> >& Repository::shadowedPackages() const
   71 {
   72     return m_shadowedPackages;
   73 }
   74 
   75 
   76 /*!
   77   \param name the package name to be returned
   78   \return a Package pointer for a package name or 0 if not found
   79 */
   80 const Package* Repository::getPackage( const string& name ) const
   81 {
   82     map<string, Package*>::const_iterator it = m_packageMap.find( name );
   83     if ( it == m_packageMap.end() ) {
   84         return 0;
   85     }
   86     return it->second;
   87 }
   88 
   89 
   90 /*!
   91   Search packages for a match of \a pattern in name, and description of
   92   \a searchDesc is true.
   93   \note Name searches can often done without opening the Pkgfiles, but not
   94   description search. Therefore, the later is much slower
   95 
   96   \param pattern the pattern to be found
   97   \param searchDesc whether descriptions should be searched as well
   98   \return a list of matching packages
   99 */
  100 
  101 void Repository::searchMatchingPackages( const string& pattern,
  102                                          list<Package*>& target,
  103                                          bool searchDesc ) const
  104     // note: searchDesc true will read _every_ Pkgfile
  105 {
  106     map<string, Package*>::const_iterator it = m_packageMap.begin();
  107     if (m_useRegex) {
  108         RegEx re(pattern);
  109         for ( ; it != m_packageMap.end(); ++it ) {
  110             if (re.match(it->first)) {
  111                 target.push_back( it->second );
  112             } else if ( searchDesc ) {
  113                 if ( re.match(it->second->description())) {
  114                     target.push_back( it->second );
  115                 }
  116             }
  117         }
  118     } else {
  119         for ( ; it != m_packageMap.end(); ++it ) {
  120             if ( it->first.find( pattern ) != string::npos ) {
  121                 target.push_back( it->second );
  122             } else if (searchDesc ) {
  123                 string s = toLowerCase( it->second->description() );
  124                 if ( s.find( toLowerCase( pattern ) ) != string::npos ) {
  125                     target.push_back( it->second );
  126                 }
  127             }
  128         }
  129     }
  130 }
  131 
  132 int Repository::compareShadowPair(pair<Package*, Package*>& p1,
  133                                   pair<Package*, Package*>& p2)
  134 {
  135     return p1.second->name() < p2.second->name();
  136 }
  137 
  138 
  139 /*!
  140   init repository by reading the directories passed. Doesn't search
  141   recursively, so if you want /dir and /dir/subdir checked, you have to
  142   specify both
  143 
  144   \param rootList a list of directories to look for ports in
  145   \param listDuplicate whether duplicates should registered (slower)
  146 */
  147 void Repository::initFromFS( const list< pair<string, string> >& rootList,
  148                              bool listDuplicate )
  149 {
  150     list< pair<string, string> >::const_iterator it = rootList.begin();
  151     DIR* d;
  152     struct dirent* de;
  153     string name;
  154 
  155     std::map<string, bool> alreadyChecked;
  156 
  157 
  158     for ( ; it != rootList.end(); ++it ) {
  159 
  160         string path = it->first;
  161         string pkgInput = stripWhiteSpace( it->second );
  162 
  163         if ( alreadyChecked[path] ) {
  164             continue;
  165         }
  166 
  167         bool filter = false;
  168         if ( pkgInput.length() > 0 ) {
  169             filter = true;
  170             // create a proper input string
  171             while ( pkgInput.find( " " ) != string::npos ) {
  172                 pkgInput = pkgInput.replace( pkgInput.find(" "), 1, "," );
  173             }
  174             while ( pkgInput.find( "\t" ) != string::npos ) {
  175                 pkgInput = pkgInput.replace( pkgInput.find("\t"), 1, "," );
  176             }
  177             while ( pkgInput.find( ",," ) != string::npos ) {
  178                 pkgInput = pkgInput.replace( pkgInput.find(",,"), 2, "," );
  179             }
  180         }
  181 
  182         if (!filter) {
  183             alreadyChecked[path] = true;
  184         }
  185 
  186         list<string> packages;
  187         split( pkgInput, ',', packages );
  188 
  189 
  190 
  191         // TODO: think about whether it would be faster (more
  192         // efficient) to put all packages into a map, and the iterate
  193         // over the list of allowed packages and copy them
  194         // over. depending in the efficiency of find(), this might be
  195         // faster
  196         d = opendir( path.c_str() );
  197         while ( ( de = readdir( d ) ) != NULL ) {
  198             name = de->d_name;
  199 
  200             // TODO: review this
  201             struct stat buf;
  202             if ( stat( (path + "/" + name + "/Pkgfile").c_str(), &buf )
  203                  != 0 ) {
  204                 // no Pkgfile -> no port
  205                 continue;
  206             }
  207 
  208             if ( filter && find( packages.begin(),
  209                                  packages.end(), name ) == packages.end() ) {
  210                 // not found -> ignore this port
  211                 continue;
  212             }
  213 
  214             if ( name != "." && name != ".." ) {
  215 
  216                 map<string, Package*>::iterator hidden;
  217                 hidden = m_packageMap.find( name );
  218                 Package* p = new Package( name, path );
  219                 if ( p ) {
  220                     if ( hidden == m_packageMap.end() ) {
  221                         // no such package found, add
  222                         m_packageMap[name] = p;
  223                     } else if ( listDuplicate ) {
  224                         m_shadowedPackages.push_back(
  225                                 make_pair( p, hidden->second ));
  226                     } else {
  227                         delete p;
  228                     }
  229                 }
  230             }
  231         }
  232         closedir( d );
  233     }
  234 
  235     m_shadowedPackages.sort(compareShadowPair);
  236 }
  237 
  238 /*!
  239   Init from a cache file
  240   \param cacheFile the name of the cache file to be parser
  241   \return true on success, false indicates file opening problems
  242 */
  243 Repository::CacheReadResult
  244 Repository::initFromCache( const string& cacheFile )
  245 {
  246     FILE* fp = fopen( cacheFile.c_str(), "r" );
  247     if ( !fp ) {
  248         return ACCESS_ERR;
  249     }
  250 
  251     const int length = BUFSIZ;
  252     char input[length];
  253     string line;
  254 
  255     // read version
  256     if ( fgets( input, length, fp ) ) {
  257         line = stripWhiteSpace( input );
  258         if ( line != CACHE_VERSION ) {
  259             return FORMAT_ERR;
  260         }
  261     }
  262 
  263     // FIELDS:
  264     // name, path, version, release,
  265     // description, dependencies, url,
  266     // packager, maintainer, hasReadme;
  267     // hasPreInstall, hasPostInstall
  268     const int fieldCount = 12;
  269     string fields[fieldCount];
  270     int fieldPos = 0;
  271 
  272     while ( fgets( input, length, fp ) ) {
  273         line = StringHelper::stripWhiteSpace( input );
  274 
  275         fields[fieldPos] = line;
  276         ++fieldPos;
  277         if ( fieldPos == fieldCount ) {
  278             fieldPos = 0;
  279             Package* p = new Package( fields[0], fields[1],
  280                                       fields[2], fields[3],
  281                                       fields[4], fields[5], fields[6],
  282                                       fields[7], fields[8], fields[9],
  283                                       fields[10], fields[11]);
  284             m_packageMap[p->name()] = p;
  285             fgets( input, length, fp ); // read empty line
  286         }
  287     }
  288     fclose( fp );
  289 
  290     return READ_OK;
  291 }
  292 
  293 /*!
  294   Store repository data in a cache file
  295   \param cacheFile the file where the data is stored
  296   \return whether the operation was successfully
  297 */
  298 Repository::WriteResult Repository::writeCache( const string& cacheFile )
  299 {
  300     string path = cacheFile;
  301     string::size_type pos = cacheFile.rfind( '/' );
  302     if ( pos != string::npos ) {
  303         path = path.erase( pos );
  304     }
  305     if ( !createOutputDir( path ) ) {
  306         return DIR_ERR;
  307     }
  308 
  309     FILE* fp = fopen( cacheFile.c_str(), "w" );
  310     if ( !fp ) {
  311         return FILE_ERR;
  312     }
  313 
  314     map<string, Package*>::const_iterator it = m_packageMap.begin();
  315 
  316     char yesStr[] = "yes";
  317     char noStr[] = "no";
  318     char* hasReadme;
  319     char* hasPreInstall;
  320     char* hasPostInstall;
  321 
  322     // write version
  323     fprintf( fp, "%s\n", CACHE_VERSION.c_str() );
  324 
  325     for ( ; it != m_packageMap.end(); ++it ) {
  326         const Package* p = it->second;
  327 
  328         // TODO: encode
  329         hasReadme = noStr;
  330         if ( p->hasReadme() ) {
  331             hasReadme = yesStr;
  332         }
  333 
  334         hasPreInstall = noStr;
  335         if ( p->hasPreInstall() ) {
  336             hasPreInstall = yesStr;
  337         }
  338 
  339         hasPostInstall = noStr;
  340         if ( p->hasPostInstall() ) {
  341             hasPostInstall = yesStr;
  342         }
  343 
  344         fprintf( fp, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n",
  345                  p->name().c_str(),
  346                  p->path().c_str(),
  347                  p->version().c_str(),
  348 
  349                  p->release().c_str(),
  350                  p->description().c_str(),
  351                  p->dependencies().c_str(),
  352                  p->url().c_str(),
  353                  p->packager().c_str(),
  354                  p->maintainer().c_str(),
  355                  hasReadme, hasPreInstall, hasPostInstall );
  356     }
  357 
  358     fclose( fp );
  359     return SUCCESS;
  360 }
  361 
  362 /*!
  363   create all components of \path which don't exist
  364   \param path the path to be created
  365   \return true on success. false indicates permission problems
  366  */
  367 bool Repository::createOutputDir( const string& path )
  368 {
  369     list<string> dirs;
  370     split( path, '/', dirs, 1 );
  371     string tmpPath;
  372 
  373     for ( list<string>::iterator it = dirs.begin(); it != dirs.end(); ++it ) {
  374 
  375         tmpPath += *it + "/";
  376         DIR* d;
  377         if ( ( d = opendir( tmpPath.c_str() ) ) == NULL ) {
  378             // doesn't exist
  379             if ( mkdir( tmpPath.c_str(), 0755 ) == -1 ) {
  380                 cout << "- can't create output directory " << tmpPath
  381                      << endl;
  382                 return false;
  383             }
  384         } else {
  385             closedir( d );
  386         }
  387 
  388     }
  389     return true;
  390 }
  391 
  392 
  393 /*!
  394   Search packages for a match of \a pattern in name. The name can
  395   contain shell wildcards.
  396 
  397   \param pattern the pattern to be found
  398   \return a list of matching packages
  399 */
  400 
  401 void Repository::getMatchingPackages( const string& pattern,
  402                                       list<Package*>& target ) const
  403 {
  404     map<string, Package*>::const_iterator it = m_packageMap.begin();
  405     RegEx re(pattern);
  406 
  407     if (m_useRegex) {
  408         for ( ; it != m_packageMap.end(); ++it ) {
  409             if (re.match(it->first)) {
  410                 target.push_back( it->second );
  411             }
  412         }
  413     } else {
  414         for ( ; it != m_packageMap.end(); ++it ) {
  415             // I assume fnmatch will be quite fast for "match all" (*), so
  416             // I didn't add a boolean to check for this explicitely
  417             if ( fnmatch( pattern.c_str(), it->first.c_str(), 0  ) == 0 ) {
  418                 target.push_back( it->second );
  419             }
  420         }
  421     }
  422 }
  423 
  424 void Repository::addDependencies( std::map<string, string>& deps )
  425 {
  426     map<string, string>::iterator it = deps.begin();
  427     for ( ; it != deps.end(); ++it ) {
  428         map<string, Package*>::const_iterator pit =
  429             m_packageMap.find( it->first );
  430         if ( pit != m_packageMap.end() ) {
  431             Package* p = pit->second;
  432             if (p->dependencies().length() == 0) {
  433                 // only use if no dependencies in Pkgfile
  434                 p->setDependencies(it->second);
  435             }
  436         }
  437     }
  438 }

Generated by cgit