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