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