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