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