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