9ab4b3d3606d212c7542892e77c91830fcae45d7
[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.attachDesiredMedia( _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 metadata cache path for a repository, this is
324    * inside the raw cache dir, plus the 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 rawmetadata_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 = rawmetadata_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 rawmetadata_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 rawpath = rawmetadata_path_for_repoinfo( _pimpl->options, info );
642     Pathname mediarootpath = rawcache_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(rawpath.asUrl());
651       break;
652       default:
653       break;
654     }
655
656     switch ( repokind.toEnum() )
657     {
658       case RepoType::RPMMD_e :
659       {
660         status = RepoStatus( rawpath + "/repodata/repomd.xml");
661       }
662       break;
663
664       case RepoType::YAST2_e :
665       {
666         status = RepoStatus( rawpath + "/content") && (RepoStatus( mediarootpath + "/media.1/media"));
667       }
668       break;
669
670       case RepoType::RPMPLAINDIR_e :
671       {
672         if ( PathInfo(Pathname(rawpath + "/cookie")).isExist() )
673           status = RepoStatus( rawpath + "/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 rawpath = rawmetadata_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(rawpath.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(rawpath + "/repodata/repomd.xml");
703         break;
704
705       case RepoType::YAST2_e :
706         p = Pathname(rawpath + "/content");
707         break;
708
709       case RepoType::RPMPLAINDIR_e :
710         p = Pathname(rawpath + "/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 rawpath = rawmetadata_path_for_repoinfo( _pimpl->options, info );
738       filesystem::assert_dir(rawpath);
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 rawpath
779       filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( rawpath ) );
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
787           repokind = probe(url);
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());
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);
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 rawpath = rawmetadata_path_for_repoinfo( _pimpl->options, info );
921         filesystem::assert_dir(rawpath);
922
923         // create temp dir as sibling of rawpath
924         filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( rawpath ) );
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(rawmetadata_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());
958
959           std::ofstream file(( tmpdir.path() + "/cookie").c_str());
960           if (!file)
961           {
962             // TranslatorExplanation '%s' is a filename
963             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (tmpdir.path()/"cookie").c_str() )));
964           }
965           file << url << endl;
966           file << newstatus.checksum() << endl;
967
968           file.close();
969         }
970         else
971         {
972           ZYPP_THROW(RepoUnknownTypeException());
973         }
974
975         // ok we have the metadata, now exchange
976         // the contents
977         filesystem::exchange( tmpdir.path(), rawpath );
978
979         // we are done.
980         return;
981       }
982       catch ( const Exception &e )
983       {
984         ZYPP_CAUGHT(e);
985         ERR << "Trying another url..." << endl;
986
987         // remember the exception caught for the *first URL*
988         // if all other URLs fail, the rexception will be thrown with the
989         // cause of the problem of the first URL remembered
990         if (it == info.baseUrlsBegin())
991           rexception.remember(e);
992       }
993     } // for every url
994     ERR << "No more urls..." << endl;
995     ZYPP_THROW(rexception);
996   }
997
998   ////////////////////////////////////////////////////////////////////////////
999
1000   void RepoManager::cleanMetadata( const RepoInfo &info,
1001                                    const ProgressData::ReceiverFnc & progressfnc )
1002   {
1003     ProgressData progress(100);
1004     progress.sendTo(progressfnc);
1005
1006     filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
1007     progress.toMax();
1008   }
1009
1010   void RepoManager::cleanPackages( const RepoInfo &info,
1011                                    const ProgressData::ReceiverFnc & progressfnc )
1012   {
1013     ProgressData progress(100);
1014     progress.sendTo(progressfnc);
1015
1016     filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
1017     progress.toMax();
1018   }
1019
1020   void RepoManager::buildCache( const RepoInfo &info,
1021                                 CacheBuildPolicy policy,
1022                                 const ProgressData::ReceiverFnc & progressrcv )
1023   {
1024     assert_alias(info);
1025     Pathname rawpath = rawmetadata_path_for_repoinfo(_pimpl->options, info);
1026
1027     filesystem::assert_dir(_pimpl->options.repoCachePath);
1028     RepoStatus raw_metadata_status = metadataStatus(info);
1029     if ( raw_metadata_status.empty() )
1030     {
1031        /* if there is no cache at this point, we refresh the raw
1032           in case this is the first time - if it's !autorefresh,
1033           we may still refresh */
1034       refreshMetadata(info, RefreshIfNeeded, progressrcv );
1035       raw_metadata_status = metadataStatus(info);
1036     }
1037
1038     bool needs_cleaning = false;
1039     if ( isCached( info ) )
1040     {
1041       MIL << info.alias() << " is already cached." << endl;
1042       RepoStatus cache_status = cacheStatus(info);
1043
1044       if ( cache_status.checksum() == raw_metadata_status.checksum() )
1045       {
1046         MIL << info.alias() << " cache is up to date with metadata." << endl;
1047         if ( policy == BuildIfNeeded ) {
1048           return;
1049         }
1050         else {
1051           MIL << info.alias() << " cache rebuild is forced" << endl;
1052         }
1053       }
1054
1055       needs_cleaning = true;
1056     }
1057
1058     ProgressData progress(100);
1059     callback::SendReport<ProgressReport> report;
1060     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1061     progress.name(str::form(_("Building repository '%s' cache"), info.name().c_str()));
1062     progress.toMin();
1063
1064     if (needs_cleaning)
1065     {
1066       cleanCache(info);
1067     }
1068
1069     MIL << info.alias() << " building cache..." << endl;
1070
1071     Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
1072     filesystem::assert_dir(base);
1073     Pathname solvfile = base / "solv";
1074
1075     // do we have type?
1076     repo::RepoType repokind = info.type();
1077
1078     // if the type is unknown, try probing.
1079     switch ( repokind.toEnum() )
1080     {
1081       case RepoType::NONE_e:
1082         // unknown, probe the local metadata
1083         repokind = probe(rawpath.asUrl());
1084       break;
1085       default:
1086       break;
1087     }
1088
1089     MIL << "repo type is " << repokind << endl;
1090
1091     switch ( repokind.toEnum() )
1092     {
1093       case RepoType::RPMMD_e :
1094       case RepoType::YAST2_e :
1095       case RepoType::RPMPLAINDIR_e :
1096       {
1097         // Take care we unlink the solvfile on exception
1098         ManagedFile guard( solvfile, filesystem::unlink );
1099         scoped_ptr<MediaMounter> forPlainDirs;
1100
1101         ExternalProgram::Arguments cmd;
1102         cmd.push_back( "repo2solv.sh" );
1103
1104         // repo2solv expects -o as 1st arg!
1105         cmd.push_back( "-o" );
1106         cmd.push_back( solvfile.asString() );
1107
1108         if ( repokind == RepoType::RPMPLAINDIR )
1109         {
1110           forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
1111           // recusive for plaindir as 2nd arg!
1112           cmd.push_back( "-R" );
1113           // FIXME this does only work form dir: URLs
1114           cmd.push_back( forPlainDirs->getPathName().c_str() );
1115         }
1116         else
1117           cmd.push_back( rawpath.asString() );
1118
1119         ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
1120         std::string errdetail;
1121
1122         for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1123           WAR << "  " << output;
1124           if ( errdetail.empty() ) {
1125             errdetail = prog.command();
1126             errdetail += '\n';
1127           }
1128           errdetail += output;
1129         }
1130
1131         int ret = prog.close();
1132         if ( ret != 0 )
1133         {
1134           RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
1135           ex.remember( errdetail );
1136           ZYPP_THROW(ex);
1137         }
1138
1139         // We keep it.
1140         guard.resetDispose();
1141       }
1142       break;
1143       default:
1144         ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
1145       break;
1146     }
1147     // update timestamp and checksum
1148     setCacheStatus(info, raw_metadata_status);
1149     MIL << "Commit cache.." << endl;
1150     progress.toMax();
1151   }
1152
1153   ////////////////////////////////////////////////////////////////////////////
1154
1155   repo::RepoType RepoManager::probe( const Url &url ) const
1156   {
1157     MIL << "going to probe the type of the repo " << endl;
1158
1159     if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName() ).isDir() )
1160     {
1161       // Handle non existing local directory in advance, as
1162       // MediaSetAccess does not support it.
1163       MIL << "Probed type NONE (not exists) at " << url << endl;
1164       return repo::RepoType::NONE;
1165     }
1166
1167     // prepare exception to be thrown if the type could not be determined
1168     // due to a media exception. We can't throw right away, because of some
1169     // problems with proxy servers returning an incorrect error
1170     // on ftp file-not-found(bnc #335906). Instead we'll check another types
1171     // before throwing.
1172
1173     // TranslatorExplanation '%s' is an URL
1174     RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
1175     bool gotMediaException = false;
1176     try
1177     {
1178       MediaSetAccess access(url);
1179       try
1180       {
1181         if ( access.doesFileExist("/repodata/repomd.xml") )
1182         {
1183           MIL << "Probed type RPMMD at " << url << endl;
1184           return repo::RepoType::RPMMD;
1185         }
1186       }
1187       catch ( const media::MediaException &e )
1188       {
1189         ZYPP_CAUGHT(e);
1190         DBG << "problem checking for repodata/repomd.xml file" << endl;
1191         enew.remember(e);
1192         gotMediaException = true;
1193       }
1194
1195       try
1196       {
1197         if ( access.doesFileExist("/content") )
1198         {
1199           MIL << "Probed type YAST2 at " << url << endl;
1200           return repo::RepoType::YAST2;
1201         }
1202       }
1203       catch ( const media::MediaException &e )
1204       {
1205         ZYPP_CAUGHT(e);
1206         DBG << "problem checking for content file" << endl;
1207         enew.remember(e);
1208         gotMediaException = true;
1209       }
1210
1211       // if it is a non-downloading URL denoting a directory
1212       if ( ! media::MediaManager::downloads(url) )
1213       {
1214         MediaMounter media( url );
1215         if ( PathInfo(media.getPathName()).isDir() )
1216         {
1217           // allow empty dirs for now
1218           MIL << "Probed type RPMPLAINDIR at " << url << endl;
1219           return repo::RepoType::RPMPLAINDIR;
1220         }
1221       }
1222     }
1223     catch ( const Exception &e )
1224     {
1225       ZYPP_CAUGHT(e);
1226       // TranslatorExplanation '%s' is an URL
1227       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
1228       enew.remember(e);
1229       ZYPP_THROW(enew);
1230     }
1231
1232     if (gotMediaException)
1233       ZYPP_THROW(enew);
1234
1235     MIL << "Probed type NONE at " << url << endl;
1236     return repo::RepoType::NONE;
1237   }
1238
1239   ////////////////////////////////////////////////////////////////////////////
1240
1241   void RepoManager::cleanCache( const RepoInfo &info,
1242                                 const ProgressData::ReceiverFnc & progressrcv )
1243   {
1244     ProgressData progress(100);
1245     progress.sendTo(progressrcv);
1246     progress.toMin();
1247
1248     filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
1249
1250     progress.toMax();
1251   }
1252
1253   ////////////////////////////////////////////////////////////////////////////
1254
1255   bool RepoManager::isCached( const RepoInfo &info ) const
1256   {
1257     return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
1258   }
1259
1260   RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
1261   {
1262
1263     Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
1264
1265     return RepoStatus::fromCookieFile(cookiefile);
1266   }
1267
1268   void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
1269   {
1270     Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
1271     filesystem::assert_dir(base);
1272     Pathname cookiefile = base / "cookie";
1273
1274     status.saveToCookieFile(cookiefile);
1275   }
1276
1277   void RepoManager::loadFromCache( const RepoInfo & info,
1278                                    const ProgressData::ReceiverFnc & progressrcv )
1279   {
1280     assert_alias(info);
1281     Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
1282
1283     if ( ! PathInfo(solvfile).isExist() )
1284       ZYPP_THROW(RepoNotCachedException(info));
1285
1286     try
1287     {
1288       sat::Pool::instance().addRepoSolv( solvfile, info );
1289     }
1290     catch ( const Exception & exp )
1291     {
1292       ZYPP_CAUGHT( exp );
1293       MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1294       cleanCache( info, progressrcv );
1295       buildCache( info, BuildIfNeeded, progressrcv );
1296
1297       sat::Pool::instance().addRepoSolv( solvfile, info );
1298     }
1299   }
1300
1301   ////////////////////////////////////////////////////////////////////////////
1302
1303   void RepoManager::addRepository( const RepoInfo &info,
1304                                    const ProgressData::ReceiverFnc & progressrcv )
1305   {
1306     assert_alias(info);
1307
1308     ProgressData progress(100);
1309     callback::SendReport<ProgressReport> report;
1310     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1311     progress.name(str::form(_("Adding repository '%s'"), info.name().c_str()));
1312     progress.toMin();
1313
1314     MIL << "Try adding repo " << info << endl;
1315
1316     RepoInfo tosave = info;
1317     if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
1318         ZYPP_THROW(RepoAlreadyExistsException(info));
1319
1320     // check the first url for now
1321     if ( _pimpl->options.probe )
1322     {
1323       DBG << "unknown repository type, probing" << endl;
1324
1325       RepoType probedtype;
1326       probedtype = probe(*tosave.baseUrlsBegin());
1327       if ( tosave.baseUrlsSize() > 0 )
1328       {
1329         if ( probedtype == RepoType::NONE )
1330           ZYPP_THROW(RepoUnknownTypeException());
1331         else
1332           tosave.setType(probedtype);
1333       }
1334     }
1335
1336     progress.set(50);
1337
1338     // assert the directory exists
1339     filesystem::assert_dir(_pimpl->options.knownReposPath);
1340
1341     Pathname repofile = _pimpl->generateNonExistingName(
1342         _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
1343     // now we have a filename that does not exists
1344     MIL << "Saving repo in " << repofile << endl;
1345
1346     std::ofstream file(repofile.c_str());
1347     if (!file)
1348     {
1349       // TranslatorExplanation '%s' is a filename
1350       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1351     }
1352
1353     tosave.dumpAsIniOn(file);
1354     tosave.setFilepath(repofile);
1355     _pimpl->repos.insert(tosave);
1356
1357     progress.set(90);
1358
1359     // check for credentials in Urls
1360     bool havePasswords = false;
1361     for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
1362       if ( urlit->hasCredentialsInAuthority() )
1363       {
1364         havePasswords = true;
1365         break;
1366       }
1367     // save the credentials
1368     if ( havePasswords )
1369     {
1370       media::CredentialManager cm(
1371           media::CredManagerOptions(_pimpl->options.rootDir) );
1372
1373       for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
1374         if (urlit->hasCredentialsInAuthority())
1375           //! \todo use a method calling UI callbacks to ask where to save creds?
1376           cm.saveInUser(media::AuthData(*urlit));
1377     }
1378
1379     HistoryLog().addRepository(tosave);
1380
1381     progress.toMax();
1382     MIL << "done" << endl;
1383   }
1384
1385   void RepoManager::addRepositories( const Url &url,
1386                                      const ProgressData::ReceiverFnc & progressrcv )
1387   {
1388     std::list<RepoInfo> repos = readRepoFile(url);
1389     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1390           it != repos.end();
1391           ++it )
1392     {
1393       // look if the alias is in the known repos.
1394       for_ ( kit, repoBegin(), repoEnd() )
1395       {
1396         if ( (*it).alias() == (*kit).alias() )
1397         {
1398           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1399           ZYPP_THROW(RepoAlreadyExistsException(*it));
1400         }
1401       }
1402     }
1403
1404     std::string filename = Pathname(url.getPathName()).basename();
1405
1406     if ( filename == Pathname() )
1407     {
1408       // TranslatorExplanation '%s' is an URL
1409       ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
1410     }
1411
1412     // assert the directory exists
1413     filesystem::assert_dir(_pimpl->options.knownReposPath);
1414
1415     Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
1416     // now we have a filename that does not exists
1417     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1418
1419     std::ofstream file(repofile.c_str());
1420     if (!file)
1421     {
1422       // TranslatorExplanation '%s' is a filename
1423       ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1424     }
1425
1426     for ( std::list<RepoInfo>::iterator it = repos.begin();
1427           it != repos.end();
1428           ++it )
1429     {
1430       MIL << "Saving " << (*it).alias() << endl;
1431       it->setFilepath(repofile.asString());
1432       it->dumpAsIniOn(file);
1433       _pimpl->repos.insert(*it);
1434
1435       HistoryLog(_pimpl->options.rootDir).addRepository(*it);
1436     }
1437
1438     MIL << "done" << endl;
1439   }
1440
1441   ////////////////////////////////////////////////////////////////////////////
1442
1443   void RepoManager::removeRepository( const RepoInfo & info,
1444                                       const ProgressData::ReceiverFnc & progressrcv)
1445   {
1446     ProgressData progress;
1447     callback::SendReport<ProgressReport> report;
1448     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1449     progress.name(str::form(_("Removing repository '%s'"), info.name().c_str()));
1450
1451     MIL << "Going to delete repo " << info.alias() << endl;
1452
1453     for_( it, repoBegin(), repoEnd() )
1454     {
1455       // they can be the same only if the provided is empty, that means
1456       // the provided repo has no alias
1457       // then skip
1458       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1459         continue;
1460
1461       // TODO match by url
1462
1463       // we have a matcing repository, now we need to know
1464       // where it does come from.
1465       RepoInfo todelete = *it;
1466       if (todelete.filepath().empty())
1467       {
1468         ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1469       }
1470       else
1471       {
1472         // figure how many repos are there in the file:
1473         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1474         if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1475         {
1476           // easy, only this one, just delete the file
1477           if ( filesystem::unlink(todelete.filepath()) != 0 )
1478           {
1479             // TranslatorExplanation '%s' is a filename
1480             ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
1481           }
1482           MIL << todelete.alias() << " sucessfully deleted." << endl;
1483         }
1484         else
1485         {
1486           // there are more repos in the same file
1487           // write them back except the deleted one.
1488           //TmpFile tmp;
1489           //std::ofstream file(tmp.path().c_str());
1490
1491           // assert the directory exists
1492           filesystem::assert_dir(todelete.filepath().dirname());
1493
1494           std::ofstream file(todelete.filepath().c_str());
1495           if (!file)
1496           {
1497             // TranslatorExplanation '%s' is a filename
1498             ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
1499           }
1500           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1501                 fit != filerepos.end();
1502                 ++fit )
1503           {
1504             if ( (*fit).alias() != todelete.alias() )
1505               (*fit).dumpAsIniOn(file);
1506           }
1507         }
1508
1509         CombinedProgressData subprogrcv(progress, 70);
1510         CombinedProgressData cleansubprogrcv(progress, 30);
1511         // now delete it from cache
1512         if ( isCached(todelete) )
1513           cleanCache( todelete, subprogrcv);
1514         // now delete metadata (#301037)
1515         cleanMetadata( todelete, cleansubprogrcv);
1516         _pimpl->repos.erase(todelete);
1517         MIL << todelete.alias() << " sucessfully deleted." << endl;
1518         HistoryLog(_pimpl->options.rootDir).removeRepository(todelete);
1519         return;
1520       } // else filepath is empty
1521
1522     }
1523     // should not be reached on a sucess workflow
1524     ZYPP_THROW(RepoNotFoundException(info));
1525   }
1526
1527   ////////////////////////////////////////////////////////////////////////////
1528
1529   void RepoManager::modifyRepository( const std::string &alias,
1530                                       const RepoInfo & newinfo_r,
1531                                       const ProgressData::ReceiverFnc & progressrcv )
1532   {
1533     RepoInfo toedit = getRepositoryInfo(alias);
1534     RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
1535
1536     // check if the new alias already exists when renaming the repo
1537     if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
1538     {
1539       ZYPP_THROW(RepoAlreadyExistsException(newinfo));
1540     }
1541
1542     if (toedit.filepath().empty())
1543     {
1544       ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1545     }
1546     else
1547     {
1548       // figure how many repos are there in the file:
1549       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1550
1551       // there are more repos in the same file
1552       // write them back except the deleted one.
1553       //TmpFile tmp;
1554       //std::ofstream file(tmp.path().c_str());
1555
1556       // assert the directory exists
1557       filesystem::assert_dir(toedit.filepath().dirname());
1558
1559       std::ofstream file(toedit.filepath().c_str());
1560       if (!file)
1561       {
1562         // TranslatorExplanation '%s' is a filename
1563         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
1564       }
1565       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1566             fit != filerepos.end();
1567             ++fit )
1568       {
1569           // if the alias is different, dump the original
1570           // if it is the same, dump the provided one
1571           if ( (*fit).alias() != toedit.alias() )
1572             (*fit).dumpAsIniOn(file);
1573           else
1574             newinfo.dumpAsIniOn(file);
1575       }
1576
1577       newinfo.setFilepath(toedit.filepath());
1578       _pimpl->repos.erase(toedit);
1579       _pimpl->repos.insert(newinfo);
1580       HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
1581       MIL << "repo " << alias << " modified" << endl;
1582     }
1583   }
1584
1585   ////////////////////////////////////////////////////////////////////////////
1586
1587   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1588                                            const ProgressData::ReceiverFnc & progressrcv )
1589   {
1590     RepoInfo info;
1591     info.setAlias(alias);
1592     RepoConstIterator it = _pimpl->repos.find( info );
1593     if( it == repoEnd() )
1594       ZYPP_THROW(RepoNotFoundException(info));
1595     else
1596       return *it;
1597   }
1598
1599   ////////////////////////////////////////////////////////////////////////////
1600
1601   RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1602                                            const url::ViewOption & urlview,
1603                                            const ProgressData::ReceiverFnc & progressrcv )
1604   {
1605     for_( it, repoBegin(), repoEnd() )
1606     {
1607       for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1608           urlit != (*it).baseUrlsEnd();
1609           ++urlit)
1610       {
1611         if ((*urlit).asString(urlview) == url.asString(urlview))
1612           return *it;
1613       }
1614     }
1615     RepoInfo info;
1616     info.setBaseUrl(url);
1617     ZYPP_THROW(RepoNotFoundException(info));
1618   }
1619
1620   ////////////////////////////////////////////////////////////////////////////
1621   //
1622   // Services
1623   //
1624   ////////////////////////////////////////////////////////////////////////////
1625
1626   bool RepoManager::serviceEmpty() const
1627   { return _pimpl->services.empty(); }
1628
1629   RepoManager::ServiceSizeType RepoManager::serviceSize() const
1630   { return _pimpl->services.size(); }
1631
1632   RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
1633   { return _pimpl->services.begin(); }
1634
1635   RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
1636   { return _pimpl->services.end(); }
1637
1638   ServiceInfo RepoManager::getService( const std::string & alias ) const
1639   {
1640     for_( it, serviceBegin(), serviceEnd() )
1641       if ( it->alias() == alias )
1642         return *it;
1643     return ServiceInfo::noService;
1644   }
1645
1646   bool RepoManager::hasService( const std::string & alias ) const
1647   {
1648     for_( it, serviceBegin(), serviceEnd() )
1649       if ( it->alias() == alias )
1650         return true;
1651     return false;
1652   }
1653
1654   ////////////////////////////////////////////////////////////////////////////
1655
1656   void RepoManager::addService( const std::string & alias, const Url & url )
1657   {
1658     addService( ServiceInfo(alias, url) );
1659   }
1660
1661   void RepoManager::addService( const ServiceInfo & service )
1662   {
1663     assert_alias( service );
1664
1665     // check if service already exists
1666     if ( hasService( service.alias() ) )
1667       ZYPP_THROW( ServiceAlreadyExistsException( service ) );
1668
1669     // Writable ServiceInfo is needed to save the location
1670     // of the .service file. Finaly insert into the service list.
1671     ServiceInfo toSave( service );
1672     _pimpl->saveService( toSave );
1673     _pimpl->services.insert( toSave );
1674
1675     // check for credentials in Url (username:password, not ?credentials param)
1676     if ( toSave.url().hasCredentialsInAuthority() )
1677     {
1678       media::CredentialManager cm(
1679           media::CredManagerOptions(_pimpl->options.rootDir) );
1680
1681       //! \todo use a method calling UI callbacks to ask where to save creds?
1682       cm.saveInUser(media::AuthData(toSave.url()));
1683     }
1684
1685     MIL << "added service " << toSave.alias() << endl;
1686   }
1687
1688   ////////////////////////////////////////////////////////////////////////////
1689
1690   void RepoManager::removeService( const std::string & alias )
1691   {
1692     MIL << "Going to delete repo " << alias << endl;
1693
1694     const ServiceInfo & service = getService( alias );
1695
1696     Pathname location = service.filepath();
1697     if( location.empty() )
1698     {
1699       ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1700     }
1701
1702     ServiceSet tmpSet;
1703     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1704
1705     // only one service definition in the file
1706     if ( tmpSet.size() == 1 )
1707     {
1708       if ( filesystem::unlink(location) != 0 )
1709       {
1710         // TranslatorExplanation '%s' is a filename
1711         ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
1712       }
1713       MIL << alias << " sucessfully deleted." << endl;
1714     }
1715     else
1716     {
1717       filesystem::assert_dir(location.dirname());
1718
1719       std::ofstream file(location.c_str());
1720       if( !file )
1721       {
1722         // TranslatorExplanation '%s' is a filename
1723         ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
1724       }
1725
1726       for_(it, tmpSet.begin(), tmpSet.end())
1727       {
1728         if( it->alias() != alias )
1729           it->dumpAsIniOn(file);
1730       }
1731
1732       MIL << alias << " sucessfully deleted from file " << location <<  endl;
1733     }
1734
1735     // now remove all repositories added by this service
1736     RepoCollector rcollector;
1737     getRepositoriesInService( alias,
1738       boost::make_function_output_iterator(
1739           bind( &RepoCollector::collect, &rcollector, _1 ) ) );
1740     // cannot do this directly in getRepositoriesInService - would invalidate iterators
1741     for_(rit, rcollector.repos.begin(), rcollector.repos.end())
1742       removeRepository(*rit);
1743   }
1744
1745   void RepoManager::removeService( const ServiceInfo & service )
1746   { removeService(service.alias()); }
1747
1748   ////////////////////////////////////////////////////////////////////////////
1749
1750   void RepoManager::refreshServices()
1751   {
1752     // copy the set of services since refreshService
1753     // can eventually invalidate the iterator
1754     ServiceSet services( serviceBegin(), serviceEnd() );
1755     for_( it, services.begin(), services.end() )
1756     {
1757       if ( !it->enabled() )
1758         continue;
1759
1760       refreshService(*it);
1761     }
1762   }
1763
1764   void RepoManager::refreshService( const ServiceInfo & service )
1765   { refreshService( service.alias() ); }
1766
1767   void RepoManager::refreshService( const std::string & alias )
1768   {
1769     ServiceInfo service( getService( alias ) );
1770     assert_alias( service );
1771     assert_url( service );
1772     // NOTE: It might be necessary to modify and rewrite the service info.
1773     // Either when probing the type, or when adjusting the repositories
1774     // enable/disable state.:
1775     bool serviceModified = false;
1776     MIL << "going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
1777
1778     //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
1779
1780     // if the type is unknown, try probing.
1781     if ( service.type() == repo::ServiceType::NONE )
1782     {
1783       repo::ServiceType type = probeService( service.url() );
1784       if ( type != ServiceType::NONE )
1785       {
1786         service.setProbedType( type ); // lazy init!
1787         serviceModified = true;
1788       }
1789     }
1790
1791     // download the repo index file
1792     media::MediaManager mediamanager;
1793     media::MediaAccessId mid = mediamanager.open( service.url() );
1794     mediamanager.attachDesiredMedia( mid );
1795     mediamanager.provideFile( mid, "repo/repoindex.xml" );
1796     Pathname path = mediamanager.localPath(mid, "repo/repoindex.xml" );
1797
1798     // get target distro identifier
1799     std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro;
1800     if ( servicesTargetDistro.empty() && getZYpp()->getTarget() )
1801       servicesTargetDistro = getZYpp()->target()->targetDistribution();
1802     DBG << "servicesTargetDistro: " << servicesTargetDistro << endl;
1803
1804     // parse it
1805     RepoCollector collector(servicesTargetDistro);
1806     parser::RepoindexFileReader reader( path, bind( &RepoCollector::collect, &collector, _1 ) );
1807     mediamanager.release( mid );
1808     mediamanager.close( mid );
1809
1810
1811     // set service alias and base url for all collected repositories
1812     for_( it, collector.repos.begin(), collector.repos.end() )
1813     {
1814       // if the repo url was not set by the repoindex parser, set service's url
1815       Url url;
1816
1817       if ( it->baseUrlsEmpty() )
1818         url = service.url();
1819       else
1820       {
1821         // service repo can contain only one URL now, so no need to iterate.
1822         url = *it->baseUrlsBegin();
1823       }
1824
1825       // libzypp currently has problem with separate url + path handling
1826       // so just append the path to the baseurl
1827       if ( !it->path().empty() )
1828       {
1829         Pathname path(url.getPathName());
1830         path /= it->path();
1831         url.setPathName( path.asString() );
1832         it->setPath("");
1833       }
1834
1835       // Prepend service alias:
1836       it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
1837
1838       // save the url
1839       it->setBaseUrl( url );
1840       // set refrence to the parent service
1841       it->setService( service.alias() );
1842     }
1843
1844     ////////////////////////////////////////////////////////////////////////////
1845     // Now compare collected repos with the ones in the system...
1846     //
1847     RepoInfoList oldRepos;
1848     getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
1849
1850     // find old repositories to remove...
1851     for_( it, oldRepos.begin(), oldRepos.end() )
1852     {
1853       if ( ! foundAliasIn( it->alias(), collector.repos ) )
1854       {
1855         removeRepository( *it );
1856       }
1857     }
1858
1859     ////////////////////////////////////////////////////////////////////////////
1860     // create missing repositories and modify exising ones if needed...
1861     for_( it, collector.repos.begin(), collector.repos.end() )
1862     {
1863       // Service explicitly requests the repo being enabled?
1864       // Service explicitly requests the repo being disabled?
1865       // And hopefully not both ;) If so, enable wins.
1866       bool beEnabled = service.repoToEnableFind( it->alias() );
1867       bool beDisabled = service.repoToDisableFind( it->alias() );
1868
1869       if ( beEnabled )
1870       {
1871         // Remove from enable request list.
1872         // NOTE: repoToDisable is handled differently.
1873         //       It gets cleared on each refresh.
1874         service.delRepoToEnable( it->alias() );
1875         serviceModified = true;
1876       }
1877
1878       RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
1879       if ( oldRepo == oldRepos.end() )
1880       {
1881         // Not found in oldRepos ==> a new repo to add
1882
1883         // Make sure the service repo is created with the
1884         // appropriate enable and autorefresh true.
1885         it->setEnabled( beEnabled );
1886         it->setAutorefresh( true );
1887
1888         // At that point check whether a repo with the same alias
1889         // exists outside this service. Maybe forcefully re-alias
1890         // the existing repo?
1891         addRepository( *it );
1892
1893         // save repo credentials
1894         // ma@: task for modifyRepository?
1895       }
1896       else
1897       {
1898         // ==> an exising repo to check
1899         bool oldRepoModified = false;
1900
1901         // changed enable?
1902         if ( beEnabled )
1903         {
1904           if ( ! oldRepo->enabled() )
1905           {
1906             oldRepo->setEnabled( true );
1907             oldRepoModified = true;
1908           }
1909         }
1910         else if ( beDisabled )
1911         {
1912           if ( oldRepo->enabled() )
1913           {
1914             oldRepo->setEnabled( false );
1915             oldRepoModified = true;
1916           }
1917         }
1918
1919         // changed url?
1920         // service repo can contain only one URL now, so no need to iterate.
1921         if ( oldRepo->url() != it->url() )
1922         {
1923           oldRepo->setBaseUrl( it->url() );
1924           oldRepoModified = true;
1925         }
1926
1927         // save if modified:
1928         if ( oldRepoModified )
1929         {
1930           modifyRepository( oldRepo->alias(), *oldRepo );
1931         }
1932       }
1933     }
1934
1935     // Unlike reposToEnable, reposToDisable is always cleared after refresh.
1936     if ( ! service.reposToDisableEmpty() )
1937     {
1938       service.clearReposToDisable();
1939       serviceModified = true;
1940     }
1941
1942     ////////////////////////////////////////////////////////////////////////////
1943     // save service if modified:
1944     if ( serviceModified )
1945     {
1946       // write out modified service file.
1947       modifyService( service.alias(), service );
1948     }
1949   }
1950
1951   ////////////////////////////////////////////////////////////////////////////
1952
1953   void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & service)
1954   {
1955     MIL << "Going to modify service " << oldAlias << endl;
1956
1957     const ServiceInfo & oldService = getService(oldAlias);
1958
1959     Pathname location = oldService.filepath();
1960     if( location.empty() )
1961     {
1962       ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1963     }
1964
1965     // remember: there may multiple services being defined in one file:
1966     ServiceSet tmpSet;
1967     parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1968
1969     filesystem::assert_dir(location.dirname());
1970     std::ofstream file(location.c_str());
1971     for_(it, tmpSet.begin(), tmpSet.end())
1972     {
1973       if( *it != oldAlias )
1974         it->dumpAsIniOn(file);
1975     }
1976     service.dumpAsIniOn(file);
1977     file.close();
1978
1979     _pimpl->services.erase(oldAlias);
1980     _pimpl->services.insert(service);
1981
1982     // changed properties affecting also repositories
1983     if( oldAlias != service.alias()                    // changed alias
1984         || oldService.enabled() != service.enabled()   // changed enabled status
1985       )
1986     {
1987       std::vector<RepoInfo> toModify;
1988       getRepositoriesInService(oldAlias, std::back_inserter(toModify));
1989       for_( it, toModify.begin(), toModify.end() )
1990       {
1991         if (oldService.enabled() && !service.enabled())
1992           it->setEnabled(false);
1993         else if (!oldService.enabled() && service.enabled())
1994         {
1995           //! \todo do nothing? the repos will be enabled on service refresh
1996           //! \todo how to know the service needs a (auto) refresh????
1997         }
1998         else
1999           it->setService(service.alias());
2000         modifyRepository(it->alias(), *it);
2001       }
2002     }
2003
2004     //! \todo refresh the service automatically if url is changed?
2005   }
2006
2007   ////////////////////////////////////////////////////////////////////////////
2008
2009   repo::ServiceType RepoManager::probeService( const Url &url ) const
2010   {
2011     try
2012     {
2013       MediaSetAccess access(url);
2014       if ( access.doesFileExist("/repo/repoindex.xml") )
2015         return repo::ServiceType::RIS;
2016     }
2017     catch ( const media::MediaException &e )
2018     {
2019       ZYPP_CAUGHT(e);
2020       // TranslatorExplanation '%s' is an URL
2021       RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
2022       enew.remember(e);
2023       ZYPP_THROW(enew);
2024     }
2025     catch ( const Exception &e )
2026     {
2027       ZYPP_CAUGHT(e);
2028       // TranslatorExplanation '%s' is an URL
2029       Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
2030       enew.remember(e);
2031       ZYPP_THROW(enew);
2032     }
2033
2034     return repo::ServiceType::NONE;
2035   }
2036
2037   ////////////////////////////////////////////////////////////////////////////
2038
2039   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
2040   {
2041     return str << *obj._pimpl;
2042   }
2043
2044   /////////////////////////////////////////////////////////////////
2045 } // namespace zypp
2046 ///////////////////////////////////////////////////////////////////