libzypp 9.41.1

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