- Pass the media set to the Downloaders instead of having one in the object
[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
313   void RepoManager::refreshMetadata( const RepoInfo &info,
314                                      RawMetadataRefreshPolicy policy,
315                                      const ProgressData::ReceiverFnc & progress )
316   {
317     assert_alias(info);
318     assert_urls(info);
319
320     RepoStatus oldstatus;
321     RepoStatus newstatus;
322     // try urls one by one
323     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
324     {
325       try
326       {
327         Url url(*it);
328         filesystem::TmpDir tmpdir;
329
330         repo::RepoType repokind = info.type();
331
332         // if the type is unknown, try probing.
333         switch ( repokind.toEnum() )
334         {
335           case RepoType::NONE_e:
336             // unknown, probe it
337             repokind = probe(*it);
338           break;
339           default:
340           break;
341         }
342
343         Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
344         oldstatus = rawMetadataStatus(info);
345
346         switch ( repokind.toEnum() )
347         {
348           case RepoType::RPMMD_e :
349           {
350             MediaSetAccess media(url);
351             yum::Downloader downloader(info.path());
352
353             RepoStatus newstatus = downloader.status(media);
354             bool refresh = false;
355             if ( oldstatus.checksum() == newstatus.checksum() )
356             {
357               MIL << "repo has not changed" << endl;
358               if ( policy == RefreshForced )
359               {
360                 MIL << "refresh set to forced" << endl;
361                 refresh = true;
362               }
363             }
364             else
365             {
366               refresh = true;
367             }
368
369             if ( refresh )
370               downloader.download( media, tmpdir.path());
371             else
372               return;
373             // no error
374           }
375           break;
376           case RepoType::YAST2_e :
377           {
378             MediaSetAccess media(url);
379             susetags::Downloader downloader(info.path());
380
381             RepoStatus newstatus = downloader.status(media);
382             bool refresh = false;
383             if ( oldstatus.checksum() == newstatus.checksum() )
384             {
385               MIL << "repo has not changed" << endl;
386               if ( policy == RefreshForced )
387               {
388                 MIL << "refresh set to forced" << endl;
389                 refresh = true;
390               }
391             }
392             else
393             {
394               refresh = true;
395             }
396
397             if ( refresh )
398               downloader.download(media, tmpdir.path());
399             else
400               return;
401             // no error
402           }
403           break;
404           
405           case RepoType::RPMPLAINDIR_e :
406           {
407             RepoStatus newstatus = parser::plaindir::dirStatus(url.getPathName());
408             bool refresh = false;
409             if ( oldstatus.checksum() == newstatus.checksum() )
410             {
411               MIL << "repo has not changed" << endl;
412               if ( policy == RefreshForced )
413               {
414                 MIL << "refresh set to forced" << endl;
415                 refresh = true;
416               }
417             }
418             else
419             {
420               refresh = true;
421             }
422
423             if ( refresh )
424             {
425               std::ofstream file(( tmpdir.path() + "/cookie").c_str());
426               if (!file) {
427                 ZYPP_THROW (Exception( "Can't open " + tmpdir.path().asString() + "/cookie" ) );
428               }
429               file << url << endl;
430               file << newstatus.checksum() << endl;
431
432               file.close();
433             }
434             else
435               return;
436             // no error
437           }
438           break;
439           default:
440             ZYPP_THROW(RepoUnknownTypeException());
441         }
442
443         // ok we have the metadata, now exchange
444         // the contents
445         TmpDir oldmetadata;
446         filesystem::assert_dir(rawpath);
447         filesystem::rename( rawpath, oldmetadata.path() );
448         // move the just downloaded there
449         filesystem::rename( tmpdir.path(), rawpath );
450         // we are done.
451         return;
452       }
453       catch ( const Exception &e )
454       {
455         ZYPP_CAUGHT(e);
456         ERR << "Trying another url..." << endl;
457       }
458     } // for every url
459     ERR << "No more urls..." << endl;
460     ZYPP_THROW(RepoException("Cant refresh metadata"));
461   }
462
463   ////////////////////////////////////////////////////////////////////////////
464
465   void RepoManager::cleanMetadata( const RepoInfo &info,
466                                    const ProgressData::ReceiverFnc & progress )
467   {
468     filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
469   }
470
471   void RepoManager::buildCache( const RepoInfo &info,
472                                 CacheBuildPolicy policy,
473                                 const ProgressData::ReceiverFnc & progressrcv )
474   {
475     ProgressData progress(100);
476     callback::SendReport<ProgressReport> report;
477     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
478     progress.name(str::form(_("Building repository '%s' cache"), info.alias().c_str()));
479     progress.toMin();
480     
481     assert_alias(info);
482     Pathname rawpath = rawcache_path_for_repoinfo(_pimpl->options, info);
483
484     cache::CacheStore store(_pimpl->options.repoCachePath);
485
486     RepoStatus raw_metadata_status = rawMetadataStatus(info);
487     if ( raw_metadata_status.empty() )
488     {
489       ZYPP_THROW(RepoMetadataException(info));
490     }
491
492     if ( store.isCached( info.alias() ) )
493     {
494       MIL << info.alias() << " is already cached." << endl;
495       data::RecordId id = store.lookupRepository(info.alias());
496       RepoStatus cache_status = store.repositoryStatus(id);
497
498       if ( cache_status.checksum() == raw_metadata_status.checksum() )
499       {
500         MIL << info.alias() << " cache is up to date with metadata." << endl;
501         if ( policy == BuildIfNeeded ) {
502           progress.toMax();
503           return;
504         }
505         else {
506           MIL << info.alias() << " cache rebuild is forced" << endl;
507         }
508       }
509       MIL << info.alias() << " cleaning cache..." << endl;
510       store.cleanRepository(id);
511     }
512
513     MIL << info.alias() << " building cache..." << endl;
514     data::RecordId id = store.lookupOrAppendRepository(info.alias());
515     // do we have type?
516     repo::RepoType repokind = info.type();
517
518     // if the type is unknown, try probing.
519     switch ( repokind.toEnum() )
520     {
521       case RepoType::NONE_e:
522         // unknown, probe the local metadata
523         repokind = probe(rawpath.asUrl());
524       break;
525       default:
526       break;
527     }
528
529     CombinedProgressData subprogrcv( progress, 100);
530     
531     switch ( repokind.toEnum() )
532     {
533       case RepoType::RPMMD_e :
534       {
535         parser::yum::RepoParser parser(id, store, parser::yum::RepoParserOpts(), subprogrcv);
536         parser.parse(rawpath);
537           // no error
538       }
539       break;
540       case RepoType::YAST2_e :
541       {
542         parser::susetags::RepoParser parser(id, store, subprogrcv);
543         parser.parse(rawpath);
544         // no error
545       }
546       break;
547       case RepoType::RPMPLAINDIR_e :
548       {
549         InputStream is(rawpath + "cookie");
550         string buffer;
551         getline( is.stream(), buffer);
552         Url url(buffer);
553         parser::plaindir::RepoParser parser(id, store, subprogrcv);
554         parser.parse(url.getPathName());
555       }
556       break;
557       default:
558         ZYPP_THROW(RepoUnknownTypeException());
559     }
560
561     // update timestamp and checksum
562     store.updateRepositoryStatus(id, raw_metadata_status);
563
564     MIL << "Commit cache.." << endl;
565     store.commit();
566     progress.toMax();
567   }
568
569   ////////////////////////////////////////////////////////////////////////////
570
571   repo::RepoType RepoManager::probe( const Url &url )
572   {
573     if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName() ).isDir() )
574     {
575       // Handle non existing local directory in advance, as
576       // MediaSetAccess does not support it.
577       return repo::RepoType::NONE;
578     }
579
580     MediaSetAccess access(url);
581     if ( access.doesFileExist("/repodata/repomd.xml") )
582       return repo::RepoType::RPMMD;
583     if ( access.doesFileExist("/content") )
584       return repo::RepoType::YAST2;
585
586     // if it is a local url of type dir
587     if ( (! media::MediaManager::downloads(url)) && ( url.getScheme() == "dir" ) )
588     {
589       Pathname path = Pathname(url.getPathName());
590       if ( PathInfo(path).isDir() )
591       {
592         // allow empty dirs for now
593         return repo::RepoType::RPMPLAINDIR;
594       }
595     }
596
597     return repo::RepoType::NONE;
598   }
599
600   ////////////////////////////////////////////////////////////////////////////
601
602   void RepoManager::cleanCache( const RepoInfo &info,
603                                 const ProgressData::ReceiverFnc & progressrcv )
604   {
605     ProgressData progress(100);
606     callback::SendReport<ProgressReport> report;
607     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
608     progress.name(str::form(_("Cleaning repository '%s' cache"), info.alias().c_str()));
609     progress.toMin();
610     
611     cache::CacheStore store(_pimpl->options.repoCachePath);
612
613     data::RecordId id = store.lookupRepository(info.alias());
614     store.cleanRepository(id);
615     store.commit();
616   }
617
618   ////////////////////////////////////////////////////////////////////////////
619
620   bool RepoManager::isCached( const RepoInfo &info ) const
621   {
622     cache::CacheStore store(_pimpl->options.repoCachePath);
623     return store.isCached(info.alias());
624   }
625
626   Repository RepoManager::createFromCache( const RepoInfo &info,
627                                            const ProgressData::ReceiverFnc & progressrcv )
628   {
629     callback::SendReport<ProgressReport> report;
630     ProgressData progress;
631     progress.sendTo(ProgressReportAdaptor( progressrcv, report ));
632     progress.sendTo( progressrcv );
633     progress.name(str::form(_("Reading repository '%s' cache"), info.alias().c_str()));
634     progress.toMin();
635     
636     cache::CacheStore store(_pimpl->options.repoCachePath);
637
638     if ( ! store.isCached( info.alias() ) )
639       ZYPP_THROW(RepoNotCachedException());
640
641     MIL << "Repository " << info.alias() << " is cached" << endl;
642
643     data::RecordId id = store.lookupRepository(info.alias());
644     
645     repo::cached::RepoOptions opts( info, _pimpl->options.repoCachePath, id );
646     opts.readingResolvablesProgress = progressrcv;
647     repo::cached::RepoImpl::Ptr repoimpl =
648         new repo::cached::RepoImpl( opts );
649     
650     repoimpl->resolvables();
651     // read the resolvables from cache
652     return Repository(repoimpl);
653   }
654
655   ////////////////////////////////////////////////////////////////////////////
656
657   /**
658    * Generate a non existing filename in a directory, using a base
659    * name. For example if a directory contains 3 files
660    *
661    * |-- bar
662    * |-- foo
663    * `-- moo
664    *
665    * If you try to generate a unique filename for this directory,
666    * based on "ruu" you will get "ruu", but if you use the base
667    * "foo" you will get "foo_1"
668    *
669    * \param dir Directory where the file needs to be unique
670    * \param basefilename string to base the filename on.
671    */
672   static Pathname generate_non_existing_name( const Pathname &dir,
673                                               const std::string &basefilename )
674   {
675     string final_filename = basefilename;
676     int counter = 1;
677     while ( PathInfo(dir + final_filename).isExist() )
678     {
679       final_filename = basefilename + "_" + str::numstring(counter);
680       counter++;
681     }
682     return dir + Pathname(final_filename);
683   }
684
685   ////////////////////////////////////////////////////////////////////////////
686
687   /**
688    * \short Generate a related filename from a repo info
689    *
690    * From a repo info, it will try to use the alias as a filename
691    * escaping it if necessary. Other fallbacks can be added to
692    * this function in case there is no way to use the alias
693    */
694   static std::string generate_filename( const RepoInfo &info )
695   {
696     std::string fnd="/";
697     std::string rep="_";
698     std::string filename = info.alias();
699     // replace slashes with underscores
700     size_t pos = filename.find(fnd);
701     while(pos!=string::npos)
702     {
703       filename.replace(pos,fnd.length(),rep);
704       pos = filename.find(fnd,pos+rep.length());
705     }
706     filename = Pathname(filename).extend(".repo").asString();
707     MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
708     return filename;
709   }
710
711
712   ////////////////////////////////////////////////////////////////////////////
713
714   void RepoManager::addRepository( const RepoInfo &info,
715                                    const ProgressData::ReceiverFnc & progressrcv )
716   {
717     assert_alias(info);
718
719     ProgressData progress(100);
720     callback::SendReport<ProgressReport> report;
721     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
722     progress.name(str::form(_("Adding repository '%s'"), info.alias().c_str()));
723     progress.toMin();
724     
725     std::list<RepoInfo> repos = knownRepositories();
726     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
727           it != repos.end();
728           ++it )
729     {
730       if ( info.alias() == (*it).alias() )
731         ZYPP_THROW(RepoAlreadyExistsException(info.alias()));
732     }
733
734     progress.set(50);
735     
736     // assert the directory exists
737     filesystem::assert_dir(_pimpl->options.knownReposPath);
738     
739     Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath,
740                                                     generate_filename(info));
741     // now we have a filename that does not exists
742     MIL << "Saving repo in " << repofile << endl;
743
744     std::ofstream file(repofile.c_str());
745     if (!file) {
746       ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
747     }
748
749     info.dumpRepoOn(file);
750     progress.toMax();
751     MIL << "done" << endl;
752   }
753
754   void RepoManager::addRepositories( const Url &url,
755                                      const ProgressData::ReceiverFnc & progressrcv )
756   {
757     std::list<RepoInfo> knownrepos = knownRepositories();
758     std::list<RepoInfo> repos = readRepoFile(url);
759     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
760           it != repos.end();
761           ++it )
762     {
763       // look if the alias is in the known repos.
764       for ( std::list<RepoInfo>::const_iterator kit = knownrepos.begin();
765           kit != knownrepos.end();
766           ++kit )
767       {
768         if ( (*it).alias() == (*kit).alias() )
769         {
770           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
771           ZYPP_THROW(RepoAlreadyExistsException((*it).alias()));
772         }
773       }
774     }
775
776     string filename = Pathname(url.getPathName()).basename();
777
778     if ( filename == Pathname() )
779       ZYPP_THROW(RepoException("Invalid repo file name at " + url.asString() ));
780
781     // assert the directory exists
782     filesystem::assert_dir(_pimpl->options.knownReposPath);
783     
784     Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath, filename);
785     // now we have a filename that does not exists
786     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
787
788     std::ofstream file(repofile.c_str());
789     if (!file) {
790       ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
791     }
792
793     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
794           it != repos.end();
795           ++it )
796     {
797       MIL << "Saving " << (*it).alias() << endl;
798       (*it).dumpRepoOn(file);
799     }
800     MIL << "done" << endl;
801   }
802
803   ////////////////////////////////////////////////////////////////////////////
804
805   void RepoManager::removeRepository( const RepoInfo & info,
806                                       const ProgressData::ReceiverFnc & progressrcv)
807   {
808     MIL << "Going to delete repo " << info.alias() << endl;
809
810     std::list<RepoInfo> repos = knownRepositories();
811     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
812           it != repos.end();
813           ++it )
814     {
815       // they can be the same only if the provided is empty, that means
816       // the provided repo has no alias
817       // then skip
818       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
819         continue;
820
821       // TODO match by url
822
823       // we have a matcing repository, now we need to know
824       // where it does come from.
825       RepoInfo todelete = *it;
826       if (todelete.filepath().empty())
827       {
828         ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
829       }
830       else
831       {
832         // figure how many repos are there in the file:
833         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
834         if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
835         {
836           // easy, only this one, just delete the file
837           if ( filesystem::unlink(todelete.filepath()) != 0 )
838           {
839             ZYPP_THROW(RepoException("Can't delete " + todelete.filepath().asString()));
840           }
841           MIL << todelete.alias() << " sucessfully deleted." << endl;
842           return;
843         }
844         else
845         {
846           // there are more repos in the same file
847           // write them back except the deleted one.
848           //TmpFile tmp;
849           //std::ofstream file(tmp.path().c_str());
850           
851           // assert the directory exists
852           filesystem::assert_dir(todelete.filepath().dirname());
853           
854           std::ofstream file(todelete.filepath().c_str());
855           if (!file) {
856             //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
857             ZYPP_THROW (Exception( "Can't open " + todelete.filepath().asString() ) );
858           }
859           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
860                 fit != filerepos.end();
861                 ++fit )
862           {
863             if ( (*fit).alias() != todelete.alias() )
864               (*fit).dumpRepoOn(file);
865           }
866
867           cache::CacheStore store(_pimpl->options.repoCachePath);
868
869           if ( store.isCached( todelete.alias() ) ) {
870             MIL << "repository was cached. cleaning cache" << endl;
871             store.cleanRepository(todelete.alias());
872           }
873
874           MIL << todelete.alias() << " sucessfully deleted." << endl;
875           return;
876         }
877       } // else filepath is empty
878
879     }
880     // should not be reached on a sucess workflow
881     ZYPP_THROW(RepoNotFoundException(info));
882   }
883
884   ////////////////////////////////////////////////////////////////////////////
885
886   void RepoManager::modifyRepository( const std::string &alias,
887                                       const RepoInfo & newinfo,
888                                       const ProgressData::ReceiverFnc & progressrcv )
889   {
890     RepoInfo toedit = getRepositoryInfo(alias);
891
892     if (toedit.filepath().empty())
893     {
894       ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
895     }
896     else
897     {
898       // figure how many repos are there in the file:
899       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
900
901       // there are more repos in the same file
902       // write them back except the deleted one.
903       //TmpFile tmp;
904       //std::ofstream file(tmp.path().c_str());
905       
906       // assert the directory exists
907       filesystem::assert_dir(toedit.filepath().dirname());
908       
909       std::ofstream file(toedit.filepath().c_str());
910       if (!file) {
911         //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
912         ZYPP_THROW (Exception( "Can't open " + toedit.filepath().asString() ) );
913       }
914       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
915             fit != filerepos.end();
916             ++fit )
917       {
918           // if the alias is different, dump the original
919           // if it is the same, dump the provided one
920           if ( (*fit).alias() != toedit.alias() )
921             (*fit).dumpRepoOn(file);
922           else
923             newinfo.dumpRepoOn(file);
924       }
925     }
926   }
927
928   ////////////////////////////////////////////////////////////////////////////
929
930   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
931                                            const ProgressData::ReceiverFnc & progressrcv )
932   {
933     std::list<RepoInfo> repos = knownRepositories();
934     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
935           it != repos.end();
936           ++it )
937     {
938       if ( (*it).alias() == alias )
939         return *it;
940     }
941     RepoInfo info;
942     info.setAlias(info.alias());
943     ZYPP_THROW(RepoNotFoundException(info));
944   }
945
946   ////////////////////////////////////////////////////////////////////////////
947
948   RepoInfo RepoManager::getRepositoryInfo( const Url & url,
949                                            const url::ViewOption & urlview,
950                                            const ProgressData::ReceiverFnc & progressrcv )
951   {
952     std::list<RepoInfo> repos = knownRepositories();
953     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
954           it != repos.end();
955           ++it )
956     {
957       for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
958           urlit != (*it).baseUrlsEnd();
959           ++urlit)
960       {
961         if ((*urlit).asString(urlview) == url.asString(urlview))
962           return *it;
963       }
964     }
965     RepoInfo info;
966     info.setAlias(info.alias());
967     info.setBaseUrl(url);
968     ZYPP_THROW(RepoNotFoundException(info));
969   }
970
971   ////////////////////////////////////////////////////////////////////////////
972
973   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
974   {
975     return str << *obj._pimpl;
976   }
977
978   /////////////////////////////////////////////////////////////////
979 } // namespace zypp
980 ///////////////////////////////////////////////////////////////////