Merge branch 'master' of gitorious.org:opensuse/libzypp
[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           if ( diff < 0 )
781           {
782             WAR << "Repository '" << info.alias() << "' was refreshed in the future!" << endl;
783           }
784           else
785           {
786             MIL << "Repository '" << info.alias()
787                 << "' has been refreshed less than repo.refresh.delay ("
788                 << ZConfig::instance().repo_refresh_delay()
789                 << ") minutes ago. Advising to skip refresh" << endl;
790             return REPO_CHECK_DELAYED;
791           }
792         }
793       }
794
795       // To test the new matadta create temp dir as sibling of mediarootpath
796       filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
797
798       repo::RepoType repokind = info.type();
799       // if the type is unknown, try probing.
800       switch ( repokind.toEnum() )
801       {
802         case RepoType::NONE_e:
803           // unknown, probe it \todo respect productdir
804           repokind = probe( url, info.path() );
805         break;
806         default:
807         break;
808       }
809
810       if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
811            ( repokind.toEnum() == RepoType::YAST2_e ) )
812       {
813         MediaSetAccess media(url);
814         shared_ptr<repo::Downloader> downloader_ptr;
815
816         if ( repokind.toEnum() == RepoType::RPMMD_e )
817           downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
818         else
819           downloader_ptr.reset( new susetags::Downloader(info, mediarootpath));
820
821         RepoStatus newstatus = downloader_ptr->status(media);
822         bool refresh = false;
823         if ( oldstatus.checksum() == newstatus.checksum() )
824         {
825           MIL << "repo has not changed" << endl;
826           if ( policy == RefreshForced )
827           {
828             MIL << "refresh set to forced" << endl;
829             refresh = true;
830           }
831         }
832         else
833         {
834           MIL << "repo has changed, going to refresh" << endl;
835           refresh = true;
836         }
837
838         if (!refresh)
839           touchIndexFile(info);
840
841         return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
842       }
843       else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
844       {
845         MediaMounter media( url );
846         RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
847         bool refresh = false;
848         if ( oldstatus.checksum() == newstatus.checksum() )
849         {
850           MIL << "repo has not changed" << endl;
851           if ( policy == RefreshForced )
852           {
853             MIL << "refresh set to forced" << endl;
854             refresh = true;
855           }
856         }
857         else
858         {
859           MIL << "repo has changed, going to refresh" << endl;
860           refresh = true;
861         }
862
863         if (!refresh)
864           touchIndexFile(info);
865
866         return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
867       }
868       else
869       {
870         ZYPP_THROW(RepoUnknownTypeException(info));
871       }
872     }
873     catch ( const Exception &e )
874     {
875       ZYPP_CAUGHT(e);
876       ERR << "refresh check failed for " << url << endl;
877       ZYPP_RETHROW(e);
878     }
879
880     return REFRESH_NEEDED; // default
881   }
882
883   void RepoManager::refreshMetadata( const RepoInfo &info,
884                                      RawMetadataRefreshPolicy policy,
885                                      const ProgressData::ReceiverFnc & progress )
886   {
887     assert_alias(info);
888     assert_urls(info);
889
890     // we will throw this later if no URL checks out fine
891     RepoException rexception(_("Valid metadata not found at specified URL(s)"));
892
893     // try urls one by one
894     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
895     {
896       try
897       {
898         Url url(*it);
899
900         // check whether to refresh metadata
901         // if the check fails for this url, it throws, so another url will be checked
902         if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
903           return;
904
905         MIL << "Going to refresh metadata from " << url << endl;
906
907         repo::RepoType repokind = info.type();
908
909         // if the type is unknown, try probing.
910         switch ( repokind.toEnum() )
911         {
912           case RepoType::NONE_e:
913             // unknown, probe it
914             repokind = probe( *it, info.path() );
915
916             if (repokind.toEnum() != RepoType::NONE_e)
917             {
918               // Adjust the probed type in RepoInfo
919               info.setProbedType( repokind ); // lazy init!
920               //save probed type only for repos in system
921               for_( it, repoBegin(), repoEnd() )
922               {
923                 if ( info.alias() == (*it).alias() )
924                 {
925                   RepoInfo modifiedrepo = info;
926                   modifiedrepo.setType( repokind );
927                   modifyRepository( info.alias(), modifiedrepo );
928                   break;
929                 }
930               }
931             }
932           break;
933           default:
934           break;
935         }
936
937         Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
938         filesystem::assert_dir(mediarootpath);
939
940         // create temp dir as sibling of mediarootpath
941         filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
942
943         if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
944              ( repokind.toEnum() == RepoType::YAST2_e ) )
945         {
946           MediaSetAccess media(url);
947           shared_ptr<repo::Downloader> downloader_ptr;
948
949           MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
950
951           if ( repokind.toEnum() == RepoType::RPMMD_e )
952             downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
953           else
954             downloader_ptr.reset( new susetags::Downloader(info, mediarootpath) );
955
956           /**
957            * Given a downloader, sets the other repos raw metadata
958            * path as cache paths for the fetcher, so if another
959            * repo has the same file, it will not download it
960            * but copy it from the other repository
961            */
962           for_( it, repoBegin(), repoEnd() )
963           {
964             Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
965             if ( PathInfo(cachepath).isExist() )
966               downloader_ptr->addCachePath(cachepath);
967           }
968
969           downloader_ptr->download( media, tmpdir.path() );
970         }
971         else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
972         {
973           MediaMounter media( url );
974           RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
975
976           Pathname productpath( tmpdir.path() / info.path() );
977           filesystem::assert_dir( productpath );
978           std::ofstream file( (productpath/"cookie").c_str() );
979           if ( !file )
980           {
981             // TranslatorExplanation '%s' is a filename
982             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() )));
983           }
984           file << url;
985           if ( ! info.path().empty() && info.path() != "/" )
986             file << " (" << info.path() << ")";
987           file << endl;
988           file << newstatus.checksum() << endl;
989
990           file.close();
991         }
992         else
993         {
994           ZYPP_THROW(RepoUnknownTypeException());
995         }
996
997         // ok we have the metadata, now exchange
998         // the contents
999         filesystem::exchange( tmpdir.path(), mediarootpath );
1000
1001         // we are done.
1002         return;
1003       }
1004       catch ( const Exception &e )
1005       {
1006         ZYPP_CAUGHT(e);
1007         ERR << "Trying another url..." << endl;
1008
1009         // remember the exception caught for the *first URL*
1010         // if all other URLs fail, the rexception will be thrown with the
1011         // cause of the problem of the first URL remembered
1012         if (it == info.baseUrlsBegin())
1013           rexception.remember(e);
1014       }
1015     } // for every url
1016     ERR << "No more urls..." << endl;
1017     ZYPP_THROW(rexception);
1018   }
1019
1020   ////////////////////////////////////////////////////////////////////////////
1021
1022   void RepoManager::cleanMetadata( const RepoInfo &info,
1023                                    const ProgressData::ReceiverFnc & progressfnc )
1024   {
1025     ProgressData progress(100);
1026     progress.sendTo(progressfnc);
1027
1028     filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
1029     progress.toMax();
1030   }
1031
1032   void RepoManager::cleanPackages( const RepoInfo &info,
1033                                    const ProgressData::ReceiverFnc & progressfnc )
1034   {
1035     ProgressData progress(100);
1036     progress.sendTo(progressfnc);
1037
1038     filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
1039     progress.toMax();
1040   }
1041
1042   void RepoManager::buildCache( const RepoInfo &info,
1043                                 CacheBuildPolicy policy,
1044                                 const ProgressData::ReceiverFnc & progressrcv )
1045   {
1046     assert_alias(info);
1047     Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
1048     Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
1049
1050     filesystem::assert_dir(_pimpl->options.repoCachePath);
1051     RepoStatus raw_metadata_status = metadataStatus(info);
1052     if ( raw_metadata_status.empty() )
1053     {
1054        /* if there is no cache at this point, we refresh the raw
1055           in case this is the first time - if it's !autorefresh,
1056           we may still refresh */
1057       refreshMetadata(info, RefreshIfNeeded, progressrcv );
1058       raw_metadata_status = metadataStatus(info);
1059     }
1060
1061     bool needs_cleaning = false;
1062     if ( isCached( info ) )
1063     {
1064       MIL << info.alias() << " is already cached." << endl;
1065       RepoStatus cache_status = cacheStatus(info);
1066
1067       if ( cache_status.checksum() == raw_metadata_status.checksum() )
1068       {
1069         MIL << info.alias() << " cache is up to date with metadata." << endl;
1070         if ( policy == BuildIfNeeded ) {
1071           return;
1072         }
1073         else {
1074           MIL << info.alias() << " cache rebuild is forced" << endl;
1075         }
1076       }
1077
1078       needs_cleaning = true;
1079     }
1080
1081     ProgressData progress(100);
1082     callback::SendReport<ProgressReport> report;
1083     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1084     progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str()));
1085     progress.toMin();
1086
1087     if (needs_cleaning)
1088     {
1089       cleanCache(info);
1090     }
1091
1092     MIL << info.alias() << " building cache..." << info.type() << endl;
1093
1094     Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
1095     filesystem::assert_dir(base);
1096     Pathname solvfile = base / "solv";
1097
1098     // do we have type?
1099     repo::RepoType repokind = info.type();
1100
1101     // if the type is unknown, try probing.
1102     switch ( repokind.toEnum() )
1103     {
1104       case RepoType::NONE_e:
1105         // unknown, probe the local metadata
1106         repokind = probe( productdatapath.asUrl() );
1107       break;
1108       default:
1109       break;
1110     }
1111
1112     MIL << "repo type is " << repokind << endl;
1113
1114     switch ( repokind.toEnum() )
1115     {
1116       case RepoType::RPMMD_e :
1117       case RepoType::YAST2_e :
1118       case RepoType::RPMPLAINDIR_e :
1119       {
1120         // Take care we unlink the solvfile on exception
1121         ManagedFile guard( solvfile, filesystem::unlink );
1122         scoped_ptr<MediaMounter> forPlainDirs;
1123
1124         ExternalProgram::Arguments cmd;
1125         cmd.push_back( "repo2solv.sh" );
1126
1127         // repo2solv expects -o as 1st arg!
1128         cmd.push_back( "-o" );
1129         cmd.push_back( solvfile.asString() );
1130
1131         if ( repokind == RepoType::RPMPLAINDIR )
1132         {
1133           forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
1134           // recusive for plaindir as 2nd arg!
1135           cmd.push_back( "-R" );
1136           // FIXME this does only work form dir: URLs
1137           cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
1138         }
1139         else
1140           cmd.push_back( productdatapath.asString() );
1141
1142         ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
1143         std::string errdetail;
1144
1145         for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1146           WAR << "  " << output;
1147           if ( errdetail.empty() ) {
1148             errdetail = prog.command();
1149             errdetail += '\n';
1150           }
1151           errdetail += output;
1152         }
1153
1154         int ret = prog.close();
1155         if ( ret != 0 )
1156         {
1157           RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
1158           ex.remember( errdetail );
1159           ZYPP_THROW(ex);
1160         }
1161
1162         // We keep it.
1163         guard.resetDispose();
1164       }
1165       break;
1166       default:
1167         ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
1168       break;
1169     }
1170     // update timestamp and checksum
1171     setCacheStatus(info, raw_metadata_status);
1172     MIL << "Commit cache.." << endl;
1173     progress.toMax();
1174   }
1175
1176   ////////////////////////////////////////////////////////////////////////////
1177
1178   repo::RepoType RepoManager::probe( const Url & url ) const
1179   { return probe( url, Pathname() ); }
1180
1181   repo::RepoType RepoManager::probe( const Url & url, const Pathname & path  ) const
1182   {
1183     MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
1184
1185     if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
1186     {
1187       // Handle non existing local directory in advance, as
1188       // MediaSetAccess does not support it.
1189       MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
1190       return repo::RepoType::NONE;
1191     }
1192
1193     // prepare exception to be thrown if the type could not be determined
1194     // due to a media exception. We can't throw right away, because of some
1195     // problems with proxy servers returning an incorrect error
1196     // on ftp file-not-found(bnc #335906). Instead we'll check another types
1197     // before throwing.
1198
1199     // TranslatorExplanation '%s' is an URL
1200     RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
1201     bool gotMediaException = false;
1202     try
1203     {
1204       MediaSetAccess access(url);
1205       try
1206       {
1207         if ( access.doesFileExist(path/"/repodata/repomd.xml") )
1208         {
1209           MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
1210           return repo::RepoType::RPMMD;
1211         }
1212       }
1213       catch ( const media::MediaException &e )
1214       {
1215         ZYPP_CAUGHT(e);
1216         DBG << "problem checking for repodata/repomd.xml file" << endl;
1217         enew.remember(e);
1218         gotMediaException = true;
1219       }
1220
1221       try
1222       {
1223         if ( access.doesFileExist(path/"/content") )
1224         {
1225           MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
1226           return repo::RepoType::YAST2;
1227         }
1228       }
1229       catch ( const media::MediaException &e )
1230       {
1231         ZYPP_CAUGHT(e);
1232         DBG << "problem checking for content file" << endl;
1233         enew.remember(e);
1234         gotMediaException = true;
1235       }
1236
1237       // if it is a non-downloading URL denoting a directory
1238       if ( ! url.schemeIsDownloading() )
1239       {
1240         MediaMounter media( url );
1241         if ( PathInfo(media.getPathName()/path).isDir() )
1242         {
1243           // allow empty dirs for now
1244           MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
1245           return repo::RepoType::RPMPLAINDIR;
1246         }
1247       }
1248     }
1249     catch ( const Exception &e )
1250     {
1251       ZYPP_CAUGHT(e);
1252       // TranslatorExplanation '%s' is an URL
1253       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
1254       enew.remember(e);
1255       ZYPP_THROW(enew);
1256     }
1257
1258     if (gotMediaException)
1259       ZYPP_THROW(enew);
1260
1261     MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
1262     return repo::RepoType::NONE;
1263   }
1264
1265   ////////////////////////////////////////////////////////////////////////////
1266
1267   void RepoManager::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
1268   {
1269     MIL << "Going to clean up garbage in cache dirs" << endl;
1270
1271     ProgressData progress(300);
1272     progress.sendTo(progressrcv);
1273     progress.toMin();
1274
1275     std::list<Pathname> cachedirs;
1276     cachedirs.push_back(_pimpl->options.repoRawCachePath);
1277     cachedirs.push_back(_pimpl->options.repoPackagesCachePath);
1278     cachedirs.push_back(_pimpl->options.repoSolvCachePath);
1279
1280     for_( dir, cachedirs.begin(), cachedirs.end() )
1281     {
1282       if ( PathInfo(*dir).isExist() )
1283       {
1284         std::list<Pathname> entries;
1285         if ( filesystem::readdir( entries, *dir, false ) != 0 )
1286           // TranslatorExplanation '%s' is a pathname
1287           ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str())));
1288
1289         unsigned sdircount   = entries.size();
1290         unsigned sdircurrent = 1;
1291         for_( subdir, entries.begin(), entries.end() )
1292         {
1293           // if it does not belong known repo, make it disappear
1294           bool found = false;
1295           for_( r, repoBegin(), repoEnd() )
1296             if ( subdir->basename() == r->escaped_alias() )
1297             { found = true; break; }
1298
1299           if ( ! found )
1300             filesystem::recursive_rmdir( *subdir );
1301
1302           progress.set( progress.val() + sdircurrent * 100 / sdircount );
1303           ++sdircurrent;
1304         }
1305       }
1306       else
1307         progress.set( progress.val() + 100 );
1308     }
1309     progress.toMax();
1310   }
1311
1312   ////////////////////////////////////////////////////////////////////////////
1313
1314   void RepoManager::cleanCache( const RepoInfo &info,
1315                                 const ProgressData::ReceiverFnc & progressrcv )
1316   {
1317     ProgressData progress(100);
1318     progress.sendTo(progressrcv);
1319     progress.toMin();
1320
1321     MIL << "Removing raw metadata cache for " << info.alias() << endl;
1322     filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
1323
1324     progress.toMax();
1325   }
1326
1327   ////////////////////////////////////////////////////////////////////////////
1328
1329   bool RepoManager::isCached( const RepoInfo &info ) const
1330   {
1331     return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
1332   }
1333
1334   RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
1335   {
1336
1337     Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
1338
1339     return RepoStatus::fromCookieFile(cookiefile);
1340   }
1341
1342   void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
1343   {
1344     Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
1345     filesystem::assert_dir(base);
1346     Pathname cookiefile = base / "cookie";
1347
1348     status.saveToCookieFile(cookiefile);
1349   }
1350
1351   void RepoManager::loadFromCache( const RepoInfo & info,
1352                                    const ProgressData::ReceiverFnc & progressrcv )
1353   {
1354     assert_alias(info);
1355     Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
1356
1357     if ( ! PathInfo(solvfile).isExist() )
1358       ZYPP_THROW(RepoNotCachedException(info));
1359
1360     try
1361     {
1362       Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info );
1363       // test toolversion in order to rebuild solv file in case
1364       // it was written by an old satsolver-tool parser.
1365       //
1366       // Known version strings used:
1367       //  - <no string>
1368       //  - "1.0"
1369       //
1370       sat::LookupRepoAttr toolversion( sat::SolvAttr::repositoryToolVersion, repo );
1371       if ( toolversion.begin().asString().empty() )
1372       {
1373         repo.eraseFromPool();
1374         ZYPP_THROW(Exception("Solv-file was created by old parser."));
1375       }
1376       // else: up-to-date (or even newer).
1377     }
1378     catch ( const Exception & exp )
1379     {
1380       ZYPP_CAUGHT( exp );
1381       MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1382       cleanCache( info, progressrcv );
1383       buildCache( info, BuildIfNeeded, progressrcv );
1384
1385       sat::Pool::instance().addRepoSolv( solvfile, info );
1386     }
1387   }
1388
1389   ////////////////////////////////////////////////////////////////////////////
1390
1391   void RepoManager::addRepository( const RepoInfo &info,
1392                                    const ProgressData::ReceiverFnc & progressrcv )
1393   {
1394     assert_alias(info);
1395
1396     ProgressData progress(100);
1397     callback::SendReport<ProgressReport> report;
1398     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1399     progress.name(str::form(_("Adding repository '%s'"), info.label().c_str()));
1400     progress.toMin();
1401
1402     MIL << "Try adding repo " << info << endl;
1403
1404     RepoInfo tosave = info;
1405     if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
1406         ZYPP_THROW(RepoAlreadyExistsException(info));
1407
1408     // check the first url for now
1409     if ( _pimpl->options.probe )
1410     {
1411       DBG << "unknown repository type, probing" << endl;
1412
1413       RepoType probedtype;
1414       probedtype = probe( *tosave.baseUrlsBegin(), info.path() );
1415       if ( tosave.baseUrlsSize() > 0 )
1416       {
1417         if ( probedtype == RepoType::NONE )
1418           ZYPP_THROW(RepoUnknownTypeException());
1419         else
1420           tosave.setType(probedtype);
1421       }
1422     }
1423
1424     progress.set(50);
1425
1426     // assert the directory exists
1427     filesystem::assert_dir(_pimpl->options.knownReposPath);
1428
1429     Pathname repofile = _pimpl->generateNonExistingName(
1430         _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
1431     // now we have a filename that does not exists
1432     MIL << "Saving repo in " << repofile << endl;
1433
1434     std::ofstream file(repofile.c_str());
1435     if (!file)
1436     {
1437       // TranslatorExplanation '%s' is a filename
1438       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1439     }
1440
1441     tosave.dumpAsIniOn(file);
1442     tosave.setFilepath(repofile);
1443     tosave.setMetadataPath( metadataPath( tosave ) );
1444     tosave.setPackagesPath( packagesPath( tosave ) );
1445     {
1446       // We chould fix the API as we must injet those paths
1447       // into the repoinfo in order to keep it usable.
1448       RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
1449       oinfo.setMetadataPath( metadataPath( tosave ) );
1450       oinfo.setPackagesPath( packagesPath( tosave ) );
1451     }
1452     _pimpl->repos.insert(tosave);
1453
1454     progress.set(90);
1455
1456     // check for credentials in Urls
1457     bool havePasswords = false;
1458     for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
1459       if ( urlit->hasCredentialsInAuthority() )
1460       {
1461         havePasswords = true;
1462         break;
1463       }
1464     // save the credentials
1465     if ( havePasswords )
1466     {
1467       media::CredentialManager cm(
1468           media::CredManagerOptions(_pimpl->options.rootDir) );
1469
1470       for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
1471         if (urlit->hasCredentialsInAuthority())
1472           //! \todo use a method calling UI callbacks to ask where to save creds?
1473           cm.saveInUser(media::AuthData(*urlit));
1474     }
1475
1476     HistoryLog().addRepository(tosave);
1477
1478     progress.toMax();
1479     MIL << "done" << endl;
1480   }
1481
1482   void RepoManager::addRepositories( const Url &url,
1483                                      const ProgressData::ReceiverFnc & progressrcv )
1484   {
1485     std::list<RepoInfo> repos = readRepoFile(url);
1486     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1487           it != repos.end();
1488           ++it )
1489     {
1490       // look if the alias is in the known repos.
1491       for_ ( kit, repoBegin(), repoEnd() )
1492       {
1493         if ( (*it).alias() == (*kit).alias() )
1494         {
1495           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1496           ZYPP_THROW(RepoAlreadyExistsException(*it));
1497         }
1498       }
1499     }
1500
1501     std::string filename = Pathname(url.getPathName()).basename();
1502
1503     if ( filename == Pathname() )
1504     {
1505       // TranslatorExplanation '%s' is an URL
1506       ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
1507     }
1508
1509     // assert the directory exists
1510     filesystem::assert_dir(_pimpl->options.knownReposPath);
1511
1512     Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
1513     // now we have a filename that does not exists
1514     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1515
1516     std::ofstream file(repofile.c_str());
1517     if (!file)
1518     {
1519       // TranslatorExplanation '%s' is a filename
1520       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1521     }
1522
1523     for ( std::list<RepoInfo>::iterator it = repos.begin();
1524           it != repos.end();
1525           ++it )
1526     {
1527       MIL << "Saving " << (*it).alias() << endl;
1528       it->setFilepath(repofile.asString());
1529       it->dumpAsIniOn(file);
1530       _pimpl->repos.insert(*it);
1531
1532       HistoryLog(_pimpl->options.rootDir).addRepository(*it);
1533     }
1534
1535     MIL << "done" << endl;
1536   }
1537
1538   ////////////////////////////////////////////////////////////////////////////
1539
1540   void RepoManager::removeRepository( const RepoInfo & info,
1541                                       const ProgressData::ReceiverFnc & progressrcv)
1542   {
1543     ProgressData progress;
1544     callback::SendReport<ProgressReport> report;
1545     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1546     progress.name(str::form(_("Removing repository '%s'"), info.label().c_str()));
1547
1548     MIL << "Going to delete repo " << info.alias() << endl;
1549
1550     for_( it, repoBegin(), repoEnd() )
1551     {
1552       // they can be the same only if the provided is empty, that means
1553       // the provided repo has no alias
1554       // then skip
1555       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1556         continue;
1557
1558       // TODO match by url
1559
1560       // we have a matcing repository, now we need to know
1561       // where it does come from.
1562       RepoInfo todelete = *it;
1563       if (todelete.filepath().empty())
1564       {
1565         ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1566       }
1567       else
1568       {
1569         // figure how many repos are there in the file:
1570         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1571         if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1572         {
1573           // easy, only this one, just delete the file
1574           if ( filesystem::unlink(todelete.filepath()) != 0 )
1575           {
1576             // TranslatorExplanation '%s' is a filename
1577             ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
1578           }
1579           MIL << todelete.alias() << " sucessfully deleted." << endl;
1580         }
1581         else
1582         {
1583           // there are more repos in the same file
1584           // write them back except the deleted one.
1585           //TmpFile tmp;
1586           //std::ofstream file(tmp.path().c_str());
1587
1588           // assert the directory exists
1589           filesystem::assert_dir(todelete.filepath().dirname());
1590
1591           std::ofstream file(todelete.filepath().c_str());
1592           if (!file)
1593           {
1594             // TranslatorExplanation '%s' is a filename
1595             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
1596           }
1597           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1598                 fit != filerepos.end();
1599                 ++fit )
1600           {
1601             if ( (*fit).alias() != todelete.alias() )
1602               (*fit).dumpAsIniOn(file);
1603           }
1604         }
1605
1606         CombinedProgressData subprogrcv(progress, 70);
1607         CombinedProgressData cleansubprogrcv(progress, 30);
1608         // now delete it from cache
1609         if ( isCached(todelete) )
1610           cleanCache( todelete, subprogrcv);
1611         // now delete metadata (#301037)
1612         cleanMetadata( todelete, cleansubprogrcv);
1613         _pimpl->repos.erase(todelete);
1614         MIL << todelete.alias() << " sucessfully deleted." << endl;
1615         HistoryLog(_pimpl->options.rootDir).removeRepository(todelete);
1616         return;
1617       } // else filepath is empty
1618
1619     }
1620     // should not be reached on a sucess workflow
1621     ZYPP_THROW(RepoNotFoundException(info));
1622   }
1623
1624   ////////////////////////////////////////////////////////////////////////////
1625
1626   void RepoManager::modifyRepository( const std::string &alias,
1627                                       const RepoInfo & newinfo_r,
1628                                       const ProgressData::ReceiverFnc & progressrcv )
1629   {
1630     RepoInfo toedit = getRepositoryInfo(alias);
1631     RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
1632
1633     // check if the new alias already exists when renaming the repo
1634     if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
1635     {
1636       ZYPP_THROW(RepoAlreadyExistsException(newinfo));
1637     }
1638
1639     if (toedit.filepath().empty())
1640     {
1641       ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1642     }
1643     else
1644     {
1645       // figure how many repos are there in the file:
1646       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1647
1648       // there are more repos in the same file
1649       // write them back except the deleted one.
1650       //TmpFile tmp;
1651       //std::ofstream file(tmp.path().c_str());
1652
1653       // assert the directory exists
1654       filesystem::assert_dir(toedit.filepath().dirname());
1655
1656       std::ofstream file(toedit.filepath().c_str());
1657       if (!file)
1658       {
1659         // TranslatorExplanation '%s' is a filename
1660         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
1661       }
1662       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1663             fit != filerepos.end();
1664             ++fit )
1665       {
1666           // if the alias is different, dump the original
1667           // if it is the same, dump the provided one
1668           if ( (*fit).alias() != toedit.alias() )
1669             (*fit).dumpAsIniOn(file);
1670           else
1671             newinfo.dumpAsIniOn(file);
1672       }
1673
1674       newinfo.setFilepath(toedit.filepath());
1675       _pimpl->repos.erase(toedit);
1676       _pimpl->repos.insert(newinfo);
1677       HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
1678       MIL << "repo " << alias << " modified" << endl;
1679     }
1680   }
1681
1682   ////////////////////////////////////////////////////////////////////////////
1683
1684   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1685                                            const ProgressData::ReceiverFnc & progressrcv )
1686   {
1687     RepoInfo info;
1688     info.setAlias(alias);
1689     RepoConstIterator it = _pimpl->repos.find( info );
1690     if( it == repoEnd() )
1691       ZYPP_THROW(RepoNotFoundException(info));
1692     else
1693       return *it;
1694   }
1695
1696   ////////////////////////////////////////////////////////////////////////////
1697
1698   RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1699                                            const url::ViewOption & urlview,
1700                                            const ProgressData::ReceiverFnc & progressrcv )
1701   {
1702     for_( it, repoBegin(), repoEnd() )
1703     {
1704       for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1705           urlit != (*it).baseUrlsEnd();
1706           ++urlit)
1707       {
1708         if ((*urlit).asString(urlview) == url.asString(urlview))
1709           return *it;
1710       }
1711     }
1712     RepoInfo info;
1713     info.setBaseUrl(url);
1714     ZYPP_THROW(RepoNotFoundException(info));
1715   }
1716
1717   ////////////////////////////////////////////////////////////////////////////
1718   //
1719   // Services
1720   //
1721   ////////////////////////////////////////////////////////////////////////////
1722
1723   bool RepoManager::serviceEmpty() const
1724   { return _pimpl->services.empty(); }
1725
1726   RepoManager::ServiceSizeType RepoManager::serviceSize() const
1727   { return _pimpl->services.size(); }
1728
1729   RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
1730   { return _pimpl->services.begin(); }
1731
1732   RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
1733   { return _pimpl->services.end(); }
1734
1735   ServiceInfo RepoManager::getService( const std::string & alias ) const
1736   {
1737     for_( it, serviceBegin(), serviceEnd() )
1738       if ( it->alias() == alias )
1739         return *it;
1740     return ServiceInfo::noService;
1741   }
1742
1743   bool RepoManager::hasService( const std::string & alias ) const
1744   {
1745     for_( it, serviceBegin(), serviceEnd() )
1746       if ( it->alias() == alias )
1747         return true;
1748     return false;
1749   }
1750
1751   ////////////////////////////////////////////////////////////////////////////
1752
1753   void RepoManager::addService( const std::string & alias, const Url & url )
1754   {
1755     addService( ServiceInfo(alias, url) );
1756   }
1757
1758   void RepoManager::addService( const ServiceInfo & service )
1759   {
1760     assert_alias( service );
1761
1762     // check if service already exists
1763     if ( hasService( service.alias() ) )
1764       ZYPP_THROW( ServiceAlreadyExistsException( service ) );
1765
1766     // Writable ServiceInfo is needed to save the location
1767     // of the .service file. Finaly insert into the service list.
1768     ServiceInfo toSave( service );
1769     _pimpl->saveService( toSave );
1770     _pimpl->services.insert( toSave );
1771
1772     // check for credentials in Url (username:password, not ?credentials param)
1773     if ( toSave.url().hasCredentialsInAuthority() )
1774     {
1775       media::CredentialManager cm(
1776           media::CredManagerOptions(_pimpl->options.rootDir) );
1777
1778       //! \todo use a method calling UI callbacks to ask where to save creds?
1779       cm.saveInUser(media::AuthData(toSave.url()));
1780     }
1781
1782     MIL << "added service " << toSave.alias() << endl;
1783   }
1784
1785   ////////////////////////////////////////////////////////////////////////////
1786
1787   void RepoManager::removeService( const std::string & alias )
1788   {
1789     MIL << "Going to delete repo " << alias << endl;
1790
1791     const ServiceInfo & service = getService( alias );
1792
1793     Pathname location = service.filepath();
1794     if( location.empty() )
1795     {
1796       ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1797     }
1798
1799     ServiceSet tmpSet;
1800     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1801
1802     // only one service definition in the file
1803     if ( tmpSet.size() == 1 )
1804     {
1805       if ( filesystem::unlink(location) != 0 )
1806       {
1807         // TranslatorExplanation '%s' is a filename
1808         ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
1809       }
1810       MIL << alias << " sucessfully deleted." << endl;
1811     }
1812     else
1813     {
1814       filesystem::assert_dir(location.dirname());
1815
1816       std::ofstream file(location.c_str());
1817       if( !file )
1818       {
1819         // TranslatorExplanation '%s' is a filename
1820         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
1821       }
1822
1823       for_(it, tmpSet.begin(), tmpSet.end())
1824       {
1825         if( it->alias() != alias )
1826           it->dumpAsIniOn(file);
1827       }
1828
1829       MIL << alias << " sucessfully deleted from file " << location <<  endl;
1830     }
1831
1832     // now remove all repositories added by this service
1833     RepoCollector rcollector;
1834     getRepositoriesInService( alias,
1835       boost::make_function_output_iterator(
1836           bind( &RepoCollector::collect, &rcollector, _1 ) ) );
1837     // cannot do this directly in getRepositoriesInService - would invalidate iterators
1838     for_(rit, rcollector.repos.begin(), rcollector.repos.end())
1839       removeRepository(*rit);
1840   }
1841
1842   void RepoManager::removeService( const ServiceInfo & service )
1843   { removeService(service.alias()); }
1844
1845   ////////////////////////////////////////////////////////////////////////////
1846
1847   void RepoManager::refreshServices()
1848   {
1849     // copy the set of services since refreshService
1850     // can eventually invalidate the iterator
1851     ServiceSet services( serviceBegin(), serviceEnd() );
1852     for_( it, services.begin(), services.end() )
1853     {
1854       if ( !it->enabled() )
1855         continue;
1856
1857       refreshService(*it);
1858     }
1859   }
1860
1861   void RepoManager::refreshService( const ServiceInfo & service )
1862   { refreshService( service.alias() ); }
1863
1864   void RepoManager::refreshService( const std::string & alias )
1865   {
1866     ServiceInfo service( getService( alias ) );
1867     assert_alias( service );
1868     assert_url( service );
1869     // NOTE: It might be necessary to modify and rewrite the service info.
1870     // Either when probing the type, or when adjusting the repositories
1871     // enable/disable state.:
1872     bool serviceModified = false;
1873     MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
1874
1875     //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
1876
1877     // if the type is unknown, try probing.
1878     if ( service.type() == repo::ServiceType::NONE )
1879     {
1880       repo::ServiceType type = probeService( service.url() );
1881       if ( type != ServiceType::NONE )
1882       {
1883         service.setProbedType( type ); // lazy init!
1884         serviceModified = true;
1885       }
1886     }
1887
1888     // repoindex.xml must be fetched always without using cookies (bnc #573897)
1889     Url serviceUrl( service.url() );
1890     serviceUrl.setQueryParam( "cookies", "0" );
1891
1892     // download the repo index file
1893     media::MediaManager mediamanager;
1894     media::MediaAccessId mid = mediamanager.open( serviceUrl );
1895     mediamanager.attach( mid );
1896     mediamanager.provideFile( mid, "repo/repoindex.xml" );
1897     Pathname path = mediamanager.localPath(mid, "repo/repoindex.xml" );
1898
1899     // get target distro identifier
1900     std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro;
1901     if ( servicesTargetDistro.empty() && getZYpp()->getTarget() )
1902       servicesTargetDistro = getZYpp()->target()->targetDistribution();
1903     DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
1904
1905     // parse it
1906     RepoCollector collector(servicesTargetDistro);
1907     parser::RepoindexFileReader reader( path, bind( &RepoCollector::collect, &collector, _1 ) );
1908     mediamanager.release( mid );
1909     mediamanager.close( mid );
1910
1911
1912     // set service alias and base url for all collected repositories
1913     for_( it, collector.repos.begin(), collector.repos.end() )
1914     {
1915       // if the repo url was not set by the repoindex parser, set service's url
1916       Url url;
1917
1918       if ( it->baseUrlsEmpty() )
1919         url = service.url();
1920       else
1921       {
1922         // service repo can contain only one URL now, so no need to iterate.
1923         url = *it->baseUrlsBegin();
1924       }
1925
1926       // libzypp currently has problem with separate url + path handling
1927       // so just append the path to the baseurl
1928       if ( !it->path().empty() )
1929       {
1930         Pathname path(url.getPathName());
1931         path /= it->path();
1932         url.setPathName( path.asString() );
1933         it->setPath("");
1934       }
1935
1936       // Prepend service alias:
1937       it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
1938
1939       // save the url
1940       it->setBaseUrl( url );
1941       // set refrence to the parent service
1942       it->setService( service.alias() );
1943     }
1944
1945     ////////////////////////////////////////////////////////////////////////////
1946     // Now compare collected repos with the ones in the system...
1947     //
1948     RepoInfoList oldRepos;
1949     getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
1950
1951     // find old repositories to remove...
1952     for_( it, oldRepos.begin(), oldRepos.end() )
1953     {
1954       if ( ! foundAliasIn( it->alias(), collector.repos ) )
1955       {
1956         if ( it->enabled() && ! service.repoToDisableFind( it->alias() ) )
1957         {
1958           DBG << "Service removes enabled repo " << it->alias() << endl;
1959           service.addRepoToEnable( it->alias() );
1960           serviceModified = true;
1961         }
1962         else
1963         {
1964           DBG << "Service removes disabled repo " << it->alias() << endl;
1965         }
1966         removeRepository( *it );
1967       }
1968     }
1969
1970     ////////////////////////////////////////////////////////////////////////////
1971     // create missing repositories and modify exising ones if needed...
1972     for_( it, collector.repos.begin(), collector.repos.end() )
1973     {
1974       // Service explicitly requests the repo being enabled?
1975       // Service explicitly requests the repo being disabled?
1976       // And hopefully not both ;) If so, enable wins.
1977       bool beEnabled = service.repoToEnableFind( it->alias() );
1978       bool beDisabled = service.repoToDisableFind( it->alias() );
1979
1980       if ( beEnabled )
1981       {
1982         // Remove from enable request list.
1983         // NOTE: repoToDisable is handled differently.
1984         //       It gets cleared on each refresh.
1985         service.delRepoToEnable( it->alias() );
1986         serviceModified = true;
1987       }
1988
1989       RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
1990       if ( oldRepo == oldRepos.end() )
1991       {
1992         // Not found in oldRepos ==> a new repo to add
1993
1994         // Make sure the service repo is created with the
1995         // appropriate enable and autorefresh true.
1996         it->setEnabled( beEnabled );
1997         it->setAutorefresh( true );
1998
1999         // At that point check whether a repo with the same alias
2000         // exists outside this service. Maybe forcefully re-alias
2001         // the existing repo?
2002         DBG << "Service adds repo " << it->alias() << " " << (beEnabled?"enabled":"disabled") << endl;
2003         addRepository( *it );
2004
2005         // save repo credentials
2006         // ma@: task for modifyRepository?
2007       }
2008       else
2009       {
2010         // ==> an exising repo to check
2011         bool oldRepoModified = false;
2012
2013         // changed enable?
2014         if ( beEnabled )
2015         {
2016           if ( ! oldRepo->enabled() )
2017           {
2018             DBG << "Service repo " << it->alias() << " gets enabled" << endl;
2019             oldRepo->setEnabled( true );
2020             oldRepoModified = true;
2021           }
2022           else
2023           {
2024             DBG << "Service repo " << it->alias() << " stays enabled" << endl;
2025           }
2026         }
2027         else if ( beDisabled )
2028         {
2029           if ( oldRepo->enabled() )
2030           {
2031             DBG << "Service repo " << it->alias() << " gets disabled" << endl;
2032             oldRepo->setEnabled( false );
2033             oldRepoModified = true;
2034           }
2035           else
2036           {
2037             DBG << "Service repo " << it->alias() << " stays disabled" << endl;
2038           }
2039         }
2040         else
2041         {
2042           DBG << "Service repo " << it->alias() << " stays " <<  (oldRepo->enabled()?"enabled":"disabled") << endl;
2043         }
2044
2045         // changed url?
2046         // service repo can contain only one URL now, so no need to iterate.
2047         if ( oldRepo->url() != it->url() )
2048         {
2049           DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
2050           oldRepo->setBaseUrl( it->url() );
2051           oldRepoModified = true;
2052         }
2053
2054         // save if modified:
2055         if ( oldRepoModified )
2056         {
2057           modifyRepository( oldRepo->alias(), *oldRepo );
2058         }
2059       }
2060     }
2061
2062     // Unlike reposToEnable, reposToDisable is always cleared after refresh.
2063     if ( ! service.reposToDisableEmpty() )
2064     {
2065       service.clearReposToDisable();
2066       serviceModified = true;
2067     }
2068
2069     ////////////////////////////////////////////////////////////////////////////
2070     // save service if modified:
2071     if ( serviceModified )
2072     {
2073       // write out modified service file.
2074       modifyService( service.alias(), service );
2075     }
2076   }
2077
2078   ////////////////////////////////////////////////////////////////////////////
2079
2080   void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & service)
2081   {
2082     MIL << "Going to modify service " << oldAlias << endl;
2083
2084     const ServiceInfo & oldService = getService(oldAlias);
2085
2086     Pathname location = oldService.filepath();
2087     if( location.empty() )
2088     {
2089       ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
2090     }
2091
2092     // remember: there may multiple services being defined in one file:
2093     ServiceSet tmpSet;
2094     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
2095
2096     filesystem::assert_dir(location.dirname());
2097     std::ofstream file(location.c_str());
2098     for_(it, tmpSet.begin(), tmpSet.end())
2099     {
2100       if( *it != oldAlias )
2101         it->dumpAsIniOn(file);
2102     }
2103     service.dumpAsIniOn(file);
2104     file.close();
2105
2106     _pimpl->services.erase(oldAlias);
2107     _pimpl->services.insert(service);
2108
2109     // changed properties affecting also repositories
2110     if( oldAlias != service.alias()                    // changed alias
2111         || oldService.enabled() != service.enabled()   // changed enabled status
2112       )
2113     {
2114       std::vector<RepoInfo> toModify;
2115       getRepositoriesInService(oldAlias, std::back_inserter(toModify));
2116       for_( it, toModify.begin(), toModify.end() )
2117       {
2118         if (oldService.enabled() && !service.enabled())
2119           it->setEnabled(false);
2120         else if (!oldService.enabled() && service.enabled())
2121         {
2122           //! \todo do nothing? the repos will be enabled on service refresh
2123           //! \todo how to know the service needs a (auto) refresh????
2124         }
2125         else
2126           it->setService(service.alias());
2127         modifyRepository(it->alias(), *it);
2128       }
2129     }
2130
2131     //! \todo refresh the service automatically if url is changed?
2132   }
2133
2134   ////////////////////////////////////////////////////////////////////////////
2135
2136   repo::ServiceType RepoManager::probeService( const Url &url ) const
2137   {
2138     try
2139     {
2140       MediaSetAccess access(url);
2141       if ( access.doesFileExist("/repo/repoindex.xml") )
2142         return repo::ServiceType::RIS;
2143     }
2144     catch ( const media::MediaException &e )
2145     {
2146       ZYPP_CAUGHT(e);
2147       // TranslatorExplanation '%s' is an URL
2148       RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
2149       enew.remember(e);
2150       ZYPP_THROW(enew);
2151     }
2152     catch ( const Exception &e )
2153     {
2154       ZYPP_CAUGHT(e);
2155       // TranslatorExplanation '%s' is an URL
2156       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
2157       enew.remember(e);
2158       ZYPP_THROW(enew);
2159     }
2160
2161     return repo::ServiceType::NONE;
2162   }
2163
2164   ////////////////////////////////////////////////////////////////////////////
2165
2166   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
2167   {
2168     return str << *obj._pimpl;
2169   }
2170
2171   /////////////////////////////////////////////////////////////////
2172 } // namespace zypp
2173 ///////////////////////////////////////////////////////////////////