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