RepoManager.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00013 #include <cstdlib>
00014 #include <iostream>
00015 #include <fstream>
00016 #include <sstream>
00017 #include <list>
00018 #include <map>
00019 #include <algorithm>
00020 
00021 #include "zypp/base/InputStream.h"
00022 #include "zypp/base/LogTools.h"
00023 #include "zypp/base/Gettext.h"
00024 #include "zypp/base/Function.h"
00025 #include "zypp/base/Regex.h"
00026 #include "zypp/PathInfo.h"
00027 #include "zypp/TmpPath.h"
00028 
00029 #include "zypp/ServiceInfo.h"
00030 #include "zypp/repo/RepoException.h"
00031 #include "zypp/RepoManager.h"
00032 
00033 #include "zypp/media/MediaManager.h"
00034 #include "zypp/media/CredentialManager.h"
00035 #include "zypp/MediaSetAccess.h"
00036 #include "zypp/ExternalProgram.h"
00037 #include "zypp/ManagedFile.h"
00038 
00039 #include "zypp/parser/RepoFileReader.h"
00040 #include "zypp/parser/ServiceFileReader.h"
00041 #include "zypp/parser/RepoindexFileReader.h"
00042 #include "zypp/repo/yum/Downloader.h"
00043 #include "zypp/repo/susetags/Downloader.h"
00044 #include "zypp/parser/plaindir/RepoParser.h"
00045 
00046 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
00047 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance
00048 #include "zypp/HistoryLog.h" // to write history :O)
00049 
00050 #include "zypp/ZYppCallbacks.h"
00051 
00052 #include "sat/Pool.h"
00053 
00054 using std::endl;
00055 using std::string;
00056 using namespace zypp::repo;
00057 
00059 namespace zypp
00060 { 
00061 
00062   namespace
00063   {
00067     class MediaMounter
00068     {
00069       public:
00071         MediaMounter( const Url & url_r )
00072         {
00073           media::MediaManager mediamanager;
00074           _mid = mediamanager.open( url_r );
00075           mediamanager.attach( _mid );
00076         }
00077 
00079         ~MediaMounter()
00080         {
00081           media::MediaManager mediamanager;
00082           mediamanager.release( _mid );
00083           mediamanager.close( _mid );
00084         }
00085 
00090         Pathname getPathName( const Pathname & path_r = Pathname() ) const
00091         {
00092           media::MediaManager mediamanager;
00093           return mediamanager.localPath( _mid, path_r );
00094         }
00095 
00096       private:
00097         media::MediaAccessId _mid;
00098     };
00099 
00101     template <class Iterator>
00102     inline bool foundAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r )
00103     {
00104       for_( it, begin_r, end_r )
00105         if ( it->alias() == alias_r )
00106           return true;
00107       return false;
00108     }
00110     template <class Container>
00111     inline bool foundAliasIn( const std::string & alias_r, const Container & cont_r )
00112     { return foundAliasIn( alias_r, cont_r.begin(), cont_r.end() ); }
00113 
00115     template <class Iterator>
00116     inline Iterator findAlias( const std::string & alias_r, Iterator begin_r, Iterator end_r )
00117     {
00118       for_( it, begin_r, end_r )
00119         if ( it->alias() == alias_r )
00120           return it;
00121       return end_r;
00122     }
00124     template <class Container>
00125     inline typename Container::iterator findAlias( const std::string & alias_r, Container & cont_r )
00126     { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
00128     template <class Container>
00129     inline typename Container::const_iterator findAlias( const std::string & alias_r, const Container & cont_r )
00130     { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
00131   }
00132 
00134   //
00135   //    CLASS NAME : RepoManagerOptions
00136   //
00138 
00139   RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
00140   {
00141     repoCachePath         = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
00142     repoRawCachePath      = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
00143     repoSolvCachePath     = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
00144     repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
00145     knownReposPath        = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
00146     knownServicesPath     = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
00147     probe                 = ZConfig::instance().repo_add_probe();
00148 
00149     rootDir = root_r;
00150   }
00151 
00152   RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r )
00153   {
00154     RepoManagerOptions ret;
00155     ret.repoCachePath         = root_r;
00156     ret.repoRawCachePath      = root_r/"raw";
00157     ret.repoSolvCachePath     = root_r/"solv";
00158     ret.repoPackagesCachePath = root_r/"packages";
00159     ret.knownReposPath        = root_r/"repos.d";
00160     ret.knownServicesPath     = root_r/"services.d";
00161     ret.rootDir = root_r;
00162     return ret;
00163   }
00164 
00166 
00182     struct RepoCollector : private base::NonCopyable
00183     {
00184       RepoCollector()
00185       {}
00186 
00187       RepoCollector(const std::string & targetDistro_)
00188         : targetDistro(targetDistro_)
00189       {}
00190 
00191       bool collect( const RepoInfo &repo )
00192       {
00193         // skip repositories meant for other distros than specified
00194         if (!targetDistro.empty()
00195             && !repo.targetDistribution().empty()
00196             && repo.targetDistribution() != targetDistro)
00197         {
00198           MIL
00199             << "Skipping repository meant for '" << targetDistro
00200             << "' distribution (current distro is '"
00201             << repo.targetDistribution() << "')." << endl;
00202 
00203           return true;
00204         }
00205 
00206         repos.push_back(repo);
00207         return true;
00208       }
00209 
00210       RepoInfoList repos;
00211       std::string targetDistro;
00212     };
00213 
00215 
00221   static std::list<RepoInfo> repositories_in_file( const Pathname & file )
00222   {
00223     MIL << "repo file: " << file << endl;
00224     RepoCollector collector;
00225     parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
00226     return collector.repos;
00227   }
00228 
00230 
00239   static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
00240   {
00241     MIL << "directory " << dir << endl;
00242     std::list<RepoInfo> repos;
00243     std::list<Pathname> entries;
00244     if ( filesystem::readdir( entries, dir, false ) != 0 )
00245     {
00246       // TranslatorExplanation '%s' is a pathname
00247       ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
00248     }
00249 
00250     str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
00251     for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
00252     {
00253       if (str::regex_match(it->extension(), allowedRepoExt))
00254       {
00255         std::list<RepoInfo> tmp = repositories_in_file( *it );
00256         repos.insert( repos.end(), tmp.begin(), tmp.end() );
00257 
00258         //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
00259         //MIL << "ok" << endl;
00260       }
00261     }
00262     return repos;
00263   }
00264 
00266 
00267    std::list<RepoInfo> readRepoFile(const Url & repo_file)
00268    {
00269      // no interface to download a specific file, using workaround:
00271      Url url(repo_file);
00272      Pathname path(url.getPathName());
00273      url.setPathName ("/");
00274      MediaSetAccess access(url);
00275      Pathname local = access.provideFile(path);
00276 
00277      DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
00278 
00279      return repositories_in_file(local);
00280    }
00281 
00283 
00284   inline void assert_alias( const RepoInfo & info )
00285   {
00286     if ( info.alias().empty() )
00287       ZYPP_THROW( RepoNoAliasException() );
00288     // bnc #473834. Maybe we can match the alias against a regex to define
00289     // and check for valid aliases
00290     if ( info.alias()[0] == '.')
00291       ZYPP_THROW(RepoInvalidAliasException(
00292          info, _("Repository alias cannot start with dot.")));
00293   }
00294 
00295   inline void assert_alias( const ServiceInfo & info )
00296   {
00297     if ( info.alias().empty() )
00298       ZYPP_THROW( ServiceNoAliasException() );
00299     // bnc #473834. Maybe we can match the alias against a regex to define
00300     // and check for valid aliases
00301     if ( info.alias()[0] == '.')
00302       ZYPP_THROW(ServiceInvalidAliasException(
00303          info, _("Service alias cannot start with dot.")));
00304   }
00305 
00307 
00308   inline void assert_urls( const RepoInfo & info )
00309   {
00310     if ( info.baseUrlsEmpty() )
00311       ZYPP_THROW( RepoNoUrlException( info ) );
00312   }
00313 
00314   inline void assert_url( const ServiceInfo & info )
00315   {
00316     if ( ! info.url().isValid() )
00317       ZYPP_THROW( ServiceNoUrlException( info ) );
00318   }
00319 
00321 
00326   inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
00327   {
00328     assert_alias(info);
00329     return opt.repoRawCachePath / info.escaped_alias();
00330   }
00331 
00340   inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
00341   {
00342     assert_alias(info);
00343     return opt.repoRawCachePath / info.escaped_alias() / info.path();
00344   }
00345 
00346 
00350   inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
00351   {
00352     assert_alias(info);
00353     return opt.repoPackagesCachePath / info.escaped_alias();
00354   }
00355 
00359   inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
00360   {
00361     assert_alias(info);
00362     return opt.repoSolvCachePath / info.escaped_alias();
00363   }
00364 
00366 
00368   class ServiceCollector
00369   {
00370     public:
00371       typedef std::set<ServiceInfo> ServiceSet;
00372 
00373       ServiceCollector( ServiceSet & services_r )
00374       : _services( services_r )
00375       {}
00376 
00377       bool operator()( const ServiceInfo & service_r ) const
00378       {
00379         _services.insert( service_r );
00380         return true;
00381       }
00382 
00383     private:
00384       ServiceSet & _services;
00385   };
00386 
00388 
00390   //
00391   //    CLASS NAME : RepoManager::Impl
00392   //
00394 
00398   struct RepoManager::Impl
00399   {
00400     Impl( const RepoManagerOptions &opt )
00401       : options(opt)
00402     {
00403       init_knownServices();
00404       init_knownRepositories();
00405     }
00406 
00407     RepoManagerOptions options;
00408 
00409     RepoSet repos;
00410 
00411     ServiceSet services;
00412 
00413   public:
00414 
00415     void saveService( ServiceInfo & service ) const;
00416 
00417     Pathname generateNonExistingName( const Pathname &dir,
00418                                       const std::string &basefilename ) const;
00419 
00420     std::string generateFilename( const RepoInfo & info ) const;
00421     std::string generateFilename( const ServiceInfo & info ) const;
00422 
00423 
00424   private:
00425     void init_knownServices();
00426     void init_knownRepositories();
00427 
00428   private:
00429     friend Impl * rwcowClone<Impl>( const Impl * rhs );
00431     Impl * clone() const
00432     { return new Impl( *this ); }
00433   };
00434 
00436 
00438   inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
00439   {
00440     return str << "RepoManager::Impl";
00441   }
00442 
00444 
00445   void RepoManager::Impl::saveService( ServiceInfo & service ) const
00446   {
00447     filesystem::assert_dir( options.knownServicesPath );
00448     Pathname servfile = generateNonExistingName( options.knownServicesPath,
00449                                                  generateFilename( service ) );
00450     service.setFilepath( servfile );
00451 
00452     MIL << "saving service in " << servfile << endl;
00453 
00454     std::ofstream file( servfile.c_str() );
00455     if ( !file )
00456     {
00457       // TranslatorExplanation '%s' is a filename
00458       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() )));
00459     }
00460     service.dumpAsIniOn( file );
00461     MIL << "done" << endl;
00462   }
00463 
00479   Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir,
00480                                                        const std::string & basefilename ) const
00481   {
00482     std::string final_filename = basefilename;
00483     int counter = 1;
00484     while ( PathInfo(dir + final_filename).isExist() )
00485     {
00486       final_filename = basefilename + "_" + str::numstring(counter);
00487       counter++;
00488     }
00489     return dir + Pathname(final_filename);
00490   }
00491 
00493 
00501   std::string RepoManager::Impl::generateFilename( const RepoInfo & info ) const
00502   {
00503     std::string filename = info.alias();
00504     // replace slashes with underscores
00505     str::replaceAll( filename, "/", "_" );
00506 
00507     filename = Pathname(filename).extend(".repo").asString();
00508     MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
00509     return filename;
00510   }
00511 
00512   std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const
00513   {
00514     std::string filename = info.alias();
00515     // replace slashes with underscores
00516     str::replaceAll( filename, "/", "_" );
00517 
00518     filename = Pathname(filename).extend(".service").asString();
00519     MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl;
00520     return filename;
00521   }
00522 
00523 
00524   void RepoManager::Impl::init_knownServices()
00525   {
00526     Pathname dir = options.knownServicesPath;
00527     std::list<Pathname> entries;
00528     if (PathInfo(dir).isExist())
00529     {
00530       if ( filesystem::readdir( entries, dir, false ) != 0 )
00531       {
00532         // TranslatorExplanation '%s' is a pathname
00533         ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
00534       }
00535 
00536       //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
00537       for_(it, entries.begin(), entries.end() )
00538       {
00539         parser::ServiceFileReader(*it, ServiceCollector(services));
00540       }
00541     }
00542   }
00543 
00544   void RepoManager::Impl::init_knownRepositories()
00545   {
00546     MIL << "start construct known repos" << endl;
00547 
00548     if ( PathInfo(options.knownReposPath).isExist() )
00549     {
00550       RepoInfoList repol = repositories_in_dir(options.knownReposPath);
00551       for ( RepoInfoList::iterator it = repol.begin();
00552             it != repol.end();
00553             ++it )
00554       {
00555         // set the metadata path for the repo
00556         Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it));
00557         (*it).setMetadataPath(metadata_path);
00558 
00559         // set the downloaded packages path for the repo
00560         Pathname packages_path = packagescache_path_for_repoinfo(options, (*it));
00561         (*it).setPackagesPath(packages_path);
00562 
00563         repos.insert(*it);
00564       }
00565     }
00566 
00567     MIL << "end construct known repos" << endl;
00568   }
00569 
00571   //
00572   //    CLASS NAME : RepoManager
00573   //
00575 
00576   RepoManager::RepoManager( const RepoManagerOptions &opt )
00577   : _pimpl( new Impl(opt) )
00578   {}
00579 
00581 
00582   RepoManager::~RepoManager()
00583   {}
00584 
00586 
00587   bool RepoManager::repoEmpty() const
00588   { return _pimpl->repos.empty(); }
00589 
00590   RepoManager::RepoSizeType RepoManager::repoSize() const
00591   { return _pimpl->repos.size(); }
00592 
00593   RepoManager::RepoConstIterator RepoManager::repoBegin() const
00594   { return _pimpl->repos.begin(); }
00595 
00596   RepoManager::RepoConstIterator RepoManager::repoEnd() const
00597   { return _pimpl->repos.end(); }
00598 
00599   RepoInfo RepoManager::getRepo( const std::string & alias ) const
00600   {
00601     for_( it, repoBegin(), repoEnd() )
00602       if ( it->alias() == alias )
00603         return *it;
00604     return RepoInfo::noRepo;
00605   }
00606 
00607   bool RepoManager::hasRepo( const std::string & alias ) const
00608   {
00609     for_( it, repoBegin(), repoEnd() )
00610       if ( it->alias() == alias )
00611         return true;
00612     return false;
00613   }
00614 
00615   std::string RepoManager::makeStupidAlias( const Url & url_r )
00616   {
00617     std::string ret( url_r.getScheme() );
00618     if ( ret.empty() )
00619       ret = "repo-";
00620     else
00621       ret += "-";
00622 
00623     std::string host( url_r.getHost() );
00624     if ( ! host.empty() )
00625     {
00626       ret += host;
00627       ret += "-";
00628     }
00629 
00630     static Date::ValueType serial = Date::now();
00631     ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8);
00632     return ret;
00633   }
00634 
00636 
00637   Pathname RepoManager::metadataPath( const RepoInfo &info ) const
00638   {
00639     return rawcache_path_for_repoinfo(_pimpl->options, info );
00640   }
00641 
00642   Pathname RepoManager::packagesPath( const RepoInfo &info ) const
00643   {
00644     return packagescache_path_for_repoinfo(_pimpl->options, info );
00645   }
00646 
00648 
00649   RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
00650   {
00651     Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
00652     Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
00653     RepoType repokind = info.type();
00654     RepoStatus status;
00655 
00656     switch ( repokind.toEnum() )
00657     {
00658       case RepoType::NONE_e:
00659       // unknown, probe the local metadata
00660         repokind = probe( productdatapath.asUrl() );
00661       break;
00662       default:
00663       break;
00664     }
00665 
00666     switch ( repokind.toEnum() )
00667     {
00668       case RepoType::RPMMD_e :
00669       {
00670         status = RepoStatus( productdatapath + "/repodata/repomd.xml");
00671       }
00672       break;
00673 
00674       case RepoType::YAST2_e :
00675       {
00676         status = RepoStatus( productdatapath + "/content") && (RepoStatus( mediarootpath + "/media.1/media"));
00677       }
00678       break;
00679 
00680       case RepoType::RPMPLAINDIR_e :
00681       {
00682         if ( PathInfo(Pathname(productdatapath + "/cookie")).isExist() )
00683           status = RepoStatus( productdatapath + "/cookie");
00684       }
00685       break;
00686 
00687       case RepoType::NONE_e :
00688         // Return default RepoStatus in case of RepoType::NONE
00689         // indicating it should be created?
00690         // ZYPP_THROW(RepoUnknownTypeException());
00691         break;
00692     }
00693     return status;
00694   }
00695 
00696   void RepoManager::touchIndexFile(const RepoInfo & info)
00697   {
00698     Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
00699 
00700     RepoType repokind = info.type();
00701     if ( repokind.toEnum() == RepoType::NONE_e )
00702       // unknown, probe the local metadata
00703       repokind = probe( productdatapath.asUrl() );
00704     // if still unknown, just return
00705     if (repokind == RepoType::NONE_e)
00706       return;
00707 
00708     Pathname p;
00709     switch ( repokind.toEnum() )
00710     {
00711       case RepoType::RPMMD_e :
00712         p = Pathname(productdatapath + "/repodata/repomd.xml");
00713         break;
00714 
00715       case RepoType::YAST2_e :
00716         p = Pathname(productdatapath + "/content");
00717         break;
00718 
00719       case RepoType::RPMPLAINDIR_e :
00720         p = Pathname(productdatapath + "/cookie");
00721         break;
00722 
00723       case RepoType::NONE_e :
00724       default:
00725         break;
00726     }
00727 
00728     // touch the file, ignore error (they are logged anyway)
00729     filesystem::touch(p);
00730   }
00731 
00732   RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata(
00733                                               const RepoInfo &info,
00734                                               const Url &url,
00735                                               RawMetadataRefreshPolicy policy )
00736   {
00737     assert_alias(info);
00738 
00739     RepoStatus oldstatus;
00740     RepoStatus newstatus;
00741 
00742     try
00743     {
00744       MIL << "Going to try to check whether refresh is needed for " << url << endl;
00745 
00746       // first check old (cached) metadata
00747       Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
00748       filesystem::assert_dir(mediarootpath);
00749       oldstatus = metadataStatus(info);
00750 
00751       if ( oldstatus.empty() )
00752       {
00753         MIL << "No cached metadata, going to refresh" << endl;
00754         return REFRESH_NEEDED;
00755       }
00756 
00757       {
00758         std::string scheme( url.getScheme() );
00759         if ( scheme == "cd" || scheme == "dvd" )
00760         {
00761           MIL << "never refresh CD/DVD" << endl;
00762           return REPO_UP_TO_DATE;
00763         }
00764       }
00765 
00766       // now we've got the old (cached) status, we can decide repo.refresh.delay
00767       if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
00768       {
00769         // difference in seconds
00770         double diff = difftime(
00771           (Date::ValueType)Date::now(),
00772           (Date::ValueType)oldstatus.timestamp()) / 60;
00773 
00774         DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
00775         DBG << "current time: " << (Date::ValueType)Date::now() << endl;
00776         DBG << "last refresh = " << diff << " minutes ago" << endl;
00777 
00778         if ( diff < ZConfig::instance().repo_refresh_delay() )
00779         {
00780           if ( diff < 0 )
00781           {
00782             WAR << "Repository '" << info.alias() << "' was refreshed in the future!" << endl;
00783           }
00784           else
00785           {
00786             MIL << "Repository '" << info.alias()
00787                 << "' has been refreshed less than repo.refresh.delay ("
00788                 << ZConfig::instance().repo_refresh_delay()
00789                 << ") minutes ago. Advising to skip refresh" << endl;
00790             return REPO_CHECK_DELAYED;
00791           }
00792         }
00793       }
00794 
00795       // To test the new matadta create temp dir as sibling of mediarootpath
00796       filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
00797 
00798       repo::RepoType repokind = info.type();
00799       // if the type is unknown, try probing.
00800       switch ( repokind.toEnum() )
00801       {
00802         case RepoType::NONE_e:
00803           // unknown, probe it \todo respect productdir
00804           repokind = probe( url, info.path() );
00805         break;
00806         default:
00807         break;
00808       }
00809 
00810       if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
00811            ( repokind.toEnum() == RepoType::YAST2_e ) )
00812       {
00813         MediaSetAccess media(url);
00814         shared_ptr<repo::Downloader> downloader_ptr;
00815 
00816         if ( repokind.toEnum() == RepoType::RPMMD_e )
00817           downloader_ptr.reset(new yum::Downloader(info));
00818         else
00819           downloader_ptr.reset( new susetags::Downloader(info));
00820 
00821         RepoStatus newstatus = downloader_ptr->status(media);
00822         bool refresh = false;
00823         if ( oldstatus.checksum() == newstatus.checksum() )
00824         {
00825           MIL << "repo has not changed" << endl;
00826           if ( policy == RefreshForced )
00827           {
00828             MIL << "refresh set to forced" << endl;
00829             refresh = true;
00830           }
00831         }
00832         else
00833         {
00834           MIL << "repo has changed, going to refresh" << endl;
00835           refresh = true;
00836         }
00837 
00838         if (!refresh)
00839           touchIndexFile(info);
00840 
00841         return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
00842       }
00843       else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
00844       {
00845         MediaMounter media( url );
00846         RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
00847         bool refresh = false;
00848         if ( oldstatus.checksum() == newstatus.checksum() )
00849         {
00850           MIL << "repo has not changed" << endl;
00851           if ( policy == RefreshForced )
00852           {
00853             MIL << "refresh set to forced" << endl;
00854             refresh = true;
00855           }
00856         }
00857         else
00858         {
00859           MIL << "repo has changed, going to refresh" << endl;
00860           refresh = true;
00861         }
00862 
00863         if (!refresh)
00864           touchIndexFile(info);
00865 
00866         return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
00867       }
00868       else
00869       {
00870         ZYPP_THROW(RepoUnknownTypeException(info));
00871       }
00872     }
00873     catch ( const Exception &e )
00874     {
00875       ZYPP_CAUGHT(e);
00876       ERR << "refresh check failed for " << url << endl;
00877       ZYPP_RETHROW(e);
00878     }
00879 
00880     return REFRESH_NEEDED; // default
00881   }
00882 
00883   void RepoManager::refreshMetadata( const RepoInfo &info,
00884                                      RawMetadataRefreshPolicy policy,
00885                                      const ProgressData::ReceiverFnc & progress )
00886   {
00887     assert_alias(info);
00888     assert_urls(info);
00889 
00890     // we will throw this later if no URL checks out fine
00891     RepoException rexception(_("Valid metadata not found at specified URL(s)"));
00892 
00893     // try urls one by one
00894     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
00895     {
00896       try
00897       {
00898         Url url(*it);
00899 
00900         // check whether to refresh metadata
00901         // if the check fails for this url, it throws, so another url will be checked
00902         if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
00903           return;
00904 
00905         MIL << "Going to refresh metadata from " << url << endl;
00906 
00907         repo::RepoType repokind = info.type();
00908 
00909         // if the type is unknown, try probing.
00910         switch ( repokind.toEnum() )
00911         {
00912           case RepoType::NONE_e:
00913             // unknown, probe it
00914             repokind = probe( *it, info.path() );
00915 
00916             if (repokind.toEnum() != RepoType::NONE_e)
00917             {
00918               // Adjust the probed type in RepoInfo
00919               info.setProbedType( repokind ); // lazy init!
00920               //save probed type only for repos in system
00921               for_( it, repoBegin(), repoEnd() )
00922               {
00923                 if ( info.alias() == (*it).alias() )
00924                 {
00925                   RepoInfo modifiedrepo = info;
00926                   modifiedrepo.setType( repokind );
00927                   modifyRepository( info.alias(), modifiedrepo );
00928                   break;
00929                 }
00930               }
00931             }
00932           break;
00933           default:
00934           break;
00935         }
00936 
00937         Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
00938         filesystem::assert_dir(mediarootpath);
00939 
00940         // create temp dir as sibling of mediarootpath
00941         filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
00942 
00943         if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
00944              ( repokind.toEnum() == RepoType::YAST2_e ) )
00945         {
00946           MediaSetAccess media(url);
00947           shared_ptr<repo::Downloader> downloader_ptr;
00948 
00949           MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
00950 
00951           if ( repokind.toEnum() == RepoType::RPMMD_e )
00952             downloader_ptr.reset(new yum::Downloader(info));
00953           else
00954             downloader_ptr.reset( new susetags::Downloader(info) );
00955 
00962           for_( it, repoBegin(), repoEnd() )
00963           {
00964             Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
00965             if ( PathInfo(cachepath).isExist() )
00966               downloader_ptr->addCachePath(cachepath);
00967           }
00968 
00969           downloader_ptr->download( media, tmpdir.path() );
00970         }
00971         else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
00972         {
00973           MediaMounter media( url );
00974           RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
00975 
00976           Pathname productpath( tmpdir.path() / info.path() );
00977           filesystem::assert_dir( productpath );
00978           std::ofstream file( (productpath/"cookie").c_str() );
00979           if ( !file )
00980           {
00981             // TranslatorExplanation '%s' is a filename
00982             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() )));
00983           }
00984           file << url;
00985           if ( ! info.path().empty() && info.path() != "/" )
00986             file << " (" << info.path() << ")";
00987           file << endl;
00988           file << newstatus.checksum() << endl;
00989 
00990           file.close();
00991         }
00992         else
00993         {
00994           ZYPP_THROW(RepoUnknownTypeException());
00995         }
00996 
00997         // ok we have the metadata, now exchange
00998         // the contents
00999         filesystem::exchange( tmpdir.path(), mediarootpath );
01000 
01001         // we are done.
01002         return;
01003       }
01004       catch ( const Exception &e )
01005       {
01006         ZYPP_CAUGHT(e);
01007         ERR << "Trying another url..." << endl;
01008 
01009         // remember the exception caught for the *first URL*
01010         // if all other URLs fail, the rexception will be thrown with the
01011         // cause of the problem of the first URL remembered
01012         if (it == info.baseUrlsBegin())
01013           rexception.remember(e);
01014       }
01015     } // for every url
01016     ERR << "No more urls..." << endl;
01017     ZYPP_THROW(rexception);
01018   }
01019 
01021 
01022   void RepoManager::cleanMetadata( const RepoInfo &info,
01023                                    const ProgressData::ReceiverFnc & progressfnc )
01024   {
01025     ProgressData progress(100);
01026     progress.sendTo(progressfnc);
01027 
01028     filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
01029     progress.toMax();
01030   }
01031 
01032   void RepoManager::cleanPackages( const RepoInfo &info,
01033                                    const ProgressData::ReceiverFnc & progressfnc )
01034   {
01035     ProgressData progress(100);
01036     progress.sendTo(progressfnc);
01037 
01038     filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
01039     progress.toMax();
01040   }
01041 
01042   void RepoManager::buildCache( const RepoInfo &info,
01043                                 CacheBuildPolicy policy,
01044                                 const ProgressData::ReceiverFnc & progressrcv )
01045   {
01046     assert_alias(info);
01047     Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
01048     Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
01049 
01050     filesystem::assert_dir(_pimpl->options.repoCachePath);
01051     RepoStatus raw_metadata_status = metadataStatus(info);
01052     if ( raw_metadata_status.empty() )
01053     {
01054        /* if there is no cache at this point, we refresh the raw
01055           in case this is the first time - if it's !autorefresh,
01056           we may still refresh */
01057       refreshMetadata(info, RefreshIfNeeded, progressrcv );
01058       raw_metadata_status = metadataStatus(info);
01059     }
01060 
01061     bool needs_cleaning = false;
01062     if ( isCached( info ) )
01063     {
01064       MIL << info.alias() << " is already cached." << endl;
01065       RepoStatus cache_status = cacheStatus(info);
01066 
01067       if ( cache_status.checksum() == raw_metadata_status.checksum() )
01068       {
01069         MIL << info.alias() << " cache is up to date with metadata." << endl;
01070         if ( policy == BuildIfNeeded ) {
01071           return;
01072         }
01073         else {
01074           MIL << info.alias() << " cache rebuild is forced" << endl;
01075         }
01076       }
01077 
01078       needs_cleaning = true;
01079     }
01080 
01081     ProgressData progress(100);
01082     callback::SendReport<ProgressReport> report;
01083     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
01084     progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str()));
01085     progress.toMin();
01086 
01087     if (needs_cleaning)
01088     {
01089       cleanCache(info);
01090     }
01091 
01092     MIL << info.alias() << " building cache..." << info.type() << endl;
01093 
01094     Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
01095     filesystem::assert_dir(base);
01096     Pathname solvfile = base / "solv";
01097 
01098     // do we have type?
01099     repo::RepoType repokind = info.type();
01100 
01101     // if the type is unknown, try probing.
01102     switch ( repokind.toEnum() )
01103     {
01104       case RepoType::NONE_e:
01105         // unknown, probe the local metadata
01106         repokind = probe( productdatapath.asUrl() );
01107       break;
01108       default:
01109       break;
01110     }
01111 
01112     MIL << "repo type is " << repokind << endl;
01113 
01114     switch ( repokind.toEnum() )
01115     {
01116       case RepoType::RPMMD_e :
01117       case RepoType::YAST2_e :
01118       case RepoType::RPMPLAINDIR_e :
01119       {
01120         // Take care we unlink the solvfile on exception
01121         ManagedFile guard( solvfile, filesystem::unlink );
01122         scoped_ptr<MediaMounter> forPlainDirs;
01123 
01124         ExternalProgram::Arguments cmd;
01125         cmd.push_back( "repo2solv.sh" );
01126 
01127         // repo2solv expects -o as 1st arg!
01128         cmd.push_back( "-o" );
01129         cmd.push_back( solvfile.asString() );
01130 
01131         if ( repokind == RepoType::RPMPLAINDIR )
01132         {
01133           forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
01134           // recusive for plaindir as 2nd arg!
01135           cmd.push_back( "-R" );
01136           // FIXME this does only work form dir: URLs
01137           cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
01138         }
01139         else
01140           cmd.push_back( productdatapath.asString() );
01141 
01142         ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
01143         std::string errdetail;
01144 
01145         for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
01146           WAR << "  " << output;
01147           if ( errdetail.empty() ) {
01148             errdetail = prog.command();
01149             errdetail += '\n';
01150           }
01151           errdetail += output;
01152         }
01153 
01154         int ret = prog.close();
01155         if ( ret != 0 )
01156         {
01157           RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
01158           ex.remember( errdetail );
01159           ZYPP_THROW(ex);
01160         }
01161 
01162         // We keep it.
01163         guard.resetDispose();
01164       }
01165       break;
01166       default:
01167         ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
01168       break;
01169     }
01170     // update timestamp and checksum
01171     setCacheStatus(info, raw_metadata_status);
01172     MIL << "Commit cache.." << endl;
01173     progress.toMax();
01174   }
01175 
01177 
01178   repo::RepoType RepoManager::probe( const Url & url ) const
01179   { return probe( url, Pathname() ); }
01180 
01181   repo::RepoType RepoManager::probe( const Url & url, const Pathname & path  ) const
01182   {
01183     MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
01184 
01185     if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
01186     {
01187       // Handle non existing local directory in advance, as
01188       // MediaSetAccess does not support it.
01189       MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
01190       return repo::RepoType::NONE;
01191     }
01192 
01193     // prepare exception to be thrown if the type could not be determined
01194     // due to a media exception. We can't throw right away, because of some
01195     // problems with proxy servers returning an incorrect error
01196     // on ftp file-not-found(bnc #335906). Instead we'll check another types
01197     // before throwing.
01198 
01199     // TranslatorExplanation '%s' is an URL
01200     RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
01201     bool gotMediaException = false;
01202     try
01203     {
01204       MediaSetAccess access(url);
01205       try
01206       {
01207         if ( access.doesFileExist(path/"/repodata/repomd.xml") )
01208         {
01209           MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
01210           return repo::RepoType::RPMMD;
01211         }
01212       }
01213       catch ( const media::MediaException &e )
01214       {
01215         ZYPP_CAUGHT(e);
01216         DBG << "problem checking for repodata/repomd.xml file" << endl;
01217         enew.remember(e);
01218         gotMediaException = true;
01219       }
01220 
01221       try
01222       {
01223         if ( access.doesFileExist(path/"/content") )
01224         {
01225           MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
01226           return repo::RepoType::YAST2;
01227         }
01228       }
01229       catch ( const media::MediaException &e )
01230       {
01231         ZYPP_CAUGHT(e);
01232         DBG << "problem checking for content file" << endl;
01233         enew.remember(e);
01234         gotMediaException = true;
01235       }
01236 
01237       // if it is a non-downloading URL denoting a directory
01238       if ( ! url.schemeIsDownloading() )
01239       {
01240         MediaMounter media( url );
01241         if ( PathInfo(media.getPathName()/path).isDir() )
01242         {
01243           // allow empty dirs for now
01244           MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
01245           return repo::RepoType::RPMPLAINDIR;
01246         }
01247       }
01248     }
01249     catch ( const Exception &e )
01250     {
01251       ZYPP_CAUGHT(e);
01252       // TranslatorExplanation '%s' is an URL
01253       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
01254       enew.remember(e);
01255       ZYPP_THROW(enew);
01256     }
01257 
01258     if (gotMediaException)
01259       ZYPP_THROW(enew);
01260 
01261     MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
01262     return repo::RepoType::NONE;
01263   }
01264 
01266 
01267   void RepoManager::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
01268   {
01269     MIL << "Going to clean up garbage in cache dirs" << endl;
01270 
01271     ProgressData progress(300);
01272     progress.sendTo(progressrcv);
01273     progress.toMin();
01274 
01275     std::list<Pathname> cachedirs;
01276     cachedirs.push_back(_pimpl->options.repoRawCachePath);
01277     cachedirs.push_back(_pimpl->options.repoPackagesCachePath);
01278     cachedirs.push_back(_pimpl->options.repoSolvCachePath);
01279 
01280     for_( dir, cachedirs.begin(), cachedirs.end() )
01281     {
01282       if ( PathInfo(*dir).isExist() )
01283       {
01284         std::list<Pathname> entries;
01285         if ( filesystem::readdir( entries, *dir, false ) != 0 )
01286           // TranslatorExplanation '%s' is a pathname
01287           ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str())));
01288 
01289         unsigned sdircount   = entries.size();
01290         unsigned sdircurrent = 1;
01291         for_( subdir, entries.begin(), entries.end() )
01292         {
01293           // if it does not belong known repo, make it disappear
01294           bool found = false;
01295           for_( r, repoBegin(), repoEnd() )
01296             if ( subdir->basename() == r->escaped_alias() )
01297             { found = true; break; }
01298 
01299           if ( ! found )
01300             filesystem::recursive_rmdir( *subdir );
01301 
01302           progress.set( progress.val() + sdircurrent * 100 / sdircount );
01303           ++sdircurrent;
01304         }
01305       }
01306       else
01307         progress.set( progress.val() + 100 );
01308     }
01309     progress.toMax();
01310   }
01311 
01313 
01314   void RepoManager::cleanCache( const RepoInfo &info,
01315                                 const ProgressData::ReceiverFnc & progressrcv )
01316   {
01317     ProgressData progress(100);
01318     progress.sendTo(progressrcv);
01319     progress.toMin();
01320 
01321     MIL << "Removing raw metadata cache for " << info.alias() << endl;
01322     filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
01323 
01324     progress.toMax();
01325   }
01326 
01328 
01329   bool RepoManager::isCached( const RepoInfo &info ) const
01330   {
01331     return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
01332   }
01333 
01334   RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
01335   {
01336 
01337     Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
01338 
01339     return RepoStatus::fromCookieFile(cookiefile);
01340   }
01341 
01342   void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
01343   {
01344     Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
01345     filesystem::assert_dir(base);
01346     Pathname cookiefile = base / "cookie";
01347 
01348     status.saveToCookieFile(cookiefile);
01349   }
01350 
01351   void RepoManager::loadFromCache( const RepoInfo & info,
01352                                    const ProgressData::ReceiverFnc & progressrcv )
01353   {
01354     assert_alias(info);
01355     Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
01356 
01357     if ( ! PathInfo(solvfile).isExist() )
01358       ZYPP_THROW(RepoNotCachedException(info));
01359 
01360     try
01361     {
01362       Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info );
01363       // test toolversion in order to rebuild solv file in case
01364       // it was written by an old satsolver-tool parser.
01365       //
01366       // Known version strings used:
01367       //  - <no string>
01368       //  - "1.0"
01369       //
01370       sat::LookupRepoAttr toolversion( sat::SolvAttr::repositoryToolVersion, repo );
01371       if ( toolversion.begin().asString().empty() )
01372       {
01373         repo.eraseFromPool();
01374         ZYPP_THROW(Exception("Solv-file was created by old parser."));
01375       }
01376       // else: up-to-date (or even newer).
01377     }
01378     catch ( const Exception & exp )
01379     {
01380       ZYPP_CAUGHT( exp );
01381       MIL << "Try to handle exception by rebuilding the solv-file" << endl;
01382       cleanCache( info, progressrcv );
01383       buildCache( info, BuildIfNeeded, progressrcv );
01384 
01385       sat::Pool::instance().addRepoSolv( solvfile, info );
01386     }
01387   }
01388 
01390 
01391   void RepoManager::addRepository( const RepoInfo &info,
01392                                    const ProgressData::ReceiverFnc & progressrcv )
01393   {
01394     assert_alias(info);
01395 
01396     ProgressData progress(100);
01397     callback::SendReport<ProgressReport> report;
01398     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
01399     progress.name(str::form(_("Adding repository '%s'"), info.label().c_str()));
01400     progress.toMin();
01401 
01402     MIL << "Try adding repo " << info << endl;
01403 
01404     RepoInfo tosave = info;
01405     if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
01406         ZYPP_THROW(RepoAlreadyExistsException(info));
01407 
01408     // check the first url for now
01409     if ( _pimpl->options.probe )
01410     {
01411       DBG << "unknown repository type, probing" << endl;
01412 
01413       RepoType probedtype;
01414       probedtype = probe( *tosave.baseUrlsBegin(), info.path() );
01415       if ( tosave.baseUrlsSize() > 0 )
01416       {
01417         if ( probedtype == RepoType::NONE )
01418           ZYPP_THROW(RepoUnknownTypeException());
01419         else
01420           tosave.setType(probedtype);
01421       }
01422     }
01423 
01424     progress.set(50);
01425 
01426     // assert the directory exists
01427     filesystem::assert_dir(_pimpl->options.knownReposPath);
01428 
01429     Pathname repofile = _pimpl->generateNonExistingName(
01430         _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
01431     // now we have a filename that does not exists
01432     MIL << "Saving repo in " << repofile << endl;
01433 
01434     std::ofstream file(repofile.c_str());
01435     if (!file)
01436     {
01437       // TranslatorExplanation '%s' is a filename
01438       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
01439     }
01440 
01441     tosave.dumpAsIniOn(file);
01442     tosave.setFilepath(repofile);
01443     tosave.setMetadataPath( metadataPath( tosave ) );
01444     tosave.setPackagesPath( packagesPath( tosave ) );
01445     {
01446       // We chould fix the API as we must injet those paths
01447       // into the repoinfo in order to keep it usable.
01448       RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
01449       oinfo.setMetadataPath( metadataPath( tosave ) );
01450       oinfo.setPackagesPath( packagesPath( tosave ) );
01451     }
01452     _pimpl->repos.insert(tosave);
01453 
01454     progress.set(90);
01455 
01456     // check for credentials in Urls
01457     bool havePasswords = false;
01458     for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
01459       if ( urlit->hasCredentialsInAuthority() )
01460       {
01461         havePasswords = true;
01462         break;
01463       }
01464     // save the credentials
01465     if ( havePasswords )
01466     {
01467       media::CredentialManager cm(
01468           media::CredManagerOptions(_pimpl->options.rootDir) );
01469 
01470       for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
01471         if (urlit->hasCredentialsInAuthority())
01473           cm.saveInUser(media::AuthData(*urlit));
01474     }
01475 
01476     HistoryLog().addRepository(tosave);
01477 
01478     progress.toMax();
01479     MIL << "done" << endl;
01480   }
01481 
01482   void RepoManager::addRepositories( const Url &url,
01483                                      const ProgressData::ReceiverFnc & progressrcv )
01484   {
01485     std::list<RepoInfo> repos = readRepoFile(url);
01486     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
01487           it != repos.end();
01488           ++it )
01489     {
01490       // look if the alias is in the known repos.
01491       for_ ( kit, repoBegin(), repoEnd() )
01492       {
01493         if ( (*it).alias() == (*kit).alias() )
01494         {
01495           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
01496           ZYPP_THROW(RepoAlreadyExistsException(*it));
01497         }
01498       }
01499     }
01500 
01501     std::string filename = Pathname(url.getPathName()).basename();
01502 
01503     if ( filename == Pathname() )
01504     {
01505       // TranslatorExplanation '%s' is an URL
01506       ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
01507     }
01508 
01509     // assert the directory exists
01510     filesystem::assert_dir(_pimpl->options.knownReposPath);
01511 
01512     Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
01513     // now we have a filename that does not exists
01514     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
01515 
01516     std::ofstream file(repofile.c_str());
01517     if (!file)
01518     {
01519       // TranslatorExplanation '%s' is a filename
01520       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
01521     }
01522 
01523     for ( std::list<RepoInfo>::iterator it = repos.begin();
01524           it != repos.end();
01525           ++it )
01526     {
01527       MIL << "Saving " << (*it).alias() << endl;
01528       it->setFilepath(repofile.asString());
01529       it->dumpAsIniOn(file);
01530       _pimpl->repos.insert(*it);
01531 
01532       HistoryLog(_pimpl->options.rootDir).addRepository(*it);
01533     }
01534 
01535     MIL << "done" << endl;
01536   }
01537 
01539 
01540   void RepoManager::removeRepository( const RepoInfo & info,
01541                                       const ProgressData::ReceiverFnc & progressrcv)
01542   {
01543     ProgressData progress;
01544     callback::SendReport<ProgressReport> report;
01545     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
01546     progress.name(str::form(_("Removing repository '%s'"), info.label().c_str()));
01547 
01548     MIL << "Going to delete repo " << info.alias() << endl;
01549 
01550     for_( it, repoBegin(), repoEnd() )
01551     {
01552       // they can be the same only if the provided is empty, that means
01553       // the provided repo has no alias
01554       // then skip
01555       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
01556         continue;
01557 
01558       // TODO match by url
01559 
01560       // we have a matcing repository, now we need to know
01561       // where it does come from.
01562       RepoInfo todelete = *it;
01563       if (todelete.filepath().empty())
01564       {
01565         ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
01566       }
01567       else
01568       {
01569         // figure how many repos are there in the file:
01570         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
01571         if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
01572         {
01573           // easy, only this one, just delete the file
01574           if ( filesystem::unlink(todelete.filepath()) != 0 )
01575           {
01576             // TranslatorExplanation '%s' is a filename
01577             ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
01578           }
01579           MIL << todelete.alias() << " sucessfully deleted." << endl;
01580         }
01581         else
01582         {
01583           // there are more repos in the same file
01584           // write them back except the deleted one.
01585           //TmpFile tmp;
01586           //std::ofstream file(tmp.path().c_str());
01587 
01588           // assert the directory exists
01589           filesystem::assert_dir(todelete.filepath().dirname());
01590 
01591           std::ofstream file(todelete.filepath().c_str());
01592           if (!file)
01593           {
01594             // TranslatorExplanation '%s' is a filename
01595             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
01596           }
01597           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
01598                 fit != filerepos.end();
01599                 ++fit )
01600           {
01601             if ( (*fit).alias() != todelete.alias() )
01602               (*fit).dumpAsIniOn(file);
01603           }
01604         }
01605 
01606         CombinedProgressData subprogrcv(progress, 70);
01607         CombinedProgressData cleansubprogrcv(progress, 30);
01608         // now delete it from cache
01609         if ( isCached(todelete) )
01610           cleanCache( todelete, subprogrcv);
01611         // now delete metadata (#301037)
01612         cleanMetadata( todelete, cleansubprogrcv);
01613         _pimpl->repos.erase(todelete);
01614         MIL << todelete.alias() << " sucessfully deleted." << endl;
01615         HistoryLog(_pimpl->options.rootDir).removeRepository(todelete);
01616         return;
01617       } // else filepath is empty
01618 
01619     }
01620     // should not be reached on a sucess workflow
01621     ZYPP_THROW(RepoNotFoundException(info));
01622   }
01623 
01625 
01626   void RepoManager::modifyRepository( const std::string &alias,
01627                                       const RepoInfo & newinfo_r,
01628                                       const ProgressData::ReceiverFnc & progressrcv )
01629   {
01630     RepoInfo toedit = getRepositoryInfo(alias);
01631     RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
01632 
01633     // check if the new alias already exists when renaming the repo
01634     if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
01635     {
01636       ZYPP_THROW(RepoAlreadyExistsException(newinfo));
01637     }
01638 
01639     if (toedit.filepath().empty())
01640     {
01641       ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
01642     }
01643     else
01644     {
01645       // figure how many repos are there in the file:
01646       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
01647 
01648       // there are more repos in the same file
01649       // write them back except the deleted one.
01650       //TmpFile tmp;
01651       //std::ofstream file(tmp.path().c_str());
01652 
01653       // assert the directory exists
01654       filesystem::assert_dir(toedit.filepath().dirname());
01655 
01656       std::ofstream file(toedit.filepath().c_str());
01657       if (!file)
01658       {
01659         // TranslatorExplanation '%s' is a filename
01660         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
01661       }
01662       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
01663             fit != filerepos.end();
01664             ++fit )
01665       {
01666           // if the alias is different, dump the original
01667           // if it is the same, dump the provided one
01668           if ( (*fit).alias() != toedit.alias() )
01669             (*fit).dumpAsIniOn(file);
01670           else
01671             newinfo.dumpAsIniOn(file);
01672       }
01673 
01674       newinfo.setFilepath(toedit.filepath());
01675       _pimpl->repos.erase(toedit);
01676       _pimpl->repos.insert(newinfo);
01677       HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
01678       MIL << "repo " << alias << " modified" << endl;
01679     }
01680   }
01681 
01683 
01684   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
01685                                            const ProgressData::ReceiverFnc & progressrcv )
01686   {
01687     RepoInfo info;
01688     info.setAlias(alias);
01689     RepoConstIterator it = _pimpl->repos.find( info );
01690     if( it == repoEnd() )
01691       ZYPP_THROW(RepoNotFoundException(info));
01692     else
01693       return *it;
01694   }
01695 
01697 
01698   RepoInfo RepoManager::getRepositoryInfo( const Url & url,
01699                                            const url::ViewOption & urlview,
01700                                            const ProgressData::ReceiverFnc & progressrcv )
01701   {
01702     for_( it, repoBegin(), repoEnd() )
01703     {
01704       for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
01705           urlit != (*it).baseUrlsEnd();
01706           ++urlit)
01707       {
01708         if ((*urlit).asString(urlview) == url.asString(urlview))
01709           return *it;
01710       }
01711     }
01712     RepoInfo info;
01713     info.setBaseUrl(url);
01714     ZYPP_THROW(RepoNotFoundException(info));
01715   }
01716 
01718   //
01719   // Services
01720   //
01722 
01723   bool RepoManager::serviceEmpty() const
01724   { return _pimpl->services.empty(); }
01725 
01726   RepoManager::ServiceSizeType RepoManager::serviceSize() const
01727   { return _pimpl->services.size(); }
01728 
01729   RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
01730   { return _pimpl->services.begin(); }
01731 
01732   RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
01733   { return _pimpl->services.end(); }
01734 
01735   ServiceInfo RepoManager::getService( const std::string & alias ) const
01736   {
01737     for_( it, serviceBegin(), serviceEnd() )
01738       if ( it->alias() == alias )
01739         return *it;
01740     return ServiceInfo::noService;
01741   }
01742 
01743   bool RepoManager::hasService( const std::string & alias ) const
01744   {
01745     for_( it, serviceBegin(), serviceEnd() )
01746       if ( it->alias() == alias )
01747         return true;
01748     return false;
01749   }
01750 
01752 
01753   void RepoManager::addService( const std::string & alias, const Url & url )
01754   {
01755     addService( ServiceInfo(alias, url) );
01756   }
01757 
01758   void RepoManager::addService( const ServiceInfo & service )
01759   {
01760     assert_alias( service );
01761 
01762     // check if service already exists
01763     if ( hasService( service.alias() ) )
01764       ZYPP_THROW( ServiceAlreadyExistsException( service ) );
01765 
01766     // Writable ServiceInfo is needed to save the location
01767     // of the .service file. Finaly insert into the service list.
01768     ServiceInfo toSave( service );
01769     _pimpl->saveService( toSave );
01770     _pimpl->services.insert( toSave );
01771 
01772     // check for credentials in Url (username:password, not ?credentials param)
01773     if ( toSave.url().hasCredentialsInAuthority() )
01774     {
01775       media::CredentialManager cm(
01776           media::CredManagerOptions(_pimpl->options.rootDir) );
01777 
01779       cm.saveInUser(media::AuthData(toSave.url()));
01780     }
01781 
01782     MIL << "added service " << toSave.alias() << endl;
01783   }
01784 
01786 
01787   void RepoManager::removeService( const std::string & alias )
01788   {
01789     MIL << "Going to delete repo " << alias << endl;
01790 
01791     const ServiceInfo & service = getService( alias );
01792 
01793     Pathname location = service.filepath();
01794     if( location.empty() )
01795     {
01796       ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
01797     }
01798 
01799     ServiceSet tmpSet;
01800     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
01801 
01802     // only one service definition in the file
01803     if ( tmpSet.size() == 1 )
01804     {
01805       if ( filesystem::unlink(location) != 0 )
01806       {
01807         // TranslatorExplanation '%s' is a filename
01808         ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
01809       }
01810       MIL << alias << " sucessfully deleted." << endl;
01811     }
01812     else
01813     {
01814       filesystem::assert_dir(location.dirname());
01815 
01816       std::ofstream file(location.c_str());
01817       if( !file )
01818       {
01819         // TranslatorExplanation '%s' is a filename
01820         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
01821       }
01822 
01823       for_(it, tmpSet.begin(), tmpSet.end())
01824       {
01825         if( it->alias() != alias )
01826           it->dumpAsIniOn(file);
01827       }
01828 
01829       MIL << alias << " sucessfully deleted from file " << location <<  endl;
01830     }
01831 
01832     // now remove all repositories added by this service
01833     RepoCollector rcollector;
01834     getRepositoriesInService( alias,
01835       boost::make_function_output_iterator(
01836           bind( &RepoCollector::collect, &rcollector, _1 ) ) );
01837     // cannot do this directly in getRepositoriesInService - would invalidate iterators
01838     for_(rit, rcollector.repos.begin(), rcollector.repos.end())
01839       removeRepository(*rit);
01840   }
01841 
01842   void RepoManager::removeService( const ServiceInfo & service )
01843   { removeService(service.alias()); }
01844 
01846 
01847   void RepoManager::refreshServices()
01848   {
01849     // copy the set of services since refreshService
01850     // can eventually invalidate the iterator
01851     ServiceSet services( serviceBegin(), serviceEnd() );
01852     for_( it, services.begin(), services.end() )
01853     {
01854       if ( !it->enabled() )
01855         continue;
01856 
01857       refreshService(*it);
01858     }
01859   }
01860 
01861   void RepoManager::refreshService( const ServiceInfo & service )
01862   { refreshService( service.alias() ); }
01863 
01864   void RepoManager::refreshService( const std::string & alias )
01865   {
01866     ServiceInfo service( getService( alias ) );
01867     assert_alias( service );
01868     assert_url( service );
01869     // NOTE: It might be necessary to modify and rewrite the service info.
01870     // Either when probing the type, or when adjusting the repositories
01871     // enable/disable state.:
01872     bool serviceModified = false;
01873     MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
01874 
01876 
01877     // if the type is unknown, try probing.
01878     if ( service.type() == repo::ServiceType::NONE )
01879     {
01880       repo::ServiceType type = probeService( service.url() );
01881       if ( type != ServiceType::NONE )
01882       {
01883         service.setProbedType( type ); // lazy init!
01884         serviceModified = true;
01885       }
01886     }
01887 
01888     // repoindex.xml must be fetched always without using cookies (bnc #573897)
01889     Url serviceUrl( service.url() );
01890     serviceUrl.setQueryParam( "cookies", "0" );
01891 
01892     // download the repo index file
01893     media::MediaManager mediamanager;
01894     media::MediaAccessId mid = mediamanager.open( serviceUrl );
01895     mediamanager.attach( mid );
01896     mediamanager.provideFile( mid, "repo/repoindex.xml" );
01897     Pathname path = mediamanager.localPath(mid, "repo/repoindex.xml" );
01898 
01899     // get target distro identifier
01900     std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro;
01901     if ( servicesTargetDistro.empty() && getZYpp()->getTarget() )
01902       servicesTargetDistro = getZYpp()->target()->targetDistribution();
01903     DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
01904 
01905     // parse it
01906     RepoCollector collector(servicesTargetDistro);
01907     parser::RepoindexFileReader reader( path, bind( &RepoCollector::collect, &collector, _1 ) );
01908     mediamanager.release( mid );
01909     mediamanager.close( mid );
01910 
01911 
01912     // set service alias and base url for all collected repositories
01913     for_( it, collector.repos.begin(), collector.repos.end() )
01914     {
01915       // if the repo url was not set by the repoindex parser, set service's url
01916       Url url;
01917 
01918       if ( it->baseUrlsEmpty() )
01919         url = service.url();
01920       else
01921       {
01922         // service repo can contain only one URL now, so no need to iterate.
01923         url = *it->baseUrlsBegin();
01924       }
01925 
01926       // libzypp currently has problem with separate url + path handling
01927       // so just append the path to the baseurl
01928       if ( !it->path().empty() )
01929       {
01930         Pathname path(url.getPathName());
01931         path /= it->path();
01932         url.setPathName( path.asString() );
01933         it->setPath("");
01934       }
01935 
01936       // Prepend service alias:
01937       it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
01938 
01939       // save the url
01940       it->setBaseUrl( url );
01941       // set refrence to the parent service
01942       it->setService( service.alias() );
01943     }
01944 
01946     // Now compare collected repos with the ones in the system...
01947     //
01948     RepoInfoList oldRepos;
01949     getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
01950 
01951     // find old repositories to remove...
01952     for_( it, oldRepos.begin(), oldRepos.end() )
01953     {
01954       if ( ! foundAliasIn( it->alias(), collector.repos ) )
01955       {
01956         if ( it->enabled() && ! service.repoToDisableFind( it->alias() ) )
01957         {
01958           DBG << "Service removes enabled repo " << it->alias() << endl;
01959           service.addRepoToEnable( it->alias() );
01960           serviceModified = true;
01961         }
01962         else
01963         {
01964           DBG << "Service removes disabled repo " << it->alias() << endl;
01965         }
01966         removeRepository( *it );
01967       }
01968     }
01969 
01971     // create missing repositories and modify exising ones if needed...
01972     for_( it, collector.repos.begin(), collector.repos.end() )
01973     {
01974       // Service explicitly requests the repo being enabled?
01975       // Service explicitly requests the repo being disabled?
01976       // And hopefully not both ;) If so, enable wins.
01977       bool beEnabled = service.repoToEnableFind( it->alias() );
01978       bool beDisabled = service.repoToDisableFind( it->alias() );
01979 
01980       if ( beEnabled )
01981       {
01982         // Remove from enable request list.
01983         // NOTE: repoToDisable is handled differently.
01984         //       It gets cleared on each refresh.
01985         service.delRepoToEnable( it->alias() );
01986         serviceModified = true;
01987       }
01988 
01989       RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
01990       if ( oldRepo == oldRepos.end() )
01991       {
01992         // Not found in oldRepos ==> a new repo to add
01993 
01994         // Make sure the service repo is created with the
01995         // appropriate enable and autorefresh true.
01996         it->setEnabled( beEnabled );
01997         it->setAutorefresh( true );
01998 
01999         // At that point check whether a repo with the same alias
02000         // exists outside this service. Maybe forcefully re-alias
02001         // the existing repo?
02002         DBG << "Service adds repo " << it->alias() << " " << (beEnabled?"enabled":"disabled") << endl;
02003         addRepository( *it );
02004 
02005         // save repo credentials
02006         // ma@: task for modifyRepository?
02007       }
02008       else
02009       {
02010         // ==> an exising repo to check
02011         bool oldRepoModified = false;
02012 
02013         // changed enable?
02014         if ( beEnabled )
02015         {
02016           if ( ! oldRepo->enabled() )
02017           {
02018             DBG << "Service repo " << it->alias() << " gets enabled" << endl;
02019             oldRepo->setEnabled( true );
02020             oldRepoModified = true;
02021           }
02022           else
02023           {
02024             DBG << "Service repo " << it->alias() << " stays enabled" << endl;
02025           }
02026         }
02027         else if ( beDisabled )
02028         {
02029           if ( oldRepo->enabled() )
02030           {
02031             DBG << "Service repo " << it->alias() << " gets disabled" << endl;
02032             oldRepo->setEnabled( false );
02033             oldRepoModified = true;
02034           }
02035           else
02036           {
02037             DBG << "Service repo " << it->alias() << " stays disabled" << endl;
02038           }
02039         }
02040         else
02041         {
02042           DBG << "Service repo " << it->alias() << " stays " <<  (oldRepo->enabled()?"enabled":"disabled") << endl;
02043         }
02044 
02045         // changed url?
02046         // service repo can contain only one URL now, so no need to iterate.
02047         if ( oldRepo->url() != it->url() )
02048         {
02049           DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
02050           oldRepo->setBaseUrl( it->url() );
02051           oldRepoModified = true;
02052         }
02053 
02054         // save if modified:
02055         if ( oldRepoModified )
02056         {
02057           modifyRepository( oldRepo->alias(), *oldRepo );
02058         }
02059       }
02060     }
02061 
02062     // Unlike reposToEnable, reposToDisable is always cleared after refresh.
02063     if ( ! service.reposToDisableEmpty() )
02064     {
02065       service.clearReposToDisable();
02066       serviceModified = true;
02067     }
02068 
02070     // save service if modified:
02071     if ( serviceModified )
02072     {
02073       // write out modified service file.
02074       modifyService( service.alias(), service );
02075     }
02076   }
02077 
02079 
02080   void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & service)
02081   {
02082     MIL << "Going to modify service " << oldAlias << endl;
02083 
02084     const ServiceInfo & oldService = getService(oldAlias);
02085 
02086     Pathname location = oldService.filepath();
02087     if( location.empty() )
02088     {
02089       ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
02090     }
02091 
02092     // remember: there may multiple services being defined in one file:
02093     ServiceSet tmpSet;
02094     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
02095 
02096     filesystem::assert_dir(location.dirname());
02097     std::ofstream file(location.c_str());
02098     for_(it, tmpSet.begin(), tmpSet.end())
02099     {
02100       if( *it != oldAlias )
02101         it->dumpAsIniOn(file);
02102     }
02103     service.dumpAsIniOn(file);
02104     file.close();
02105 
02106     _pimpl->services.erase(oldAlias);
02107     _pimpl->services.insert(service);
02108 
02109     // changed properties affecting also repositories
02110     if( oldAlias != service.alias()                    // changed alias
02111         || oldService.enabled() != service.enabled()   // changed enabled status
02112       )
02113     {
02114       std::vector<RepoInfo> toModify;
02115       getRepositoriesInService(oldAlias, std::back_inserter(toModify));
02116       for_( it, toModify.begin(), toModify.end() )
02117       {
02118         if (oldService.enabled() && !service.enabled())
02119           it->setEnabled(false);
02120         else if (!oldService.enabled() && service.enabled())
02121         {
02124         }
02125         else
02126           it->setService(service.alias());
02127         modifyRepository(it->alias(), *it);
02128       }
02129     }
02130 
02132   }
02133 
02135 
02136   repo::ServiceType RepoManager::probeService( const Url &url ) const
02137   {
02138     try
02139     {
02140       MediaSetAccess access(url);
02141       if ( access.doesFileExist("/repo/repoindex.xml") )
02142         return repo::ServiceType::RIS;
02143     }
02144     catch ( const media::MediaException &e )
02145     {
02146       ZYPP_CAUGHT(e);
02147       // TranslatorExplanation '%s' is an URL
02148       RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
02149       enew.remember(e);
02150       ZYPP_THROW(enew);
02151     }
02152     catch ( const Exception &e )
02153     {
02154       ZYPP_CAUGHT(e);
02155       // TranslatorExplanation '%s' is an URL
02156       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
02157       enew.remember(e);
02158       ZYPP_THROW(enew);
02159     }
02160 
02161     return repo::ServiceType::NONE;
02162   }
02163 
02165 
02166   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
02167   {
02168     return str << *obj._pimpl;
02169   }
02170 
02172 } // namespace zypp
Generated on Fri Mar 2 09:45:53 2012 for libzypp by  doxygen 1.6.3