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