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