- only look for repositories in file ending ".repo" (bug #294779)
[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().repoCachePath();
61     repoRawCachePath = ZConfig::instance().repoMetadataPath();
62     knownReposPath   = ZConfig::instance().knownReposPath();
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     * \short Internal version of clean cache
104     *
105     * Takes an extra CacheStore reference, so we avoid internally
106     * having 2 CacheStores writing to the same database.
107     */
108   static void cleanCacheInternal( cache::CacheStore &store,
109                                   const RepoInfo &info,
110                                   const ProgressData::ReceiverFnc & progressrcv = ProgressData::ReceiverFnc() )
111   {
112     ProgressData progress;
113     callback::SendReport<ProgressReport> report;
114     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
115     progress.name(str::form(_("Cleaning repository '%s' cache"), info.name().c_str()));
116
117     if ( !store.isCached(info.alias()) )
118       return;
119    
120     MIL << info.alias() << " cleaning cache..." << endl;
121     data::RecordId id = store.lookupRepository(info.alias());
122     
123     CombinedProgressData subprogrcv(progress);
124     
125     store.cleanRepository(id, subprogrcv);
126   }
127   
128   ////////////////////////////////////////////////////////////////////////////
129   
130   /**
131    * Reads RepoInfo's from a repo file.
132    *
133    * \param file pathname of the file to read.
134    */
135   static std::list<RepoInfo> repositories_in_file( const Pathname & file )
136   {
137     MIL << "repo file: " << file << endl;
138     RepoCollector collector;
139     parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
140     return collector.repos;
141   }
142
143   ////////////////////////////////////////////////////////////////////////////
144
145   std::list<RepoInfo> readRepoFile(const Url & repo_file)
146    {
147      // no interface to download a specific file, using workaround:
148      //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
149      Url url(repo_file);
150      Pathname path(url.getPathName());
151      url.setPathName ("/");
152      MediaSetAccess access(url);
153      Pathname local = access.provideFile(path);
154
155      DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
156
157      return repositories_in_file(local);
158    }
159
160   ////////////////////////////////////////////////////////////////////////////
161
162   /**
163    * \short List of RepoInfo's from a directory
164    *
165    * Goes trough every file ending with ".repo" in a directory and adds all
166    * RepoInfo's contained in that file.
167    *
168    * \param dir pathname of the directory to read.
169    */
170   static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
171   {
172     MIL << "directory " << dir << endl;
173     list<RepoInfo> repos;
174     list<Pathname> entries;
175     if ( filesystem::readdir( entries, Pathname(dir), false ) != 0 )
176       ZYPP_THROW(Exception("failed to read directory"));
177
178     for ( list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
179     {
180       if (it->extension() == ".repo")
181       {
182         list<RepoInfo> tmp = repositories_in_file( *it );
183         repos.insert( repos.end(), tmp.begin(), tmp.end() );
184
185         //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
186         //MIL << "ok" << endl;
187       }
188     }
189     return repos;
190   }
191
192   ////////////////////////////////////////////////////////////////////////////
193
194   static void assert_alias( const RepoInfo &info )
195   {
196     if (info.alias().empty())
197         ZYPP_THROW(RepoNoAliasException());
198   }
199
200   ////////////////////////////////////////////////////////////////////////////
201
202   static void assert_urls( const RepoInfo &info )
203   {
204     if (info.baseUrlsEmpty())
205         ZYPP_THROW(RepoNoUrlException());
206   }
207
208   ////////////////////////////////////////////////////////////////////////////
209
210   /**
211    * \short Calculates the raw cache path for a repository
212    */
213   static Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
214   {
215     assert_alias(info);
216     return opt.repoRawCachePath + info.alias();
217   }
218
219   ///////////////////////////////////////////////////////////////////
220   //
221   //    CLASS NAME : RepoManager::Impl
222   //
223   ///////////////////////////////////////////////////////////////////
224
225   /**
226    * \short RepoManager implementation.
227    */
228   struct RepoManager::Impl
229   {
230     Impl( const RepoManagerOptions &opt )
231       : options(opt)
232     {
233
234     }
235
236     Impl()
237     {
238
239     }
240
241     RepoManagerOptions options;
242
243   public:
244     /** Offer default Impl. */
245     static shared_ptr<Impl> nullimpl()
246     {
247       static shared_ptr<Impl> _nullimpl( new Impl );
248       return _nullimpl;
249     }
250     
251   private:
252     friend Impl * rwcowClone<Impl>( const Impl * rhs );
253     /** clone for RWCOW_pointer */
254     Impl * clone() const
255     { return new Impl( *this ); }
256   };
257   ///////////////////////////////////////////////////////////////////
258
259   /** \relates RepoManager::Impl Stream output */
260   inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
261   {
262     return str << "RepoManager::Impl";
263   }
264
265   ///////////////////////////////////////////////////////////////////
266   //
267   //    CLASS NAME : RepoManager
268   //
269   ///////////////////////////////////////////////////////////////////
270
271   RepoManager::RepoManager( const RepoManagerOptions &opt )
272   : _pimpl( new Impl(opt) )
273   {}
274
275   ////////////////////////////////////////////////////////////////////////////
276
277   RepoManager::~RepoManager()
278   {}
279
280   ////////////////////////////////////////////////////////////////////////////
281
282   std::list<RepoInfo> RepoManager::knownRepositories() const
283   {
284     MIL << endl;
285
286     if ( PathInfo(_pimpl->options.knownReposPath).isExist() )
287     {
288       RepoInfoList repos = repositories_in_dir(_pimpl->options.knownReposPath);
289       for ( RepoInfoList::iterator it = repos.begin();
290             it != repos.end();
291             ++it )
292       {
293         // set the metadata path for the repo
294         Pathname metadata_path = rawcache_path_for_repoinfo(_pimpl->options, (*it));
295         (*it).setMetadataPath(metadata_path);
296       }
297       return repos;
298     }
299     else
300       return std::list<RepoInfo>();
301
302     MIL << endl;
303   }
304
305   ////////////////////////////////////////////////////////////////////////////
306
307   Pathname RepoManager::metadataPath( const RepoInfo &info ) const
308   {
309     return rawcache_path_for_repoinfo(_pimpl->options, info );
310   }
311
312   ////////////////////////////////////////////////////////////////////////////
313       
314   RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
315   {
316     Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
317     RepoType repokind = info.type();
318     RepoStatus status;
319
320     switch ( repokind.toEnum() )
321     {
322       case RepoType::NONE_e:
323       // unknown, probe the local metadata
324         repokind = probe(rawpath.asUrl());
325       break;
326       default:
327       break;
328     }
329
330     switch ( repokind.toEnum() )
331     {
332       case RepoType::RPMMD_e :
333       {
334         status = RepoStatus( rawpath + "/repodata/repomd.xml");
335       }
336       break;
337
338       case RepoType::YAST2_e :
339       {
340         // the order of RepoStatus && RepoStatus matters! (#304310)
341         status = RepoStatus( rawpath + "/content") && (RepoStatus( rawpath + "/media.1/media"));
342       }
343       break;
344
345       case RepoType::RPMPLAINDIR_e :
346       {
347         if ( PathInfo(Pathname(rawpath + "/cookie")).isExist() )
348           status = RepoStatus( rawpath + "/cookie");
349       }
350       break;
351
352       case RepoType::NONE_e :
353         // Return default RepoStatus in case of RepoType::NONE
354         // indicating it should be created?
355         // ZYPP_THROW(RepoUnknownTypeException());
356         break;
357     }
358     return status;
359   }
360
361   void RepoManager::touchIndexFile(const RepoInfo & info)
362   {
363     Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
364
365     RepoType repokind = info.type();
366     if ( repokind.toEnum() == RepoType::NONE_e )
367       // unknown, probe the local metadata
368       repokind = probe(rawpath.asUrl());
369     // if still unknown, just return
370     if (repokind == RepoType::NONE_e)
371       return;
372
373     Pathname p;
374     switch ( repokind.toEnum() )
375     {
376       case RepoType::RPMMD_e :
377         p = Pathname(rawpath + "/repodata/repomd.xml");
378         break;
379
380       case RepoType::YAST2_e :
381         p = Pathname(rawpath + "/content");
382         break;
383
384       case RepoType::RPMPLAINDIR_e :
385         p = Pathname(rawpath + "/cookie");
386         break;
387
388       case RepoType::NONE_e :
389       default:
390         break;
391     }
392
393     // touch the file, ignore error (they are logged anyway)
394     filesystem::touch(p);
395   }
396
397   bool RepoManager::checkIfToRefreshMetadata( const RepoInfo &info,
398                                               const Url &url,
399                                               RawMetadataRefreshPolicy policy )
400   {
401     assert_alias(info);
402
403     RepoStatus oldstatus;
404     RepoStatus newstatus;
405
406     try
407     {
408       MIL << "Going to try to check whether refresh is needed for " << url << endl;
409
410       repo::RepoType repokind = info.type();
411
412       // if the type is unknown, try probing.
413       switch ( repokind.toEnum() )
414       {
415         case RepoType::NONE_e:
416           // unknown, probe it
417           repokind = probe(url);
418         break;
419         default:
420         break;
421       }
422
423       Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
424       filesystem::assert_dir(rawpath);
425       oldstatus = metadataStatus(info);
426
427       // now we've got the old (cached) status, we can decide repo.refresh.delay
428       if (policy != RefreshForced)
429       {
430         // difference in seconds
431         double diff = difftime(
432           (Date::ValueType)Date::now(),
433           (Date::ValueType)oldstatus.timestamp()) / 60;
434
435         DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
436         DBG << "current time: " << (Date::ValueType)Date::now() << endl;
437         DBG << "last refresh = " << diff << " minutes ago" << endl;
438
439         if (diff < ZConfig::instance().repo_refresh_delay())
440         {
441           MIL << "Repository '" << info.alias()
442               << "' has been refreshed less than repo.refresh.delay ("
443               << ZConfig::instance().repo_refresh_delay()
444               << ") minutes ago. Advising to skip refresh" << endl;
445           return false;
446         }
447       }
448
449       // create temp dir as sibling of rawpath
450       filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( rawpath ) );
451
452       if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
453            ( repokind.toEnum() == RepoType::YAST2_e ) )
454       {
455         MediaSetAccess media(url);
456         shared_ptr<repo::Downloader> downloader_ptr;
457
458         if ( repokind.toEnum() == RepoType::RPMMD_e )
459           downloader_ptr.reset(new yum::Downloader(info.path()));
460         else
461           downloader_ptr.reset( new susetags::Downloader(info.path()));
462
463         RepoStatus newstatus = downloader_ptr->status(media);
464         bool refresh = false;
465         if ( oldstatus.checksum() == newstatus.checksum() )
466         {
467           MIL << "repo has not changed" << endl;
468           if ( policy == RefreshForced )
469           {
470             MIL << "refresh set to forced" << endl;
471             refresh = true;
472           }
473         }
474         else
475         {
476           MIL << "repo has changed, going to refresh" << endl;
477           refresh = true;
478         }
479
480         if (!refresh)
481           touchIndexFile(info);
482
483         return refresh;
484       }
485       else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
486       {
487         RepoStatus newstatus = parser::plaindir::dirStatus(url.getPathName());
488         bool refresh = false;
489         if ( oldstatus.checksum() == newstatus.checksum() )
490         {
491           MIL << "repo has not changed" << endl;
492           if ( policy == RefreshForced )
493           {
494             MIL << "refresh set to forced" << endl;
495             refresh = true;
496           }
497         }
498         else
499         {
500           MIL << "repo has changed, going to refresh" << endl;
501           refresh = true;
502         }
503
504         if (!refresh)
505           touchIndexFile(info);
506
507         return refresh;
508       }
509       else
510       {
511         ZYPP_THROW(RepoUnknownTypeException());
512       }
513     }
514     catch ( const Exception &e )
515     {
516       ZYPP_CAUGHT(e);
517       ERR << "refresh check failed for " << url << endl;
518       ZYPP_RETHROW(e);
519     }
520     
521     return true; // default
522   }
523
524   void RepoManager::refreshMetadata( const RepoInfo &info,
525                                      RawMetadataRefreshPolicy policy,
526                                      const ProgressData::ReceiverFnc & progress )
527   {
528     assert_alias(info);
529     assert_urls(info);
530
531     // we will throw this later if no URL checks out fine
532     RepoException rexception(_("Valid metadata not found at specified URL(s)"));
533
534     // try urls one by one
535     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
536     {
537       try
538       {
539         Url url(*it);
540
541         // check whether to refresh metadata
542         // if the check fails for this url, it throws, so another url will be checked
543         if (!checkIfToRefreshMetadata(info, url, policy))
544           return;
545
546         MIL << "Going to refresh metadata from " << url << endl;
547
548         repo::RepoType repokind = info.type();
549
550         // if the type is unknown, try probing.
551         switch ( repokind.toEnum() )
552         {
553           case RepoType::NONE_e:
554             // unknown, probe it
555             repokind = probe(*it);
556           break;
557           default:
558           break;
559         }
560
561         Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
562         filesystem::assert_dir(rawpath);
563
564         // create temp dir as sibling of rawpath
565         filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( rawpath ) );
566
567         if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
568              ( repokind.toEnum() == RepoType::YAST2_e ) )
569         {
570           MediaSetAccess media(url);
571           shared_ptr<repo::Downloader> downloader_ptr;
572
573           if ( repokind.toEnum() == RepoType::RPMMD_e )
574             downloader_ptr.reset(new yum::Downloader(info.path()));
575           else
576             downloader_ptr.reset( new susetags::Downloader(info.path()));
577
578           /**
579            * Given a downloader, sets the other repos raw metadata
580            * path as cache paths for the fetcher, so if another
581            * repo has the same file, it will not download it
582            * but copy it from the other repository
583            */
584           std::list<RepoInfo> repos = knownRepositories();
585           for ( std::list<RepoInfo>::const_iterator it = repos.begin();
586                 it != repos.end();
587                 ++it )
588           {
589             downloader_ptr->addCachePath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
590           }
591
592           downloader_ptr->download( media, tmpdir.path());
593         }
594         else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
595         {
596           RepoStatus newstatus = parser::plaindir::dirStatus(url.getPathName());
597
598           std::ofstream file(( tmpdir.path() + "/cookie").c_str());
599           if (!file) {
600             ZYPP_THROW (Exception( "Can't open " + tmpdir.path().asString() + "/cookie" ) );
601           }
602           file << url << endl;
603           file << newstatus.checksum() << endl;
604
605           file.close();
606         }
607         else
608         {
609           ZYPP_THROW(RepoUnknownTypeException());
610         }
611
612         // ok we have the metadata, now exchange
613         // the contents
614         TmpDir oldmetadata( TmpDir::makeSibling( rawpath ) );
615         filesystem::rename( rawpath, oldmetadata.path() );
616         // move the just downloaded there
617         filesystem::rename( tmpdir.path(), rawpath );
618         // we are done.
619         return;
620       }
621       catch ( const Exception &e )
622       {
623         ZYPP_CAUGHT(e);
624         ERR << "Trying another url..." << endl;
625         
626         // remember the exception caught for the *first URL*
627         // if all other URLs fail, the rexception will be thrown with the
628         // cause of the problem of the first URL remembered
629         if (it == info.baseUrlsBegin())
630           rexception.remember(e);
631       }
632     } // for every url
633     ERR << "No more urls..." << endl;
634     ZYPP_THROW(rexception);
635   }
636
637   ////////////////////////////////////////////////////////////////////////////
638
639   void RepoManager::cleanMetadata( const RepoInfo &info,
640                                    const ProgressData::ReceiverFnc & progressfnc )
641   {
642     ProgressData progress(100);
643     progress.sendTo(progressfnc);
644
645     filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
646     progress.toMax();
647   }
648
649   void RepoManager::buildCache( const RepoInfo &info,
650                                 CacheBuildPolicy policy,
651                                 const ProgressData::ReceiverFnc & progressrcv )
652   {
653     assert_alias(info);
654     Pathname rawpath = rawcache_path_for_repoinfo(_pimpl->options, info);
655
656     cache::CacheStore store(_pimpl->options.repoCachePath);
657
658     RepoStatus raw_metadata_status = metadataStatus(info);
659     if ( raw_metadata_status.empty() )
660     {
661       ZYPP_THROW(RepoMetadataException(info));
662     }
663
664     bool needs_cleaning = false;
665     if ( store.isCached( info.alias() ) )
666     {
667       MIL << info.alias() << " is already cached." << endl;
668       data::RecordId id = store.lookupRepository(info.alias());
669       RepoStatus cache_status = store.repositoryStatus(id);
670
671       if ( cache_status.checksum() == raw_metadata_status.checksum() )
672       {
673         MIL << info.alias() << " cache is up to date with metadata." << endl;
674         if ( policy == BuildIfNeeded ) {
675           return;
676         }
677         else {
678           MIL << info.alias() << " cache rebuild is forced" << endl;
679         }
680       }
681       
682       needs_cleaning = true;
683     }
684
685     ProgressData progress(100);
686     callback::SendReport<ProgressReport> report;
687     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
688     progress.name(str::form(_("Building repository '%s' cache"), info.name().c_str()));
689     progress.toMin();
690
691     if (needs_cleaning)
692       cleanCacheInternal( store, info);
693
694     MIL << info.alias() << " building cache..." << endl;
695     data::RecordId id = store.lookupOrAppendRepository(info.alias());
696     // do we have type?
697     repo::RepoType repokind = info.type();
698
699     // if the type is unknown, try probing.
700     switch ( repokind.toEnum() )
701     {
702       case RepoType::NONE_e:
703         // unknown, probe the local metadata
704         repokind = probe(rawpath.asUrl());
705       break;
706       default:
707       break;
708     }
709
710     
711     switch ( repokind.toEnum() )
712     {
713       case RepoType::RPMMD_e :
714       {
715         CombinedProgressData subprogrcv( progress, 100);
716         parser::yum::RepoParser parser(id, store, parser::yum::RepoParserOpts(), subprogrcv);
717         parser.parse(rawpath);
718           // no error
719       }
720       break;
721       case RepoType::YAST2_e :
722       {
723         CombinedProgressData subprogrcv( progress, 100);
724         parser::susetags::RepoParser parser(id, store, subprogrcv);
725         parser.parse(rawpath);
726         // no error
727       }
728       break;
729       case RepoType::RPMPLAINDIR_e :
730       {
731         CombinedProgressData subprogrcv( progress, 100);
732         InputStream is(rawpath + "cookie");
733         string buffer;
734         getline( is.stream(), buffer);
735         Url url(buffer);
736         parser::plaindir::RepoParser parser(id, store, subprogrcv);
737         parser.parse(url.getPathName());
738       }
739       break;
740       default:
741         ZYPP_THROW(RepoUnknownTypeException());
742     }
743
744     // update timestamp and checksum
745     store.updateRepositoryStatus(id, raw_metadata_status);
746
747     MIL << "Commit cache.." << endl;
748     store.commit();
749     //progress.toMax();
750   }
751
752   ////////////////////////////////////////////////////////////////////////////
753
754   repo::RepoType RepoManager::probe( const Url &url ) const
755   {
756     if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName() ).isDir() )
757     {
758       // Handle non existing local directory in advance, as
759       // MediaSetAccess does not support it.
760       return repo::RepoType::NONE;
761     }
762
763     try
764     {
765       MediaSetAccess access(url);
766       if ( access.doesFileExist("/repodata/repomd.xml") )
767         return repo::RepoType::RPMMD;
768       if ( access.doesFileExist("/content") )
769         return repo::RepoType::YAST2;
770   
771       // if it is a local url of type dir
772       if ( (! media::MediaManager::downloads(url)) && ( url.getScheme() == "dir" ) )
773       {
774         Pathname path = Pathname(url.getPathName());
775         if ( PathInfo(path).isDir() )
776         {
777           // allow empty dirs for now
778           return repo::RepoType::RPMPLAINDIR;
779         }
780       }
781     }
782     catch ( const media::MediaException &e )
783     {
784       ZYPP_CAUGHT(e);
785       RepoException enew("Error trying to read from " + url.asString());
786       enew.remember(e);
787       ZYPP_THROW(enew);
788     }
789     catch ( const Exception &e )
790     {
791       ZYPP_CAUGHT(e);
792       Exception enew("Unknown error reading from " + url.asString());
793       enew.remember(e);
794       ZYPP_THROW(enew);
795     }
796
797     return repo::RepoType::NONE;
798   }
799     
800   ////////////////////////////////////////////////////////////////////////////
801   
802   void RepoManager::cleanCache( const RepoInfo &info,
803                                 const ProgressData::ReceiverFnc & progressrcv )
804   {
805     cache::CacheStore store(_pimpl->options.repoCachePath);
806     cleanCacheInternal( store, info, progressrcv );
807     store.commit();
808   }
809
810   ////////////////////////////////////////////////////////////////////////////
811
812   bool RepoManager::isCached( const RepoInfo &info ) const
813   {
814     cache::CacheStore store(_pimpl->options.repoCachePath);
815     return store.isCached(info.alias());
816   }
817
818   RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
819   {
820     cache::CacheStore store(_pimpl->options.repoCachePath);
821     data::RecordId id = store.lookupRepository(info.alias());
822     RepoStatus cache_status = store.repositoryStatus(id);
823     return cache_status;
824   }
825
826   Repository RepoManager::createFromCache( const RepoInfo &info,
827                                            const ProgressData::ReceiverFnc & progressrcv )
828   {
829     callback::SendReport<ProgressReport> report;
830     ProgressData progress;
831     progress.sendTo(ProgressReportAdaptor( progressrcv, report ));
832     //progress.sendTo( progressrcv );
833     progress.name(str::form(_("Reading repository '%s' cache"), info.name().c_str()));
834     
835     cache::CacheStore store(_pimpl->options.repoCachePath);
836
837     if ( ! store.isCached( info.alias() ) )
838       ZYPP_THROW(RepoNotCachedException());
839
840     MIL << "Repository " << info.alias() << " is cached" << endl;
841
842     data::RecordId id = store.lookupRepository(info.alias());
843     
844     CombinedProgressData subprogrcv(progress);
845     
846     repo::cached::RepoOptions opts( info, _pimpl->options.repoCachePath, id );
847     opts.readingResolvablesProgress = subprogrcv;
848     repo::cached::RepoImpl::Ptr repoimpl =
849         new repo::cached::RepoImpl( opts );
850
851     repoimpl->resolvables();
852     // read the resolvables from cache
853     return Repository(repoimpl);
854   }
855
856   ////////////////////////////////////////////////////////////////////////////
857
858   /**
859    * Generate a non existing filename in a directory, using a base
860    * name. For example if a directory contains 3 files
861    *
862    * |-- bar
863    * |-- foo
864    * `-- moo
865    *
866    * If you try to generate a unique filename for this directory,
867    * based on "ruu" you will get "ruu", but if you use the base
868    * "foo" you will get "foo_1"
869    *
870    * \param dir Directory where the file needs to be unique
871    * \param basefilename string to base the filename on.
872    */
873   static Pathname generate_non_existing_name( const Pathname &dir,
874                                               const std::string &basefilename )
875   {
876     string final_filename = basefilename;
877     int counter = 1;
878     while ( PathInfo(dir + final_filename).isExist() )
879     {
880       final_filename = basefilename + "_" + str::numstring(counter);
881       counter++;
882     }
883     return dir + Pathname(final_filename);
884   }
885
886   ////////////////////////////////////////////////////////////////////////////
887
888   /**
889    * \short Generate a related filename from a repo info
890    *
891    * From a repo info, it will try to use the alias as a filename
892    * escaping it if necessary. Other fallbacks can be added to
893    * this function in case there is no way to use the alias
894    */
895   static std::string generate_filename( const RepoInfo &info )
896   {
897     std::string fnd="/";
898     std::string rep="_";
899     std::string filename = info.alias();
900     // replace slashes with underscores
901     size_t pos = filename.find(fnd);
902     while(pos!=string::npos)
903     {
904       filename.replace(pos,fnd.length(),rep);
905       pos = filename.find(fnd,pos+rep.length());
906     }
907     filename = Pathname(filename).extend(".repo").asString();
908     MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
909     return filename;
910   }
911
912
913   ////////////////////////////////////////////////////////////////////////////
914
915   void RepoManager::addRepository( const RepoInfo &info,
916                                    const ProgressData::ReceiverFnc & progressrcv )
917   {
918     assert_alias(info);
919
920     ProgressData progress(100);
921     callback::SendReport<ProgressReport> report;
922     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
923     progress.name(str::form(_("Adding repository '%s'"), info.name().c_str()));
924     progress.toMin();
925
926     std::list<RepoInfo> repos = knownRepositories();
927     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
928           it != repos.end();
929           ++it )
930     {
931       if ( info.alias() == (*it).alias() )
932         ZYPP_THROW(RepoAlreadyExistsException(info.alias()));
933     }
934
935     RepoInfo tosave = info;
936
937     // check the first url for now
938     if ( ZConfig::instance().repo_add_probe()
939         || ( tosave.type() == RepoType::NONE && tosave.enabled()) )
940     {
941       DBG << "unknown repository type, probing" << endl;
942
943       RepoType probedtype;
944       probedtype = probe(*tosave.baseUrlsBegin());
945       if ( tosave.baseUrlsSize() > 0 )
946       {
947         if ( probedtype == RepoType::NONE )
948           ZYPP_THROW(RepoUnknownTypeException());
949         else
950           tosave.setType(probedtype);
951       }
952     }
953
954     progress.set(50);
955
956     // assert the directory exists
957     filesystem::assert_dir(_pimpl->options.knownReposPath);
958
959     Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath,
960                                                     generate_filename(tosave));
961     // now we have a filename that does not exists
962     MIL << "Saving repo in " << repofile << endl;
963
964     std::ofstream file(repofile.c_str());
965     if (!file) {
966       ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
967     }
968
969     tosave.dumpRepoOn(file);
970     progress.toMax();
971     MIL << "done" << endl;
972   }
973
974   void RepoManager::addRepositories( const Url &url,
975                                      const ProgressData::ReceiverFnc & progressrcv )
976   {
977     std::list<RepoInfo> knownrepos = knownRepositories();
978     std::list<RepoInfo> repos = readRepoFile(url);
979     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
980           it != repos.end();
981           ++it )
982     {
983       // look if the alias is in the known repos.
984       for ( std::list<RepoInfo>::const_iterator kit = knownrepos.begin();
985           kit != knownrepos.end();
986           ++kit )
987       {
988         if ( (*it).alias() == (*kit).alias() )
989         {
990           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
991           ZYPP_THROW(RepoAlreadyExistsException((*it).alias()));
992         }
993       }
994     }
995
996     string filename = Pathname(url.getPathName()).basename();
997
998     if ( filename == Pathname() )
999       ZYPP_THROW(RepoException("Invalid repo file name at " + url.asString() ));
1000
1001     // assert the directory exists
1002     filesystem::assert_dir(_pimpl->options.knownReposPath);
1003
1004     Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath, filename);
1005     // now we have a filename that does not exists
1006     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1007
1008     std::ofstream file(repofile.c_str());
1009     if (!file) {
1010       ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
1011     }
1012
1013     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1014           it != repos.end();
1015           ++it )
1016     {
1017       MIL << "Saving " << (*it).alias() << endl;
1018       (*it).dumpRepoOn(file);
1019     }
1020     MIL << "done" << endl;
1021   }
1022
1023   ////////////////////////////////////////////////////////////////////////////
1024
1025   void RepoManager::removeRepository( const RepoInfo & info,
1026                                       const ProgressData::ReceiverFnc & progressrcv)
1027   {
1028     ProgressData progress;
1029     callback::SendReport<ProgressReport> report;
1030     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1031     progress.name(str::form(_("Removing repository '%s'"), info.name().c_str()));
1032     
1033     MIL << "Going to delete repo " << info.alias() << endl;
1034
1035     std::list<RepoInfo> repos = knownRepositories();
1036     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1037           it != repos.end();
1038           ++it )
1039     {
1040       // they can be the same only if the provided is empty, that means
1041       // the provided repo has no alias
1042       // then skip
1043       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1044         continue;
1045
1046       // TODO match by url
1047
1048       // we have a matcing repository, now we need to know
1049       // where it does come from.
1050       RepoInfo todelete = *it;
1051       if (todelete.filepath().empty())
1052       {
1053         ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
1054       }
1055       else
1056       {
1057         // figure how many repos are there in the file:
1058         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1059         if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1060         {
1061           // easy, only this one, just delete the file
1062           if ( filesystem::unlink(todelete.filepath()) != 0 )
1063           {
1064             ZYPP_THROW(RepoException("Can't delete " + todelete.filepath().asString()));
1065           }
1066           MIL << todelete.alias() << " sucessfully deleted." << endl;
1067         }
1068         else
1069         {
1070           // there are more repos in the same file
1071           // write them back except the deleted one.
1072           //TmpFile tmp;
1073           //std::ofstream file(tmp.path().c_str());
1074
1075           // assert the directory exists
1076           filesystem::assert_dir(todelete.filepath().dirname());
1077
1078           std::ofstream file(todelete.filepath().c_str());
1079           if (!file) {
1080             //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
1081             ZYPP_THROW (Exception( "Can't open " + todelete.filepath().asString() ) );
1082           }
1083           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1084                 fit != filerepos.end();
1085                 ++fit )
1086           {
1087             if ( (*fit).alias() != todelete.alias() )
1088               (*fit).dumpRepoOn(file);
1089           }
1090         }
1091
1092         CombinedProgressData subprogrcv(progress, 70);
1093         CombinedProgressData cleansubprogrcv(progress, 30);
1094         // now delete it from cache
1095         cleanCache( todelete, subprogrcv);
1096         // now delete metadata (#301037)
1097         cleanMetadata( todelete, cleansubprogrcv);
1098         MIL << todelete.alias() << " sucessfully deleted." << endl;
1099         return;
1100       } // else filepath is empty
1101
1102     }
1103     // should not be reached on a sucess workflow
1104     ZYPP_THROW(RepoNotFoundException(info));
1105   }
1106
1107   ////////////////////////////////////////////////////////////////////////////
1108
1109   void RepoManager::modifyRepository( const std::string &alias,
1110                                       const RepoInfo & newinfo,
1111                                       const ProgressData::ReceiverFnc & progressrcv )
1112   {
1113     RepoInfo toedit = getRepositoryInfo(alias);
1114
1115     if (toedit.filepath().empty())
1116     {
1117       ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
1118     }
1119     else
1120     {
1121       // figure how many repos are there in the file:
1122       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1123
1124       // there are more repos in the same file
1125       // write them back except the deleted one.
1126       //TmpFile tmp;
1127       //std::ofstream file(tmp.path().c_str());
1128
1129       // assert the directory exists
1130       filesystem::assert_dir(toedit.filepath().dirname());
1131
1132       std::ofstream file(toedit.filepath().c_str());
1133       if (!file) {
1134         //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
1135         ZYPP_THROW (Exception( "Can't open " + toedit.filepath().asString() ) );
1136       }
1137       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1138             fit != filerepos.end();
1139             ++fit )
1140       {
1141           // if the alias is different, dump the original
1142           // if it is the same, dump the provided one
1143           if ( (*fit).alias() != toedit.alias() )
1144             (*fit).dumpRepoOn(file);
1145           else
1146             newinfo.dumpRepoOn(file);
1147       }
1148     }
1149   }
1150
1151   ////////////////////////////////////////////////////////////////////////////
1152
1153   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1154                                            const ProgressData::ReceiverFnc & progressrcv )
1155   {
1156     std::list<RepoInfo> repos = knownRepositories();
1157     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1158           it != repos.end();
1159           ++it )
1160     {
1161       if ( (*it).alias() == alias )
1162         return *it;
1163     }
1164     RepoInfo info;
1165     info.setAlias(info.alias());
1166     ZYPP_THROW(RepoNotFoundException(info));
1167   }
1168
1169   ////////////////////////////////////////////////////////////////////////////
1170
1171   RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1172                                            const url::ViewOption & urlview,
1173                                            const ProgressData::ReceiverFnc & progressrcv )
1174   {
1175     std::list<RepoInfo> repos = knownRepositories();
1176     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1177           it != repos.end();
1178           ++it )
1179     {
1180       for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1181           urlit != (*it).baseUrlsEnd();
1182           ++urlit)
1183       {
1184         if ((*urlit).asString(urlview) == url.asString(urlview))
1185           return *it;
1186       }
1187     }
1188     RepoInfo info;
1189     info.setAlias(info.alias());
1190     info.setBaseUrl(url);
1191     ZYPP_THROW(RepoNotFoundException(info));
1192   }
1193
1194   ////////////////////////////////////////////////////////////////////////////
1195
1196   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
1197   {
1198     return str << *obj._pimpl;
1199   }
1200
1201   /////////////////////////////////////////////////////////////////
1202 } // namespace zypp
1203 ///////////////////////////////////////////////////////////////////