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