Consider pending disable requests when removing service repositories. (bnc #572634)
[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 "zypp/base/InputStream.h"
22 #include "zypp/base/LogTools.h"
23 #include "zypp/base/Gettext.h"
24 #include "zypp/base/Function.h"
25 #include "zypp/base/Regex.h"
26 #include "zypp/PathInfo.h"
27 #include "zypp/TmpPath.h"
28
29 #include "zypp/ServiceInfo.h"
30 #include "zypp/repo/RepoException.h"
31 #include "zypp/RepoManager.h"
32
33 #include "zypp/media/MediaManager.h"
34 #include "zypp/media/CredentialManager.h"
35 #include "zypp/MediaSetAccess.h"
36 #include "zypp/ExternalProgram.h"
37 #include "zypp/ManagedFile.h"
38
39 #include "zypp/parser/RepoFileReader.h"
40 #include "zypp/parser/ServiceFileReader.h"
41 #include "zypp/parser/RepoindexFileReader.h"
42 #include "zypp/repo/yum/Downloader.h"
43 #include "zypp/repo/susetags/Downloader.h"
44 #include "zypp/parser/plaindir/RepoParser.h"
45
46 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
47 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance
48 #include "zypp/HistoryLog.h" // to write history :O)
49
50 #include "zypp/ZYppCallbacks.h"
51
52 #include "sat/Pool.h"
53
54 using std::endl;
55 using std::string;
56 using namespace zypp::repo;
57
58 ///////////////////////////////////////////////////////////////////
59 namespace zypp
60 { /////////////////////////////////////////////////////////////////
61
62   namespace
63   {
64     /** Simple media mounter to access non-downloading URLs e.g. for non-local plaindir repos.
65      * \ingroup g_RAII
66     */
67     class MediaMounter
68     {
69       public:
70         /** Ctor provides media access. */
71         MediaMounter( const Url & url_r )
72         {
73           media::MediaManager mediamanager;
74           _mid = mediamanager.open( url_r );
75           mediamanager.attach( _mid );
76         }
77
78         /** Ctor releases the media. */
79         ~MediaMounter()
80         {
81           media::MediaManager mediamanager;
82           mediamanager.release( _mid );
83           mediamanager.close( _mid );
84         }
85
86         /** Convert a path relative to the media into an absolute path.
87          *
88          * Called without argument it returns the path to the medias root directory.
89         */
90         Pathname getPathName( const Pathname & path_r = Pathname() ) const
91         {
92           media::MediaManager mediamanager;
93           return mediamanager.localPath( _mid, path_r );
94         }
95
96       private:
97         media::MediaAccessId _mid;
98     };
99
100     /** Check if alias_r is present in repo/service container. */
101     template <class Iterator>
102     inline bool foundAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r )
103     {
104       for_( it, begin_r, end_r )
105         if ( it->alias() == alias_r )
106           return true;
107       return false;
108     }
109     /** \overload */
110     template <class Container>
111     inline bool foundAliasIn( const std::string & alias_r, const Container & cont_r )
112     { return foundAliasIn( alias_r, cont_r.begin(), cont_r.end() ); }
113
114     /** Find alias_r in repo/service container. */
115     template <class Iterator>
116     inline Iterator findAlias( const std::string & alias_r, Iterator begin_r, Iterator end_r )
117     {
118       for_( it, begin_r, end_r )
119         if ( it->alias() == alias_r )
120           return it;
121       return end_r;
122     }
123     /** \overload */
124     template <class Container>
125     inline typename Container::iterator findAlias( const std::string & alias_r, Container & cont_r )
126     { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
127     /** \overload */
128     template <class Container>
129     inline typename Container::const_iterator findAlias( const std::string & alias_r, const Container & cont_r )
130     { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
131   }
132
133   ///////////////////////////////////////////////////////////////////
134   //
135   //    CLASS NAME : RepoManagerOptions
136   //
137   ///////////////////////////////////////////////////////////////////
138
139   RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
140   {
141     repoCachePath         = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
142     repoRawCachePath      = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
143     repoSolvCachePath     = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
144     repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
145     knownReposPath        = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
146     knownServicesPath     = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
147     probe                 = ZConfig::instance().repo_add_probe();
148
149     rootDir = root_r;
150   }
151
152   RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r )
153   {
154     RepoManagerOptions ret;
155     ret.repoCachePath         = root_r;
156     ret.repoRawCachePath      = root_r/"raw";
157     ret.repoSolvCachePath     = root_r/"solv";
158     ret.repoPackagesCachePath = root_r/"packages";
159     ret.knownReposPath        = root_r/"repos.d";
160     ret.knownServicesPath     = root_r/"services.d";
161     ret.rootDir = root_r;
162     return ret;
163   }
164
165   ////////////////////////////////////////////////////////////////////////////
166
167   /**
168     * \short Simple callback to collect the results
169     *
170     * Classes like RepoFileParser call the callback
171     * once per each repo in a file.
172     *
173     * Passing this functor as callback, you can collect
174     * all results at the end, without dealing with async
175     * code.
176     *
177     * If targetDistro is set, all repos with non-empty RepoInfo::targetDistribution()
178     * will be skipped.
179     *
180     * \todo do this through a separate filter
181     */
182     struct RepoCollector : private base::NonCopyable
183     {
184       RepoCollector()
185       {}
186
187       RepoCollector(const std::string & targetDistro_)
188         : targetDistro(targetDistro_)
189       {}
190
191       bool collect( const RepoInfo &repo )
192       {
193         // skip repositories meant for other distros than specified
194         if (!targetDistro.empty()
195             && !repo.targetDistribution().empty()
196             && repo.targetDistribution() != targetDistro)
197         {
198           MIL
199             << "Skipping repository meant for '" << targetDistro
200             << "' distribution (current distro is '"
201             << repo.targetDistribution() << "')." << endl;
202
203           return true;
204         }
205
206         repos.push_back(repo);
207         return true;
208       }
209
210       RepoInfoList repos;
211       std::string targetDistro;
212     };
213
214   ////////////////////////////////////////////////////////////////////////////
215
216   /**
217    * Reads RepoInfo's from a repo file.
218    *
219    * \param file pathname of the file to read.
220    */
221   static std::list<RepoInfo> repositories_in_file( const Pathname & file )
222   {
223     MIL << "repo file: " << file << endl;
224     RepoCollector collector;
225     parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
226     return collector.repos;
227   }
228
229   ////////////////////////////////////////////////////////////////////////////
230
231   /**
232    * \short List of RepoInfo's from a directory
233    *
234    * Goes trough every file ending with ".repo" in a directory and adds all
235    * RepoInfo's contained in that file.
236    *
237    * \param dir pathname of the directory to read.
238    */
239   static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
240   {
241     MIL << "directory " << dir << endl;
242     std::list<RepoInfo> repos;
243     std::list<Pathname> entries;
244     if ( filesystem::readdir( entries, dir, false ) != 0 )
245     {
246       // TranslatorExplanation '%s' is a pathname
247       ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
248     }
249
250     str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
251     for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
252     {
253       if (str::regex_match(it->extension(), allowedRepoExt))
254       {
255         std::list<RepoInfo> tmp = repositories_in_file( *it );
256         repos.insert( repos.end(), tmp.begin(), tmp.end() );
257
258         //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
259         //MIL << "ok" << endl;
260       }
261     }
262     return repos;
263   }
264
265   ////////////////////////////////////////////////////////////////////////////
266
267    std::list<RepoInfo> readRepoFile(const Url & repo_file)
268    {
269      // no interface to download a specific file, using workaround:
270      //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
271      Url url(repo_file);
272      Pathname path(url.getPathName());
273      url.setPathName ("/");
274      MediaSetAccess access(url);
275      Pathname local = access.provideFile(path);
276
277      DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
278
279      return repositories_in_file(local);
280    }
281
282   ////////////////////////////////////////////////////////////////////////////
283
284   inline void assert_alias( const RepoInfo & info )
285   {
286     if ( info.alias().empty() )
287       ZYPP_THROW( RepoNoAliasException() );
288     // bnc #473834. Maybe we can match the alias against a regex to define
289     // and check for valid aliases
290     if ( info.alias()[0] == '.')
291       ZYPP_THROW(RepoInvalidAliasException(
292          info, _("Repository alias cannot start with dot.")));
293   }
294
295   inline void assert_alias( const ServiceInfo & info )
296   {
297     if ( info.alias().empty() )
298       ZYPP_THROW( ServiceNoAliasException() );
299     // bnc #473834. Maybe we can match the alias against a regex to define
300     // and check for valid aliases
301     if ( info.alias()[0] == '.')
302       ZYPP_THROW(ServiceInvalidAliasException(
303          info, _("Service alias cannot start with dot.")));
304   }
305
306   ////////////////////////////////////////////////////////////////////////////
307
308   inline void assert_urls( const RepoInfo & info )
309   {
310     if ( info.baseUrlsEmpty() )
311       ZYPP_THROW( RepoNoUrlException( info ) );
312   }
313
314   inline void assert_url( const ServiceInfo & info )
315   {
316     if ( ! info.url().isValid() )
317       ZYPP_THROW( ServiceNoUrlException( info ) );
318   }
319
320   ////////////////////////////////////////////////////////////////////////////
321
322   /**
323    * \short Calculates the raw cache path for a repository, this is usually
324    * /var/cache/zypp/alias
325    */
326   inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
327   {
328     assert_alias(info);
329     return opt.repoRawCachePath / info.escaped_alias();
330   }
331
332   /**
333    * \short Calculates the raw product metadata path for a repository, this is
334    * inside the raw cache dir, plus an optional path where the metadata is.
335    *
336    * It should be different only for repositories that are not in the root of
337    * the media.
338    * for example /var/cache/zypp/alias/addondir
339    */
340   inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
341   {
342     assert_alias(info);
343     return opt.repoRawCachePath / info.escaped_alias() / info.path();
344   }
345
346
347   /**
348    * \short Calculates the packages cache path for a repository
349    */
350   inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
351   {
352     assert_alias(info);
353     return opt.repoPackagesCachePath / info.escaped_alias();
354   }
355
356   /**
357    * \short Calculates the solv cache path for a repository
358    */
359   inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
360   {
361     assert_alias(info);
362     return opt.repoSolvCachePath / info.escaped_alias();
363   }
364
365   ////////////////////////////////////////////////////////////////////////////
366
367   /** Functor collecting ServiceInfos into a ServiceSet. */
368   class ServiceCollector
369   {
370     public:
371       typedef std::set<ServiceInfo> ServiceSet;
372
373       ServiceCollector( ServiceSet & services_r )
374       : _services( services_r )
375       {}
376
377       bool operator()( const ServiceInfo & service_r ) const
378       {
379         _services.insert( service_r );
380         return true;
381       }
382
383     private:
384       ServiceSet & _services;
385   };
386
387   ////////////////////////////////////////////////////////////////////////////
388
389   ///////////////////////////////////////////////////////////////////
390   //
391   //    CLASS NAME : RepoManager::Impl
392   //
393   ///////////////////////////////////////////////////////////////////
394
395   /**
396    * \short RepoManager implementation.
397    */
398   struct RepoManager::Impl
399   {
400     Impl( const RepoManagerOptions &opt )
401       : options(opt)
402     {
403       init_knownServices();
404       init_knownRepositories();
405     }
406
407     RepoManagerOptions options;
408
409     RepoSet repos;
410
411     ServiceSet services;
412
413   public:
414
415     void saveService( ServiceInfo & service ) const;
416
417     Pathname generateNonExistingName( const Pathname &dir,
418                                       const std::string &basefilename ) const;
419
420     std::string generateFilename( const RepoInfo & info ) const;
421     std::string generateFilename( const ServiceInfo & info ) const;
422
423
424   private:
425     void init_knownServices();
426     void init_knownRepositories();
427
428   private:
429     friend Impl * rwcowClone<Impl>( const Impl * rhs );
430     /** clone for RWCOW_pointer */
431     Impl * clone() const
432     { return new Impl( *this ); }
433   };
434
435   ///////////////////////////////////////////////////////////////////
436
437   /** \relates RepoManager::Impl Stream output */
438   inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
439   {
440     return str << "RepoManager::Impl";
441   }
442
443   ///////////////////////////////////////////////////////////////////
444
445   void RepoManager::Impl::saveService( ServiceInfo & service ) const
446   {
447     filesystem::assert_dir( options.knownServicesPath );
448     Pathname servfile = generateNonExistingName( options.knownServicesPath,
449                                                  generateFilename( service ) );
450     service.setFilepath( servfile );
451
452     MIL << "saving service in " << servfile << endl;
453
454     std::ofstream file( servfile.c_str() );
455     if ( !file )
456     {
457       // TranslatorExplanation '%s' is a filename
458       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() )));
459     }
460     service.dumpAsIniOn( file );
461     MIL << "done" << endl;
462   }
463
464   /**
465    * Generate a non existing filename in a directory, using a base
466    * name. For example if a directory contains 3 files
467    *
468    * |-- bar
469    * |-- foo
470    * `-- moo
471    *
472    * If you try to generate a unique filename for this directory,
473    * based on "ruu" you will get "ruu", but if you use the base
474    * "foo" you will get "foo_1"
475    *
476    * \param dir Directory where the file needs to be unique
477    * \param basefilename string to base the filename on.
478    */
479   Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir,
480                                                        const std::string & basefilename ) const
481   {
482     std::string final_filename = basefilename;
483     int counter = 1;
484     while ( PathInfo(dir + final_filename).isExist() )
485     {
486       final_filename = basefilename + "_" + str::numstring(counter);
487       counter++;
488     }
489     return dir + Pathname(final_filename);
490   }
491
492   ////////////////////////////////////////////////////////////////////////////
493
494   /**
495    * \short Generate a related filename from a repo info
496    *
497    * From a repo info, it will try to use the alias as a filename
498    * escaping it if necessary. Other fallbacks can be added to
499    * this function in case there is no way to use the alias
500    */
501   std::string RepoManager::Impl::generateFilename( const RepoInfo & info ) const
502   {
503     std::string filename = info.alias();
504     // replace slashes with underscores
505     str::replaceAll( filename, "/", "_" );
506
507     filename = Pathname(filename).extend(".repo").asString();
508     MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
509     return filename;
510   }
511
512   std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const
513   {
514     std::string filename = info.alias();
515     // replace slashes with underscores
516     str::replaceAll( filename, "/", "_" );
517
518     filename = Pathname(filename).extend(".service").asString();
519     MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl;
520     return filename;
521   }
522
523
524   void RepoManager::Impl::init_knownServices()
525   {
526     Pathname dir = options.knownServicesPath;
527     std::list<Pathname> entries;
528     if (PathInfo(dir).isExist())
529     {
530       if ( filesystem::readdir( entries, dir, false ) != 0 )
531       {
532         // TranslatorExplanation '%s' is a pathname
533         ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
534       }
535
536       //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
537       for_(it, entries.begin(), entries.end() )
538       {
539         parser::ServiceFileReader(*it, ServiceCollector(services));
540       }
541     }
542   }
543
544   void RepoManager::Impl::init_knownRepositories()
545   {
546     MIL << "start construct known repos" << endl;
547
548     if ( PathInfo(options.knownReposPath).isExist() )
549     {
550       RepoInfoList repol = repositories_in_dir(options.knownReposPath);
551       for ( RepoInfoList::iterator it = repol.begin();
552             it != repol.end();
553             ++it )
554       {
555         // set the metadata path for the repo
556         Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it));
557         (*it).setMetadataPath(metadata_path);
558
559         // set the downloaded packages path for the repo
560         Pathname packages_path = packagescache_path_for_repoinfo(options, (*it));
561         (*it).setPackagesPath(packages_path);
562
563         repos.insert(*it);
564       }
565     }
566
567     MIL << "end construct known repos" << endl;
568   }
569
570   ///////////////////////////////////////////////////////////////////
571   //
572   //    CLASS NAME : RepoManager
573   //
574   ///////////////////////////////////////////////////////////////////
575
576   RepoManager::RepoManager( const RepoManagerOptions &opt )
577   : _pimpl( new Impl(opt) )
578   {}
579
580   ////////////////////////////////////////////////////////////////////////////
581
582   RepoManager::~RepoManager()
583   {}
584
585   ////////////////////////////////////////////////////////////////////////////
586
587   bool RepoManager::repoEmpty() const
588   { return _pimpl->repos.empty(); }
589
590   RepoManager::RepoSizeType RepoManager::repoSize() const
591   { return _pimpl->repos.size(); }
592
593   RepoManager::RepoConstIterator RepoManager::repoBegin() const
594   { return _pimpl->repos.begin(); }
595
596   RepoManager::RepoConstIterator RepoManager::repoEnd() const
597   { return _pimpl->repos.end(); }
598
599   RepoInfo RepoManager::getRepo( const std::string & alias ) const
600   {
601     for_( it, repoBegin(), repoEnd() )
602       if ( it->alias() == alias )
603         return *it;
604     return RepoInfo::noRepo;
605   }
606
607   bool RepoManager::hasRepo( const std::string & alias ) const
608   {
609     for_( it, repoBegin(), repoEnd() )
610       if ( it->alias() == alias )
611         return true;
612     return false;
613   }
614
615   std::string RepoManager::makeStupidAlias( const Url & url_r )
616   {
617     std::string ret( url_r.getScheme() );
618     if ( ret.empty() )
619       ret = "repo-";
620     else
621       ret += "-";
622
623     std::string host( url_r.getHost() );
624     if ( ! host.empty() )
625     {
626       ret += host;
627       ret += "-";
628     }
629
630     static Date::ValueType serial = Date::now();
631     ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8);
632     return ret;
633   }
634
635   ////////////////////////////////////////////////////////////////////////////
636
637   Pathname RepoManager::metadataPath( const RepoInfo &info ) const
638   {
639     return rawcache_path_for_repoinfo(_pimpl->options, info );
640   }
641
642   Pathname RepoManager::packagesPath( const RepoInfo &info ) const
643   {
644     return packagescache_path_for_repoinfo(_pimpl->options, info );
645   }
646
647   ////////////////////////////////////////////////////////////////////////////
648
649   RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
650   {
651     Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
652     Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
653     RepoType repokind = info.type();
654     RepoStatus status;
655
656     switch ( repokind.toEnum() )
657     {
658       case RepoType::NONE_e:
659       // unknown, probe the local metadata
660         repokind = probe( productdatapath.asUrl() );
661       break;
662       default:
663       break;
664     }
665
666     switch ( repokind.toEnum() )
667     {
668       case RepoType::RPMMD_e :
669       {
670         status = RepoStatus( productdatapath + "/repodata/repomd.xml");
671       }
672       break;
673
674       case RepoType::YAST2_e :
675       {
676         status = RepoStatus( productdatapath + "/content") && (RepoStatus( mediarootpath + "/media.1/media"));
677       }
678       break;
679
680       case RepoType::RPMPLAINDIR_e :
681       {
682         if ( PathInfo(Pathname(productdatapath + "/cookie")).isExist() )
683           status = RepoStatus( productdatapath + "/cookie");
684       }
685       break;
686
687       case RepoType::NONE_e :
688         // Return default RepoStatus in case of RepoType::NONE
689         // indicating it should be created?
690         // ZYPP_THROW(RepoUnknownTypeException());
691         break;
692     }
693     return status;
694   }
695
696   void RepoManager::touchIndexFile(const RepoInfo & info)
697   {
698     Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
699
700     RepoType repokind = info.type();
701     if ( repokind.toEnum() == RepoType::NONE_e )
702       // unknown, probe the local metadata
703       repokind = probe( productdatapath.asUrl() );
704     // if still unknown, just return
705     if (repokind == RepoType::NONE_e)
706       return;
707
708     Pathname p;
709     switch ( repokind.toEnum() )
710     {
711       case RepoType::RPMMD_e :
712         p = Pathname(productdatapath + "/repodata/repomd.xml");
713         break;
714
715       case RepoType::YAST2_e :
716         p = Pathname(productdatapath + "/content");
717         break;
718
719       case RepoType::RPMPLAINDIR_e :
720         p = Pathname(productdatapath + "/cookie");
721         break;
722
723       case RepoType::NONE_e :
724       default:
725         break;
726     }
727
728     // touch the file, ignore error (they are logged anyway)
729     filesystem::touch(p);
730   }
731
732   RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata(
733                                               const RepoInfo &info,
734                                               const Url &url,
735                                               RawMetadataRefreshPolicy policy )
736   {
737     assert_alias(info);
738
739     RepoStatus oldstatus;
740     RepoStatus newstatus;
741
742     try
743     {
744       MIL << "Going to try to check whether refresh is needed for " << url << endl;
745
746       // first check old (cached) metadata
747       Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
748       filesystem::assert_dir(mediarootpath);
749       oldstatus = metadataStatus(info);
750
751       if ( oldstatus.empty() )
752       {
753         MIL << "No cached metadata, going to refresh" << endl;
754         return REFRESH_NEEDED;
755       }
756
757       {
758         std::string scheme( url.getScheme() );
759         if ( scheme == "cd" || scheme == "dvd" )
760         {
761           MIL << "never refresh CD/DVD" << endl;
762           return REPO_UP_TO_DATE;
763         }
764       }
765
766       // now we've got the old (cached) status, we can decide repo.refresh.delay
767       if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
768       {
769         // difference in seconds
770         double diff = difftime(
771           (Date::ValueType)Date::now(),
772           (Date::ValueType)oldstatus.timestamp()) / 60;
773
774         DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
775         DBG << "current time: " << (Date::ValueType)Date::now() << endl;
776         DBG << "last refresh = " << diff << " minutes ago" << endl;
777
778         if (diff < ZConfig::instance().repo_refresh_delay())
779         {
780           MIL << "Repository '" << info.alias()
781               << "' has been refreshed less than repo.refresh.delay ("
782               << ZConfig::instance().repo_refresh_delay()
783               << ") minutes ago. Advising to skip refresh" << endl;
784           return REPO_CHECK_DELAYED;
785         }
786       }
787
788       // To test the new matadta create temp dir as sibling of mediarootpath
789       filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
790
791       repo::RepoType repokind = info.type();
792       // if the type is unknown, try probing.
793       switch ( repokind.toEnum() )
794       {
795         case RepoType::NONE_e:
796           // unknown, probe it \todo respect productdir
797           repokind = probe( url, info.path() );
798         break;
799         default:
800         break;
801       }
802
803       if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
804            ( repokind.toEnum() == RepoType::YAST2_e ) )
805       {
806         MediaSetAccess media(url);
807         shared_ptr<repo::Downloader> downloader_ptr;
808
809         if ( repokind.toEnum() == RepoType::RPMMD_e )
810           downloader_ptr.reset(new yum::Downloader(info));
811         else
812           downloader_ptr.reset( new susetags::Downloader(info));
813
814         RepoStatus newstatus = downloader_ptr->status(media);
815         bool refresh = false;
816         if ( oldstatus.checksum() == newstatus.checksum() )
817         {
818           MIL << "repo has not changed" << endl;
819           if ( policy == RefreshForced )
820           {
821             MIL << "refresh set to forced" << endl;
822             refresh = true;
823           }
824         }
825         else
826         {
827           MIL << "repo has changed, going to refresh" << endl;
828           refresh = true;
829         }
830
831         if (!refresh)
832           touchIndexFile(info);
833
834         return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
835       }
836       else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
837       {
838         MediaMounter media( url );
839         RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
840         bool refresh = false;
841         if ( oldstatus.checksum() == newstatus.checksum() )
842         {
843           MIL << "repo has not changed" << endl;
844           if ( policy == RefreshForced )
845           {
846             MIL << "refresh set to forced" << endl;
847             refresh = true;
848           }
849         }
850         else
851         {
852           MIL << "repo has changed, going to refresh" << endl;
853           refresh = true;
854         }
855
856         if (!refresh)
857           touchIndexFile(info);
858
859         return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
860       }
861       else
862       {
863         ZYPP_THROW(RepoUnknownTypeException(info));
864       }
865     }
866     catch ( const Exception &e )
867     {
868       ZYPP_CAUGHT(e);
869       ERR << "refresh check failed for " << url << endl;
870       ZYPP_RETHROW(e);
871     }
872
873     return REFRESH_NEEDED; // default
874   }
875
876   void RepoManager::refreshMetadata( const RepoInfo &info,
877                                      RawMetadataRefreshPolicy policy,
878                                      const ProgressData::ReceiverFnc & progress )
879   {
880     assert_alias(info);
881     assert_urls(info);
882
883     // we will throw this later if no URL checks out fine
884     RepoException rexception(_("Valid metadata not found at specified URL(s)"));
885
886     // try urls one by one
887     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
888     {
889       try
890       {
891         Url url(*it);
892
893         // check whether to refresh metadata
894         // if the check fails for this url, it throws, so another url will be checked
895         if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
896           return;
897
898         MIL << "Going to refresh metadata from " << url << endl;
899
900         repo::RepoType repokind = info.type();
901
902         // if the type is unknown, try probing.
903         switch ( repokind.toEnum() )
904         {
905           case RepoType::NONE_e:
906             // unknown, probe it
907             repokind = probe( *it, info.path() );
908
909             if (repokind.toEnum() != RepoType::NONE_e)
910             {
911               // Adjust the probed type in RepoInfo
912               info.setProbedType( repokind ); // lazy init!
913               //save probed type only for repos in system
914               for_( it, repoBegin(), repoEnd() )
915               {
916                 if ( info.alias() == (*it).alias() )
917                 {
918                   RepoInfo modifiedrepo = info;
919                   modifiedrepo.setType( repokind );
920                   modifyRepository( info.alias(), modifiedrepo );
921                   break;
922                 }
923               }
924             }
925           break;
926           default:
927           break;
928         }
929
930         Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
931         filesystem::assert_dir(mediarootpath);
932
933         // create temp dir as sibling of mediarootpath
934         filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
935
936         if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
937              ( repokind.toEnum() == RepoType::YAST2_e ) )
938         {
939           MediaSetAccess media(url);
940           shared_ptr<repo::Downloader> downloader_ptr;
941
942           MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
943
944           if ( repokind.toEnum() == RepoType::RPMMD_e )
945             downloader_ptr.reset(new yum::Downloader(info));
946           else
947             downloader_ptr.reset( new susetags::Downloader(info) );
948
949           /**
950            * Given a downloader, sets the other repos raw metadata
951            * path as cache paths for the fetcher, so if another
952            * repo has the same file, it will not download it
953            * but copy it from the other repository
954            */
955           for_( it, repoBegin(), repoEnd() )
956           {
957             Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
958             if ( PathInfo(cachepath).isExist() )
959               downloader_ptr->addCachePath(cachepath);
960           }
961
962           downloader_ptr->download( media, tmpdir.path() );
963         }
964         else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
965         {
966           MediaMounter media( url );
967           RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
968
969           Pathname productpath( tmpdir.path() / info.path() );
970           filesystem::assert_dir( productpath );
971           std::ofstream file( (productpath/"cookie").c_str() );
972           if ( !file )
973           {
974             // TranslatorExplanation '%s' is a filename
975             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() )));
976           }
977           file << url;
978           if ( ! info.path().empty() && info.path() != "/" )
979             file << " (" << info.path() << ")";
980           file << endl;
981           file << newstatus.checksum() << endl;
982
983           file.close();
984         }
985         else
986         {
987           ZYPP_THROW(RepoUnknownTypeException());
988         }
989
990         // ok we have the metadata, now exchange
991         // the contents
992         filesystem::exchange( tmpdir.path(), mediarootpath );
993
994         // we are done.
995         return;
996       }
997       catch ( const Exception &e )
998       {
999         ZYPP_CAUGHT(e);
1000         ERR << "Trying another url..." << endl;
1001
1002         // remember the exception caught for the *first URL*
1003         // if all other URLs fail, the rexception will be thrown with the
1004         // cause of the problem of the first URL remembered
1005         if (it == info.baseUrlsBegin())
1006           rexception.remember(e);
1007       }
1008     } // for every url
1009     ERR << "No more urls..." << endl;
1010     ZYPP_THROW(rexception);
1011   }
1012
1013   ////////////////////////////////////////////////////////////////////////////
1014
1015   void RepoManager::cleanMetadata( const RepoInfo &info,
1016                                    const ProgressData::ReceiverFnc & progressfnc )
1017   {
1018     ProgressData progress(100);
1019     progress.sendTo(progressfnc);
1020
1021     filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
1022     progress.toMax();
1023   }
1024
1025   void RepoManager::cleanPackages( const RepoInfo &info,
1026                                    const ProgressData::ReceiverFnc & progressfnc )
1027   {
1028     ProgressData progress(100);
1029     progress.sendTo(progressfnc);
1030
1031     filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
1032     progress.toMax();
1033   }
1034
1035   void RepoManager::buildCache( const RepoInfo &info,
1036                                 CacheBuildPolicy policy,
1037                                 const ProgressData::ReceiverFnc & progressrcv )
1038   {
1039     assert_alias(info);
1040     Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
1041     Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
1042
1043     filesystem::assert_dir(_pimpl->options.repoCachePath);
1044     RepoStatus raw_metadata_status = metadataStatus(info);
1045     if ( raw_metadata_status.empty() )
1046     {
1047        /* if there is no cache at this point, we refresh the raw
1048           in case this is the first time - if it's !autorefresh,
1049           we may still refresh */
1050       refreshMetadata(info, RefreshIfNeeded, progressrcv );
1051       raw_metadata_status = metadataStatus(info);
1052     }
1053
1054     bool needs_cleaning = false;
1055     if ( isCached( info ) )
1056     {
1057       MIL << info.alias() << " is already cached." << endl;
1058       RepoStatus cache_status = cacheStatus(info);
1059
1060       if ( cache_status.checksum() == raw_metadata_status.checksum() )
1061       {
1062         MIL << info.alias() << " cache is up to date with metadata." << endl;
1063         if ( policy == BuildIfNeeded ) {
1064           return;
1065         }
1066         else {
1067           MIL << info.alias() << " cache rebuild is forced" << endl;
1068         }
1069       }
1070
1071       needs_cleaning = true;
1072     }
1073
1074     ProgressData progress(100);
1075     callback::SendReport<ProgressReport> report;
1076     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1077     progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str()));
1078     progress.toMin();
1079
1080     if (needs_cleaning)
1081     {
1082       cleanCache(info);
1083     }
1084
1085     MIL << info.alias() << " building cache..." << info.type() << endl;
1086
1087     Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
1088     filesystem::assert_dir(base);
1089     Pathname solvfile = base / "solv";
1090
1091     // do we have type?
1092     repo::RepoType repokind = info.type();
1093
1094     // if the type is unknown, try probing.
1095     switch ( repokind.toEnum() )
1096     {
1097       case RepoType::NONE_e:
1098         // unknown, probe the local metadata
1099         repokind = probe( productdatapath.asUrl() );
1100       break;
1101       default:
1102       break;
1103     }
1104
1105     MIL << "repo type is " << repokind << endl;
1106
1107     switch ( repokind.toEnum() )
1108     {
1109       case RepoType::RPMMD_e :
1110       case RepoType::YAST2_e :
1111       case RepoType::RPMPLAINDIR_e :
1112       {
1113         // Take care we unlink the solvfile on exception
1114         ManagedFile guard( solvfile, filesystem::unlink );
1115         scoped_ptr<MediaMounter> forPlainDirs;
1116
1117         ExternalProgram::Arguments cmd;
1118         cmd.push_back( "repo2solv.sh" );
1119
1120         // repo2solv expects -o as 1st arg!
1121         cmd.push_back( "-o" );
1122         cmd.push_back( solvfile.asString() );
1123
1124         if ( repokind == RepoType::RPMPLAINDIR )
1125         {
1126           forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
1127           // recusive for plaindir as 2nd arg!
1128           cmd.push_back( "-R" );
1129           // FIXME this does only work form dir: URLs
1130           cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
1131         }
1132         else
1133           cmd.push_back( productdatapath.asString() );
1134
1135         ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
1136         std::string errdetail;
1137
1138         for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1139           WAR << "  " << output;
1140           if ( errdetail.empty() ) {
1141             errdetail = prog.command();
1142             errdetail += '\n';
1143           }
1144           errdetail += output;
1145         }
1146
1147         int ret = prog.close();
1148         if ( ret != 0 )
1149         {
1150           RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
1151           ex.remember( errdetail );
1152           ZYPP_THROW(ex);
1153         }
1154
1155         // We keep it.
1156         guard.resetDispose();
1157       }
1158       break;
1159       default:
1160         ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
1161       break;
1162     }
1163     // update timestamp and checksum
1164     setCacheStatus(info, raw_metadata_status);
1165     MIL << "Commit cache.." << endl;
1166     progress.toMax();
1167   }
1168
1169   ////////////////////////////////////////////////////////////////////////////
1170
1171   repo::RepoType RepoManager::probe( const Url & url ) const
1172   { return probe( url, Pathname() ); }
1173
1174   repo::RepoType RepoManager::probe( const Url & url, const Pathname & path  ) const
1175   {
1176     MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
1177
1178     if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
1179     {
1180       // Handle non existing local directory in advance, as
1181       // MediaSetAccess does not support it.
1182       MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
1183       return repo::RepoType::NONE;
1184     }
1185
1186     // prepare exception to be thrown if the type could not be determined
1187     // due to a media exception. We can't throw right away, because of some
1188     // problems with proxy servers returning an incorrect error
1189     // on ftp file-not-found(bnc #335906). Instead we'll check another types
1190     // before throwing.
1191
1192     // TranslatorExplanation '%s' is an URL
1193     RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
1194     bool gotMediaException = false;
1195     try
1196     {
1197       MediaSetAccess access(url);
1198       try
1199       {
1200         if ( access.doesFileExist(path/"/repodata/repomd.xml") )
1201         {
1202           MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
1203           return repo::RepoType::RPMMD;
1204         }
1205       }
1206       catch ( const media::MediaException &e )
1207       {
1208         ZYPP_CAUGHT(e);
1209         DBG << "problem checking for repodata/repomd.xml file" << endl;
1210         enew.remember(e);
1211         gotMediaException = true;
1212       }
1213
1214       try
1215       {
1216         if ( access.doesFileExist(path/"/content") )
1217         {
1218           MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
1219           return repo::RepoType::YAST2;
1220         }
1221       }
1222       catch ( const media::MediaException &e )
1223       {
1224         ZYPP_CAUGHT(e);
1225         DBG << "problem checking for content file" << endl;
1226         enew.remember(e);
1227         gotMediaException = true;
1228       }
1229
1230       // if it is a non-downloading URL denoting a directory
1231       if ( ! url.schemeIsDownloading() )
1232       {
1233         MediaMounter media( url );
1234         if ( PathInfo(media.getPathName()/path).isDir() )
1235         {
1236           // allow empty dirs for now
1237           MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
1238           return repo::RepoType::RPMPLAINDIR;
1239         }
1240       }
1241     }
1242     catch ( const Exception &e )
1243     {
1244       ZYPP_CAUGHT(e);
1245       // TranslatorExplanation '%s' is an URL
1246       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
1247       enew.remember(e);
1248       ZYPP_THROW(enew);
1249     }
1250
1251     if (gotMediaException)
1252       ZYPP_THROW(enew);
1253
1254     MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
1255     return repo::RepoType::NONE;
1256   }
1257
1258   ////////////////////////////////////////////////////////////////////////////
1259
1260   void RepoManager::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
1261   {
1262     MIL << "Going to clean up garbage in cache dirs" << endl;
1263
1264     ProgressData progress(300);
1265     progress.sendTo(progressrcv);
1266     progress.toMin();
1267
1268     std::list<Pathname> cachedirs;
1269     cachedirs.push_back(_pimpl->options.repoRawCachePath);
1270     cachedirs.push_back(_pimpl->options.repoPackagesCachePath);
1271     cachedirs.push_back(_pimpl->options.repoSolvCachePath);
1272
1273     for_( dir, cachedirs.begin(), cachedirs.end() )
1274     {
1275       if ( PathInfo(*dir).isExist() )
1276       {
1277         std::list<Pathname> entries;
1278         if ( filesystem::readdir( entries, *dir, false ) != 0 )
1279           // TranslatorExplanation '%s' is a pathname
1280           ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str())));
1281
1282         unsigned sdircount   = entries.size();
1283         unsigned sdircurrent = 1;
1284         for_( subdir, entries.begin(), entries.end() )
1285         {
1286           // if it does not belong known repo, make it disappear
1287           bool found = false;
1288           for_( r, repoBegin(), repoEnd() )
1289             if ( subdir->basename() == r->escaped_alias() )
1290             { found = true; break; }
1291
1292           if ( ! found )
1293             filesystem::recursive_rmdir( *subdir );
1294
1295           progress.set( progress.val() + sdircurrent * 100 / sdircount );
1296           ++sdircurrent;
1297         }
1298       }
1299       else
1300         progress.set( progress.val() + 100 );
1301     }
1302     progress.toMax();
1303   }
1304
1305   ////////////////////////////////////////////////////////////////////////////
1306
1307   void RepoManager::cleanCache( const RepoInfo &info,
1308                                 const ProgressData::ReceiverFnc & progressrcv )
1309   {
1310     ProgressData progress(100);
1311     progress.sendTo(progressrcv);
1312     progress.toMin();
1313
1314     MIL << "Removing raw metadata cache for " << info.alias() << endl;
1315     filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
1316
1317     progress.toMax();
1318   }
1319
1320   ////////////////////////////////////////////////////////////////////////////
1321
1322   bool RepoManager::isCached( const RepoInfo &info ) const
1323   {
1324     return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
1325   }
1326
1327   RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
1328   {
1329
1330     Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
1331
1332     return RepoStatus::fromCookieFile(cookiefile);
1333   }
1334
1335   void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
1336   {
1337     Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
1338     filesystem::assert_dir(base);
1339     Pathname cookiefile = base / "cookie";
1340
1341     status.saveToCookieFile(cookiefile);
1342   }
1343
1344   void RepoManager::loadFromCache( const RepoInfo & info,
1345                                    const ProgressData::ReceiverFnc & progressrcv )
1346   {
1347     assert_alias(info);
1348     Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
1349
1350     if ( ! PathInfo(solvfile).isExist() )
1351       ZYPP_THROW(RepoNotCachedException(info));
1352
1353     try
1354     {
1355       Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info );
1356       // test toolversion in order to rebuild solv file in case
1357       // it was written by an old satsolver-tool parser.
1358       //
1359       // Known version strings used:
1360       //  - <no string>
1361       //  - "1.0"
1362       //
1363       sat::LookupRepoAttr toolversion( sat::SolvAttr::repositoryToolVersion, repo );
1364       if ( toolversion.begin().asString().empty() )
1365       {
1366         repo.eraseFromPool();
1367         ZYPP_THROW(Exception("Solv-file was created by old parser."));
1368       }
1369       // else: up-to-date (or even newer).
1370     }
1371     catch ( const Exception & exp )
1372     {
1373       ZYPP_CAUGHT( exp );
1374       MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1375       cleanCache( info, progressrcv );
1376       buildCache( info, BuildIfNeeded, progressrcv );
1377
1378       sat::Pool::instance().addRepoSolv( solvfile, info );
1379     }
1380   }
1381
1382   ////////////////////////////////////////////////////////////////////////////
1383
1384   void RepoManager::addRepository( const RepoInfo &info,
1385                                    const ProgressData::ReceiverFnc & progressrcv )
1386   {
1387     assert_alias(info);
1388
1389     ProgressData progress(100);
1390     callback::SendReport<ProgressReport> report;
1391     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1392     progress.name(str::form(_("Adding repository '%s'"), info.label().c_str()));
1393     progress.toMin();
1394
1395     MIL << "Try adding repo " << info << endl;
1396
1397     RepoInfo tosave = info;
1398     if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
1399         ZYPP_THROW(RepoAlreadyExistsException(info));
1400
1401     // check the first url for now
1402     if ( _pimpl->options.probe )
1403     {
1404       DBG << "unknown repository type, probing" << endl;
1405
1406       RepoType probedtype;
1407       probedtype = probe( *tosave.baseUrlsBegin(), info.path() );
1408       if ( tosave.baseUrlsSize() > 0 )
1409       {
1410         if ( probedtype == RepoType::NONE )
1411           ZYPP_THROW(RepoUnknownTypeException());
1412         else
1413           tosave.setType(probedtype);
1414       }
1415     }
1416
1417     progress.set(50);
1418
1419     // assert the directory exists
1420     filesystem::assert_dir(_pimpl->options.knownReposPath);
1421
1422     Pathname repofile = _pimpl->generateNonExistingName(
1423         _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
1424     // now we have a filename that does not exists
1425     MIL << "Saving repo in " << repofile << endl;
1426
1427     std::ofstream file(repofile.c_str());
1428     if (!file)
1429     {
1430       // TranslatorExplanation '%s' is a filename
1431       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1432     }
1433
1434     tosave.dumpAsIniOn(file);
1435     tosave.setFilepath(repofile);
1436     tosave.setMetadataPath( metadataPath( tosave ) );
1437     tosave.setPackagesPath( packagesPath( tosave ) );
1438     {
1439       // We chould fix the API as we must injet those paths
1440       // into the repoinfo in order to keep it usable.
1441       RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
1442       oinfo.setMetadataPath( metadataPath( tosave ) );
1443       oinfo.setPackagesPath( packagesPath( tosave ) );
1444     }
1445     _pimpl->repos.insert(tosave);
1446
1447     progress.set(90);
1448
1449     // check for credentials in Urls
1450     bool havePasswords = false;
1451     for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
1452       if ( urlit->hasCredentialsInAuthority() )
1453       {
1454         havePasswords = true;
1455         break;
1456       }
1457     // save the credentials
1458     if ( havePasswords )
1459     {
1460       media::CredentialManager cm(
1461           media::CredManagerOptions(_pimpl->options.rootDir) );
1462
1463       for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
1464         if (urlit->hasCredentialsInAuthority())
1465           //! \todo use a method calling UI callbacks to ask where to save creds?
1466           cm.saveInUser(media::AuthData(*urlit));
1467     }
1468
1469     HistoryLog().addRepository(tosave);
1470
1471     progress.toMax();
1472     MIL << "done" << endl;
1473   }
1474
1475   void RepoManager::addRepositories( const Url &url,
1476                                      const ProgressData::ReceiverFnc & progressrcv )
1477   {
1478     std::list<RepoInfo> repos = readRepoFile(url);
1479     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1480           it != repos.end();
1481           ++it )
1482     {
1483       // look if the alias is in the known repos.
1484       for_ ( kit, repoBegin(), repoEnd() )
1485       {
1486         if ( (*it).alias() == (*kit).alias() )
1487         {
1488           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1489           ZYPP_THROW(RepoAlreadyExistsException(*it));
1490         }
1491       }
1492     }
1493
1494     std::string filename = Pathname(url.getPathName()).basename();
1495
1496     if ( filename == Pathname() )
1497     {
1498       // TranslatorExplanation '%s' is an URL
1499       ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
1500     }
1501
1502     // assert the directory exists
1503     filesystem::assert_dir(_pimpl->options.knownReposPath);
1504
1505     Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
1506     // now we have a filename that does not exists
1507     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1508
1509     std::ofstream file(repofile.c_str());
1510     if (!file)
1511     {
1512       // TranslatorExplanation '%s' is a filename
1513       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1514     }
1515
1516     for ( std::list<RepoInfo>::iterator it = repos.begin();
1517           it != repos.end();
1518           ++it )
1519     {
1520       MIL << "Saving " << (*it).alias() << endl;
1521       it->setFilepath(repofile.asString());
1522       it->dumpAsIniOn(file);
1523       _pimpl->repos.insert(*it);
1524
1525       HistoryLog(_pimpl->options.rootDir).addRepository(*it);
1526     }
1527
1528     MIL << "done" << endl;
1529   }
1530
1531   ////////////////////////////////////////////////////////////////////////////
1532
1533   void RepoManager::removeRepository( const RepoInfo & info,
1534                                       const ProgressData::ReceiverFnc & progressrcv)
1535   {
1536     ProgressData progress;
1537     callback::SendReport<ProgressReport> report;
1538     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1539     progress.name(str::form(_("Removing repository '%s'"), info.label().c_str()));
1540
1541     MIL << "Going to delete repo " << info.alias() << endl;
1542
1543     for_( it, repoBegin(), repoEnd() )
1544     {
1545       // they can be the same only if the provided is empty, that means
1546       // the provided repo has no alias
1547       // then skip
1548       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1549         continue;
1550
1551       // TODO match by url
1552
1553       // we have a matcing repository, now we need to know
1554       // where it does come from.
1555       RepoInfo todelete = *it;
1556       if (todelete.filepath().empty())
1557       {
1558         ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1559       }
1560       else
1561       {
1562         // figure how many repos are there in the file:
1563         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1564         if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1565         {
1566           // easy, only this one, just delete the file
1567           if ( filesystem::unlink(todelete.filepath()) != 0 )
1568           {
1569             // TranslatorExplanation '%s' is a filename
1570             ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
1571           }
1572           MIL << todelete.alias() << " sucessfully deleted." << endl;
1573         }
1574         else
1575         {
1576           // there are more repos in the same file
1577           // write them back except the deleted one.
1578           //TmpFile tmp;
1579           //std::ofstream file(tmp.path().c_str());
1580
1581           // assert the directory exists
1582           filesystem::assert_dir(todelete.filepath().dirname());
1583
1584           std::ofstream file(todelete.filepath().c_str());
1585           if (!file)
1586           {
1587             // TranslatorExplanation '%s' is a filename
1588             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
1589           }
1590           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1591                 fit != filerepos.end();
1592                 ++fit )
1593           {
1594             if ( (*fit).alias() != todelete.alias() )
1595               (*fit).dumpAsIniOn(file);
1596           }
1597         }
1598
1599         CombinedProgressData subprogrcv(progress, 70);
1600         CombinedProgressData cleansubprogrcv(progress, 30);
1601         // now delete it from cache
1602         if ( isCached(todelete) )
1603           cleanCache( todelete, subprogrcv);
1604         // now delete metadata (#301037)
1605         cleanMetadata( todelete, cleansubprogrcv);
1606         _pimpl->repos.erase(todelete);
1607         MIL << todelete.alias() << " sucessfully deleted." << endl;
1608         HistoryLog(_pimpl->options.rootDir).removeRepository(todelete);
1609         return;
1610       } // else filepath is empty
1611
1612     }
1613     // should not be reached on a sucess workflow
1614     ZYPP_THROW(RepoNotFoundException(info));
1615   }
1616
1617   ////////////////////////////////////////////////////////////////////////////
1618
1619   void RepoManager::modifyRepository( const std::string &alias,
1620                                       const RepoInfo & newinfo_r,
1621                                       const ProgressData::ReceiverFnc & progressrcv )
1622   {
1623     RepoInfo toedit = getRepositoryInfo(alias);
1624     RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
1625
1626     // check if the new alias already exists when renaming the repo
1627     if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
1628     {
1629       ZYPP_THROW(RepoAlreadyExistsException(newinfo));
1630     }
1631
1632     if (toedit.filepath().empty())
1633     {
1634       ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1635     }
1636     else
1637     {
1638       // figure how many repos are there in the file:
1639       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1640
1641       // there are more repos in the same file
1642       // write them back except the deleted one.
1643       //TmpFile tmp;
1644       //std::ofstream file(tmp.path().c_str());
1645
1646       // assert the directory exists
1647       filesystem::assert_dir(toedit.filepath().dirname());
1648
1649       std::ofstream file(toedit.filepath().c_str());
1650       if (!file)
1651       {
1652         // TranslatorExplanation '%s' is a filename
1653         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
1654       }
1655       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1656             fit != filerepos.end();
1657             ++fit )
1658       {
1659           // if the alias is different, dump the original
1660           // if it is the same, dump the provided one
1661           if ( (*fit).alias() != toedit.alias() )
1662             (*fit).dumpAsIniOn(file);
1663           else
1664             newinfo.dumpAsIniOn(file);
1665       }
1666
1667       newinfo.setFilepath(toedit.filepath());
1668       _pimpl->repos.erase(toedit);
1669       _pimpl->repos.insert(newinfo);
1670       HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
1671       MIL << "repo " << alias << " modified" << endl;
1672     }
1673   }
1674
1675   ////////////////////////////////////////////////////////////////////////////
1676
1677   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1678                                            const ProgressData::ReceiverFnc & progressrcv )
1679   {
1680     RepoInfo info;
1681     info.setAlias(alias);
1682     RepoConstIterator it = _pimpl->repos.find( info );
1683     if( it == repoEnd() )
1684       ZYPP_THROW(RepoNotFoundException(info));
1685     else
1686       return *it;
1687   }
1688
1689   ////////////////////////////////////////////////////////////////////////////
1690
1691   RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1692                                            const url::ViewOption & urlview,
1693                                            const ProgressData::ReceiverFnc & progressrcv )
1694   {
1695     for_( it, repoBegin(), repoEnd() )
1696     {
1697       for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1698           urlit != (*it).baseUrlsEnd();
1699           ++urlit)
1700       {
1701         if ((*urlit).asString(urlview) == url.asString(urlview))
1702           return *it;
1703       }
1704     }
1705     RepoInfo info;
1706     info.setBaseUrl(url);
1707     ZYPP_THROW(RepoNotFoundException(info));
1708   }
1709
1710   ////////////////////////////////////////////////////////////////////////////
1711   //
1712   // Services
1713   //
1714   ////////////////////////////////////////////////////////////////////////////
1715
1716   bool RepoManager::serviceEmpty() const
1717   { return _pimpl->services.empty(); }
1718
1719   RepoManager::ServiceSizeType RepoManager::serviceSize() const
1720   { return _pimpl->services.size(); }
1721
1722   RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
1723   { return _pimpl->services.begin(); }
1724
1725   RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
1726   { return _pimpl->services.end(); }
1727
1728   ServiceInfo RepoManager::getService( const std::string & alias ) const
1729   {
1730     for_( it, serviceBegin(), serviceEnd() )
1731       if ( it->alias() == alias )
1732         return *it;
1733     return ServiceInfo::noService;
1734   }
1735
1736   bool RepoManager::hasService( const std::string & alias ) const
1737   {
1738     for_( it, serviceBegin(), serviceEnd() )
1739       if ( it->alias() == alias )
1740         return true;
1741     return false;
1742   }
1743
1744   ////////////////////////////////////////////////////////////////////////////
1745
1746   void RepoManager::addService( const std::string & alias, const Url & url )
1747   {
1748     addService( ServiceInfo(alias, url) );
1749   }
1750
1751   void RepoManager::addService( const ServiceInfo & service )
1752   {
1753     assert_alias( service );
1754
1755     // check if service already exists
1756     if ( hasService( service.alias() ) )
1757       ZYPP_THROW( ServiceAlreadyExistsException( service ) );
1758
1759     // Writable ServiceInfo is needed to save the location
1760     // of the .service file. Finaly insert into the service list.
1761     ServiceInfo toSave( service );
1762     _pimpl->saveService( toSave );
1763     _pimpl->services.insert( toSave );
1764
1765     // check for credentials in Url (username:password, not ?credentials param)
1766     if ( toSave.url().hasCredentialsInAuthority() )
1767     {
1768       media::CredentialManager cm(
1769           media::CredManagerOptions(_pimpl->options.rootDir) );
1770
1771       //! \todo use a method calling UI callbacks to ask where to save creds?
1772       cm.saveInUser(media::AuthData(toSave.url()));
1773     }
1774
1775     MIL << "added service " << toSave.alias() << endl;
1776   }
1777
1778   ////////////////////////////////////////////////////////////////////////////
1779
1780   void RepoManager::removeService( const std::string & alias )
1781   {
1782     MIL << "Going to delete repo " << alias << endl;
1783
1784     const ServiceInfo & service = getService( alias );
1785
1786     Pathname location = service.filepath();
1787     if( location.empty() )
1788     {
1789       ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1790     }
1791
1792     ServiceSet tmpSet;
1793     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1794
1795     // only one service definition in the file
1796     if ( tmpSet.size() == 1 )
1797     {
1798       if ( filesystem::unlink(location) != 0 )
1799       {
1800         // TranslatorExplanation '%s' is a filename
1801         ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
1802       }
1803       MIL << alias << " sucessfully deleted." << endl;
1804     }
1805     else
1806     {
1807       filesystem::assert_dir(location.dirname());
1808
1809       std::ofstream file(location.c_str());
1810       if( !file )
1811       {
1812         // TranslatorExplanation '%s' is a filename
1813         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
1814       }
1815
1816       for_(it, tmpSet.begin(), tmpSet.end())
1817       {
1818         if( it->alias() != alias )
1819           it->dumpAsIniOn(file);
1820       }
1821
1822       MIL << alias << " sucessfully deleted from file " << location <<  endl;
1823     }
1824
1825     // now remove all repositories added by this service
1826     RepoCollector rcollector;
1827     getRepositoriesInService( alias,
1828       boost::make_function_output_iterator(
1829           bind( &RepoCollector::collect, &rcollector, _1 ) ) );
1830     // cannot do this directly in getRepositoriesInService - would invalidate iterators
1831     for_(rit, rcollector.repos.begin(), rcollector.repos.end())
1832       removeRepository(*rit);
1833   }
1834
1835   void RepoManager::removeService( const ServiceInfo & service )
1836   { removeService(service.alias()); }
1837
1838   ////////////////////////////////////////////////////////////////////////////
1839
1840   void RepoManager::refreshServices()
1841   {
1842     // copy the set of services since refreshService
1843     // can eventually invalidate the iterator
1844     ServiceSet services( serviceBegin(), serviceEnd() );
1845     for_( it, services.begin(), services.end() )
1846     {
1847       if ( !it->enabled() )
1848         continue;
1849
1850       refreshService(*it);
1851     }
1852   }
1853
1854   void RepoManager::refreshService( const ServiceInfo & service )
1855   { refreshService( service.alias() ); }
1856
1857   void RepoManager::refreshService( const std::string & alias )
1858   {
1859     ServiceInfo service( getService( alias ) );
1860     assert_alias( service );
1861     assert_url( service );
1862     // NOTE: It might be necessary to modify and rewrite the service info.
1863     // Either when probing the type, or when adjusting the repositories
1864     // enable/disable state.:
1865     bool serviceModified = false;
1866     MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
1867
1868     //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
1869
1870     // if the type is unknown, try probing.
1871     if ( service.type() == repo::ServiceType::NONE )
1872     {
1873       repo::ServiceType type = probeService( service.url() );
1874       if ( type != ServiceType::NONE )
1875       {
1876         service.setProbedType( type ); // lazy init!
1877         serviceModified = true;
1878       }
1879     }
1880
1881     // download the repo index file
1882     media::MediaManager mediamanager;
1883     media::MediaAccessId mid = mediamanager.open( service.url() );
1884     mediamanager.attach( mid );
1885     mediamanager.provideFile( mid, "repo/repoindex.xml" );
1886     Pathname path = mediamanager.localPath(mid, "repo/repoindex.xml" );
1887
1888     // get target distro identifier
1889     std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro;
1890     if ( servicesTargetDistro.empty() && getZYpp()->getTarget() )
1891       servicesTargetDistro = getZYpp()->target()->targetDistribution();
1892     DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
1893
1894     // parse it
1895     RepoCollector collector(servicesTargetDistro);
1896     parser::RepoindexFileReader reader( path, bind( &RepoCollector::collect, &collector, _1 ) );
1897     mediamanager.release( mid );
1898     mediamanager.close( mid );
1899
1900
1901     // set service alias and base url for all collected repositories
1902     for_( it, collector.repos.begin(), collector.repos.end() )
1903     {
1904       // if the repo url was not set by the repoindex parser, set service's url
1905       Url url;
1906
1907       if ( it->baseUrlsEmpty() )
1908         url = service.url();
1909       else
1910       {
1911         // service repo can contain only one URL now, so no need to iterate.
1912         url = *it->baseUrlsBegin();
1913       }
1914
1915       // libzypp currently has problem with separate url + path handling
1916       // so just append the path to the baseurl
1917       if ( !it->path().empty() )
1918       {
1919         Pathname path(url.getPathName());
1920         path /= it->path();
1921         url.setPathName( path.asString() );
1922         it->setPath("");
1923       }
1924
1925       // Prepend service alias:
1926       it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
1927
1928       // save the url
1929       it->setBaseUrl( url );
1930       // set refrence to the parent service
1931       it->setService( service.alias() );
1932     }
1933
1934     ////////////////////////////////////////////////////////////////////////////
1935     // Now compare collected repos with the ones in the system...
1936     //
1937     RepoInfoList oldRepos;
1938     getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
1939
1940     // find old repositories to remove...
1941     for_( it, oldRepos.begin(), oldRepos.end() )
1942     {
1943       if ( ! foundAliasIn( it->alias(), collector.repos ) )
1944       {
1945         if ( it->enabled() && ! service.repoToDisableFind( it->alias() ) )
1946         {
1947           DBG << "Service removes enabled repo " << it->alias() << endl;
1948           service.addRepoToEnable( it->alias() );
1949           serviceModified = true;
1950         }
1951         else
1952         {
1953           DBG << "Service removes disabled repo " << it->alias() << endl;
1954         }
1955         removeRepository( *it );
1956       }
1957     }
1958
1959     ////////////////////////////////////////////////////////////////////////////
1960     // create missing repositories and modify exising ones if needed...
1961     for_( it, collector.repos.begin(), collector.repos.end() )
1962     {
1963       // Service explicitly requests the repo being enabled?
1964       // Service explicitly requests the repo being disabled?
1965       // And hopefully not both ;) If so, enable wins.
1966       bool beEnabled = service.repoToEnableFind( it->alias() );
1967       bool beDisabled = service.repoToDisableFind( it->alias() );
1968
1969       if ( beEnabled )
1970       {
1971         // Remove from enable request list.
1972         // NOTE: repoToDisable is handled differently.
1973         //       It gets cleared on each refresh.
1974         service.delRepoToEnable( it->alias() );
1975         serviceModified = true;
1976       }
1977
1978       RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
1979       if ( oldRepo == oldRepos.end() )
1980       {
1981         // Not found in oldRepos ==> a new repo to add
1982
1983         // Make sure the service repo is created with the
1984         // appropriate enable and autorefresh true.
1985         it->setEnabled( beEnabled );
1986         it->setAutorefresh( true );
1987
1988         // At that point check whether a repo with the same alias
1989         // exists outside this service. Maybe forcefully re-alias
1990         // the existing repo?
1991         DBG << "Service adds repo " << it->alias() << " " << (beEnabled?"enabled":"disabled") << endl;
1992         addRepository( *it );
1993
1994         // save repo credentials
1995         // ma@: task for modifyRepository?
1996       }
1997       else
1998       {
1999         // ==> an exising repo to check
2000         bool oldRepoModified = false;
2001
2002         // changed enable?
2003         if ( beEnabled )
2004         {
2005           if ( ! oldRepo->enabled() )
2006           {
2007             DBG << "Service repo " << it->alias() << " gets enabled" << endl;
2008             oldRepo->setEnabled( true );
2009             oldRepoModified = true;
2010           }
2011           else
2012           {
2013             DBG << "Service repo " << it->alias() << " stays enabled" << endl;
2014           }
2015         }
2016         else if ( beDisabled )
2017         {
2018           if ( oldRepo->enabled() )
2019           {
2020             DBG << "Service repo " << it->alias() << " gets disabled" << endl;
2021             oldRepo->setEnabled( false );
2022             oldRepoModified = true;
2023           }
2024           else
2025           {
2026             DBG << "Service repo " << it->alias() << " stays disabled" << endl;
2027           }
2028         }
2029         else
2030         {
2031           DBG << "Service repo " << it->alias() << " stays " <<  (oldRepo->enabled()?"enabled":"disabled") << endl;
2032         }
2033
2034         // changed url?
2035         // service repo can contain only one URL now, so no need to iterate.
2036         if ( oldRepo->url() != it->url() )
2037         {
2038           DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
2039           oldRepo->setBaseUrl( it->url() );
2040           oldRepoModified = true;
2041         }
2042
2043         // save if modified:
2044         if ( oldRepoModified )
2045         {
2046           modifyRepository( oldRepo->alias(), *oldRepo );
2047         }
2048       }
2049     }
2050
2051     // Unlike reposToEnable, reposToDisable is always cleared after refresh.
2052     if ( ! service.reposToDisableEmpty() )
2053     {
2054       service.clearReposToDisable();
2055       serviceModified = true;
2056     }
2057
2058     ////////////////////////////////////////////////////////////////////////////
2059     // save service if modified:
2060     if ( serviceModified )
2061     {
2062       // write out modified service file.
2063       modifyService( service.alias(), service );
2064     }
2065   }
2066
2067   ////////////////////////////////////////////////////////////////////////////
2068
2069   void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & service)
2070   {
2071     MIL << "Going to modify service " << oldAlias << endl;
2072
2073     const ServiceInfo & oldService = getService(oldAlias);
2074
2075     Pathname location = oldService.filepath();
2076     if( location.empty() )
2077     {
2078       ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
2079     }
2080
2081     // remember: there may multiple services being defined in one file:
2082     ServiceSet tmpSet;
2083     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
2084
2085     filesystem::assert_dir(location.dirname());
2086     std::ofstream file(location.c_str());
2087     for_(it, tmpSet.begin(), tmpSet.end())
2088     {
2089       if( *it != oldAlias )
2090         it->dumpAsIniOn(file);
2091     }
2092     service.dumpAsIniOn(file);
2093     file.close();
2094
2095     _pimpl->services.erase(oldAlias);
2096     _pimpl->services.insert(service);
2097
2098     // changed properties affecting also repositories
2099     if( oldAlias != service.alias()                    // changed alias
2100         || oldService.enabled() != service.enabled()   // changed enabled status
2101       )
2102     {
2103       std::vector<RepoInfo> toModify;
2104       getRepositoriesInService(oldAlias, std::back_inserter(toModify));
2105       for_( it, toModify.begin(), toModify.end() )
2106       {
2107         if (oldService.enabled() && !service.enabled())
2108           it->setEnabled(false);
2109         else if (!oldService.enabled() && service.enabled())
2110         {
2111           //! \todo do nothing? the repos will be enabled on service refresh
2112           //! \todo how to know the service needs a (auto) refresh????
2113         }
2114         else
2115           it->setService(service.alias());
2116         modifyRepository(it->alias(), *it);
2117       }
2118     }
2119
2120     //! \todo refresh the service automatically if url is changed?
2121   }
2122
2123   ////////////////////////////////////////////////////////////////////////////
2124
2125   repo::ServiceType RepoManager::probeService( const Url &url ) const
2126   {
2127     try
2128     {
2129       MediaSetAccess access(url);
2130       if ( access.doesFileExist("/repo/repoindex.xml") )
2131         return repo::ServiceType::RIS;
2132     }
2133     catch ( const media::MediaException &e )
2134     {
2135       ZYPP_CAUGHT(e);
2136       // TranslatorExplanation '%s' is an URL
2137       RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
2138       enew.remember(e);
2139       ZYPP_THROW(enew);
2140     }
2141     catch ( const Exception &e )
2142     {
2143       ZYPP_CAUGHT(e);
2144       // TranslatorExplanation '%s' is an URL
2145       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
2146       enew.remember(e);
2147       ZYPP_THROW(enew);
2148     }
2149
2150     return repo::ServiceType::NONE;
2151   }
2152
2153   ////////////////////////////////////////////////////////////////////////////
2154
2155   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
2156   {
2157     return str << *obj._pimpl;
2158   }
2159
2160   /////////////////////////////////////////////////////////////////
2161 } // namespace zypp
2162 ///////////////////////////////////////////////////////////////////