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