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