1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/RepoManager.cc
20 #include "zypp/base/InputStream.h"
21 #include "zypp/base/Logger.h"
22 #include "zypp/base/Gettext.h"
23 #include "zypp/base/Function.h"
24 #include "zypp/base/Regex.h"
25 #include "zypp/PathInfo.h"
26 #include "zypp/TmpPath.h"
27 #include "zypp/ServiceInfo.h"
29 #include "zypp/repo/RepoException.h"
30 #include "zypp/RepoManager.h"
32 #include "zypp/media/MediaManager.h"
33 #include "zypp/MediaSetAccess.h"
34 #include "zypp/ExternalProgram.h"
35 #include "zypp/ManagedFile.h"
37 #include "zypp/parser/RepoFileReader.h"
38 #include "zypp/parser/ServiceFileReader.h"
39 #include "zypp/parser/RepoindexFileReader.h"
40 #include "zypp/repo/yum/Downloader.h"
41 #include "zypp/repo/susetags/Downloader.h"
42 #include "zypp/parser/plaindir/RepoParser.h"
44 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
45 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance
47 #include "zypp/ZYppCallbacks.h"
50 #include "satsolver/pool.h"
51 #include "satsolver/repo.h"
52 #include "satsolver/repo_solv.h"
56 using namespace zypp::repo;
57 using namespace zypp::filesystem;
59 using namespace zypp::repo;
61 ///////////////////////////////////////////////////////////////////
63 { /////////////////////////////////////////////////////////////////
67 /** Check if alias_r is present in repo/service container. */
68 template <class Iterator>
69 inline bool findAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r )
71 for_( it, begin_r, end_r )
72 if ( it->alias() == alias_r )
77 template <class Container>
78 inline bool findAliasIn( const std::string & alias_r, const Container & cont_r )
79 { return findAliasIn( alias_r, cont_r.begin(), cont_r.end() ); }
82 ///////////////////////////////////////////////////////////////////
84 // CLASS NAME : RepoManagerOptions
86 ///////////////////////////////////////////////////////////////////
88 RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
90 repoCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
91 repoRawCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
92 repoSolvCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
93 repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
94 knownReposPath = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
95 knownServicesPath = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
96 probe = ZConfig::instance().repo_add_probe();
99 servicesTargetDistro = getZYpp()->target()->targetDistribution();
101 catch (const Exception & e)
103 DBG << "Target not initialized, using an empty servicesTargetDistro." << endl;
107 RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r )
109 RepoManagerOptions ret;
110 ret.repoCachePath = root_r;
111 ret.repoRawCachePath = root_r/"raw";
112 ret.repoSolvCachePath = root_r/"solv";
113 ret.repoPackagesCachePath = root_r/"packages";
114 ret.knownReposPath = root_r/"repos.d";
115 ret.knownServicesPath = root_r/"services.d";
119 ////////////////////////////////////////////////////////////////////////////
122 * \short Simple callback to collect the results
124 * Classes like RepoFileParser call the callback
125 * once per each repo in a file.
127 * Passing this functor as callback, you can collect
128 * all results at the end, without dealing with async
131 * If targetDistro is set, all repos with non-empty RepoInfo::targetDistribution()
133 * \todo do this through a separate filter
140 RepoCollector(const string & targetDistro_)
141 : targetDistro(targetDistro_)
147 bool collect( const RepoInfo &repo )
149 // skip repositories meant for other distros than specified
150 if (!targetDistro.empty()
151 && !repo.targetDistribution().empty()
152 && repo.targetDistribution() != targetDistro)
155 << "Skipping repository meant for '" << targetDistro
156 << "' distribution (current distro is '"
157 << repo.targetDistribution() << "')." << endl;
162 repos.push_back(repo);
170 ////////////////////////////////////////////////////////////////////////////
173 * Reads RepoInfo's from a repo file.
175 * \param file pathname of the file to read.
177 static std::list<RepoInfo> repositories_in_file( const Pathname & file )
179 MIL << "repo file: " << file << endl;
180 RepoCollector collector;
181 parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
182 return collector.repos;
185 ////////////////////////////////////////////////////////////////////////////
187 std::list<RepoInfo> readRepoFile(const Url & repo_file)
189 // no interface to download a specific file, using workaround:
190 //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
192 Pathname path(url.getPathName());
193 url.setPathName ("/");
194 MediaSetAccess access(url);
195 Pathname local = access.provideFile(path);
197 DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
199 return repositories_in_file(local);
202 ////////////////////////////////////////////////////////////////////////////
205 * \short List of RepoInfo's from a directory
207 * Goes trough every file ending with ".repo" in a directory and adds all
208 * RepoInfo's contained in that file.
210 * \param dir pathname of the directory to read.
212 static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
214 MIL << "directory " << dir << endl;
215 list<RepoInfo> repos;
216 list<Pathname> entries;
217 if ( filesystem::readdir( entries, Pathname(dir), false ) != 0 )
218 ZYPP_THROW(Exception("failed to read directory"));
220 str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
221 for ( list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
223 if (str::regex_match(it->extension(), allowedRepoExt))
225 list<RepoInfo> tmp = repositories_in_file( *it );
226 repos.insert( repos.end(), tmp.begin(), tmp.end() );
228 //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
229 //MIL << "ok" << endl;
235 ////////////////////////////////////////////////////////////////////////////
237 static void assert_alias( const RepoInfo &info )
239 if (info.alias().empty())
240 ZYPP_THROW(RepoNoAliasException());
243 ////////////////////////////////////////////////////////////////////////////
245 static void assert_urls( const RepoInfo &info )
247 if (info.baseUrlsEmpty())
248 ZYPP_THROW(RepoNoUrlException());
251 ////////////////////////////////////////////////////////////////////////////
254 * \short Calculates the raw cache path for a repository
256 static Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
259 return opt.repoRawCachePath / info.escaped_alias();
263 * \short Calculates the packages cache path for a repository
265 static Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
268 return opt.repoPackagesCachePath / info.escaped_alias();
271 static Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
274 return opt.repoSolvCachePath / info.escaped_alias();
277 ///////////////////////////////////////////////////////////////////
279 // CLASS NAME : RepoManager::Impl
281 ///////////////////////////////////////////////////////////////////
284 * \short RepoManager implementation.
286 struct RepoManager::Impl
288 Impl( const RepoManagerOptions &opt )
300 RepoManagerOptions options;
307 /** Offer default Impl. */
308 static shared_ptr<Impl> nullimpl()
310 static shared_ptr<Impl> _nullimpl( new Impl );
314 void saveService( const ServiceInfo & service ) const;
316 Pathname generateNonExistingName( const Pathname &dir,
317 const std::string &basefilename ) const;
319 std::string generateFilename( const RepoInfo & info ) const;
320 std::string generateFilename( const ServiceInfo & info ) const;
322 struct ServiceCollector
324 ServiceCollector(ServiceSet & services_) : services(services_) {}
326 bool collect(ServiceInfo service) { services.insert(service); return true; }
329 ServiceSet & services;
332 void knownServices();
334 void knownRepositories();
337 friend Impl * rwcowClone<Impl>( const Impl * rhs );
338 /** clone for RWCOW_pointer */
340 { return new Impl( *this ); }
343 ///////////////////////////////////////////////////////////////////
345 /** \relates RepoManager::Impl Stream output */
346 inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
348 return str << "RepoManager::Impl";
351 ///////////////////////////////////////////////////////////////////
353 // CLASS NAME : RepoManager
355 ///////////////////////////////////////////////////////////////////
357 RepoManager::RepoManager( const RepoManagerOptions &opt )
358 : _pimpl( new Impl(opt) )
361 ////////////////////////////////////////////////////////////////////////////
363 RepoManager::~RepoManager()
366 ////////////////////////////////////////////////////////////////////////////
368 bool RepoManager::repoEmpty() const { return _pimpl->repos.empty(); }
369 RepoManager::RepoSizeType RepoManager::repoSize() const
370 { return _pimpl->repos.size(); }
371 RepoManager::RepoConstIterator RepoManager::repoBegin() const
372 { return _pimpl->repos.begin(); }
373 RepoManager::RepoConstIterator RepoManager::repoEnd() const
374 { return _pimpl->repos.end(); }
377 std::list<RepoInfo> RepoManager::knownRepositories() const
379 return std::list<RepoInfo>(repoBegin(),repoEnd());
382 void RepoManager::Impl::knownRepositories()
384 MIL << "start construct known repos" << endl;
386 if ( PathInfo(options.knownReposPath).isExist() )
388 RepoInfoList repol = repositories_in_dir(options.knownReposPath);
389 for ( RepoInfoList::iterator it = repol.begin();
393 // set the metadata path for the repo
394 Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it));
395 (*it).setMetadataPath(metadata_path);
397 // set the downloaded packages path for the repo
398 Pathname packages_path = packagescache_path_for_repoinfo(options, (*it));
399 (*it).setPackagesPath(packages_path);
405 MIL << "end construct known repos" << endl;
408 ////////////////////////////////////////////////////////////////////////////
410 Pathname RepoManager::metadataPath( const RepoInfo &info ) const
412 return rawcache_path_for_repoinfo(_pimpl->options, info );
415 Pathname RepoManager::packagesPath( const RepoInfo &info ) const
417 return packagescache_path_for_repoinfo(_pimpl->options, info );
420 ////////////////////////////////////////////////////////////////////////////
422 RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
424 Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
425 RepoType repokind = info.type();
428 switch ( repokind.toEnum() )
430 case RepoType::NONE_e:
431 // unknown, probe the local metadata
432 repokind = probe(rawpath.asUrl());
438 switch ( repokind.toEnum() )
440 case RepoType::RPMMD_e :
442 status = RepoStatus( rawpath + "/repodata/repomd.xml");
446 case RepoType::YAST2_e :
448 status = RepoStatus( rawpath + "/content") && (RepoStatus( rawpath + "/media.1/media"));
452 case RepoType::RPMPLAINDIR_e :
454 if ( PathInfo(Pathname(rawpath + "/cookie")).isExist() )
455 status = RepoStatus( rawpath + "/cookie");
459 case RepoType::NONE_e :
460 // Return default RepoStatus in case of RepoType::NONE
461 // indicating it should be created?
462 // ZYPP_THROW(RepoUnknownTypeException());
468 void RepoManager::touchIndexFile(const RepoInfo & info)
470 Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
472 RepoType repokind = info.type();
473 if ( repokind.toEnum() == RepoType::NONE_e )
474 // unknown, probe the local metadata
475 repokind = probe(rawpath.asUrl());
476 // if still unknown, just return
477 if (repokind == RepoType::NONE_e)
481 switch ( repokind.toEnum() )
483 case RepoType::RPMMD_e :
484 p = Pathname(rawpath + "/repodata/repomd.xml");
487 case RepoType::YAST2_e :
488 p = Pathname(rawpath + "/content");
491 case RepoType::RPMPLAINDIR_e :
492 p = Pathname(rawpath + "/cookie");
495 case RepoType::NONE_e :
500 // touch the file, ignore error (they are logged anyway)
501 filesystem::touch(p);
504 RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata(
505 const RepoInfo &info,
507 RawMetadataRefreshPolicy policy )
511 RepoStatus oldstatus;
512 RepoStatus newstatus;
516 MIL << "Going to try to check whether refresh is needed for " << url << endl;
518 repo::RepoType repokind = info.type();
520 // if the type is unknown, try probing.
521 switch ( repokind.toEnum() )
523 case RepoType::NONE_e:
525 repokind = probe(url);
531 Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
532 filesystem::assert_dir(rawpath);
533 oldstatus = metadataStatus(info);
535 // now we've got the old (cached) status, we can decide repo.refresh.delay
536 if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
538 // difference in seconds
539 double diff = difftime(
540 (Date::ValueType)Date::now(),
541 (Date::ValueType)oldstatus.timestamp()) / 60;
543 DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
544 DBG << "current time: " << (Date::ValueType)Date::now() << endl;
545 DBG << "last refresh = " << diff << " minutes ago" << endl;
547 if (diff < ZConfig::instance().repo_refresh_delay())
549 MIL << "Repository '" << info.alias()
550 << "' has been refreshed less than repo.refresh.delay ("
551 << ZConfig::instance().repo_refresh_delay()
552 << ") minutes ago. Advising to skip refresh" << endl;
553 return REPO_CHECK_DELAYED;
557 // create temp dir as sibling of rawpath
558 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( rawpath ) );
560 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
561 ( repokind.toEnum() == RepoType::YAST2_e ) )
563 MediaSetAccess media(url);
564 shared_ptr<repo::Downloader> downloader_ptr;
566 if ( repokind.toEnum() == RepoType::RPMMD_e )
567 downloader_ptr.reset(new yum::Downloader(info));
569 downloader_ptr.reset( new susetags::Downloader(info));
571 RepoStatus newstatus = downloader_ptr->status(media);
572 bool refresh = false;
573 if ( oldstatus.checksum() == newstatus.checksum() )
575 MIL << "repo has not changed" << endl;
576 if ( policy == RefreshForced )
578 MIL << "refresh set to forced" << endl;
584 MIL << "repo has changed, going to refresh" << endl;
589 touchIndexFile(info);
591 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
593 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
595 RepoStatus newstatus = parser::plaindir::dirStatus(url.getPathName());
596 bool refresh = false;
597 if ( oldstatus.checksum() == newstatus.checksum() )
599 MIL << "repo has not changed" << endl;
600 if ( policy == RefreshForced )
602 MIL << "refresh set to forced" << endl;
608 MIL << "repo has changed, going to refresh" << endl;
613 touchIndexFile(info);
615 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
619 ZYPP_THROW(RepoUnknownTypeException(info));
622 catch ( const Exception &e )
625 ERR << "refresh check failed for " << url << endl;
629 return REFRESH_NEEDED; // default
632 void RepoManager::refreshMetadata( const RepoInfo &info,
633 RawMetadataRefreshPolicy policy,
634 const ProgressData::ReceiverFnc & progress )
639 // we will throw this later if no URL checks out fine
640 RepoException rexception(_("Valid metadata not found at specified URL(s)"));
642 // try urls one by one
643 for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
649 // check whether to refresh metadata
650 // if the check fails for this url, it throws, so another url will be checked
651 if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
654 MIL << "Going to refresh metadata from " << url << endl;
656 repo::RepoType repokind = info.type();
658 // if the type is unknown, try probing.
659 switch ( repokind.toEnum() )
661 case RepoType::NONE_e:
663 repokind = probe(*it);
665 if (repokind.toEnum() != RepoType::NONE_e)
667 // Adjust the probed type in RepoInfo
668 info.setProbedType( repokind ); // lazy init!
669 //save probed type only for repos in system
670 for_( it, repoBegin(), repoEnd() )
672 if ( info.alias() == (*it).alias() )
674 RepoInfo modifiedrepo = info;
675 modifiedrepo.setType(repokind);
676 modifyRepository(info.alias(),modifiedrepo);
685 Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
686 filesystem::assert_dir(rawpath);
688 // create temp dir as sibling of rawpath
689 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( rawpath ) );
691 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
692 ( repokind.toEnum() == RepoType::YAST2_e ) )
694 MediaSetAccess media(url);
695 shared_ptr<repo::Downloader> downloader_ptr;
697 MIL << "Creating downloader for [ " << info.name() << " ]" << endl;
699 if ( repokind.toEnum() == RepoType::RPMMD_e )
700 downloader_ptr.reset(new yum::Downloader(info));
702 downloader_ptr.reset( new susetags::Downloader(info) );
705 * Given a downloader, sets the other repos raw metadata
706 * path as cache paths for the fetcher, so if another
707 * repo has the same file, it will not download it
708 * but copy it from the other repository
710 for_( it, repoBegin(), repoEnd() )
712 Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
713 if ( PathInfo(cachepath).isExist() )
714 downloader_ptr->addCachePath(cachepath);
717 downloader_ptr->download( media, tmpdir.path());
719 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
721 RepoStatus newstatus = parser::plaindir::dirStatus(url.getPathName());
723 std::ofstream file(( tmpdir.path() + "/cookie").c_str());
725 ZYPP_THROW (Exception( "Can't open " + tmpdir.path().asString() + "/cookie" ) );
728 file << newstatus.checksum() << endl;
734 ZYPP_THROW(RepoUnknownTypeException());
737 // ok we have the metadata, now exchange
740 TmpDir oldmetadata( TmpDir::makeSibling( rawpath ) );
741 filesystem::rename( rawpath, oldmetadata.path() );
742 // move the just downloaded there
743 filesystem::rename( tmpdir.path(), rawpath );
748 catch ( const Exception &e )
751 ERR << "Trying another url..." << endl;
753 // remember the exception caught for the *first URL*
754 // if all other URLs fail, the rexception will be thrown with the
755 // cause of the problem of the first URL remembered
756 if (it == info.baseUrlsBegin())
757 rexception.remember(e);
760 ERR << "No more urls..." << endl;
761 ZYPP_THROW(rexception);
764 ////////////////////////////////////////////////////////////////////////////
766 void RepoManager::cleanMetadata( const RepoInfo &info,
767 const ProgressData::ReceiverFnc & progressfnc )
769 ProgressData progress(100);
770 progress.sendTo(progressfnc);
772 filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
776 void RepoManager::cleanPackages( const RepoInfo &info,
777 const ProgressData::ReceiverFnc & progressfnc )
779 ProgressData progress(100);
780 progress.sendTo(progressfnc);
782 filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
786 void RepoManager::buildCache( const RepoInfo &info,
787 CacheBuildPolicy policy,
788 const ProgressData::ReceiverFnc & progressrcv )
791 Pathname rawpath = rawcache_path_for_repoinfo(_pimpl->options, info);
793 filesystem::assert_dir(_pimpl->options.repoCachePath);
794 RepoStatus raw_metadata_status = metadataStatus(info);
795 if ( raw_metadata_status.empty() )
797 /* if there is no cache at this point, we refresh the raw
798 in case this is the first time - if it's !autorefresh,
799 we may still refresh */
800 refreshMetadata(info, RefreshIfNeeded, progressrcv );
801 raw_metadata_status = metadataStatus(info);
804 bool needs_cleaning = false;
805 if ( isCached( info ) )
807 MIL << info.alias() << " is already cached." << endl;
808 RepoStatus cache_status = cacheStatus(info);
810 if ( cache_status.checksum() == raw_metadata_status.checksum() )
812 MIL << info.alias() << " cache is up to date with metadata." << endl;
813 if ( policy == BuildIfNeeded ) {
817 MIL << info.alias() << " cache rebuild is forced" << endl;
821 needs_cleaning = true;
824 ProgressData progress(100);
825 callback::SendReport<ProgressReport> report;
826 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
827 progress.name(str::form(_("Building repository '%s' cache"), info.name().c_str()));
835 MIL << info.alias() << " building cache..." << endl;
837 Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
838 filesystem::assert_dir(base);
839 Pathname solvfile = base / "solv";
842 repo::RepoType repokind = info.type();
844 // if the type is unknown, try probing.
845 switch ( repokind.toEnum() )
847 case RepoType::NONE_e:
848 // unknown, probe the local metadata
849 repokind = probe(rawpath.asUrl());
855 MIL << "repo type is " << repokind << endl;
857 switch ( repokind.toEnum() )
859 case RepoType::RPMMD_e :
860 case RepoType::YAST2_e :
861 case RepoType::RPMPLAINDIR_e :
863 // Take care we unlink the solvfile on exception
864 ManagedFile guard( solvfile, filesystem::unlink );
867 std::string toFile( str::gsub(solvfile.asString(),"\"","\\\"") );
868 if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
870 // FIXME this does only work form dir: URLs
871 cmd << str::form( "repo2solv.sh \"%s\" > \"%s\"",
872 str::gsub( info.baseUrlsBegin()->getPathName(),"\"","\\\"" ).c_str(),
877 cmd << str::form( "repo2solv.sh \"%s\" > \"%s\"",
878 str::gsub( rawpath.asString(),"\"","\\\"" ).c_str(),
881 MIL << "Executing: " << cmd.str() << endl;
882 ExternalProgram prog( cmd.str(), ExternalProgram::Stderr_To_Stdout );
885 for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
886 WAR << " " << output;
887 cmd << " " << output;
890 int ret = prog.close();
893 RepoException ex(str::form("Failed to cache repo (%d).", ret));
894 ex.remember( cmd.str() );
899 guard.resetDispose();
903 ZYPP_THROW(RepoUnknownTypeException("Unhandled repository type"));
906 // update timestamp and checksum
907 setCacheStatus(info, raw_metadata_status);
908 MIL << "Commit cache.." << endl;
912 ////////////////////////////////////////////////////////////////////////////
914 repo::RepoType RepoManager::probe( const Url &url ) const
916 if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName() ).isDir() )
918 // Handle non existing local directory in advance, as
919 // MediaSetAccess does not support it.
920 return repo::RepoType::NONE;
925 MediaSetAccess access(url);
926 if ( access.doesFileExist("/repodata/repomd.xml") )
927 return repo::RepoType::RPMMD;
928 if ( access.doesFileExist("/content") )
929 return repo::RepoType::YAST2;
931 // if it is a local url of type dir
932 if ( (! media::MediaManager::downloads(url)) && ( url.getScheme() == "dir" ) )
934 Pathname path = Pathname(url.getPathName());
935 if ( PathInfo(path).isDir() )
937 // allow empty dirs for now
938 return repo::RepoType::RPMPLAINDIR;
942 catch ( const media::MediaException &e )
945 RepoException enew("Error trying to read from " + url.asString());
949 catch ( const Exception &e )
952 Exception enew("Unknown error reading from " + url.asString());
957 return repo::RepoType::NONE;
960 ////////////////////////////////////////////////////////////////////////////
962 void RepoManager::cleanCache( const RepoInfo &info,
963 const ProgressData::ReceiverFnc & progressrcv )
965 ProgressData progress(100);
966 progress.sendTo(progressrcv);
969 filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
974 ////////////////////////////////////////////////////////////////////////////
976 bool RepoManager::isCached( const RepoInfo &info ) const
978 return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
981 RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
984 Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
986 return RepoStatus::fromCookieFile(cookiefile);
989 void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
991 Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
992 filesystem::assert_dir(base);
993 Pathname cookiefile = base / "cookie";
995 status.saveToCookieFile(cookiefile);
998 void RepoManager::loadFromCache( const RepoInfo & info,
999 const ProgressData::ReceiverFnc & progressrcv )
1002 Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
1004 if ( ! PathInfo(solvfile).isExist() )
1005 ZYPP_THROW(RepoNotCachedException(info));
1009 sat::Pool::instance().addRepoSolv( solvfile, info );
1011 catch ( const Exception & exp )
1014 MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1015 cleanCache( info, progressrcv );
1016 buildCache( info, BuildIfNeeded, progressrcv );
1018 sat::Pool::instance().addRepoSolv( solvfile, info );
1022 ////////////////////////////////////////////////////////////////////////////
1025 * Generate a non existing filename in a directory, using a base
1026 * name. For example if a directory contains 3 files
1032 * If you try to generate a unique filename for this directory,
1033 * based on "ruu" you will get "ruu", but if you use the base
1034 * "foo" you will get "foo_1"
1036 * \param dir Directory where the file needs to be unique
1037 * \param basefilename string to base the filename on.
1039 Pathname RepoManager::Impl::generateNonExistingName( const Pathname &dir,
1040 const std::string &basefilename ) const
1042 string final_filename = basefilename;
1044 while ( PathInfo(dir + final_filename).isExist() )
1046 final_filename = basefilename + "_" + str::numstring(counter);
1049 return dir + Pathname(final_filename);
1052 ////////////////////////////////////////////////////////////////////////////
1055 * \short Generate a related filename from a repo info
1057 * From a repo info, it will try to use the alias as a filename
1058 * escaping it if necessary. Other fallbacks can be added to
1059 * this function in case there is no way to use the alias
1061 std::string RepoManager::Impl::generateFilename( const RepoInfo &info ) const
1063 std::string filename = info.alias();
1064 // replace slashes with underscores
1065 str::replaceAll( filename, "/", "_" );
1067 filename = Pathname(filename).extend(".repo").asString();
1068 MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
1072 std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const
1074 std::string filename = info.alias();
1075 // replace slashes with underscores
1076 str::replaceAll( filename, "/", "_" );
1078 filename = Pathname(filename).extend(".service").asString();
1079 MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl;
1083 ////////////////////////////////////////////////////////////////////////////
1085 void RepoManager::addRepository( const RepoInfo &info,
1086 const ProgressData::ReceiverFnc & progressrcv )
1090 ProgressData progress(100);
1091 callback::SendReport<ProgressReport> report;
1092 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1093 progress.name(str::form(_("Adding repository '%s'"), info.name().c_str()));
1096 RepoInfo tosave = info;
1097 if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
1098 ZYPP_THROW(RepoAlreadyExistsException(info));
1101 // check the first url for now
1102 if ( _pimpl->options.probe )
1104 DBG << "unknown repository type, probing" << endl;
1106 RepoType probedtype;
1107 probedtype = probe(*tosave.baseUrlsBegin());
1108 if ( tosave.baseUrlsSize() > 0 )
1110 if ( probedtype == RepoType::NONE )
1111 ZYPP_THROW(RepoUnknownTypeException());
1113 tosave.setType(probedtype);
1119 // assert the directory exists
1120 filesystem::assert_dir(_pimpl->options.knownReposPath);
1122 Pathname repofile = _pimpl->generateNonExistingName(
1123 _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
1124 // now we have a filename that does not exists
1125 MIL << "Saving repo in " << repofile << endl;
1127 std::ofstream file(repofile.c_str());
1129 ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
1132 tosave.dumpAsIniOn(file);
1133 tosave.setFilepath(repofile);
1134 _pimpl->repos.insert(tosave);
1136 MIL << "done" << endl;
1139 void RepoManager::addRepositories( const Url &url,
1140 const ProgressData::ReceiverFnc & progressrcv )
1142 std::list<RepoInfo> repos = readRepoFile(url);
1143 for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1147 // look if the alias is in the known repos.
1148 for_ ( kit, repoBegin(), repoEnd() )
1150 if ( (*it).alias() == (*kit).alias() )
1152 ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1153 ZYPP_THROW(RepoAlreadyExistsException(*it));
1158 string filename = Pathname(url.getPathName()).basename();
1160 if ( filename == Pathname() )
1161 ZYPP_THROW(RepoException("Invalid repo file name at " + url.asString() ));
1163 // assert the directory exists
1164 filesystem::assert_dir(_pimpl->options.knownReposPath);
1166 Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
1167 // now we have a filename that does not exists
1168 MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1170 std::ofstream file(repofile.c_str());
1172 ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
1175 for ( std::list<RepoInfo>::iterator it = repos.begin();
1179 MIL << "Saving " << (*it).alias() << endl;
1180 it->setFilepath(repofile.asString());
1181 it->dumpAsIniOn(file);
1182 _pimpl->repos.insert(*it);
1184 MIL << "done" << endl;
1187 ////////////////////////////////////////////////////////////////////////////
1189 void RepoManager::removeRepository( const RepoInfo & info,
1190 const ProgressData::ReceiverFnc & progressrcv)
1192 ProgressData progress;
1193 callback::SendReport<ProgressReport> report;
1194 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1195 progress.name(str::form(_("Removing repository '%s'"), info.name().c_str()));
1197 MIL << "Going to delete repo " << info.alias() << endl;
1199 for_( it, repoBegin(), repoEnd() )
1201 // they can be the same only if the provided is empty, that means
1202 // the provided repo has no alias
1204 if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1207 // TODO match by url
1209 // we have a matcing repository, now we need to know
1210 // where it does come from.
1211 RepoInfo todelete = *it;
1212 if (todelete.filepath().empty())
1214 ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
1218 // figure how many repos are there in the file:
1219 std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1220 if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1222 // easy, only this one, just delete the file
1223 if ( filesystem::unlink(todelete.filepath()) != 0 )
1225 ZYPP_THROW(RepoException("Can't delete " + todelete.filepath().asString()));
1227 MIL << todelete.alias() << " sucessfully deleted." << endl;
1231 // there are more repos in the same file
1232 // write them back except the deleted one.
1234 //std::ofstream file(tmp.path().c_str());
1236 // assert the directory exists
1237 filesystem::assert_dir(todelete.filepath().dirname());
1239 std::ofstream file(todelete.filepath().c_str());
1241 //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
1242 ZYPP_THROW (Exception( "Can't open " + todelete.filepath().asString() ) );
1244 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1245 fit != filerepos.end();
1248 if ( (*fit).alias() != todelete.alias() )
1249 (*fit).dumpAsIniOn(file);
1253 CombinedProgressData subprogrcv(progress, 70);
1254 CombinedProgressData cleansubprogrcv(progress, 30);
1255 // now delete it from cache
1256 if ( isCached(todelete) )
1257 cleanCache( todelete, subprogrcv);
1258 // now delete metadata (#301037)
1259 cleanMetadata( todelete, cleansubprogrcv);
1260 _pimpl->repos.erase(todelete);
1261 MIL << todelete.alias() << " sucessfully deleted." << endl;
1263 } // else filepath is empty
1266 // should not be reached on a sucess workflow
1267 ZYPP_THROW(RepoNotFoundException(info));
1270 ////////////////////////////////////////////////////////////////////////////
1272 void RepoManager::modifyRepository( const std::string &alias,
1273 const RepoInfo & newinfo,
1274 const ProgressData::ReceiverFnc & progressrcv )
1276 RepoInfo toedit = getRepositoryInfo(alias);
1278 // check if the new alias already exists when renaming the repo
1279 if (alias != newinfo.alias())
1281 for_( it, repoBegin(), repoEnd() )
1283 if ( newinfo.alias() == (*it).alias() )
1284 ZYPP_THROW(RepoAlreadyExistsException(newinfo));
1288 if (toedit.filepath().empty())
1290 ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
1294 // figure how many repos are there in the file:
1295 std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1297 // there are more repos in the same file
1298 // write them back except the deleted one.
1300 //std::ofstream file(tmp.path().c_str());
1302 // assert the directory exists
1303 filesystem::assert_dir(toedit.filepath().dirname());
1305 std::ofstream file(toedit.filepath().c_str());
1307 //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
1308 ZYPP_THROW (Exception( "Can't open " + toedit.filepath().asString() ) );
1310 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1311 fit != filerepos.end();
1314 // if the alias is different, dump the original
1315 // if it is the same, dump the provided one
1316 if ( (*fit).alias() != toedit.alias() )
1317 (*fit).dumpAsIniOn(file);
1319 newinfo.dumpAsIniOn(file);
1322 _pimpl->repos.erase(toedit);
1323 _pimpl->repos.insert(newinfo);
1327 ////////////////////////////////////////////////////////////////////////////
1329 RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1330 const ProgressData::ReceiverFnc & progressrcv )
1333 info.setAlias(alias);
1334 RepoConstIterator it = _pimpl->repos.find( info );
1335 if( it == repoEnd() )
1336 ZYPP_THROW(RepoNotFoundException(info));
1341 ////////////////////////////////////////////////////////////////////////////
1343 RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1344 const url::ViewOption & urlview,
1345 const ProgressData::ReceiverFnc & progressrcv )
1347 for_( it, repoBegin(), repoEnd() )
1349 for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1350 urlit != (*it).baseUrlsEnd();
1353 if ((*urlit).asString(urlview) == url.asString(urlview))
1358 info.setBaseUrl(url);
1359 ZYPP_THROW(RepoNotFoundException(info));
1362 void RepoManager::addService( const std::string & alias, const Url & url )
1364 addService( ServiceInfo(alias, url) );
1368 void RepoManager::addService( const ServiceInfo & service )
1370 // check if service already exists
1371 if( _pimpl->services.find(service) != _pimpl->services.end() )
1372 return; //FIXME ZYPP_THROW(RepoAlreadyExistsException(service.name()));
1374 // this is need to save location to correct service
1375 const ServiceInfo & savedService =
1376 *(_pimpl->services.insert( service )).first;
1378 MIL << "added service " << savedService.alias() << endl;
1380 _pimpl->saveService( savedService );
1384 void RepoManager::removeService( const string & alias)
1386 MIL << "Going to delete repo " << alias << endl;
1388 const ServiceInfo & service = getService( alias );
1390 Pathname location = service.filepath();
1391 if( location.empty() )
1393 ZYPP_THROW(RepoException("Can't figure where the service is stored"));
1397 Impl::ServiceCollector collector(tmpSet);
1399 parser::ServiceFileReader reader( location,
1400 bind(&Impl::ServiceCollector::collect,collector,_1) );
1402 // only one service definition in the file
1403 if ( tmpSet.size() == 1 )
1405 if ( filesystem::unlink(location) != 0 )
1407 ZYPP_THROW(RepoException("Can't delete " + location.asString()));
1409 MIL << alias << " sucessfully deleted." << endl;
1413 filesystem::assert_dir(location.dirname());
1415 std::ofstream file(location.c_str());
1417 ZYPP_THROW(Exception("failed open file to write"));
1419 for_(it, tmpSet.begin(), tmpSet.end())
1421 if( it->alias() != alias )
1422 it->dumpAsIniOn(file);
1425 MIL << alias << " sucessfully deleted from file " << location << endl;
1428 // now remove all repositories added by this service
1429 RepoCollector rcollector;
1430 getRepositoriesInService( alias,
1431 boost::make_function_output_iterator(
1432 bind( &RepoCollector::collect, &rcollector, _1 ) ) );
1433 // cannot do this directly in getRepositoriesInService - would invalidate iterators
1434 for_(rit, rcollector.repos.begin(), rcollector.repos.end())
1435 removeRepository(*rit);
1439 void RepoManager::removeService( const ServiceInfo & service )
1440 { removeService(service.alias()); }
1443 void RepoManager::Impl::saveService( const ServiceInfo & service ) const
1445 filesystem::assert_dir( options.knownServicesPath );
1447 Pathname servfile = generateNonExistingName( options.knownServicesPath,
1448 generateFilename( service ) );
1450 MIL << "saving service in " << servfile << endl;
1452 std::ofstream file(servfile.c_str());
1454 ZYPP_THROW (Exception( "Can't open " + servfile.asString() ) );
1457 service.dumpAsIniOn( file );
1459 const_cast<ServiceInfo&>(service).setFilepath( servfile );
1460 MIL << "done" << endl;
1463 ServiceInfo RepoManager::getService( const std::string & alias ) const
1465 for_ (it, serviceBegin(), serviceEnd())
1466 if ( it->alias() == alias )
1468 return ServiceInfo::noService;
1471 bool RepoManager::serviceEmpty() const { return _pimpl->services.empty(); }
1473 RepoManager::ServiceSizeType RepoManager::serviceSize() const
1475 return _pimpl->services.size();
1478 RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
1480 return _pimpl->services.begin();
1483 RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
1485 return _pimpl->services.end();
1488 void RepoManager::refreshServices()
1490 // copy the set of services since refreshService
1491 // can eventually invalidate the iterator
1492 ServiceSet services;
1493 services.insert(serviceBegin(), serviceEnd());
1494 for_(it, services.begin(), services.end())
1496 if ( !it->enabled() )
1499 refreshService(*it);
1503 void RepoManager::refreshService( const ServiceInfo & service )
1505 MIL << "going to refresh service '" << service.alias()
1506 << "', url: "<< service.url().asString() << endl;
1508 //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
1510 repo::ServiceType type = service.type();
1511 // if the type is unknown, try probing.
1512 if ( type == repo::ServiceType::NONE )
1514 // unknown, probe it
1515 type = probeService(service.url());
1517 if (type != ServiceType::NONE)
1519 // Adjust the probed type in ServiceInfo
1520 service.setProbedType( type ); // lazy init!
1521 // save probed type only for repos in system
1522 for_( sit, serviceBegin(), serviceEnd() )
1524 if ( service.alias() == sit->alias() )
1526 ServiceInfo modifiedservice = service;
1527 modifiedservice.setType(type);
1528 modifyService(service.alias(), modifiedservice);
1535 // download the repo index file
1536 media::MediaManager mediamanager;
1537 //if (service.url().empty())
1538 // throw RepoNoUrlException();
1539 media::MediaAccessId mid = mediamanager.open( service.url() );
1540 mediamanager.attachDesiredMedia( mid );
1541 mediamanager.provideFile( mid, "repo/repoindex.xml" );
1542 Pathname path = mediamanager.localPath(mid, "repo/repoindex.xml" );
1545 RepoCollector collector(_pimpl->options.servicesTargetDistro);
1546 parser::RepoindexFileReader reader( path,
1547 bind( &RepoCollector::collect, &collector, _1 ) );
1548 mediamanager.release( mid );
1549 mediamanager.close( mid );
1551 // set service alias and base url for all collected repositories
1552 for_( it, collector.repos.begin(), collector.repos.end() )
1556 // if the repo url was not set by the repoindex parser, set service's url
1557 if ( it->baseUrlsEmpty() )
1558 url = service.url();
1560 // service repo can contain only one URL now, so no need to iterate
1561 url = *it->baseUrlsBegin();
1563 // libzypp currently has problem with separate url + path handling
1564 // so just append the path to the baseurl
1565 if (!it->path().empty())
1567 Pathname path(url.getPathName());
1569 url.setPathName( path.asString() );
1574 it->setBaseUrl( url );
1575 // set refrence to the parent service
1576 it->setService( service.alias() );
1579 // compare old and new repositories (hope not too much, if it change
1580 // then construct set and use set operation on it)
1581 std::list<RepoInfo> oldRepos;
1582 getRepositoriesInService(service.alias(),
1583 insert_iterator<std::list<RepoInfo> > (oldRepos, oldRepos.begin()));
1585 //! \todo fix enabled/disable with respect to ServiceInfo reposTo...
1587 // find old to remove
1588 for_( it, oldRepos.begin(), oldRepos.end() )
1590 if ( ! findAliasIn( it->alias(), collector.repos ) )
1592 removeRepository( *it );
1597 for_( it, collector.repos.begin(), collector.repos.end() )
1599 if ( ! findAliasIn( it->alias(), oldRepos ) )
1601 #warning check whether a repo with the same alias exists
1602 // At that point check whether a repo with the same alias
1603 // exists outside this service. Maybe forcefully re-alias
1604 // the existing repo?
1606 // make sure the service is created in disabled
1607 // autorefresh true.
1608 it->setEnabled( false );
1609 it->setAutorefresh( true );
1611 addRepository( *it );
1616 void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & service)
1618 MIL << "Going to modify service " << oldAlias << endl;
1620 const ServiceInfo & oldService = getService(oldAlias);
1622 Pathname location = oldService.filepath();
1623 if( location.empty() )
1625 ZYPP_THROW(RepoException(
1626 "Cannot figure out where the service file is stored."));
1630 Impl::ServiceCollector collector(tmpSet);
1632 parser::ServiceFileReader reader( location,
1633 bind(&Impl::ServiceCollector::collect,collector,_1) );
1635 filesystem::assert_dir(location.dirname());
1637 std::ofstream file(location.c_str());
1639 for_(it, tmpSet.begin(), tmpSet.end())
1641 if( *it != oldAlias )
1642 it->dumpAsIniOn(file);
1645 service.dumpAsIniOn(file);
1649 _pimpl->services.erase(oldAlias);
1650 _pimpl->services.insert(service);
1652 // changed name, must change also repositories
1653 if( oldAlias != service.alias() )
1655 std::vector<RepoInfo> toModify;
1656 getRepositoriesInService(oldAlias,
1657 insert_iterator<std::vector<RepoInfo> >( toModify, toModify.begin() ));
1658 for_( it, toModify.begin(), toModify.end() )
1660 it->setService(service.alias());
1661 modifyRepository(it->alias(), *it);
1665 //! \todo changed enabled status
1666 if ( oldService.enabled() != service.enabled() )
1671 //! \todo refresh the service automatically if url is changed?
1674 void RepoManager::Impl::knownServices()
1676 ServiceCollector collector(services);
1677 Pathname dir = options.knownServicesPath;
1678 list<Pathname> entries;
1679 if (PathInfo(dir).isExist())
1681 if ( filesystem::readdir( entries, Pathname(dir), false ) != 0 )
1682 ZYPP_THROW(Exception("failed to read directory"));
1684 //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
1685 for_(it, entries.begin(), entries.end() )
1687 parser::ServiceFileReader reader(*it,
1688 bind(&ServiceCollector::collect, collector, _1) );
1693 repo::ServiceType RepoManager::probeService( const Url &url ) const
1697 MediaSetAccess access(url);
1698 if ( access.doesFileExist("/repo/repoindex.xml") )
1699 return repo::ServiceType::RIS;
1701 catch ( const media::MediaException &e )
1704 RepoException enew("Error trying to read from " + url.asString());
1708 catch ( const Exception &e )
1711 Exception enew("Unknown error reading from " + url.asString());
1716 return repo::ServiceType::NONE;
1719 ////////////////////////////////////////////////////////////////////////////
1721 std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
1723 return str << *obj._pimpl;
1726 /////////////////////////////////////////////////////////////////
1728 ///////////////////////////////////////////////////////////////////