changes
[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() || ( tosave.type() == RepoType::NONE ) )
929     {
930       RepoType probedtype;
931       probedtype = probe(*tosave.baseUrlsBegin());
932       if ( tosave.baseUrlsSize() > 0 )
933       {
934         if ( probedtype == RepoType::NONE )
935           ZYPP_THROW(RepoUnknownTypeException());
936         else
937           tosave.setType(probedtype);
938       }
939     }
940     
941     progress.set(50);
942
943     // assert the directory exists
944     filesystem::assert_dir(_pimpl->options.knownReposPath);
945
946     Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath,
947                                                     generate_filename(tosave));
948     // now we have a filename that does not exists
949     MIL << "Saving repo in " << repofile << endl;
950
951     std::ofstream file(repofile.c_str());
952     if (!file) {
953       ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
954     }
955
956     tosave.dumpRepoOn(file);
957     progress.toMax();
958     MIL << "done" << endl;
959   }
960
961   void RepoManager::addRepositories( const Url &url,
962                                      const ProgressData::ReceiverFnc & progressrcv )
963   {
964     std::list<RepoInfo> knownrepos = knownRepositories();
965     std::list<RepoInfo> repos = readRepoFile(url);
966     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
967           it != repos.end();
968           ++it )
969     {
970       // look if the alias is in the known repos.
971       for ( std::list<RepoInfo>::const_iterator kit = knownrepos.begin();
972           kit != knownrepos.end();
973           ++kit )
974       {
975         if ( (*it).alias() == (*kit).alias() )
976         {
977           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
978           ZYPP_THROW(RepoAlreadyExistsException((*it).alias()));
979         }
980       }
981     }
982
983     string filename = Pathname(url.getPathName()).basename();
984
985     if ( filename == Pathname() )
986       ZYPP_THROW(RepoException("Invalid repo file name at " + url.asString() ));
987
988     // assert the directory exists
989     filesystem::assert_dir(_pimpl->options.knownReposPath);
990
991     Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath, filename);
992     // now we have a filename that does not exists
993     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
994
995     std::ofstream file(repofile.c_str());
996     if (!file) {
997       ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
998     }
999
1000     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1001           it != repos.end();
1002           ++it )
1003     {
1004       MIL << "Saving " << (*it).alias() << endl;
1005       (*it).dumpRepoOn(file);
1006     }
1007     MIL << "done" << endl;
1008   }
1009
1010   ////////////////////////////////////////////////////////////////////////////
1011
1012   void RepoManager::removeRepository( const RepoInfo & info,
1013                                       const ProgressData::ReceiverFnc & progressrcv)
1014   {
1015     ProgressData progress;
1016     callback::SendReport<ProgressReport> report;
1017     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1018     progress.name(str::form(_("Removing repository '%s'"), info.name().c_str()));
1019     
1020     MIL << "Going to delete repo " << info.alias() << endl;
1021
1022     std::list<RepoInfo> repos = knownRepositories();
1023     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1024           it != repos.end();
1025           ++it )
1026     {
1027       // they can be the same only if the provided is empty, that means
1028       // the provided repo has no alias
1029       // then skip
1030       if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1031         continue;
1032
1033       // TODO match by url
1034
1035       // we have a matcing repository, now we need to know
1036       // where it does come from.
1037       RepoInfo todelete = *it;
1038       if (todelete.filepath().empty())
1039       {
1040         ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
1041       }
1042       else
1043       {
1044         // figure how many repos are there in the file:
1045         std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1046         if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1047         {
1048           // easy, only this one, just delete the file
1049           if ( filesystem::unlink(todelete.filepath()) != 0 )
1050           {
1051             ZYPP_THROW(RepoException("Can't delete " + todelete.filepath().asString()));
1052           }
1053           MIL << todelete.alias() << " sucessfully deleted." << endl;
1054         }
1055         else
1056         {
1057           // there are more repos in the same file
1058           // write them back except the deleted one.
1059           //TmpFile tmp;
1060           //std::ofstream file(tmp.path().c_str());
1061
1062           // assert the directory exists
1063           filesystem::assert_dir(todelete.filepath().dirname());
1064
1065           std::ofstream file(todelete.filepath().c_str());
1066           if (!file) {
1067             //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
1068             ZYPP_THROW (Exception( "Can't open " + todelete.filepath().asString() ) );
1069           }
1070           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1071                 fit != filerepos.end();
1072                 ++fit )
1073           {
1074             if ( (*fit).alias() != todelete.alias() )
1075               (*fit).dumpRepoOn(file);
1076           }
1077         }
1078
1079         CombinedProgressData subprogrcv(progress, 70);
1080         CombinedProgressData cleansubprogrcv(progress, 30);
1081         // now delete it from cache
1082         cleanCache( todelete, subprogrcv);
1083         // now delete metadata (#301037)
1084         cleanMetadata( todelete, cleansubprogrcv);
1085         MIL << todelete.alias() << " sucessfully deleted." << endl;
1086         return;
1087       } // else filepath is empty
1088
1089     }
1090     // should not be reached on a sucess workflow
1091     ZYPP_THROW(RepoNotFoundException(info));
1092   }
1093
1094   ////////////////////////////////////////////////////////////////////////////
1095
1096   void RepoManager::modifyRepository( const std::string &alias,
1097                                       const RepoInfo & newinfo,
1098                                       const ProgressData::ReceiverFnc & progressrcv )
1099   {
1100     RepoInfo toedit = getRepositoryInfo(alias);
1101
1102     if (toedit.filepath().empty())
1103     {
1104       ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
1105     }
1106     else
1107     {
1108       // figure how many repos are there in the file:
1109       std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1110
1111       // there are more repos in the same file
1112       // write them back except the deleted one.
1113       //TmpFile tmp;
1114       //std::ofstream file(tmp.path().c_str());
1115
1116       // assert the directory exists
1117       filesystem::assert_dir(toedit.filepath().dirname());
1118
1119       std::ofstream file(toedit.filepath().c_str());
1120       if (!file) {
1121         //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
1122         ZYPP_THROW (Exception( "Can't open " + toedit.filepath().asString() ) );
1123       }
1124       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1125             fit != filerepos.end();
1126             ++fit )
1127       {
1128           // if the alias is different, dump the original
1129           // if it is the same, dump the provided one
1130           if ( (*fit).alias() != toedit.alias() )
1131             (*fit).dumpRepoOn(file);
1132           else
1133             newinfo.dumpRepoOn(file);
1134       }
1135     }
1136   }
1137
1138   ////////////////////////////////////////////////////////////////////////////
1139
1140   RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1141                                            const ProgressData::ReceiverFnc & progressrcv )
1142   {
1143     std::list<RepoInfo> repos = knownRepositories();
1144     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1145           it != repos.end();
1146           ++it )
1147     {
1148       if ( (*it).alias() == alias )
1149         return *it;
1150     }
1151     RepoInfo info;
1152     info.setAlias(info.alias());
1153     ZYPP_THROW(RepoNotFoundException(info));
1154   }
1155
1156   ////////////////////////////////////////////////////////////////////////////
1157
1158   RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1159                                            const url::ViewOption & urlview,
1160                                            const ProgressData::ReceiverFnc & progressrcv )
1161   {
1162     std::list<RepoInfo> repos = knownRepositories();
1163     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1164           it != repos.end();
1165           ++it )
1166     {
1167       for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1168           urlit != (*it).baseUrlsEnd();
1169           ++urlit)
1170       {
1171         if ((*urlit).asString(urlview) == url.asString(urlview))
1172           return *it;
1173       }
1174     }
1175     RepoInfo info;
1176     info.setAlias(info.alias());
1177     info.setBaseUrl(url);
1178     ZYPP_THROW(RepoNotFoundException(info));
1179   }
1180
1181   ////////////////////////////////////////////////////////////////////////////
1182
1183   std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
1184   {
1185     return str << *obj._pimpl;
1186   }
1187
1188   /////////////////////////////////////////////////////////////////
1189 } // namespace zypp
1190 ///////////////////////////////////////////////////////////////////