summaryrefslogtreecommitdiff
path: root/src/repository.cpp
blob: 02562a40c83e5f5583c522a8c1a944ebb5c47929 (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             fclose( fp );
  260             return FORMAT_ERR;
  261         }
  262     }
  263 
  264     // FIELDS:
  265     // name, path, version, release,
  266     // description, dependencies, url,
  267     // packager, maintainer, hasReadme;
  268     // hasPreInstall, hasPostInstall
  269     const int fieldCount = 12;
  270     string fields[fieldCount];
  271     int fieldPos = 0;
  272 
  273     while ( fgets( input, length, fp ) ) {
  274         line = StringHelper::stripWhiteSpace( input );
  275 
  276         fields[fieldPos] = line;
  277         ++fieldPos;
  278         if ( fieldPos == fieldCount ) {
  279             fieldPos = 0;
  280             Package* p = new Package( fields[0], fields[1],
  281                                       fields[2], fields[3],
  282                                       fields[4], fields[5], fields[6],
  283                                       fields[7], fields[8], fields[9],
  284                                       fields[10], fields[11]);
  285             m_packageMap[p->name()] = p;
  286             fgets( input, length, fp ); // read empty line
  287         }
  288     }
  289     fclose( fp );
  290 
  291     return READ_OK;
  292 }
  293 
  294 /*!
  295   Store repository data in a cache file
  296   \param cacheFile the file where the data is stored
  297   \return whether the operation was successfully
  298 */
  299 Repository::WriteResult Repository::writeCache( const string& cacheFile )
  300 {
  301     string path = cacheFile;
  302     string::size_type pos = cacheFile.rfind( '/' );
  303     if ( pos != string::npos ) {
  304         path = path.erase( pos );
  305     }
  306     if ( !createOutputDir( path ) ) {
  307         return DIR_ERR;
  308     }
  309 
  310     FILE* fp = fopen( cacheFile.c_str(), "w" );
  311     if ( !fp ) {
  312         return FILE_ERR;
  313     }
  314 
  315     map<string, Package*>::const_iterator it = m_packageMap.begin();
  316 
  317     char yesStr[] = "yes";
  318     char noStr[] = "no";
  319     char* hasReadme;
  320     char* hasPreInstall;
  321     char* hasPostInstall;
  322 
  323     // write version
  324     fprintf( fp, "%s\n", CACHE_VERSION.c_str() );
  325 
  326     for ( ; it != m_packageMap.end(); ++it ) {
  327         const Package* p = it->second;
  328 
  329         // TODO: encode
  330         hasReadme = noStr;
  331         if ( p->hasReadme() ) {
  332             hasReadme = yesStr;
  333         }
  334 
  335         hasPreInstall = noStr;
  336         if ( p->hasPreInstall() ) {
  337             hasPreInstall = yesStr;
  338         }
  339 
  340         hasPostInstall = noStr;
  341         if ( p->hasPostInstall() ) {
  342             hasPostInstall = yesStr;
  343         }
  344 
  345         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",
  346                  p->name().c_str(),
  347                  p->path().c_str(),
  348                  p->version().c_str(),
  349 
  350                  p->release().c_str(),
  351                  p->description().c_str(),
  352                  p->dependencies().c_str(),
  353                  p->url().c_str(),
  354                  p->packager().c_str(),
  355                  p->maintainer().c_str(),
  356                  hasReadme, hasPreInstall, hasPostInstall );
  357     }
  358 
  359     fclose( fp );
  360     return SUCCESS;
  361 }
  362 
  363 /*!
  364   create all components of \path which don't exist
  365   \param path the path to be created
  366   \return true on success. false indicates permission problems
  367  */
  368 bool Repository::createOutputDir( const string& path )
  369 {
  370     list<string> dirs;
  371     split( path, '/', dirs, 1 );
  372     string tmpPath;
  373 
  374     for ( list<string>::iterator it = dirs.begin(); it != dirs.end(); ++it ) {
  375 
  376         tmpPath += *it + "/";
  377         DIR* d;
  378         if ( ( d = opendir( tmpPath.c_str() ) ) == NULL ) {
  379             // doesn't exist
  380             if ( mkdir( tmpPath.c_str(), 0755 ) == -1 ) {
  381                 cout << "- can't create output directory " << tmpPath
  382                      << endl;
  383                 return false;
  384             }
  385         } else {
  386             closedir( d );
  387         }
  388 
  389     }
  390     return true;
  391 }
  392 
  393 
  394 /*!
  395   Search packages for a match of \a pattern in name. The name can
  396   contain shell wildcards.
  397 
  398   \param pattern the pattern to be found
  399   \return a list of matching packages
  400 */
  401 
  402 void Repository::getMatchingPackages( const string& pattern,
  403                                       list<Package*>& target ) const
  404 {
  405     map<string, Package*>::const_iterator it = m_packageMap.begin();
  406     RegEx re(pattern);
  407 
  408     if (m_useRegex) {
  409         for ( ; it != m_packageMap.end(); ++it ) {
  410             if (re.match(it->first)) {
  411                 target.push_back( it->second );
  412             }
  413         }
  414     } else {
  415         for ( ; it != m_packageMap.end(); ++it ) {
  416             // I assume fnmatch will be quite fast for "match all" (*), so
  417             // I didn't add a boolean to check for this explicitely
  418             if ( fnmatch( pattern.c_str(), it->first.c_str(), 0  ) == 0 ) {
  419                 target.push_back( it->second );
  420             }
  421         }
  422     }
  423 }
  424 
  425 void Repository::addDependencies( std::map<string, string>& deps )
  426 {
  427     map<string, string>::iterator it = deps.begin();
  428     for ( ; it != deps.end(); ++it ) {
  429         map<string, Package*>::const_iterator pit =
  430             m_packageMap.find( it->first );
  431         if ( pit != m_packageMap.end() ) {
  432             Package* p = pit->second;
  433             if (p->dependencies().length() == 0) {
  434                 // only use if no dependencies in Pkgfile
  435                 p->setDependencies(it->second);
  436             }
  437         }
  438     }
  439 }

Generated by cgit