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