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