Imported Upstream version 17.0.0
[platform/upstream/libzypp.git] / zypp / RepoManager.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/RepoManager.cc
10  *
11 */
12
13 #include <cstdlib>
14 #include <iostream>
15 #include <fstream>
16 #include <sstream>
17 #include <list>
18 #include <map>
19 #include <algorithm>
20
21 #include <solv/solvversion.h>
22
23 #include "zypp/base/InputStream.h"
24 #include "zypp/base/LogTools.h"
25 #include "zypp/base/Gettext.h"
26 #include "zypp/base/DefaultIntegral.h"
27 #include "zypp/base/Function.h"
28 #include "zypp/base/Regex.h"
29 #include "zypp/PathInfo.h"
30 #include "zypp/TmpPath.h"
31
32 #include "zypp/ServiceInfo.h"
33 #include "zypp/repo/RepoException.h"
34 #include "zypp/RepoManager.h"
35
36 #include "zypp/media/MediaManager.h"
37 #include "zypp/media/CredentialManager.h"
38 #include "zypp/MediaSetAccess.h"
39 #include "zypp/ExternalProgram.h"
40 #include "zypp/ManagedFile.h"
41
42 #include "zypp/parser/RepoFileReader.h"
43 #include "zypp/parser/ServiceFileReader.h"
44 #include "zypp/repo/ServiceRepos.h"
45 #include "zypp/repo/yum/Downloader.h"
46 #include "zypp/repo/susetags/Downloader.h"
47 #include "zypp/repo/PluginServices.h"
48
49 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
50 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance
51 #include "zypp/HistoryLog.h" // to write history :O)
52
53 #include "zypp/ZYppCallbacks.h"
54
55 #include "sat/Pool.h"
56
57 using std::endl;
58 using std::string;
59 using namespace zypp::repo;
60
61 #define OPT_PROGRESS const ProgressData::ReceiverFnc & = ProgressData::ReceiverFnc()
62
63 ///////////////////////////////////////////////////////////////////
64 namespace zypp
65 {
66
67   ///////////////////////////////////////////////////////////////////
68   namespace env
69   {
70     /** To trigger appdata refresh unconditionally */
71     inline bool ZYPP_PLUGIN_APPDATA_FORCE_COLLECT()
72     {
73       const char * env = getenv("ZYPP_PLUGIN_APPDATA_FORCE_COLLECT");
74       return( env && str::strToBool( env, true ) );
75     }
76   } // namespace env
77   ///////////////////////////////////////////////////////////////////
78
79   ///////////////////////////////////////////////////////////////////
80   namespace
81   {
82     ///////////////////////////////////////////////////////////////////
83     /// \class UrlCredentialExtractor
84     /// \brief Extract credentials in \ref Url authority and store them via \ref CredentialManager.
85     ///
86     /// Lazy init CredentialManager and save collected credentials when
87     /// going out of scope.
88     ///
89     /// Methods return whether a password has been collected/extracted.
90     ///
91     /// \code
92     /// UrlCredentialExtractor( "/rootdir" ).collect( oneUrlOrUrlContainer );
93     /// \endcode
94     /// \code
95     /// {
96     ///   UrlCredentialExtractor extractCredentials;
97     ///   extractCredentials.collect( oneUrlOrUrlContainer );
98     ///   extractCredentials.extract( oneMoreUrlOrUrlContainer );
99     ///   ....
100     /// }
101     /// \endcode
102     ///
103     class UrlCredentialExtractor
104     {
105     public:
106       UrlCredentialExtractor( Pathname & root_r )
107       : _root( root_r )
108       {}
109
110       ~UrlCredentialExtractor()
111       { if ( _cmPtr ) _cmPtr->save(); }
112
113       /** Remember credentials stored in URL authority leaving the password in \a url_r. */
114       bool collect( const Url & url_r )
115       {
116         bool ret = url_r.hasCredentialsInAuthority();
117         if ( ret )
118         {
119           if ( !_cmPtr ) _cmPtr.reset( new media::CredentialManager( _root ) );
120           _cmPtr->addUserCred( url_r );
121         }
122         return ret;
123       }
124       /** \overload operating on Url container */
125       template<class TContainer>
126       bool collect( const TContainer & urls_r )
127       { bool ret = false; for ( const Url & url : urls_r ) { if ( collect( url ) && !ret ) ret = true; } return ret; }
128
129       /** Remember credentials stored in URL authority stripping the passowrd from \a url_r. */
130       bool extract( Url & url_r )
131       {
132         bool ret = collect( url_r );
133         if ( ret )
134           url_r.setPassword( std::string() );
135         return ret;
136       }
137       /** \overload operating on Url container */
138       template<class TContainer>
139       bool extract( TContainer & urls_r )
140       { bool ret = false; for ( Url & url : urls_r ) { if ( extract( url ) && !ret ) ret = true; } return ret; }
141
142     private:
143       const Pathname & _root;
144       scoped_ptr<media::CredentialManager> _cmPtr;
145     };
146   } // namespace
147   ///////////////////////////////////////////////////////////////////
148
149   ///////////////////////////////////////////////////////////////////
150   namespace
151   {
152     /** Simple media mounter to access non-downloading URLs e.g. for non-local plaindir repos.
153      * \ingroup g_RAII
154      */
155     class MediaMounter
156     {
157       public:
158         /** Ctor provides media access. */
159         MediaMounter( const Url & url_r )
160         {
161           media::MediaManager mediamanager;
162           _mid = mediamanager.open( url_r );
163           mediamanager.attach( _mid );
164         }
165
166         /** Ctor releases the media. */
167         ~MediaMounter()
168         {
169           media::MediaManager mediamanager;
170           mediamanager.release( _mid );
171           mediamanager.close( _mid );
172         }
173
174         /** Convert a path relative to the media into an absolute path.
175          *
176          * Called without argument it returns the path to the medias root directory.
177         */
178         Pathname getPathName( const Pathname & path_r = Pathname() ) const
179         {
180           media::MediaManager mediamanager;
181           return mediamanager.localPath( _mid, path_r );
182         }
183
184       private:
185         media::MediaAccessId _mid;
186     };
187     ///////////////////////////////////////////////////////////////////
188
189     /** Check if alias_r is present in repo/service container. */
190     template <class Iterator>
191     inline bool foundAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r )
192     {
193       for_( it, begin_r, end_r )
194         if ( it->alias() == alias_r )
195           return true;
196       return false;
197     }
198     /** \overload */
199     template <class Container>
200     inline bool foundAliasIn( const std::string & alias_r, const Container & cont_r )
201     { return foundAliasIn( alias_r, cont_r.begin(), cont_r.end() ); }
202
203     /** Find alias_r in repo/service container. */
204     template <class Iterator>
205     inline Iterator findAlias( const std::string & alias_r, Iterator begin_r, Iterator end_r )
206     {
207       for_( it, begin_r, end_r )
208         if ( it->alias() == alias_r )
209           return it;
210       return end_r;
211     }
212     /** \overload */
213     template <class Container>
214     inline typename Container::iterator findAlias( const std::string & alias_r, Container & cont_r )
215     { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
216     /** \overload */
217     template <class Container>
218     inline typename Container::const_iterator findAlias( const std::string & alias_r, const Container & cont_r )
219     { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
220
221
222     /** \short Generate a related filename from a repo/service infos alias */
223     inline std::string filenameFromAlias( const std::string & alias_r, const std::string & stem_r )
224     {
225       std::string filename( alias_r );
226       // replace slashes with underscores
227       str::replaceAll( filename, "/", "_" );
228
229       filename = Pathname(filename).extend("."+stem_r).asString();
230       MIL << "generating filename for " << stem_r << " [" << alias_r << "] : '" << filename << "'" << endl;
231       return filename;
232     }
233
234     /**
235      * \short Simple callback to collect the results
236      *
237      * Classes like RepoFileReader call the callback
238      * once per each repo in a file.
239      *
240      * Passing this functor as callback, you can collect
241      * all results at the end, without dealing with async
242      * code.
243      *
244      * If targetDistro is set, all repos with non-empty RepoInfo::targetDistribution()
245      * will be skipped.
246      *
247      * \todo do this through a separate filter
248      */
249     struct RepoCollector : private base::NonCopyable
250     {
251       RepoCollector()
252       {}
253
254       RepoCollector(const std::string & targetDistro_)
255         : targetDistro(targetDistro_)
256       {}
257
258       bool collect( const RepoInfo &repo )
259       {
260         // skip repositories meant for other distros than specified
261         if (!targetDistro.empty()
262             && !repo.targetDistribution().empty()
263             && repo.targetDistribution() != targetDistro)
264         {
265           MIL
266             << "Skipping repository meant for '" << repo.targetDistribution()
267             << "' distribution (current distro is '"
268             << targetDistro << "')." << endl;
269
270           return true;
271         }
272
273         repos.push_back(repo);
274         return true;
275       }
276
277       RepoInfoList repos;
278       std::string targetDistro;
279     };
280     ////////////////////////////////////////////////////////////////////////////
281
282     /**
283      * Reads RepoInfo's from a repo file.
284      *
285      * \param file pathname of the file to read.
286      */
287     std::list<RepoInfo> repositories_in_file( const Pathname & file )
288     {
289       MIL << "repo file: " << file << endl;
290       RepoCollector collector;
291       parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
292       return std::move(collector.repos);
293     }
294
295     ////////////////////////////////////////////////////////////////////////////
296
297     /**
298      * \short List of RepoInfo's from a directory
299      *
300      * Goes trough every file ending with ".repo" in a directory and adds all
301      * RepoInfo's contained in that file.
302      *
303      * \param dir pathname of the directory to read.
304      */
305     std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
306     {
307       MIL << "directory " << dir << endl;
308       std::list<RepoInfo> repos;
309       bool nonroot( geteuid() != 0 );
310       if ( nonroot && ! PathInfo(dir).userMayRX() )
311       {
312         JobReport::warning( str::FormatNAC(_("Cannot read repo directory '%1%': Permission denied")) % dir );
313       }
314       else
315       {
316         std::list<Pathname> entries;
317         if ( filesystem::readdir( entries, dir, false ) != 0 )
318         {
319           // TranslatorExplanation '%s' is a pathname
320           ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
321         }
322
323         str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
324         for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
325         {
326           if ( str::regex_match(it->extension(), allowedRepoExt) )
327           {
328             if ( nonroot && ! PathInfo(*it).userMayR() )
329             {
330               JobReport::warning( str::FormatNAC(_("Cannot read repo file '%1%': Permission denied")) % *it );
331             }
332             else
333             {
334               const std::list<RepoInfo> & tmp( repositories_in_file( *it ) );
335               repos.insert( repos.end(), tmp.begin(), tmp.end() );
336             }
337           }
338         }
339       }
340       return repos;
341     }
342
343     ////////////////////////////////////////////////////////////////////////////
344
345     inline void assert_alias( const RepoInfo & info )
346     {
347       if ( info.alias().empty() )
348         ZYPP_THROW( RepoNoAliasException( info ) );
349       // bnc #473834. Maybe we can match the alias against a regex to define
350       // and check for valid aliases
351       if ( info.alias()[0] == '.')
352         ZYPP_THROW(RepoInvalidAliasException(
353           info, _("Repository alias cannot start with dot.")));
354     }
355
356     inline void assert_alias( const ServiceInfo & info )
357     {
358       if ( info.alias().empty() )
359         ZYPP_THROW( ServiceNoAliasException( info ) );
360       // bnc #473834. Maybe we can match the alias against a regex to define
361       // and check for valid aliases
362       if ( info.alias()[0] == '.')
363         ZYPP_THROW(ServiceInvalidAliasException(
364           info, _("Service alias cannot start with dot.")));
365     }
366
367     ////////////////////////////////////////////////////////////////////////////
368
369     inline void assert_urls( const RepoInfo & info )
370     {
371       if ( info.baseUrlsEmpty() )
372         ZYPP_THROW( RepoNoUrlException( info ) );
373     }
374
375     inline void assert_url( const ServiceInfo & info )
376     {
377       if ( ! info.url().isValid() )
378         ZYPP_THROW( ServiceNoUrlException( info ) );
379     }
380
381     ////////////////////////////////////////////////////////////////////////////
382
383     ///////////////////////////////////////////////////////////////////
384     namespace
385     {
386       /** Whether repo is not under RM control and provides it's own methadata paths. */
387       inline bool isTmpRepo( const RepoInfo & info_r )
388       { return( info_r.filepath().empty() && info_r.usesAutoMethadataPaths() ); }
389     } // namespace
390     ///////////////////////////////////////////////////////////////////
391
392     /**
393      * \short Calculates the raw cache path for a repository, this is usually
394      * /var/cache/zypp/alias
395      */
396     inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
397     {
398       assert_alias(info);
399       return isTmpRepo( info ) ? info.metadataPath() : opt.repoRawCachePath / info.escaped_alias();
400     }
401
402     /**
403      * \short Calculates the raw product metadata path for a repository, this is
404      * inside the raw cache dir, plus an optional path where the metadata is.
405      *
406      * It should be different only for repositories that are not in the root of
407      * the media.
408      * for example /var/cache/zypp/alias/addondir
409      */
410     inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
411     { return rawcache_path_for_repoinfo( opt, info ) / info.path(); }
412
413     /**
414      * \short Calculates the packages cache path for a repository
415      */
416     inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
417     {
418       assert_alias(info);
419       return isTmpRepo( info ) ? info.packagesPath() : opt.repoPackagesCachePath / info.escaped_alias();
420     }
421
422     /**
423      * \short Calculates the solv cache path for a repository
424      */
425     inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
426     {
427       assert_alias(info);
428       return isTmpRepo( info ) ? info.metadataPath().dirname() / "%SLV%" : opt.repoSolvCachePath / info.escaped_alias();
429     }
430
431     ////////////////////////////////////////////////////////////////////////////
432
433     /** Functor collecting ServiceInfos into a ServiceSet. */
434     class ServiceCollector
435     {
436     public:
437       typedef std::set<ServiceInfo> ServiceSet;
438
439       ServiceCollector( ServiceSet & services_r )
440       : _services( services_r )
441       {}
442
443       bool operator()( const ServiceInfo & service_r ) const
444       {
445         _services.insert( service_r );
446         return true;
447       }
448
449     private:
450       ServiceSet & _services;
451     };
452     ////////////////////////////////////////////////////////////////////////////
453
454   } // namespace
455   ///////////////////////////////////////////////////////////////////
456
457   std::list<RepoInfo> readRepoFile( const Url & repo_file )
458   {
459     // no interface to download a specific file, using workaround:
460     //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
461     Url url(repo_file);
462     Pathname path(url.getPathName());
463     url.setPathName ("/");
464     MediaSetAccess access(url);
465     Pathname local = access.provideFile(path);
466
467     DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
468
469     return repositories_in_file(local);
470   }
471
472   ///////////////////////////////////////////////////////////////////
473   //
474   //    class RepoManagerOptions
475   //
476   ////////////////////////////////////////////////////////////////////
477
478   RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
479   {
480     repoCachePath         = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
481     repoRawCachePath      = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
482     repoSolvCachePath     = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
483     repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
484     knownReposPath        = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
485     knownServicesPath     = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
486     pluginsPath           = Pathname::assertprefix( root_r, ZConfig::instance().pluginsPath() );
487     probe                 = ZConfig::instance().repo_add_probe();
488
489     rootDir = root_r;
490   }
491
492   RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r )
493   {
494     RepoManagerOptions ret;
495     ret.repoCachePath         = root_r;
496     ret.repoRawCachePath      = root_r/"raw";
497     ret.repoSolvCachePath     = root_r/"solv";
498     ret.repoPackagesCachePath = root_r/"packages";
499     ret.knownReposPath        = root_r/"repos.d";
500     ret.knownServicesPath     = root_r/"services.d";
501     ret.pluginsPath           = root_r/"plugins";
502     ret.rootDir = root_r;
503     return ret;
504   }
505
506   std:: ostream & operator<<( std::ostream & str, const RepoManagerOptions & obj )
507   {
508 #define OUTS(X) str << "  " #X "\t" << obj.X << endl
509     str << "RepoManagerOptions (" << obj.rootDir << ") {" << endl;
510     OUTS( repoRawCachePath );
511     OUTS( repoSolvCachePath );
512     OUTS( repoPackagesCachePath );
513     OUTS( knownReposPath );
514     OUTS( knownServicesPath );
515     OUTS( pluginsPath );
516     str << "}" << endl;
517 #undef OUTS
518     return str;
519   }
520
521   ///////////////////////////////////////////////////////////////////
522   /// \class RepoManager::Impl
523   /// \brief RepoManager implementation.
524   ///
525   ///////////////////////////////////////////////////////////////////
526   struct RepoManager::Impl
527   {
528   public:
529     Impl( const RepoManagerOptions &opt )
530       : _options(opt)
531     {
532       init_knownServices();
533       init_knownRepositories();
534     }
535
536     ~Impl()
537     {
538       // trigger appdata refresh if some repos change
539       if ( ( _reposDirty || env::ZYPP_PLUGIN_APPDATA_FORCE_COLLECT() )
540         && geteuid() == 0 && ( _options.rootDir.empty() || _options.rootDir == "/" ) )
541       {
542         try {
543           std::list<Pathname> entries;
544           filesystem::readdir( entries, _options.pluginsPath/"appdata", false );
545           if ( ! entries.empty() )
546           {
547             ExternalProgram::Arguments cmd;
548             cmd.push_back( "<" );               // discard stdin
549             cmd.push_back( ">" );               // discard stdout
550             cmd.push_back( "PROGRAM" );         // [2] - fix index below if changing!
551             for ( const auto & rinfo : repos() )
552             {
553               if ( ! rinfo.enabled() )
554                 continue;
555               cmd.push_back( "-R" );
556               cmd.push_back( rinfo.alias() );
557               cmd.push_back( "-t" );
558               cmd.push_back( rinfo.type().asString() );
559               cmd.push_back( "-p" );
560               cmd.push_back( rinfo.metadataPath().asString() );
561             }
562
563             for_( it, entries.begin(), entries.end() )
564             {
565               PathInfo pi( *it );
566               //DBG << "/tmp/xx ->" << pi << endl;
567               if ( pi.isFile() && pi.userMayRX() )
568               {
569                 // trigger plugin
570                 cmd[2] = pi.asString();         // [2] - PROGRAM
571                 ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
572               }
573             }
574           }
575         }
576         catch (...) {}  // no throw in dtor
577       }
578     }
579
580   public:
581     bool repoEmpty() const              { return repos().empty(); }
582     RepoSizeType repoSize() const       { return repos().size(); }
583     RepoConstIterator repoBegin() const { return repos().begin(); }
584     RepoConstIterator repoEnd() const   { return repos().end(); }
585
586     bool hasRepo( const std::string & alias ) const
587     { return foundAliasIn( alias, repos() ); }
588
589     RepoInfo getRepo( const std::string & alias ) const
590     {
591       RepoConstIterator it( findAlias( alias, repos() ) );
592       return it == repos().end() ? RepoInfo::noRepo : *it;
593     }
594
595   public:
596     Pathname metadataPath( const RepoInfo & info ) const
597     { return rawcache_path_for_repoinfo( _options, info ); }
598
599     Pathname packagesPath( const RepoInfo & info ) const
600     { return packagescache_path_for_repoinfo( _options, info ); }
601
602     RepoStatus metadataStatus( const RepoInfo & info ) const;
603
604     RefreshCheckStatus checkIfToRefreshMetadata( const RepoInfo & info, const Url & url, RawMetadataRefreshPolicy policy );
605
606     void refreshMetadata( const RepoInfo & info, RawMetadataRefreshPolicy policy, OPT_PROGRESS );
607
608     void cleanMetadata( const RepoInfo & info, OPT_PROGRESS );
609
610     void cleanPackages( const RepoInfo & info, OPT_PROGRESS );
611
612     void buildCache( const RepoInfo & info, CacheBuildPolicy policy, OPT_PROGRESS );
613
614     repo::RepoType probe( const Url & url, const Pathname & path = Pathname() ) const;
615     repo::RepoType probeCache( const Pathname & path_r ) const;
616
617     void cleanCacheDirGarbage( OPT_PROGRESS );
618
619     void cleanCache( const RepoInfo & info, OPT_PROGRESS );
620
621     bool isCached( const RepoInfo & info ) const
622     { return PathInfo(solv_path_for_repoinfo( _options, info ) / "solv").isExist(); }
623
624     RepoStatus cacheStatus( const RepoInfo & info ) const
625     { return RepoStatus::fromCookieFile(solv_path_for_repoinfo(_options, info) / "cookie"); }
626
627     void loadFromCache( const RepoInfo & info, OPT_PROGRESS );
628
629     void addRepository( const RepoInfo & info, OPT_PROGRESS );
630
631     void addRepositories( const Url & url, OPT_PROGRESS );
632
633     void removeRepository( const RepoInfo & info, OPT_PROGRESS );
634
635     void modifyRepository( const std::string & alias, const RepoInfo & newinfo_r, OPT_PROGRESS );
636
637     RepoInfo getRepositoryInfo( const std::string & alias, OPT_PROGRESS );
638     RepoInfo getRepositoryInfo( const Url & url, const url::ViewOption & urlview, OPT_PROGRESS );
639
640   public:
641     bool serviceEmpty() const                   { return _services.empty(); }
642     ServiceSizeType serviceSize() const         { return _services.size(); }
643     ServiceConstIterator serviceBegin() const   { return _services.begin(); }
644     ServiceConstIterator serviceEnd() const     { return _services.end(); }
645
646     bool hasService( const std::string & alias ) const
647     { return foundAliasIn( alias, _services ); }
648
649     ServiceInfo getService( const std::string & alias ) const
650     {
651       ServiceConstIterator it( findAlias( alias, _services ) );
652       return it == _services.end() ? ServiceInfo::noService : *it;
653     }
654
655   public:
656     void addService( const ServiceInfo & service );
657     void addService( const std::string & alias, const Url & url )
658     { addService( ServiceInfo( alias, url ) ); }
659
660     void removeService( const std::string & alias );
661     void removeService( const ServiceInfo & service )
662     { removeService( service.alias() ); }
663
664     void refreshServices( const RefreshServiceOptions & options_r );
665
666     void refreshService( const std::string & alias, const RefreshServiceOptions & options_r );
667     void refreshService( const ServiceInfo & service, const RefreshServiceOptions & options_r )
668     {  refreshService( service.alias(), options_r ); }
669
670     void modifyService( const std::string & oldAlias, const ServiceInfo & newService );
671
672     repo::ServiceType probeService( const Url & url ) const;
673
674   private:
675     void saveService( ServiceInfo & service ) const;
676
677     Pathname generateNonExistingName( const Pathname & dir, const std::string & basefilename ) const;
678
679     std::string generateFilename( const RepoInfo & info ) const
680     { return filenameFromAlias( info.alias(), "repo" ); }
681
682     std::string generateFilename( const ServiceInfo & info ) const
683     { return filenameFromAlias( info.alias(), "service" ); }
684
685     void setCacheStatus( const RepoInfo & info, const RepoStatus & status )
686     {
687       Pathname base = solv_path_for_repoinfo( _options, info );
688       filesystem::assert_dir(base);
689       status.saveToCookieFile( base / "cookie" );
690     }
691
692     void touchIndexFile( const RepoInfo & info );
693
694     template<typename OutputIterator>
695     void getRepositoriesInService( const std::string & alias, OutputIterator out ) const
696     {
697       MatchServiceAlias filter( alias );
698       std::copy( boost::make_filter_iterator( filter, repos().begin(), repos().end() ),
699                  boost::make_filter_iterator( filter, repos().end(), repos().end() ),
700                  out);
701     }
702
703   private:
704     void init_knownServices();
705     void init_knownRepositories();
706
707     const RepoSet & repos() const { return _reposX; }
708     RepoSet & reposManip()        { if ( ! _reposDirty ) _reposDirty = true; return _reposX; }
709
710   private:
711     RepoManagerOptions  _options;
712     RepoSet             _reposX;
713     ServiceSet          _services;
714
715     DefaultIntegral<bool,false> _reposDirty;
716
717   private:
718     friend Impl * rwcowClone<Impl>( const Impl * rhs );
719     /** clone for RWCOW_pointer */
720     Impl * clone() const
721     { return new Impl( *this ); }
722   };
723   ///////////////////////////////////////////////////////////////////
724
725   /** \relates RepoManager::Impl Stream output */
726   inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
727   { return str << "RepoManager::Impl"; }
728
729   ///////////////////////////////////////////////////////////////////
730
731   void RepoManager::Impl::saveService( ServiceInfo & service ) const
732   {
733     filesystem::assert_dir( _options.knownServicesPath );
734     Pathname servfile = generateNonExistingName( _options.knownServicesPath,
735                                                  generateFilename( service ) );
736     service.setFilepath( servfile );
737
738     MIL << "saving service in " << servfile << endl;
739
740     std::ofstream file( servfile.c_str() );
741     if ( !file )
742     {
743       // TranslatorExplanation '%s' is a filename
744       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() )));
745     }
746     service.dumpAsIniOn( file );
747     MIL << "done" << endl;
748   }
749
750   /**
751    * Generate a non existing filename in a directory, using a base
752    * name. For example if a directory contains 3 files
753    *
754    * |-- bar
755    * |-- foo
756    * `-- moo
757    *
758    * If you try to generate a unique filename for this directory,
759    * based on "ruu" you will get "ruu", but if you use the base
760    * "foo" you will get "foo_1"
761    *
762    * \param dir Directory where the file needs to be unique
763    * \param basefilename string to base the filename on.
764    */
765   Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir,
766                                                        const std::string & basefilename ) const
767   {
768     std::string final_filename = basefilename;
769     int counter = 1;
770     while ( PathInfo(dir + final_filename).isExist() )
771     {
772       final_filename = basefilename + "_" + str::numstring(counter);
773       ++counter;
774     }
775     return dir + Pathname(final_filename);
776   }
777
778   ////////////////////////////////////////////////////////////////////////////
779
780   void RepoManager::Impl::init_knownServices()
781   {
782     Pathname dir = _options.knownServicesPath;
783     std::list<Pathname> entries;
784     if (PathInfo(dir).isExist())
785     {
786       if ( filesystem::readdir( entries, dir, false ) != 0 )
787       {
788         // TranslatorExplanation '%s' is a pathname
789         ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
790       }
791
792       //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
793       for_(it, entries.begin(), entries.end() )
794       {
795         parser::ServiceFileReader(*it, ServiceCollector(_services));
796       }
797     }
798
799     repo::PluginServices(_options.pluginsPath/"services", ServiceCollector(_services));
800   }
801
802   ///////////////////////////////////////////////////////////////////
803   namespace {
804     /** Delete \a cachePath_r subdirs not matching known aliases in \a repoEscAliases_r (must be sorted!)
805      * \note bnc#891515: Auto-cleanup only zypp.conf default locations. Otherwise
806      * we'd need some magic file to identify zypp cache directories. Without this
807      * we may easily remove user data (zypper --pkg-cache-dir . download ...)
808      */
809     inline void cleanupNonRepoMetadtaFolders( const Pathname & cachePath_r,
810                                               const Pathname & defaultCachePath_r,
811                                               const std::list<std::string> & repoEscAliases_r )
812     {
813       if ( cachePath_r != defaultCachePath_r )
814         return;
815
816       std::list<std::string> entries;
817       if ( filesystem::readdir( entries, cachePath_r, false ) == 0 )
818       {
819         entries.sort();
820         std::set<std::string> oldfiles;
821         set_difference( entries.begin(), entries.end(), repoEscAliases_r.begin(), repoEscAliases_r.end(),
822                         std::inserter( oldfiles, oldfiles.end() ) );
823         for ( const std::string & old : oldfiles )
824         {
825           if ( old == Repository::systemRepoAlias() )   // don't remove the @System solv file
826             continue;
827           filesystem::recursive_rmdir( cachePath_r / old );
828         }
829       }
830     }
831   } // namespace
832   ///////////////////////////////////////////////////////////////////
833   void RepoManager::Impl::init_knownRepositories()
834   {
835     MIL << "start construct known repos" << endl;
836
837     if ( PathInfo(_options.knownReposPath).isExist() )
838     {
839       std::list<std::string> repoEscAliases;
840       std::list<RepoInfo> orphanedRepos;
841       for ( RepoInfo & repoInfo : repositories_in_dir(_options.knownReposPath) )
842       {
843         // set the metadata path for the repo
844         repoInfo.setMetadataPath( rawcache_path_for_repoinfo(_options, repoInfo) );
845         // set the downloaded packages path for the repo
846         repoInfo.setPackagesPath( packagescache_path_for_repoinfo(_options, repoInfo) );
847         // remember it
848         _reposX.insert( repoInfo );     // direct access via _reposX in ctor! no reposManip.
849
850         // detect orphaned repos belonging to a deleted service
851         const std::string & serviceAlias( repoInfo.service() );
852         if ( ! ( serviceAlias.empty() || hasService( serviceAlias ) ) )
853         {
854           WAR << "Schedule orphaned service repo for deletion: " << repoInfo << endl;
855           orphanedRepos.push_back( repoInfo );
856           continue;     // don't remember it in repoEscAliases
857         }
858
859         repoEscAliases.push_back(repoInfo.escaped_alias());
860       }
861
862       // Cleanup orphanded service repos:
863       if ( ! orphanedRepos.empty() )
864       {
865         for ( const auto & repoInfo : orphanedRepos )
866         {
867           MIL << "Delete orphaned service repo " << repoInfo.alias() << endl;
868           // translators: Cleanup a repository previously owned by a meanwhile unknown (deleted) service.
869           //   %1% = service name
870           //   %2% = repository name
871           JobReport::warning( str::FormatNAC(_("Unknown service '%1%': Removing orphaned service repository '%2%'"))
872                               % repoInfo.service()
873                               % repoInfo.alias() );
874           try {
875             removeRepository( repoInfo );
876           }
877           catch ( const Exception & caugth )
878           {
879             JobReport::error( caugth.asUserHistory() );
880           }
881         }
882       }
883
884       // delete metadata folders without corresponding repo (e.g. old tmp directories)
885       //
886       // bnc#891515: Auto-cleanup only zypp.conf default locations. Otherwise
887       // we'd need somemagic file to identify zypp cache directories. Without this
888       // we may easily remove user data (zypper --pkg-cache-dir . download ...)
889       repoEscAliases.sort();
890       RepoManagerOptions defaultCache( _options.rootDir );
891       cleanupNonRepoMetadtaFolders( _options.repoRawCachePath,          defaultCache.repoRawCachePath,          repoEscAliases );
892       cleanupNonRepoMetadtaFolders( _options.repoSolvCachePath,         defaultCache.repoSolvCachePath,         repoEscAliases );
893       cleanupNonRepoMetadtaFolders( _options.repoPackagesCachePath,     defaultCache.repoPackagesCachePath,     repoEscAliases );
894     }
895     MIL << "end construct known repos" << endl;
896   }
897
898   ///////////////////////////////////////////////////////////////////
899
900   RepoStatus RepoManager::Impl::metadataStatus( const RepoInfo & info ) const
901   {
902     Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
903     Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
904
905     RepoType repokind = info.type();
906     // If unknown, probe the local metadata
907     if ( repokind == RepoType::NONE )
908       repokind = probeCache( productdatapath );
909
910     RepoStatus status;
911     switch ( repokind.toEnum() )
912     {
913       case RepoType::RPMMD_e :
914         status = RepoStatus( productdatapath/"repodata/repomd.xml") && RepoStatus( mediarootpath/"media.1/media" );
915         break;
916
917       case RepoType::YAST2_e :
918         status = RepoStatus( productdatapath/"content" ) && RepoStatus( mediarootpath/"media.1/media" );
919         break;
920
921       case RepoType::RPMPLAINDIR_e :
922         status = RepoStatus::fromCookieFile( productdatapath/"cookie" );
923         break;
924
925       case RepoType::NONE_e :
926         // Return default RepoStatus in case of RepoType::NONE
927         // indicating it should be created?
928         // ZYPP_THROW(RepoUnknownTypeException());
929         break;
930     }
931     return status;
932   }
933
934
935   void RepoManager::Impl::touchIndexFile( const RepoInfo & info )
936   {
937     Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
938
939     RepoType repokind = info.type();
940     if ( repokind.toEnum() == RepoType::NONE_e )
941       // unknown, probe the local metadata
942       repokind = probeCache( productdatapath );
943     // if still unknown, just return
944     if (repokind == RepoType::NONE_e)
945       return;
946
947     Pathname p;
948     switch ( repokind.toEnum() )
949     {
950       case RepoType::RPMMD_e :
951         p = Pathname(productdatapath + "/repodata/repomd.xml");
952         break;
953
954       case RepoType::YAST2_e :
955         p = Pathname(productdatapath + "/content");
956         break;
957
958       case RepoType::RPMPLAINDIR_e :
959         p = Pathname(productdatapath + "/cookie");
960         break;
961
962       case RepoType::NONE_e :
963       default:
964         break;
965     }
966
967     // touch the file, ignore error (they are logged anyway)
968     filesystem::touch(p);
969   }
970
971
972   RepoManager::RefreshCheckStatus RepoManager::Impl::checkIfToRefreshMetadata( const RepoInfo & info, const Url & url, RawMetadataRefreshPolicy policy )
973   {
974     assert_alias(info);
975     try
976     {
977       MIL << "Going to try to check whether refresh is needed for " << url << " (" << info.type() << ")" << endl;
978
979       // first check old (cached) metadata
980       Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
981       filesystem::assert_dir( mediarootpath );
982       RepoStatus oldstatus = metadataStatus( info );
983       if ( oldstatus.empty() )
984       {
985         MIL << "No cached metadata, going to refresh" << endl;
986         return REFRESH_NEEDED;
987       }
988
989       if ( url.schemeIsVolatile() )
990       {
991         MIL << "Never refresh CD/DVD" << endl;
992         return REPO_UP_TO_DATE;
993       }
994
995       if ( policy == RefreshForced )
996       {
997         MIL << "Forced refresh!" << endl;
998         return REFRESH_NEEDED;
999       }
1000
1001       if ( url.schemeIsLocal() )
1002       {
1003         policy = RefreshIfNeededIgnoreDelay;
1004       }
1005
1006       // now we've got the old (cached) status, we can decide repo.refresh.delay
1007       if ( policy != RefreshIfNeededIgnoreDelay )
1008       {
1009         // difference in seconds
1010         double diff = difftime(
1011           (Date::ValueType)Date::now(),
1012           (Date::ValueType)oldstatus.timestamp()) / 60;
1013
1014         DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
1015         DBG << "current time: " << (Date::ValueType)Date::now() << endl;
1016         DBG << "last refresh = " << diff << " minutes ago" << endl;
1017
1018         if ( diff < ZConfig::instance().repo_refresh_delay() )
1019         {
1020           if ( diff < 0 )
1021           {
1022             WAR << "Repository '" << info.alias() << "' was refreshed in the future!" << endl;
1023           }
1024           else
1025           {
1026             MIL << "Repository '" << info.alias()
1027                 << "' has been refreshed less than repo.refresh.delay ("
1028                 << ZConfig::instance().repo_refresh_delay()
1029                 << ") minutes ago. Advising to skip refresh" << endl;
1030             return REPO_CHECK_DELAYED;
1031           }
1032         }
1033       }
1034
1035       repo::RepoType repokind = info.type();
1036       // if unknown: probe it
1037       if ( repokind == RepoType::NONE )
1038         repokind = probe( url, info.path() );
1039
1040       // retrieve newstatus
1041       RepoStatus newstatus;
1042       switch ( repokind.toEnum() )
1043       {
1044         case RepoType::RPMMD_e:
1045         {
1046           MediaSetAccess media( url );
1047           newstatus = yum::Downloader( info, mediarootpath ).status( media );
1048         }
1049         break;
1050
1051         case RepoType::YAST2_e:
1052         {
1053           MediaSetAccess media( url );
1054           newstatus = susetags::Downloader( info, mediarootpath ).status( media );
1055         }
1056         break;
1057
1058         case RepoType::RPMPLAINDIR_e:
1059           newstatus = RepoStatus( MediaMounter(url).getPathName(info.path()) ); // dir status
1060           break;
1061
1062         default:
1063         case RepoType::NONE_e:
1064           ZYPP_THROW( RepoUnknownTypeException( info ) );
1065           break;
1066       }
1067
1068       // check status
1069       if ( oldstatus == newstatus )
1070       {
1071         MIL << "repo has not changed" << endl;
1072         touchIndexFile( info );
1073         return REPO_UP_TO_DATE;
1074       }
1075       else // includes newstatus.empty() if e.g. repo format changed
1076       {
1077         MIL << "repo has changed, going to refresh" << endl;
1078         return REFRESH_NEEDED;
1079       }
1080     }
1081     catch ( const Exception &e )
1082     {
1083       ZYPP_CAUGHT(e);
1084       ERR << "refresh check failed for " << url << endl;
1085       ZYPP_RETHROW(e);
1086     }
1087
1088     return REFRESH_NEEDED; // default
1089   }
1090
1091
1092   void RepoManager::Impl::refreshMetadata( const RepoInfo & info, RawMetadataRefreshPolicy policy, const ProgressData::ReceiverFnc & progress )
1093   {
1094     assert_alias(info);
1095     assert_urls(info);
1096
1097     // we will throw this later if no URL checks out fine
1098     RepoException rexception( info, PL_("Valid metadata not found at specified URL",
1099                                         "Valid metadata not found at specified URLs",
1100                                         info.baseUrlsSize() ) );
1101
1102     // Suppress (interactive) media::MediaChangeReport if we in have multiple basurls (>1)
1103     media::ScopedDisableMediaChangeReport guard( info.baseUrlsSize() > 1 );
1104     // try urls one by one
1105     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
1106     {
1107       try
1108       {
1109         Url url(*it);
1110
1111         // check whether to refresh metadata
1112         // if the check fails for this url, it throws, so another url will be checked
1113         if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
1114           return;
1115
1116         MIL << "Going to refresh metadata from " << url << endl;
1117
1118         // bsc#1048315: Always re-probe in case of repo format change.
1119         // TODO: Would be sufficient to verify the type and re-probe
1120         // if verification failed (or type is RepoType::NONE)
1121         repo::RepoType repokind = info.type();
1122         {
1123           repo::RepoType probed = probe( *it, info.path() );
1124           if ( repokind != probed )
1125           {
1126             repokind = probed;
1127             // Adjust the probed type in RepoInfo
1128             info.setProbedType( repokind ); // lazy init!
1129             //save probed type only for repos in system
1130             for_( it, repoBegin(), repoEnd() )
1131             {
1132               if ( info.alias() == (*it).alias() )
1133               {
1134                 RepoInfo modifiedrepo = info;
1135                 modifiedrepo.setType( repokind );
1136                 modifyRepository( info.alias(), modifiedrepo );
1137                 break;
1138               }
1139             }
1140           }
1141         }
1142
1143         Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
1144         if( filesystem::assert_dir(mediarootpath) )
1145         {
1146           Exception ex(str::form( _("Can't create %s"), mediarootpath.c_str()) );
1147           ZYPP_THROW(ex);
1148         }
1149
1150         // create temp dir as sibling of mediarootpath
1151         filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
1152         if( tmpdir.path().empty() )
1153         {
1154           Exception ex(_("Can't create metadata cache directory."));
1155           ZYPP_THROW(ex);
1156         }
1157
1158         if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
1159              ( repokind.toEnum() == RepoType::YAST2_e ) )
1160         {
1161           MediaSetAccess media(url);
1162           shared_ptr<repo::Downloader> downloader_ptr;
1163
1164           MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
1165
1166           if ( repokind.toEnum() == RepoType::RPMMD_e )
1167             downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
1168           else
1169             downloader_ptr.reset( new susetags::Downloader(info, mediarootpath) );
1170
1171           /**
1172            * Given a downloader, sets the other repos raw metadata
1173            * path as cache paths for the fetcher, so if another
1174            * repo has the same file, it will not download it
1175            * but copy it from the other repository
1176            */
1177           for_( it, repoBegin(), repoEnd() )
1178           {
1179             Pathname cachepath(rawcache_path_for_repoinfo( _options, *it ));
1180             if ( PathInfo(cachepath).isExist() )
1181               downloader_ptr->addCachePath(cachepath);
1182           }
1183
1184           downloader_ptr->download( media, tmpdir.path() );
1185         }
1186         else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
1187         {
1188           MediaMounter media( url );
1189           RepoStatus newstatus = RepoStatus( media.getPathName( info.path() ) );        // dir status
1190
1191           Pathname productpath( tmpdir.path() / info.path() );
1192           filesystem::assert_dir( productpath );
1193           newstatus.saveToCookieFile( productpath/"cookie" );
1194         }
1195         else
1196         {
1197           ZYPP_THROW(RepoUnknownTypeException( info ));
1198         }
1199
1200         // ok we have the metadata, now exchange
1201         // the contents
1202         filesystem::exchange( tmpdir.path(), mediarootpath );
1203         if ( ! isTmpRepo( info ) )
1204           reposManip(); // remember to trigger appdata refresh
1205
1206         // we are done.
1207         return;
1208       }
1209       catch ( const Exception &e )
1210       {
1211         ZYPP_CAUGHT(e);
1212         ERR << "Trying another url..." << endl;
1213
1214         // remember the exception caught for the *first URL*
1215         // if all other URLs fail, the rexception will be thrown with the
1216         // cause of the problem of the first URL remembered
1217         if (it == info.baseUrlsBegin())
1218           rexception.remember(e);
1219         else
1220           rexception.addHistory(  e.asUserString() );
1221
1222       }
1223     } // for every url
1224     ERR << "No more urls..." << endl;
1225     ZYPP_THROW(rexception);
1226   }
1227
1228   ////////////////////////////////////////////////////////////////////////////
1229
1230   void RepoManager::Impl::cleanMetadata( const RepoInfo & info, const ProgressData::ReceiverFnc & progressfnc )
1231   {
1232     ProgressData progress(100);
1233     progress.sendTo(progressfnc);
1234
1235     filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_options, info));
1236     progress.toMax();
1237   }
1238
1239
1240   void RepoManager::Impl::cleanPackages( const RepoInfo & info, const ProgressData::ReceiverFnc & progressfnc )
1241   {
1242     ProgressData progress(100);
1243     progress.sendTo(progressfnc);
1244
1245     filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_options, info));
1246     progress.toMax();
1247   }
1248
1249
1250   void RepoManager::Impl::buildCache( const RepoInfo & info, CacheBuildPolicy policy, const ProgressData::ReceiverFnc & progressrcv )
1251   {
1252     assert_alias(info);
1253     Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
1254     Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
1255
1256     if( filesystem::assert_dir(_options.repoCachePath) )
1257     {
1258       Exception ex(str::form( _("Can't create %s"), _options.repoCachePath.c_str()) );
1259       ZYPP_THROW(ex);
1260     }
1261     RepoStatus raw_metadata_status = metadataStatus(info);
1262     if ( raw_metadata_status.empty() )
1263     {
1264        /* if there is no cache at this point, we refresh the raw
1265           in case this is the first time - if it's !autorefresh,
1266           we may still refresh */
1267       refreshMetadata(info, RefreshIfNeeded, progressrcv );
1268       raw_metadata_status = metadataStatus(info);
1269     }
1270
1271     bool needs_cleaning = false;
1272     if ( isCached( info ) )
1273     {
1274       MIL << info.alias() << " is already cached." << endl;
1275       RepoStatus cache_status = cacheStatus(info);
1276
1277       if ( cache_status == raw_metadata_status )
1278       {
1279         MIL << info.alias() << " cache is up to date with metadata." << endl;
1280         if ( policy == BuildIfNeeded )
1281         {
1282           // On the fly add missing solv.idx files for bash completion.
1283           const Pathname & base = solv_path_for_repoinfo( _options, info);
1284           if ( ! PathInfo(base/"solv.idx").isExist() )
1285             sat::updateSolvFileIndex( base/"solv" );
1286
1287           return;
1288         }
1289         else {
1290           MIL << info.alias() << " cache rebuild is forced" << endl;
1291         }
1292       }
1293
1294       needs_cleaning = true;
1295     }
1296
1297     ProgressData progress(100);
1298     callback::SendReport<ProgressReport> report;
1299     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1300     progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str()));
1301     progress.toMin();
1302
1303     if (needs_cleaning)
1304     {
1305       cleanCache(info);
1306     }
1307
1308     MIL << info.alias() << " building cache..." << info.type() << endl;
1309
1310     Pathname base = solv_path_for_repoinfo( _options, info);
1311
1312     if( filesystem::assert_dir(base) )
1313     {
1314       Exception ex(str::form( _("Can't create %s"), base.c_str()) );
1315       ZYPP_THROW(ex);
1316     }
1317
1318     if( ! PathInfo(base).userMayW() )
1319     {
1320       Exception ex(str::form( _("Can't create cache at %s - no writing permissions."), base.c_str()) );
1321       ZYPP_THROW(ex);
1322     }
1323     Pathname solvfile = base / "solv";
1324
1325     // do we have type?
1326     repo::RepoType repokind = info.type();
1327
1328     // if the type is unknown, try probing.
1329     switch ( repokind.toEnum() )
1330     {
1331       case RepoType::NONE_e:
1332         // unknown, probe the local metadata
1333         repokind = probeCache( productdatapath );
1334       break;
1335       default:
1336       break;
1337     }
1338
1339     MIL << "repo type is " << repokind << endl;
1340
1341     switch ( repokind.toEnum() )
1342     {
1343       case RepoType::RPMMD_e :
1344       case RepoType::YAST2_e :
1345       case RepoType::RPMPLAINDIR_e :
1346       {
1347         // Take care we unlink the solvfile on exception
1348         ManagedFile guard( solvfile, filesystem::unlink );
1349         scoped_ptr<MediaMounter> forPlainDirs;
1350
1351         ExternalProgram::Arguments cmd;
1352         cmd.push_back( PathInfo( "/usr/bin/repo2solv" ).isFile() ? "repo2solv" : "repo2solv.sh" );
1353         // repo2solv expects -o as 1st arg!
1354         cmd.push_back( "-o" );
1355         cmd.push_back( solvfile.asString() );
1356         cmd.push_back( "-X" );  // autogenerate pattern from pattern-package
1357
1358         if ( repokind == RepoType::RPMPLAINDIR )
1359         {
1360           forPlainDirs.reset( new MediaMounter( info.url() ) );
1361           // recusive for plaindir as 2nd arg!
1362           cmd.push_back( "-R" );
1363           // FIXME this does only work form dir: URLs
1364           cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
1365         }
1366         else
1367           cmd.push_back( productdatapath.asString() );
1368
1369         ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
1370         std::string errdetail;
1371
1372         for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1373           WAR << "  " << output;
1374           if ( errdetail.empty() ) {
1375             errdetail = prog.command();
1376             errdetail += '\n';
1377           }
1378           errdetail += output;
1379         }
1380
1381         int ret = prog.close();
1382         if ( ret != 0 )
1383         {
1384           RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
1385           ex.remember( errdetail );
1386           ZYPP_THROW(ex);
1387         }
1388
1389         // We keep it.
1390         guard.resetDispose();
1391         sat::updateSolvFileIndex( solvfile );   // content digest for zypper bash completion
1392       }
1393       break;
1394       default:
1395         ZYPP_THROW(RepoUnknownTypeException( info, _("Unhandled repository type") ));
1396       break;
1397     }
1398     // update timestamp and checksum
1399     setCacheStatus(info, raw_metadata_status);
1400     MIL << "Commit cache.." << endl;
1401     progress.toMax();
1402   }
1403
1404   ////////////////////////////////////////////////////////////////////////////
1405
1406
1407   /** Probe the metadata type of a repository located at \c url.
1408    * Urls here may be rewritten by \ref MediaSetAccess to reflect the correct media number.
1409    *
1410    * \note Metadata in local cache directories must be probed using \ref probeCache as
1411    * a cache path must not be rewritten (bnc#946129)
1412    */
1413   repo::RepoType RepoManager::Impl::probe( const Url & url, const Pathname & path  ) const
1414   {
1415     MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
1416
1417     if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
1418     {
1419       // Handle non existing local directory in advance, as
1420       // MediaSetAccess does not support it.
1421       MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
1422       return repo::RepoType::NONE;
1423     }
1424
1425     // prepare exception to be thrown if the type could not be determined
1426     // due to a media exception. We can't throw right away, because of some
1427     // problems with proxy servers returning an incorrect error
1428     // on ftp file-not-found(bnc #335906). Instead we'll check another types
1429     // before throwing.
1430
1431     // TranslatorExplanation '%s' is an URL
1432     RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
1433     bool gotMediaException = false;
1434     try
1435     {
1436       MediaSetAccess access(url);
1437       try
1438       {
1439         if ( access.doesFileExist(path/"/repodata/repomd.xml") )
1440         {
1441           MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
1442           return repo::RepoType::RPMMD;
1443         }
1444       }
1445       catch ( const media::MediaException &e )
1446       {
1447         ZYPP_CAUGHT(e);
1448         DBG << "problem checking for repodata/repomd.xml file" << endl;
1449         enew.remember(e);
1450         gotMediaException = true;
1451       }
1452
1453       try
1454       {
1455         if ( access.doesFileExist(path/"/content") )
1456         {
1457           MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
1458           return repo::RepoType::YAST2;
1459         }
1460       }
1461       catch ( const media::MediaException &e )
1462       {
1463         ZYPP_CAUGHT(e);
1464         DBG << "problem checking for content file" << endl;
1465         enew.remember(e);
1466         gotMediaException = true;
1467       }
1468
1469       // if it is a non-downloading URL denoting a directory
1470       if ( ! url.schemeIsDownloading() )
1471       {
1472         MediaMounter media( url );
1473         if ( PathInfo(media.getPathName()/path).isDir() )
1474         {
1475           // allow empty dirs for now
1476           MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
1477           return repo::RepoType::RPMPLAINDIR;
1478         }
1479       }
1480     }
1481     catch ( const Exception &e )
1482     {
1483       ZYPP_CAUGHT(e);
1484       // TranslatorExplanation '%s' is an URL
1485       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
1486       enew.remember(e);
1487       ZYPP_THROW(enew);
1488     }
1489
1490     if (gotMediaException)
1491       ZYPP_THROW(enew);
1492
1493     MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
1494     return repo::RepoType::NONE;
1495   }
1496
1497   /** Probe Metadata in a local cache directory
1498    *
1499    * \note Metadata in local cache directories must not be probed using \ref probe as
1500    * a cache path must not be rewritten (bnc#946129)
1501    */
1502   repo::RepoType RepoManager::Impl::probeCache( const Pathname & path_r ) const
1503   {
1504     MIL << "going to probe the cached repo at " << path_r << endl;
1505
1506     repo::RepoType ret = repo::RepoType::NONE;
1507
1508     if ( PathInfo(path_r/"/repodata/repomd.xml").isFile() )
1509     { ret = repo::RepoType::RPMMD; }
1510     else if ( PathInfo(path_r/"/content").isFile() )
1511     { ret = repo::RepoType::YAST2; }
1512     else if ( PathInfo(path_r).isDir() )
1513     { ret = repo::RepoType::RPMPLAINDIR; }
1514
1515     MIL << "Probed cached type " << ret << " at " << path_r << endl;
1516     return ret;
1517   }
1518
1519   ////////////////////////////////////////////////////////////////////////////
1520
1521   void RepoManager::Impl::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
1522   {
1523     MIL << "Going to clean up garbage in cache dirs" << endl;
1524
1525     ProgressData progress(300);
1526     progress.sendTo(progressrcv);
1527     progress.toMin();
1528
1529     std::list<Pathname> cachedirs;
1530     cachedirs.push_back(_options.repoRawCachePath);
1531     cachedirs.push_back(_options.repoPackagesCachePath);
1532     cachedirs.push_back(_options.repoSolvCachePath);
1533
1534     for_( dir, cachedirs.begin(), cachedirs.end() )
1535     {
1536       if ( PathInfo(*dir).isExist() )
1537       {
1538         std::list<Pathname> entries;
1539         if ( filesystem::readdir( entries, *dir, false ) != 0 )
1540           // TranslatorExplanation '%s' is a pathname
1541           ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str())));
1542
1543         unsigned sdircount   = entries.size();
1544         unsigned sdircurrent = 1;
1545         for_( subdir, entries.begin(), entries.end() )
1546         {
1547           // if it does not belong known repo, make it disappear
1548           bool found = false;
1549           for_( r, repoBegin(), repoEnd() )
1550             if ( subdir->basename() == r->escaped_alias() )
1551             { found = true; break; }
1552
1553           if ( ! found && ( Date::now()-PathInfo(*subdir).mtime() > Date::day ) )
1554             filesystem::recursive_rmdir( *subdir );
1555
1556           progress.set( progress.val() + sdircurrent * 100 / sdircount );
1557           ++sdircurrent;
1558         }
1559       }
1560       else
1561         progress.set( progress.val() + 100 );
1562     }
1563     progress.toMax();
1564   }
1565
1566   ////////////////////////////////////////////////////////////////////////////
1567
1568   void RepoManager::Impl::cleanCache( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
1569   {
1570     ProgressData progress(100);
1571     progress.sendTo(progressrcv);
1572     progress.toMin();
1573
1574     MIL << "Removing raw metadata cache for " << info.alias() << endl;
1575     filesystem::recursive_rmdir(solv_path_for_repoinfo(_options, info));
1576
1577     progress.toMax();
1578   }
1579
1580   ////////////////////////////////////////////////////////////////////////////
1581
1582   void RepoManager::Impl::loadFromCache( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
1583   {
1584     assert_alias(info);
1585     Pathname solvfile = solv_path_for_repoinfo(_options, info) / "solv";
1586
1587     if ( ! PathInfo(solvfile).isExist() )
1588       ZYPP_THROW(RepoNotCachedException(info));
1589
1590     sat::Pool::instance().reposErase( info.alias() );
1591     try
1592     {
1593       Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info );
1594       // test toolversion in order to rebuild solv file in case
1595       // it was written by a different libsolv-tool parser.
1596       const std::string & toolversion( sat::LookupRepoAttr( sat::SolvAttr::repositoryToolVersion, repo ).begin().asString() );
1597       if ( toolversion != LIBSOLV_TOOLVERSION )
1598       {
1599         repo.eraseFromPool();
1600         ZYPP_THROW(Exception(str::Str() << "Solv-file was created by '"<<toolversion<<"'-parser (want "<<LIBSOLV_TOOLVERSION<<")."));
1601       }
1602     }
1603     catch ( const Exception & exp )
1604     {
1605       ZYPP_CAUGHT( exp );
1606       MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1607       cleanCache( info, progressrcv );
1608       buildCache( info, BuildIfNeeded, progressrcv );
1609
1610       sat::Pool::instance().addRepoSolv( solvfile, info );
1611     }
1612   }
1613
1614   ////////////////////////////////////////////////////////////////////////////
1615
1616   void RepoManager::Impl::addRepository( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
1617   {
1618     assert_alias(info);
1619
1620     ProgressData progress(100);
1621     callback::SendReport<ProgressReport> report;
1622     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1623     progress.name(str::form(_("Adding repository '%s'"), info.label().c_str()));
1624     progress.toMin();
1625
1626     MIL << "Try adding repo " << info << endl;
1627
1628     RepoInfo tosave = info;
1629     if ( repos().find(tosave) != repos().end() )
1630       ZYPP_THROW(RepoAlreadyExistsException(info));
1631
1632     // check the first url for now
1633     if ( _options.probe )
1634     {
1635       DBG << "unknown repository type, probing" << endl;
1636       assert_urls(tosave);
1637
1638       RepoType probedtype( probe( tosave.url(), info.path() ) );
1639       if ( probedtype == RepoType::NONE )
1640         ZYPP_THROW(RepoUnknownTypeException(info));
1641       else
1642         tosave.setType(probedtype);
1643     }
1644
1645     progress.set(50);
1646
1647     // assert the directory exists
1648     filesystem::assert_dir(_options.knownReposPath);
1649
1650     Pathname repofile = generateNonExistingName(
1651         _options.knownReposPath, generateFilename(tosave));
1652     // now we have a filename that does not exists
1653     MIL << "Saving repo in " << repofile << endl;
1654
1655     std::ofstream file(repofile.c_str());
1656     if (!file)
1657     {
1658       // TranslatorExplanation '%s' is a filename
1659       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1660     }
1661
1662     tosave.dumpAsIniOn(file);
1663     tosave.setFilepath(repofile);
1664     tosave.setMetadataPath( rawcache_path_for_repoinfo( _options, tosave ) );
1665     tosave.setPackagesPath( packagescache_path_for_repoinfo( _options, tosave ) );
1666     {
1667       // We should fix the API as we must inject those paths
1668       // into the repoinfo in order to keep it usable.
1669       RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
1670       oinfo.setFilepath(repofile);
1671       oinfo.setMetadataPath( rawcache_path_for_repoinfo( _options, tosave ) );
1672       oinfo.setPackagesPath( packagescache_path_for_repoinfo( _options, tosave ) );
1673     }
1674     reposManip().insert(tosave);
1675
1676     progress.set(90);
1677
1678     // check for credentials in Urls
1679     UrlCredentialExtractor( _options.rootDir ).collect( tosave.baseUrls() );
1680
1681     HistoryLog(_options.rootDir).addRepository(tosave);
1682
1683     progress.toMax();
1684     MIL << "done" << endl;
1685   }
1686
1687
1688   void RepoManager::Impl::addRepositories( const Url & url, const ProgressData::ReceiverFnc & progressrcv )
1689   {
1690     std::list<RepoInfo> repos = readRepoFile(url);
1691     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1692           it != repos.end();
1693           ++it )
1694     {
1695       // look if the alias is in the known repos.
1696       for_ ( kit, repoBegin(), repoEnd() )
1697       {
1698         if ( (*it).alias() == (*kit).alias() )
1699         {
1700           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1701           ZYPP_THROW(RepoAlreadyExistsException(*it));
1702         }
1703       }
1704     }
1705
1706     std::string filename = Pathname(url.getPathName()).basename();
1707
1708     if ( filename == Pathname() )
1709     {
1710       // TranslatorExplanation '%s' is an URL
1711       ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
1712     }
1713
1714     // assert the directory exists
1715     filesystem::assert_dir(_options.knownReposPath);
1716
1717     Pathname repofile = generateNonExistingName(_options.knownReposPath, filename);
1718     // now we have a filename that does not exists
1719     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1720
1721     std::ofstream file(repofile.c_str());
1722     if (!file)
1723     {
1724       // TranslatorExplanation '%s' is a filename
1725       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1726     }
1727
1728     for ( std::list<RepoInfo>::iterator it = repos.begin();
1729           it != repos.end();
1730           ++it )
1731     {
1732       MIL << "Saving " << (*it).alias() << endl;
1733       it->dumpAsIniOn(file);
1734       it->setFilepath(repofile);
1735       it->setMetadataPath( rawcache_path_for_repoinfo( _options, *it ) );
1736       it->setPackagesPath( packagescache_path_for_repoinfo( _options, *it ) );
1737       reposManip().insert(*it);
1738
1739       HistoryLog(_options.rootDir).addRepository(*it);
1740     }
1741
1742     MIL << "done" << endl;
1743   }
1744
1745   ////////////////////////////////////////////////////////////////////////////
1746
1747   void RepoManager::Impl::removeRepository( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
1748   {
1749     ProgressData progress;
1750     callback::SendReport<ProgressReport> report;
1751     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1752     progress.name(str::form(_("Removing repository '%s'"), info.label().c_str()));
1753
1754     MIL << "Going to delete repo " << info.alias() << endl;
1755
1756     for_( it, repoBegin(), repoEnd() )
1757     {
1758       // they can be the same only if the provided is empty, that means
1759       // the provided repo has no alias
1760       // then skip
1761       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1762         continue;
1763
1764       // TODO match by url
1765
1766       // we have a matcing repository, now we need to know
1767       // where it does come from.
1768       RepoInfo todelete = *it;
1769       if (todelete.filepath().empty())
1770       {
1771         ZYPP_THROW(RepoException( todelete, _("Can't figure out where the repo is stored.") ));
1772       }
1773       else
1774       {
1775         // figure how many repos are there in the file:
1776         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1777         if ( filerepos.size() == 0      // bsc#984494: file may have already been deleted
1778           ||(filerepos.size() == 1 && filerepos.front().alias() == todelete.alias() ) )
1779         {
1780           // easy: file does not exist, contains no or only the repo to delete: delete the file
1781           int ret = filesystem::unlink( todelete.filepath() );
1782           if ( ! ( ret == 0 || ret == ENOENT ) )
1783           {
1784             // TranslatorExplanation '%s' is a filename
1785             ZYPP_THROW(RepoException( todelete, str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
1786           }
1787           MIL << todelete.alias() << " successfully deleted." << endl;
1788         }
1789         else
1790         {
1791           // there are more repos in the same file
1792           // write them back except the deleted one.
1793           //TmpFile tmp;
1794           //std::ofstream file(tmp.path().c_str());
1795
1796           // assert the directory exists
1797           filesystem::assert_dir(todelete.filepath().dirname());
1798
1799           std::ofstream file(todelete.filepath().c_str());
1800           if (!file)
1801           {
1802             // TranslatorExplanation '%s' is a filename
1803             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
1804           }
1805           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1806                 fit != filerepos.end();
1807                 ++fit )
1808           {
1809             if ( (*fit).alias() != todelete.alias() )
1810               (*fit).dumpAsIniOn(file);
1811           }
1812         }
1813
1814         CombinedProgressData cSubprogrcv(progress, 20);
1815         CombinedProgressData mSubprogrcv(progress, 40);
1816         CombinedProgressData pSubprogrcv(progress, 40);
1817         // now delete it from cache
1818         if ( isCached(todelete) )
1819           cleanCache( todelete, cSubprogrcv);
1820         // now delete metadata (#301037)
1821         cleanMetadata( todelete, mSubprogrcv );
1822         cleanPackages( todelete, pSubprogrcv );
1823         reposManip().erase(todelete);
1824         MIL << todelete.alias() << " successfully deleted." << endl;
1825         HistoryLog(_options.rootDir).removeRepository(todelete);
1826         return;
1827       } // else filepath is empty
1828
1829     }
1830     // should not be reached on a sucess workflow
1831     ZYPP_THROW(RepoNotFoundException(info));
1832   }
1833
1834   ////////////////////////////////////////////////////////////////////////////
1835
1836   void RepoManager::Impl::modifyRepository( const std::string & alias, const RepoInfo & newinfo_r, const ProgressData::ReceiverFnc & progressrcv )
1837   {
1838     RepoInfo toedit = getRepositoryInfo(alias);
1839     RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
1840
1841     // check if the new alias already exists when renaming the repo
1842     if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
1843     {
1844       ZYPP_THROW(RepoAlreadyExistsException(newinfo));
1845     }
1846
1847     if (toedit.filepath().empty())
1848     {
1849       ZYPP_THROW(RepoException( toedit, _("Can't figure out where the repo is stored.") ));
1850     }
1851     else
1852     {
1853       // figure how many repos are there in the file:
1854       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1855
1856       // there are more repos in the same file
1857       // write them back except the deleted one.
1858       //TmpFile tmp;
1859       //std::ofstream file(tmp.path().c_str());
1860
1861       // assert the directory exists
1862       filesystem::assert_dir(toedit.filepath().dirname());
1863
1864       std::ofstream file(toedit.filepath().c_str());
1865       if (!file)
1866       {
1867         // TranslatorExplanation '%s' is a filename
1868         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
1869       }
1870       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1871             fit != filerepos.end();
1872             ++fit )
1873       {
1874           // if the alias is different, dump the original
1875           // if it is the same, dump the provided one
1876           if ( (*fit).alias() != toedit.alias() )
1877             (*fit).dumpAsIniOn(file);
1878           else
1879             newinfo.dumpAsIniOn(file);
1880       }
1881
1882       if ( toedit.enabled() && !newinfo.enabled() )
1883       {
1884         // On the fly remove solv.idx files for bash completion if a repo gets disabled.
1885         const Pathname & solvidx = solv_path_for_repoinfo(_options, newinfo)/"solv.idx";
1886         if ( PathInfo(solvidx).isExist() )
1887           filesystem::unlink( solvidx );
1888       }
1889
1890       newinfo.setFilepath(toedit.filepath());
1891       newinfo.setMetadataPath( rawcache_path_for_repoinfo( _options, newinfo ) );
1892       newinfo.setPackagesPath( packagescache_path_for_repoinfo( _options, newinfo ) );
1893       {
1894         // We should fix the API as we must inject those paths
1895         // into the repoinfo in order to keep it usable.
1896         RepoInfo & oinfo( const_cast<RepoInfo &>(newinfo_r) );
1897         oinfo.setFilepath(toedit.filepath());
1898         oinfo.setMetadataPath( rawcache_path_for_repoinfo( _options, newinfo ) );
1899         oinfo.setPackagesPath( packagescache_path_for_repoinfo( _options, newinfo ) );
1900       }
1901       reposManip().erase(toedit);
1902       reposManip().insert(newinfo);
1903       // check for credentials in Urls
1904       UrlCredentialExtractor( _options.rootDir ).collect( newinfo.baseUrls() );
1905       HistoryLog(_options.rootDir).modifyRepository(toedit, newinfo);
1906       MIL << "repo " << alias << " modified" << endl;
1907     }
1908   }
1909
1910   ////////////////////////////////////////////////////////////////////////////
1911
1912   RepoInfo RepoManager::Impl::getRepositoryInfo( const std::string & alias, const ProgressData::ReceiverFnc & progressrcv )
1913   {
1914     RepoConstIterator it( findAlias( alias, repos() ) );
1915     if ( it != repos().end() )
1916       return *it;
1917     RepoInfo info;
1918     info.setAlias( alias );
1919     ZYPP_THROW( RepoNotFoundException(info) );
1920   }
1921
1922
1923   RepoInfo RepoManager::Impl::getRepositoryInfo( const Url & url, const url::ViewOption & urlview, const ProgressData::ReceiverFnc & progressrcv )
1924   {
1925     for_( it, repoBegin(), repoEnd() )
1926     {
1927       for_( urlit, (*it).baseUrlsBegin(), (*it).baseUrlsEnd() )
1928       {
1929         if ( (*urlit).asString(urlview) == url.asString(urlview) )
1930           return *it;
1931       }
1932     }
1933     RepoInfo info;
1934     info.setBaseUrl( url );
1935     ZYPP_THROW( RepoNotFoundException(info) );
1936   }
1937
1938   ////////////////////////////////////////////////////////////////////////////
1939   //
1940   // Services
1941   //
1942   ////////////////////////////////////////////////////////////////////////////
1943
1944   void RepoManager::Impl::addService( const ServiceInfo & service )
1945   {
1946     assert_alias( service );
1947
1948     // check if service already exists
1949     if ( hasService( service.alias() ) )
1950       ZYPP_THROW( ServiceAlreadyExistsException( service ) );
1951
1952     // Writable ServiceInfo is needed to save the location
1953     // of the .service file. Finaly insert into the service list.
1954     ServiceInfo toSave( service );
1955     saveService( toSave );
1956     _services.insert( toSave );
1957
1958     // check for credentials in Url
1959     UrlCredentialExtractor( _options.rootDir ).collect( toSave.url() );
1960
1961     MIL << "added service " << toSave.alias() << endl;
1962   }
1963
1964   ////////////////////////////////////////////////////////////////////////////
1965
1966   void RepoManager::Impl::removeService( const std::string & alias )
1967   {
1968     MIL << "Going to delete service " << alias << endl;
1969
1970     const ServiceInfo & service = getService( alias );
1971
1972     Pathname location = service.filepath();
1973     if( location.empty() )
1974     {
1975       ZYPP_THROW(ServiceException( service, _("Can't figure out where the service is stored.") ));
1976     }
1977
1978     ServiceSet tmpSet;
1979     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1980
1981     // only one service definition in the file
1982     if ( tmpSet.size() == 1 )
1983     {
1984       if ( filesystem::unlink(location) != 0 )
1985       {
1986         // TranslatorExplanation '%s' is a filename
1987         ZYPP_THROW(ServiceException( service, str::form( _("Can't delete '%s'"), location.c_str() ) ));
1988       }
1989       MIL << alias << " successfully deleted." << endl;
1990     }
1991     else
1992     {
1993       filesystem::assert_dir(location.dirname());
1994
1995       std::ofstream file(location.c_str());
1996       if( !file )
1997       {
1998         // TranslatorExplanation '%s' is a filename
1999         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
2000       }
2001
2002       for_(it, tmpSet.begin(), tmpSet.end())
2003       {
2004         if( it->alias() != alias )
2005           it->dumpAsIniOn(file);
2006       }
2007
2008       MIL << alias << " successfully deleted from file " << location <<  endl;
2009     }
2010
2011     // now remove all repositories added by this service
2012     RepoCollector rcollector;
2013     getRepositoriesInService( alias,
2014                               boost::make_function_output_iterator( bind( &RepoCollector::collect, &rcollector, _1 ) ) );
2015     // cannot do this directly in getRepositoriesInService - would invalidate iterators
2016     for_(rit, rcollector.repos.begin(), rcollector.repos.end())
2017       removeRepository(*rit);
2018   }
2019
2020   ////////////////////////////////////////////////////////////////////////////
2021
2022   void RepoManager::Impl::refreshServices( const RefreshServiceOptions & options_r )
2023   {
2024     // copy the set of services since refreshService
2025     // can eventually invalidate the iterator
2026     ServiceSet services( serviceBegin(), serviceEnd() );
2027     for_( it, services.begin(), services.end() )
2028     {
2029       if ( !it->enabled() )
2030         continue;
2031
2032       try {
2033         refreshService(*it, options_r);
2034       }
2035       catch ( const repo::ServicePluginInformalException & e )
2036       { ;/* ignore ServicePluginInformalException */ }
2037     }
2038   }
2039
2040   void RepoManager::Impl::refreshService( const std::string & alias, const RefreshServiceOptions & options_r )
2041   {
2042     ServiceInfo service( getService( alias ) );
2043     assert_alias( service );
2044     assert_url( service );
2045     MIL << "Going to refresh service '" << service.alias() <<  "', url: " << service.url() << ", opts: " << options_r << endl;
2046
2047     if ( service.ttl() && !( options_r.testFlag( RefreshService_forceRefresh) || options_r.testFlag( RefreshService_restoreStatus ) ) )
2048     {
2049       // Service defines a TTL; maybe we can re-use existing data without refresh.
2050       Date lrf = service.lrf();
2051       if ( lrf )
2052       {
2053         Date now( Date::now() );
2054         if ( lrf <= now )
2055         {
2056           if ( (lrf+=service.ttl()) > now ) // lrf+= !
2057           {
2058             MIL << "Skip: '" << service.alias() << "' metadata valid until " << lrf << endl;
2059             return;
2060           }
2061         }
2062         else
2063           WAR << "Force: '" << service.alias() << "' metadata last refresh in the future: " << lrf << endl;
2064       }
2065     }
2066
2067     // NOTE: It might be necessary to modify and rewrite the service info.
2068     // Either when probing the type, or when adjusting the repositories
2069     // enable/disable state.:
2070     bool serviceModified = false;
2071
2072     //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)?
2073
2074     // if the type is unknown, try probing.
2075     if ( service.type() == repo::ServiceType::NONE )
2076     {
2077       repo::ServiceType type = probeService( service.url() );
2078       if ( type != ServiceType::NONE )
2079       {
2080         service.setProbedType( type ); // lazy init!
2081         serviceModified = true;
2082       }
2083     }
2084
2085     // get target distro identifier
2086     std::string servicesTargetDistro = _options.servicesTargetDistro;
2087     if ( servicesTargetDistro.empty() )
2088     {
2089       servicesTargetDistro = Target::targetDistribution( Pathname() );
2090     }
2091     DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
2092
2093     // parse it
2094     Date::Duration origTtl = service.ttl();     // FIXME Ugly hack: const service.ttl modified when parsing
2095     RepoCollector collector(servicesTargetDistro);
2096     // FIXME Ugly hack: ServiceRepos may throw ServicePluginInformalException
2097     // which is actually a notification. Using an exception for this
2098     // instead of signal/callback is bad. Needs to be fixed here, in refreshServices()
2099     // and in zypper.
2100     std::pair<DefaultIntegral<bool,false>, repo::ServicePluginInformalException> uglyHack;
2101     try {
2102       ServiceRepos( service, bind( &RepoCollector::collect, &collector, _1 ) );
2103     }
2104     catch ( const repo::ServicePluginInformalException & e )
2105     {
2106       /* ignore ServicePluginInformalException and throw later */
2107       uglyHack.first = true;
2108       uglyHack.second = e;
2109     }
2110     if ( service.ttl() != origTtl )     // repoindex.xml changed ttl
2111     {
2112       if ( !service.ttl() )
2113         service.setLrf( Date() );       // don't need lrf when zero ttl
2114       serviceModified = true;
2115     }
2116     ////////////////////////////////////////////////////////////////////////////
2117     // On the fly remember the new repo states as defined the reopoindex.xml.
2118     // Move into ServiceInfo later.
2119     ServiceInfo::RepoStates newRepoStates;
2120
2121     // set service alias and base url for all collected repositories
2122     for_( it, collector.repos.begin(), collector.repos.end() )
2123     {
2124       // First of all: Prepend service alias:
2125       it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
2126       // set reference to the parent service
2127       it->setService( service.alias() );
2128
2129       // remember the new parsed repo state
2130       newRepoStates[it->alias()] = *it;
2131
2132       // - If the repo url was not set by the repoindex parser, set service's url.
2133       // - Libzypp currently has problem with separate url + path handling so just
2134       //   append a path, if set, to the baseurls
2135       // - Credentials in the url authority will be extracted later, either if the
2136       //   repository is added or if we check for changed urls.
2137       Pathname path;
2138       if ( !it->path().empty() )
2139       {
2140         if ( it->path() != "/" )
2141           path = it->path();
2142         it->setPath("");
2143       }
2144
2145       if ( it->baseUrlsEmpty() )
2146       {
2147         Url url( service.rawUrl() );
2148         if ( !path.empty() )
2149           url.setPathName( url.getPathName() / path );
2150         it->setBaseUrl( std::move(url) );
2151       }
2152       else if ( !path.empty() )
2153       {
2154         RepoInfo::url_set urls( it->rawBaseUrls() );
2155         for ( Url & url : urls )
2156         {
2157           url.setPathName( url.getPathName() / path );
2158         }
2159         it->setBaseUrls( std::move(urls) );
2160       }
2161     }
2162
2163     ////////////////////////////////////////////////////////////////////////////
2164     // Now compare collected repos with the ones in the system...
2165     //
2166     RepoInfoList oldRepos;
2167     getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
2168
2169     ////////////////////////////////////////////////////////////////////////////
2170     // find old repositories to remove...
2171     for_( oldRepo, oldRepos.begin(), oldRepos.end() )
2172     {
2173       if ( ! foundAliasIn( oldRepo->alias(), collector.repos ) )
2174       {
2175         if ( oldRepo->enabled() )
2176         {
2177           // Currently enabled. If this was a user modification remember the state.
2178           const auto & last = service.repoStates().find( oldRepo->alias() );
2179           if ( last != service.repoStates().end() && ! last->second.enabled )
2180           {
2181             DBG << "Service removes user enabled repo " << oldRepo->alias() << endl;
2182             service.addRepoToEnable( oldRepo->alias() );
2183             serviceModified = true;
2184           }
2185           else
2186             DBG << "Service removes enabled repo " << oldRepo->alias() << endl;
2187         }
2188         else
2189           DBG << "Service removes disabled repo " << oldRepo->alias() << endl;
2190
2191         removeRepository( *oldRepo );
2192       }
2193     }
2194
2195     ////////////////////////////////////////////////////////////////////////////
2196     // create missing repositories and modify existing ones if needed...
2197     UrlCredentialExtractor urlCredentialExtractor( _options.rootDir );  // To collect any credentials stored in repo URLs
2198     for_( it, collector.repos.begin(), collector.repos.end() )
2199     {
2200       // User explicitly requested the repo being enabled?
2201       // User explicitly requested the repo being disabled?
2202       // And hopefully not both ;) If so, enable wins.
2203
2204       TriBool toBeEnabled( indeterminate );     // indeterminate - follow the service request
2205       DBG << "Service request to " << (it->enabled()?"enable":"disable") << " service repo " << it->alias() << endl;
2206
2207       if ( options_r.testFlag( RefreshService_restoreStatus ) )
2208       {
2209         DBG << "Opt RefreshService_restoreStatus " << it->alias() << endl;
2210         // this overrides any pending request!
2211         // Remove from enable request list.
2212         // NOTE: repoToDisable is handled differently.
2213         //       It gets cleared on each refresh.
2214         service.delRepoToEnable( it->alias() );
2215         // toBeEnabled stays indeterminate!
2216       }
2217       else
2218       {
2219         if ( service.repoToEnableFind( it->alias() ) )
2220         {
2221           DBG << "User request to enable service repo " << it->alias() << endl;
2222           toBeEnabled = true;
2223           // Remove from enable request list.
2224           // NOTE: repoToDisable is handled differently.
2225           //       It gets cleared on each refresh.
2226           service.delRepoToEnable( it->alias() );
2227           serviceModified = true;
2228         }
2229         else if ( service.repoToDisableFind( it->alias() ) )
2230         {
2231           DBG << "User request to disable service repo " << it->alias() << endl;
2232           toBeEnabled = false;
2233         }
2234       }
2235
2236       RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
2237       if ( oldRepo == oldRepos.end() )
2238       {
2239         // Not found in oldRepos ==> a new repo to add
2240
2241         // Make sure the service repo is created with the appropriate enablement
2242         if ( ! indeterminate(toBeEnabled) )
2243           it->setEnabled( toBeEnabled );
2244
2245         DBG << "Service adds repo " << it->alias() << " " << (it->enabled()?"enabled":"disabled") << endl;
2246         addRepository( *it );
2247       }
2248       else
2249       {
2250         // ==> an exising repo to check
2251         bool oldRepoModified = false;
2252
2253         if ( indeterminate(toBeEnabled) )
2254         {
2255           // No user request: check for an old user modificaton otherwise follow service request.
2256           // NOTE: Assert toBeEnabled is boolean afterwards!
2257           if ( oldRepo->enabled() == it->enabled() )
2258             toBeEnabled = it->enabled();        // service requests no change to the system
2259           else if (options_r.testFlag( RefreshService_restoreStatus ) )
2260           {
2261             toBeEnabled = it->enabled();        // RefreshService_restoreStatus forced
2262             DBG << "Opt RefreshService_restoreStatus " << it->alias() <<  " forces " << (toBeEnabled?"enabled":"disabled") << endl;
2263           }
2264           else
2265           {
2266             const auto & last = service.repoStates().find( oldRepo->alias() );
2267             if ( last == service.repoStates().end() || last->second.enabled != it->enabled() )
2268               toBeEnabled = it->enabled();      // service request has changed since last refresh -> follow
2269             else
2270             {
2271               toBeEnabled = oldRepo->enabled(); // service request unchaned since last refresh -> keep user modification
2272               DBG << "User modified service repo " << it->alias() <<  " may stay " << (toBeEnabled?"enabled":"disabled") << endl;
2273             }
2274           }
2275         }
2276
2277         // changed enable?
2278         if ( toBeEnabled == oldRepo->enabled() )
2279         {
2280           DBG << "Service repo " << it->alias() << " stays " <<  (oldRepo->enabled()?"enabled":"disabled") << endl;
2281         }
2282         else if ( toBeEnabled )
2283         {
2284           DBG << "Service repo " << it->alias() << " gets enabled" << endl;
2285           oldRepo->setEnabled( true );
2286           oldRepoModified = true;
2287         }
2288         else
2289         {
2290           DBG << "Service repo " << it->alias() << " gets disabled" << endl;
2291           oldRepo->setEnabled( false );
2292           oldRepoModified = true;
2293         }
2294
2295         // all other attributes follow the service request:
2296
2297         // changed name (raw!)
2298         if ( oldRepo->rawName() != it->rawName() )
2299         {
2300           DBG << "Service repo " << it->alias() << " gets new NAME " << it->rawName() << endl;
2301           oldRepo->setName( it->rawName() );
2302           oldRepoModified = true;
2303         }
2304
2305         // changed autorefresh
2306         if ( oldRepo->autorefresh() != it->autorefresh() )
2307         {
2308           DBG << "Service repo " << it->alias() << " gets new AUTOREFRESH " << it->autorefresh() << endl;
2309           oldRepo->setAutorefresh( it->autorefresh() );
2310           oldRepoModified = true;
2311         }
2312
2313         // changed priority?
2314         if ( oldRepo->priority() != it->priority() )
2315         {
2316           DBG << "Service repo " << it->alias() << " gets new PRIORITY " << it->priority() << endl;
2317           oldRepo->setPriority( it->priority() );
2318           oldRepoModified = true;
2319         }
2320
2321         // changed url?
2322         {
2323           RepoInfo::url_set newUrls( it->rawBaseUrls() );
2324           urlCredentialExtractor.extract( newUrls );    // Extract! to prevent passwds from disturbing the comparison below
2325           if ( oldRepo->rawBaseUrls() != newUrls )
2326           {
2327             DBG << "Service repo " << it->alias() << " gets new URLs " << newUrls << endl;
2328             oldRepo->setBaseUrls( std::move(newUrls) );
2329             oldRepoModified = true;
2330           }
2331         }
2332
2333         // changed gpg check settings?
2334         // ATM only plugin services can set GPG values.
2335         if ( service.type() == ServiceType::PLUGIN )
2336         {
2337           TriBool ogpg[3];      // Gpg RepoGpg PkgGpg
2338           TriBool ngpg[3];
2339           oldRepo->getRawGpgChecks( ogpg[0], ogpg[1], ogpg[2] );
2340           it->     getRawGpgChecks( ngpg[0], ngpg[1], ngpg[2] );
2341 #define Z_CHKGPG(I,N)                                                                           \
2342           if ( ! sameTriboolState( ogpg[I], ngpg[I] ) )                                         \
2343           {                                                                                     \
2344             DBG << "Service repo " << it->alias() << " gets new "#N"Check " << ngpg[I] << endl; \
2345             oldRepo->set##N##Check( ngpg[I] );                                                  \
2346             oldRepoModified = true;                                                             \
2347           }
2348           Z_CHKGPG( 0, Gpg );
2349           Z_CHKGPG( 1, RepoGpg );
2350           Z_CHKGPG( 2, PkgGpg );
2351 #undef Z_CHKGPG
2352         }
2353
2354         // save if modified:
2355         if ( oldRepoModified )
2356         {
2357           modifyRepository( oldRepo->alias(), *oldRepo );
2358         }
2359       }
2360     }
2361
2362     // Unlike reposToEnable, reposToDisable is always cleared after refresh.
2363     if ( ! service.reposToDisableEmpty() )
2364     {
2365       service.clearReposToDisable();
2366       serviceModified = true;
2367     }
2368
2369     // Remember original service request for next refresh
2370     if ( service.repoStates() != newRepoStates )
2371     {
2372       service.setRepoStates( std::move(newRepoStates) );
2373       serviceModified = true;
2374     }
2375
2376     ////////////////////////////////////////////////////////////////////////////
2377     // save service if modified: (unless a plugin service)
2378     if ( service.type() != ServiceType::PLUGIN )
2379     {
2380       if ( service.ttl() )
2381       {
2382         service.setLrf( Date::now() );  // remember last refresh
2383         serviceModified =  true;        // or use a cookie file
2384       }
2385
2386       if ( serviceModified )
2387       {
2388         // write out modified service file.
2389         modifyService( service.alias(), service );
2390       }
2391     }
2392
2393     if ( uglyHack.first )
2394     {
2395       throw( uglyHack.second ); // intentionally not ZYPP_THROW
2396     }
2397   }
2398
2399   ////////////////////////////////////////////////////////////////////////////
2400
2401   void RepoManager::Impl::modifyService( const std::string & oldAlias, const ServiceInfo & newService )
2402   {
2403     MIL << "Going to modify service " << oldAlias << endl;
2404
2405     // we need a writable copy to link it to the file where
2406     // it is saved if we modify it
2407     ServiceInfo service(newService);
2408
2409     if ( service.type() == ServiceType::PLUGIN )
2410     {
2411       ZYPP_THROW(ServicePluginImmutableException( service ));
2412     }
2413
2414     const ServiceInfo & oldService = getService(oldAlias);
2415
2416     Pathname location = oldService.filepath();
2417     if( location.empty() )
2418     {
2419       ZYPP_THROW(ServiceException( oldService, _("Can't figure out where the service is stored.") ));
2420     }
2421
2422     // remember: there may multiple services being defined in one file:
2423     ServiceSet tmpSet;
2424     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
2425
2426     filesystem::assert_dir(location.dirname());
2427     std::ofstream file(location.c_str());
2428     for_(it, tmpSet.begin(), tmpSet.end())
2429     {
2430       if( *it != oldAlias )
2431         it->dumpAsIniOn(file);
2432     }
2433     service.dumpAsIniOn(file);
2434     file.close();
2435     service.setFilepath(location);
2436
2437     _services.erase(oldAlias);
2438     _services.insert(service);
2439     // check for credentials in Urls
2440     UrlCredentialExtractor( _options.rootDir ).collect( service.url() );
2441
2442
2443     // changed properties affecting also repositories
2444     if ( oldAlias != service.alias()                    // changed alias
2445       || oldService.enabled() != service.enabled() )    // changed enabled status
2446     {
2447       std::vector<RepoInfo> toModify;
2448       getRepositoriesInService(oldAlias, std::back_inserter(toModify));
2449       for_( it, toModify.begin(), toModify.end() )
2450       {
2451         if ( oldService.enabled() != service.enabled() )
2452         {
2453           if ( service.enabled() )
2454           {
2455             // reset to last refreshs state
2456             const auto & last = service.repoStates().find( it->alias() );
2457             if ( last != service.repoStates().end() )
2458               it->setEnabled( last->second.enabled );
2459           }
2460           else
2461             it->setEnabled( false );
2462         }
2463
2464         if ( oldAlias != service.alias() )
2465           it->setService(service.alias());
2466
2467         modifyRepository(it->alias(), *it);
2468       }
2469     }
2470
2471     //! \todo refresh the service automatically if url is changed?
2472   }
2473
2474   ////////////////////////////////////////////////////////////////////////////
2475
2476   repo::ServiceType RepoManager::Impl::probeService( const Url & url ) const
2477   {
2478     try
2479     {
2480       MediaSetAccess access(url);
2481       if ( access.doesFileExist("/repo/repoindex.xml") )
2482         return repo::ServiceType::RIS;
2483     }
2484     catch ( const media::MediaException &e )
2485     {
2486       ZYPP_CAUGHT(e);
2487       // TranslatorExplanation '%s' is an URL
2488       RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
2489       enew.remember(e);
2490       ZYPP_THROW(enew);
2491     }
2492     catch ( const Exception &e )
2493     {
2494       ZYPP_CAUGHT(e);
2495       // TranslatorExplanation '%s' is an URL
2496       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
2497       enew.remember(e);
2498       ZYPP_THROW(enew);
2499     }
2500
2501     return repo::ServiceType::NONE;
2502   }
2503
2504   ///////////////////////////////////////////////////////////////////
2505   //
2506   //    CLASS NAME : RepoManager
2507   //
2508   ///////////////////////////////////////////////////////////////////
2509
2510   RepoManager::RepoManager( const RepoManagerOptions & opt )
2511   : _pimpl( new Impl(opt) )
2512   {}
2513
2514   RepoManager::~RepoManager()
2515   {}
2516
2517   bool RepoManager::repoEmpty() const
2518   { return _pimpl->repoEmpty(); }
2519
2520   RepoManager::RepoSizeType RepoManager::repoSize() const
2521   { return _pimpl->repoSize(); }
2522
2523   RepoManager::RepoConstIterator RepoManager::repoBegin() const
2524   { return _pimpl->repoBegin(); }
2525
2526   RepoManager::RepoConstIterator RepoManager::repoEnd() const
2527   { return _pimpl->repoEnd(); }
2528
2529   RepoInfo RepoManager::getRepo( const std::string & alias ) const
2530   { return _pimpl->getRepo( alias ); }
2531
2532   bool RepoManager::hasRepo( const std::string & alias ) const
2533   { return _pimpl->hasRepo( alias ); }
2534
2535   std::string RepoManager::makeStupidAlias( const Url & url_r )
2536   {
2537     std::string ret( url_r.getScheme() );
2538     if ( ret.empty() )
2539       ret = "repo-";
2540     else
2541       ret += "-";
2542
2543     std::string host( url_r.getHost() );
2544     if ( ! host.empty() )
2545     {
2546       ret += host;
2547       ret += "-";
2548     }
2549
2550     static Date::ValueType serial = Date::now();
2551     ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8);
2552     return ret;
2553   }
2554
2555   RepoStatus RepoManager::metadataStatus( const RepoInfo & info ) const
2556   { return _pimpl->metadataStatus( info ); }
2557
2558   RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata( const RepoInfo &info, const Url &url, RawMetadataRefreshPolicy policy )
2559   { return _pimpl->checkIfToRefreshMetadata( info, url, policy ); }
2560
2561   Pathname RepoManager::metadataPath( const RepoInfo &info ) const
2562   { return _pimpl->metadataPath( info ); }
2563
2564   Pathname RepoManager::packagesPath( const RepoInfo &info ) const
2565   { return _pimpl->packagesPath( info ); }
2566
2567   void RepoManager::refreshMetadata( const RepoInfo &info, RawMetadataRefreshPolicy policy, const ProgressData::ReceiverFnc & progressrcv )
2568   { return _pimpl->refreshMetadata( info, policy, progressrcv ); }
2569
2570   void RepoManager::cleanMetadata( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
2571   { return _pimpl->cleanMetadata( info, progressrcv ); }
2572
2573   void RepoManager::cleanPackages( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
2574   { return _pimpl->cleanPackages( info, progressrcv ); }
2575
2576   RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
2577   { return _pimpl->cacheStatus( info ); }
2578
2579   void RepoManager::buildCache( const RepoInfo &info, CacheBuildPolicy policy, const ProgressData::ReceiverFnc & progressrcv )
2580   { return _pimpl->buildCache( info, policy, progressrcv ); }
2581
2582   void RepoManager::cleanCache( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
2583   { return _pimpl->cleanCache( info, progressrcv ); }
2584
2585   bool RepoManager::isCached( const RepoInfo &info ) const
2586   { return _pimpl->isCached( info ); }
2587
2588   void RepoManager::loadFromCache( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
2589   { return _pimpl->loadFromCache( info, progressrcv ); }
2590
2591   void RepoManager::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
2592   { return _pimpl->cleanCacheDirGarbage( progressrcv ); }
2593
2594   repo::RepoType RepoManager::probe( const Url & url, const Pathname & path ) const
2595   { return _pimpl->probe( url, path ); }
2596
2597   repo::RepoType RepoManager::probe( const Url & url ) const
2598   { return _pimpl->probe( url ); }
2599
2600   void RepoManager::addRepository( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
2601   { return _pimpl->addRepository( info, progressrcv ); }
2602
2603   void RepoManager::addRepositories( const Url &url, const ProgressData::ReceiverFnc & progressrcv )
2604   { return _pimpl->addRepositories( url, progressrcv ); }
2605
2606   void RepoManager::removeRepository( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
2607   { return _pimpl->removeRepository( info, progressrcv ); }
2608
2609   void RepoManager::modifyRepository( const std::string &alias, const RepoInfo & newinfo, const ProgressData::ReceiverFnc & progressrcv )
2610   { return _pimpl->modifyRepository( alias, newinfo, progressrcv ); }
2611
2612   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias, const ProgressData::ReceiverFnc & progressrcv )
2613   { return _pimpl->getRepositoryInfo( alias, progressrcv ); }
2614
2615   RepoInfo RepoManager::getRepositoryInfo( const Url & url, const url::ViewOption & urlview, const ProgressData::ReceiverFnc & progressrcv )
2616   { return _pimpl->getRepositoryInfo( url, urlview, progressrcv ); }
2617
2618   bool RepoManager::serviceEmpty() const
2619   { return _pimpl->serviceEmpty(); }
2620
2621   RepoManager::ServiceSizeType RepoManager::serviceSize() const
2622   { return _pimpl->serviceSize(); }
2623
2624   RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
2625   { return _pimpl->serviceBegin(); }
2626
2627   RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
2628   { return _pimpl->serviceEnd(); }
2629
2630   ServiceInfo RepoManager::getService( const std::string & alias ) const
2631   { return _pimpl->getService( alias ); }
2632
2633   bool RepoManager::hasService( const std::string & alias ) const
2634   { return _pimpl->hasService( alias ); }
2635
2636   repo::ServiceType RepoManager::probeService( const Url &url ) const
2637   { return _pimpl->probeService( url ); }
2638
2639   void RepoManager::addService( const std::string & alias, const Url& url )
2640   { return _pimpl->addService( alias, url ); }
2641
2642   void RepoManager::addService( const ServiceInfo & service )
2643   { return _pimpl->addService( service ); }
2644
2645   void RepoManager::removeService( const std::string & alias )
2646   { return _pimpl->removeService( alias ); }
2647
2648   void RepoManager::removeService( const ServiceInfo & service )
2649   { return _pimpl->removeService( service ); }
2650
2651   void RepoManager::refreshServices( const RefreshServiceOptions & options_r )
2652   { return _pimpl->refreshServices( options_r ); }
2653
2654   void RepoManager::refreshService( const std::string & alias, const RefreshServiceOptions & options_r )
2655   { return _pimpl->refreshService( alias, options_r ); }
2656
2657   void RepoManager::refreshService( const ServiceInfo & service, const RefreshServiceOptions & options_r )
2658   { return _pimpl->refreshService( service, options_r ); }
2659
2660   void RepoManager::modifyService( const std::string & oldAlias, const ServiceInfo & service )
2661   { return _pimpl->modifyService( oldAlias, service ); }
2662
2663   ////////////////////////////////////////////////////////////////////////////
2664
2665   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
2666   { return str << *obj._pimpl; }
2667
2668   /////////////////////////////////////////////////////////////////
2669 } // namespace zypp
2670 ///////////////////////////////////////////////////////////////////