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