libzypp 8.13.6

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