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

doxygen