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