rawCachePath -> metadataPath
[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 <iostream>
14 #include <fstream>
15 #include <list>
16 #include <algorithm>
17 #include "zypp/base/InputStream.h"
18 #include "zypp/base/Logger.h"
19 #include "zypp/base/Gettext.h"
20 #include "zypp/base/Function.h"
21 #include "zypp/PathInfo.h"
22 #include "zypp/TmpPath.h"
23
24 #include "zypp/repo/RepoException.h"
25 #include "zypp/RepoManager.h"
26
27 #include "zypp/cache/CacheStore.h"
28 #include "zypp/repo/cached/RepoImpl.h"
29 #include "zypp/media/MediaManager.h"
30 #include "zypp/MediaSetAccess.h"
31
32 #include "zypp/parser/RepoFileReader.h"
33 #include "zypp/repo/yum/Downloader.h"
34 #include "zypp/parser/yum/RepoParser.h"
35 #include "zypp/parser/plaindir/RepoParser.h"
36 #include "zypp/repo/susetags/Downloader.h"
37 #include "zypp/parser/susetags/RepoParser.h"
38
39 #include "zypp/ZYppCallbacks.h"
40
41 using namespace std;
42 using namespace zypp;
43 using namespace zypp::repo;
44 using namespace zypp::filesystem;
45
46 using namespace zypp::repo;
47
48 ///////////////////////////////////////////////////////////////////
49 namespace zypp
50 { /////////////////////////////////////////////////////////////////
51
52   ///////////////////////////////////////////////////////////////////
53   //
54   //    CLASS NAME : RepoManagerOptions
55   //
56   ///////////////////////////////////////////////////////////////////
57
58   RepoManagerOptions::RepoManagerOptions()
59   {
60     repoCachePath    = ZConfig::instance().defaultRepoCachePath();
61     repoRawCachePath = ZConfig::instance().defaultRepoMetadataPath();
62     knownReposPath   = ZConfig::instance().defaultKnownReposPath();
63   }
64
65   ////////////////////////////////////////////////////////////////////////////
66
67   /**
68     * \short Simple callback to collect the results
69     *
70     * Classes like RepoFileParser call the callback
71     * once per each repo in a file.
72     *
73     * Passing this functor as callback, you can collect
74     * all resuls at the end, without dealing with async
75     * code.
76     */
77     struct RepoCollector
78     {
79       RepoCollector()
80       {
81         MIL << endl;
82       }
83
84       ~RepoCollector()
85       {
86         MIL << endl;
87       }
88
89       bool collect( const RepoInfo &repo )
90       {
91         //MIL << "here in collector: " << repo.alias() << endl;
92         repos.push_back(repo);
93         //MIL << "added: " << repo.alias() << endl;
94         return true;
95       }
96
97       RepoInfoList repos;
98     };
99
100   ////////////////////////////////////////////////////////////////////////////
101
102   /**
103    * Reads RepoInfo's from a repo file.
104    *
105    * \param file pathname of the file to read.
106    */
107   static std::list<RepoInfo> repositories_in_file( const Pathname & file )
108   {
109     MIL << "repo file: " << file << endl;
110     RepoCollector collector;
111     parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
112     return collector.repos;
113   }
114
115   ////////////////////////////////////////////////////////////////////////////
116
117   std::list<RepoInfo> readRepoFile(const Url & repo_file)
118    {
119      // no interface to download a specific file, using workaround:
120      //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
121      Url url(repo_file);
122      Pathname path(url.getPathName());
123      url.setPathName ("/");
124      MediaSetAccess access(url);
125      Pathname local = access.provideFile(path);
126
127      DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
128
129      return repositories_in_file(local);
130    }
131
132   ////////////////////////////////////////////////////////////////////////////
133
134   /**
135    * \short List of RepoInfo's from a directory
136    *
137    * Goes trough every file in a directory and adds all
138    * RepoInfo's contained in that file.
139    *
140    * \param dir pathname of the directory to read.
141    */
142   static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
143   {
144     MIL << "directory " << dir << endl;
145     list<RepoInfo> repos;
146     list<Pathname> entries;
147     if ( filesystem::readdir( entries, Pathname(dir), false ) != 0 )
148       ZYPP_THROW(Exception("failed to read directory"));
149
150     for ( list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
151     {
152       list<RepoInfo> tmp = repositories_in_file( *it );
153       repos.insert( repos.end(), tmp.begin(), tmp.end() );
154
155       //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
156       //MIL << "ok" << endl;
157     }
158     return repos;
159   }
160
161   ////////////////////////////////////////////////////////////////////////////
162
163   static void assert_alias( const RepoInfo &info )
164   {
165     if (info.alias().empty())
166         ZYPP_THROW(RepoNoAliasException());
167   }
168
169   ////////////////////////////////////////////////////////////////////////////
170
171   static void assert_urls( const RepoInfo &info )
172   {
173     if (info.baseUrls().empty())
174         ZYPP_THROW(RepoNoUrlException());
175   }
176
177   ////////////////////////////////////////////////////////////////////////////
178
179   /**
180    * \short Calculates the raw cache path for a repository
181    */
182   static Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
183   {
184     assert_alias(info);
185     return opt.repoRawCachePath + info.alias();
186   }
187
188   ///////////////////////////////////////////////////////////////////
189   //
190   //    CLASS NAME : RepoManager::Impl
191   //
192   ///////////////////////////////////////////////////////////////////
193
194   /**
195    * \short RepoManager implementation.
196    */
197   struct RepoManager::Impl
198   {
199     Impl( const RepoManagerOptions &opt )
200       : options(opt)
201     {
202
203     }
204
205     Impl()
206     {
207
208     }
209
210     RepoManagerOptions options;
211
212   public:
213     /** Offer default Impl. */
214     static shared_ptr<Impl> nullimpl()
215     {
216       static shared_ptr<Impl> _nullimpl( new Impl );
217       return _nullimpl;
218     }
219
220   private:
221     friend Impl * rwcowClone<Impl>( const Impl * rhs );
222     /** clone for RWCOW_pointer */
223     Impl * clone() const
224     { return new Impl( *this ); }
225   };
226   ///////////////////////////////////////////////////////////////////
227
228   /** \relates RepoManager::Impl Stream output */
229   inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
230   {
231     return str << "RepoManager::Impl";
232   }
233
234   ///////////////////////////////////////////////////////////////////
235   //
236   //    CLASS NAME : RepoManager
237   //
238   ///////////////////////////////////////////////////////////////////
239
240   RepoManager::RepoManager( const RepoManagerOptions &opt )
241   : _pimpl( new Impl(opt) )
242   {}
243
244   ////////////////////////////////////////////////////////////////////////////
245
246   RepoManager::~RepoManager()
247   {}
248
249   ////////////////////////////////////////////////////////////////////////////
250
251   std::list<RepoInfo> RepoManager::knownRepositories() const
252   {
253     MIL << endl;
254
255     if ( PathInfo(_pimpl->options.knownReposPath).isExist() )
256       return repositories_in_dir(_pimpl->options.knownReposPath);
257     else
258       return std::list<RepoInfo>();
259
260     MIL << endl;
261   }
262
263   ////////////////////////////////////////////////////////////////////////////
264
265   RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
266   {
267     Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
268     RepoType repokind = info.type();
269     RepoStatus status;
270
271     switch ( repokind.toEnum() )
272     {
273       case RepoType::NONE_e:
274       // unknown, probe the local metadata
275         repokind = probe(rawpath.asUrl());
276       break;
277       default:
278       break;
279     }
280
281     switch ( repokind.toEnum() )
282     {
283       case RepoType::RPMMD_e :
284       {
285         status = RepoStatus( rawpath + "/repodata/repomd.xml");
286       }
287       break;
288
289       case RepoType::YAST2_e :
290       {
291         status = RepoStatus( rawpath + "/content");
292       }
293       break;
294
295       case RepoType::RPMPLAINDIR_e :
296       {
297         if ( PathInfo(Pathname(rawpath + "/cookie")).isExist() )
298           status = RepoStatus( rawpath + "/cookie");
299       }
300       break;
301
302       case RepoType::NONE_e :
303         // Return default RepoStatus in case of RepoType::NONE
304         // indicating it should be created?
305         // ZYPP_THROW(RepoUnknownTypeException());
306         break;
307     }
308     return status;
309   }
310
311   void RepoManager::refreshMetadata( const RepoInfo &info,
312                                      RawMetadataRefreshPolicy policy,
313                                      const ProgressData::ReceiverFnc & progress )
314   {
315     assert_alias(info);
316     assert_urls(info);
317
318     RepoStatus oldstatus;
319     RepoStatus newstatus;
320     // try urls one by one
321     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
322     {
323       try
324       {
325         Url url(*it);
326         filesystem::TmpDir tmpdir;
327
328         repo::RepoType repokind = info.type();
329
330         // if the type is unknown, try probing.
331         switch ( repokind.toEnum() )
332         {
333           case RepoType::NONE_e:
334             // unknown, probe it
335             repokind = probe(*it);
336           break;
337           default:
338           break;
339         }
340
341         Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
342         filesystem::assert_dir(rawpath);
343         oldstatus = metadataStatus(info);
344
345         if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
346              ( repokind.toEnum() == RepoType::YAST2_e ) )
347         {
348           MediaSetAccess media(url);
349           shared_ptr<repo::Downloader> downloader_ptr;
350
351           if ( repokind.toEnum() == RepoType::RPMMD_e )
352             downloader_ptr.reset(new yum::Downloader(info.path()));
353           else
354             downloader_ptr.reset( new susetags::Downloader(info.path()));
355
356           /**
357            * Given a downloader, sets the other repos raw metadata
358            * path as cache paths for the fetcher, so if another
359            * repo has the same file, it will not download it
360            * but copy it from the other repository
361            */
362           std::list<RepoInfo> repos = knownRepositories();
363           for ( std::list<RepoInfo>::const_iterator it = repos.begin();
364                 it != repos.end();
365                 ++it )
366           {
367             downloader_ptr->addCachePath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
368           }
369
370           RepoStatus newstatus = downloader_ptr->status(media);
371           bool refresh = false;
372           if ( oldstatus.checksum() == newstatus.checksum() )
373           {
374             MIL << "repo has not changed" << endl;
375             if ( policy == RefreshForced )
376             {
377               MIL << "refresh set to forced" << endl;
378               refresh = true;
379             }
380           }
381           else
382           {
383             refresh = true;
384           }
385           if ( refresh )
386             downloader_ptr->download( media, tmpdir.path());
387           else
388             return;
389           // no error
390         }
391         else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
392         {
393           RepoStatus newstatus = parser::plaindir::dirStatus(url.getPathName());
394           bool refresh = false;
395           if ( oldstatus.checksum() == newstatus.checksum() )
396           {
397             MIL << "repo has not changed" << endl;
398             if ( policy == RefreshForced )
399             {
400               MIL << "refresh set to forced" << endl;
401               refresh = true;
402             }
403           }
404           else
405           {
406             refresh = true;
407           }
408
409           if ( refresh )
410           {
411             std::ofstream file(( tmpdir.path() + "/cookie").c_str());
412             if (!file) {
413               ZYPP_THROW (Exception( "Can't open " + tmpdir.path().asString() + "/cookie" ) );
414             }
415             file << url << endl;
416             file << newstatus.checksum() << endl;
417
418             file.close();
419           }
420           else
421             return;
422           // no error
423         }
424         else
425         {
426           ZYPP_THROW(RepoUnknownTypeException());
427         }
428
429         // ok we have the metadata, now exchange
430         // the contents
431         TmpDir oldmetadata;
432         filesystem::rename( rawpath, oldmetadata.path() );
433         // move the just downloaded there
434         filesystem::rename( tmpdir.path(), rawpath );
435         // we are done.
436         return;
437       }
438       catch ( const Exception &e )
439       {
440         ZYPP_CAUGHT(e);
441         ERR << "Trying another url..." << endl;
442       }
443     } // for every url
444     ERR << "No more urls..." << endl;
445     ZYPP_THROW(RepoException("Cant refresh metadata"));
446   }
447
448   ////////////////////////////////////////////////////////////////////////////
449
450   void RepoManager::cleanMetadata( const RepoInfo &info,
451                                    const ProgressData::ReceiverFnc & progress )
452   {
453     filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
454   }
455
456   void RepoManager::buildCache( const RepoInfo &info,
457                                 CacheBuildPolicy policy,
458                                 const ProgressData::ReceiverFnc & progressrcv )
459   {
460     ProgressData progress(100);
461     callback::SendReport<ProgressReport> report;
462     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
463     progress.name(str::form(_("Building repository '%s' cache"), info.alias().c_str()));
464     progress.toMin();
465
466     assert_alias(info);
467     Pathname rawpath = rawcache_path_for_repoinfo(_pimpl->options, info);
468
469     cache::CacheStore store(_pimpl->options.repoCachePath);
470
471     RepoStatus raw_metadata_status = metadataStatus(info);
472     if ( raw_metadata_status.empty() )
473     {
474       ZYPP_THROW(RepoMetadataException(info));
475     }
476
477     if ( store.isCached( info.alias() ) )
478     {
479       MIL << info.alias() << " is already cached." << endl;
480       data::RecordId id = store.lookupRepository(info.alias());
481       RepoStatus cache_status = store.repositoryStatus(id);
482
483       if ( cache_status.checksum() == raw_metadata_status.checksum() )
484       {
485         MIL << info.alias() << " cache is up to date with metadata." << endl;
486         if ( policy == BuildIfNeeded ) {
487           progress.toMax();
488           return;
489         }
490         else {
491           MIL << info.alias() << " cache rebuild is forced" << endl;
492         }
493       }
494       MIL << info.alias() << " cleaning cache..." << endl;
495       store.cleanRepository(id);
496     }
497
498     MIL << info.alias() << " building cache..." << endl;
499     data::RecordId id = store.lookupOrAppendRepository(info.alias());
500     // do we have type?
501     repo::RepoType repokind = info.type();
502
503     // if the type is unknown, try probing.
504     switch ( repokind.toEnum() )
505     {
506       case RepoType::NONE_e:
507         // unknown, probe the local metadata
508         repokind = probe(rawpath.asUrl());
509       break;
510       default:
511       break;
512     }
513
514     CombinedProgressData subprogrcv( progress, 100);
515
516     switch ( repokind.toEnum() )
517     {
518       case RepoType::RPMMD_e :
519       {
520         parser::yum::RepoParser parser(id, store, parser::yum::RepoParserOpts(), subprogrcv);
521         parser.parse(rawpath);
522           // no error
523       }
524       break;
525       case RepoType::YAST2_e :
526       {
527         parser::susetags::RepoParser parser(id, store, subprogrcv);
528         parser.parse(rawpath);
529         // no error
530       }
531       break;
532       case RepoType::RPMPLAINDIR_e :
533       {
534         InputStream is(rawpath + "cookie");
535         string buffer;
536         getline( is.stream(), buffer);
537         Url url(buffer);
538         parser::plaindir::RepoParser parser(id, store, subprogrcv);
539         parser.parse(url.getPathName());
540       }
541       break;
542       default:
543         ZYPP_THROW(RepoUnknownTypeException());
544     }
545
546     // update timestamp and checksum
547     store.updateRepositoryStatus(id, raw_metadata_status);
548
549     MIL << "Commit cache.." << endl;
550     store.commit();
551     progress.toMax();
552   }
553
554   ////////////////////////////////////////////////////////////////////////////
555
556   repo::RepoType RepoManager::probe( const Url &url ) const
557   {
558     if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName() ).isDir() )
559     {
560       // Handle non existing local directory in advance, as
561       // MediaSetAccess does not support it.
562       return repo::RepoType::NONE;
563     }
564
565     MediaSetAccess access(url);
566     if ( access.doesFileExist("/repodata/repomd.xml") )
567       return repo::RepoType::RPMMD;
568     if ( access.doesFileExist("/content") )
569       return repo::RepoType::YAST2;
570
571     // if it is a local url of type dir
572     if ( (! media::MediaManager::downloads(url)) && ( url.getScheme() == "dir" ) )
573     {
574       Pathname path = Pathname(url.getPathName());
575       if ( PathInfo(path).isDir() )
576       {
577         // allow empty dirs for now
578         return repo::RepoType::RPMPLAINDIR;
579       }
580     }
581
582     return repo::RepoType::NONE;
583   }
584
585   ////////////////////////////////////////////////////////////////////////////
586
587   void RepoManager::cleanCache( const RepoInfo &info,
588                                 const ProgressData::ReceiverFnc & progressrcv )
589   {
590     ProgressData progress(100);
591     callback::SendReport<ProgressReport> report;
592     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
593     progress.name(str::form(_("Cleaning repository '%s' cache"), info.alias().c_str()));
594     progress.toMin();
595
596     cache::CacheStore store(_pimpl->options.repoCachePath);
597
598     data::RecordId id = store.lookupRepository(info.alias());
599     store.cleanRepository(id);
600     store.commit();
601   }
602
603   ////////////////////////////////////////////////////////////////////////////
604
605   bool RepoManager::isCached( const RepoInfo &info ) const
606   {
607     cache::CacheStore store(_pimpl->options.repoCachePath);
608     return store.isCached(info.alias());
609   }
610
611   RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
612   {
613     cache::CacheStore store(_pimpl->options.repoCachePath);
614     data::RecordId id = store.lookupRepository(info.alias());
615     RepoStatus cache_status = store.repositoryStatus(id);
616     return cache_status;
617   }
618
619   Repository RepoManager::createFromCache( const RepoInfo &info,
620                                            const ProgressData::ReceiverFnc & progressrcv )
621   {
622     callback::SendReport<ProgressReport> report;
623     ProgressData progress;
624     progress.sendTo(ProgressReportAdaptor( progressrcv, report ));
625     progress.sendTo( progressrcv );
626     progress.name(str::form(_("Reading repository '%s' cache"), info.alias().c_str()));
627     progress.toMin();
628
629     cache::CacheStore store(_pimpl->options.repoCachePath);
630
631     if ( ! store.isCached( info.alias() ) )
632       ZYPP_THROW(RepoNotCachedException());
633
634     MIL << "Repository " << info.alias() << " is cached" << endl;
635
636     data::RecordId id = store.lookupRepository(info.alias());
637
638     repo::cached::RepoOptions opts( info, _pimpl->options.repoCachePath, id );
639     opts.readingResolvablesProgress = progressrcv;
640     repo::cached::RepoImpl::Ptr repoimpl =
641         new repo::cached::RepoImpl( opts );
642
643     repoimpl->resolvables();
644     // read the resolvables from cache
645     return Repository(repoimpl);
646   }
647
648   ////////////////////////////////////////////////////////////////////////////
649
650   /**
651    * Generate a non existing filename in a directory, using a base
652    * name. For example if a directory contains 3 files
653    *
654    * |-- bar
655    * |-- foo
656    * `-- moo
657    *
658    * If you try to generate a unique filename for this directory,
659    * based on "ruu" you will get "ruu", but if you use the base
660    * "foo" you will get "foo_1"
661    *
662    * \param dir Directory where the file needs to be unique
663    * \param basefilename string to base the filename on.
664    */
665   static Pathname generate_non_existing_name( const Pathname &dir,
666                                               const std::string &basefilename )
667   {
668     string final_filename = basefilename;
669     int counter = 1;
670     while ( PathInfo(dir + final_filename).isExist() )
671     {
672       final_filename = basefilename + "_" + str::numstring(counter);
673       counter++;
674     }
675     return dir + Pathname(final_filename);
676   }
677
678   ////////////////////////////////////////////////////////////////////////////
679
680   /**
681    * \short Generate a related filename from a repo info
682    *
683    * From a repo info, it will try to use the alias as a filename
684    * escaping it if necessary. Other fallbacks can be added to
685    * this function in case there is no way to use the alias
686    */
687   static std::string generate_filename( const RepoInfo &info )
688   {
689     std::string fnd="/";
690     std::string rep="_";
691     std::string filename = info.alias();
692     // replace slashes with underscores
693     size_t pos = filename.find(fnd);
694     while(pos!=string::npos)
695     {
696       filename.replace(pos,fnd.length(),rep);
697       pos = filename.find(fnd,pos+rep.length());
698     }
699     filename = Pathname(filename).extend(".repo").asString();
700     MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
701     return filename;
702   }
703
704
705   ////////////////////////////////////////////////////////////////////////////
706
707   void RepoManager::addRepository( const RepoInfo &info,
708                                    const ProgressData::ReceiverFnc & progressrcv )
709   {
710     assert_alias(info);
711
712     ProgressData progress(100);
713     callback::SendReport<ProgressReport> report;
714     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
715     progress.name(str::form(_("Adding repository '%s'"), info.alias().c_str()));
716     progress.toMin();
717
718     std::list<RepoInfo> repos = knownRepositories();
719     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
720           it != repos.end();
721           ++it )
722     {
723       if ( info.alias() == (*it).alias() )
724         ZYPP_THROW(RepoAlreadyExistsException(info.alias()));
725     }
726
727     progress.set(50);
728
729     // assert the directory exists
730     filesystem::assert_dir(_pimpl->options.knownReposPath);
731
732     Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath,
733                                                     generate_filename(info));
734     // now we have a filename that does not exists
735     MIL << "Saving repo in " << repofile << endl;
736
737     std::ofstream file(repofile.c_str());
738     if (!file) {
739       ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
740     }
741
742     info.dumpRepoOn(file);
743     progress.toMax();
744     MIL << "done" << endl;
745   }
746
747   void RepoManager::addRepositories( const Url &url,
748                                      const ProgressData::ReceiverFnc & progressrcv )
749   {
750     std::list<RepoInfo> knownrepos = knownRepositories();
751     std::list<RepoInfo> repos = readRepoFile(url);
752     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
753           it != repos.end();
754           ++it )
755     {
756       // look if the alias is in the known repos.
757       for ( std::list<RepoInfo>::const_iterator kit = knownrepos.begin();
758           kit != knownrepos.end();
759           ++kit )
760       {
761         if ( (*it).alias() == (*kit).alias() )
762         {
763           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
764           ZYPP_THROW(RepoAlreadyExistsException((*it).alias()));
765         }
766       }
767     }
768
769     string filename = Pathname(url.getPathName()).basename();
770
771     if ( filename == Pathname() )
772       ZYPP_THROW(RepoException("Invalid repo file name at " + url.asString() ));
773
774     // assert the directory exists
775     filesystem::assert_dir(_pimpl->options.knownReposPath);
776
777     Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath, filename);
778     // now we have a filename that does not exists
779     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
780
781     std::ofstream file(repofile.c_str());
782     if (!file) {
783       ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
784     }
785
786     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
787           it != repos.end();
788           ++it )
789     {
790       MIL << "Saving " << (*it).alias() << endl;
791       (*it).dumpRepoOn(file);
792     }
793     MIL << "done" << endl;
794   }
795
796   ////////////////////////////////////////////////////////////////////////////
797
798   void RepoManager::removeRepository( const RepoInfo & info,
799                                       const ProgressData::ReceiverFnc & progressrcv)
800   {
801     MIL << "Going to delete repo " << info.alias() << endl;
802
803     std::list<RepoInfo> repos = knownRepositories();
804     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
805           it != repos.end();
806           ++it )
807     {
808       // they can be the same only if the provided is empty, that means
809       // the provided repo has no alias
810       // then skip
811       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
812         continue;
813
814       // TODO match by url
815
816       // we have a matcing repository, now we need to know
817       // where it does come from.
818       RepoInfo todelete = *it;
819       if (todelete.filepath().empty())
820       {
821         ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
822       }
823       else
824       {
825         // figure how many repos are there in the file:
826         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
827         if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
828         {
829           // easy, only this one, just delete the file
830           if ( filesystem::unlink(todelete.filepath()) != 0 )
831           {
832             ZYPP_THROW(RepoException("Can't delete " + todelete.filepath().asString()));
833           }
834           MIL << todelete.alias() << " sucessfully deleted." << endl;
835           return;
836         }
837         else
838         {
839           // there are more repos in the same file
840           // write them back except the deleted one.
841           //TmpFile tmp;
842           //std::ofstream file(tmp.path().c_str());
843
844           // assert the directory exists
845           filesystem::assert_dir(todelete.filepath().dirname());
846
847           std::ofstream file(todelete.filepath().c_str());
848           if (!file) {
849             //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
850             ZYPP_THROW (Exception( "Can't open " + todelete.filepath().asString() ) );
851           }
852           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
853                 fit != filerepos.end();
854                 ++fit )
855           {
856             if ( (*fit).alias() != todelete.alias() )
857               (*fit).dumpRepoOn(file);
858           }
859
860           cache::CacheStore store(_pimpl->options.repoCachePath);
861
862           if ( store.isCached( todelete.alias() ) ) {
863             MIL << "repository was cached. cleaning cache" << endl;
864             store.cleanRepository(todelete.alias());
865           }
866
867           MIL << todelete.alias() << " sucessfully deleted." << endl;
868           return;
869         }
870       } // else filepath is empty
871
872     }
873     // should not be reached on a sucess workflow
874     ZYPP_THROW(RepoNotFoundException(info));
875   }
876
877   ////////////////////////////////////////////////////////////////////////////
878
879   void RepoManager::modifyRepository( const std::string &alias,
880                                       const RepoInfo & newinfo,
881                                       const ProgressData::ReceiverFnc & progressrcv )
882   {
883     RepoInfo toedit = getRepositoryInfo(alias);
884
885     if (toedit.filepath().empty())
886     {
887       ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
888     }
889     else
890     {
891       // figure how many repos are there in the file:
892       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
893
894       // there are more repos in the same file
895       // write them back except the deleted one.
896       //TmpFile tmp;
897       //std::ofstream file(tmp.path().c_str());
898
899       // assert the directory exists
900       filesystem::assert_dir(toedit.filepath().dirname());
901
902       std::ofstream file(toedit.filepath().c_str());
903       if (!file) {
904         //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
905         ZYPP_THROW (Exception( "Can't open " + toedit.filepath().asString() ) );
906       }
907       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
908             fit != filerepos.end();
909             ++fit )
910       {
911           // if the alias is different, dump the original
912           // if it is the same, dump the provided one
913           if ( (*fit).alias() != toedit.alias() )
914             (*fit).dumpRepoOn(file);
915           else
916             newinfo.dumpRepoOn(file);
917       }
918     }
919   }
920
921   ////////////////////////////////////////////////////////////////////////////
922
923   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
924                                            const ProgressData::ReceiverFnc & progressrcv )
925   {
926     std::list<RepoInfo> repos = knownRepositories();
927     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
928           it != repos.end();
929           ++it )
930     {
931       if ( (*it).alias() == alias )
932         return *it;
933     }
934     RepoInfo info;
935     info.setAlias(info.alias());
936     ZYPP_THROW(RepoNotFoundException(info));
937   }
938
939   ////////////////////////////////////////////////////////////////////////////
940
941   RepoInfo RepoManager::getRepositoryInfo( const Url & url,
942                                            const url::ViewOption & urlview,
943                                            const ProgressData::ReceiverFnc & progressrcv )
944   {
945     std::list<RepoInfo> repos = knownRepositories();
946     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
947           it != repos.end();
948           ++it )
949     {
950       for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
951           urlit != (*it).baseUrlsEnd();
952           ++urlit)
953       {
954         if ((*urlit).asString(urlview) == url.asString(urlview))
955           return *it;
956       }
957     }
958     RepoInfo info;
959     info.setAlias(info.alias());
960     info.setBaseUrl(url);
961     ZYPP_THROW(RepoNotFoundException(info));
962   }
963
964   ////////////////////////////////////////////////////////////////////////////
965
966   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
967   {
968     return str << *obj._pimpl;
969   }
970
971   /////////////////////////////////////////////////////////////////
972 } // namespace zypp
973 ///////////////////////////////////////////////////////////////////