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