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