1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/RepoManager.cc
21 #include "zypp/base/InputStream.h"
22 #include "zypp/base/LogTools.h"
23 #include "zypp/base/Gettext.h"
24 #include "zypp/base/Function.h"
25 #include "zypp/base/Regex.h"
26 #include "zypp/PathInfo.h"
27 #include "zypp/TmpPath.h"
29 #include "zypp/ServiceInfo.h"
30 #include "zypp/repo/RepoException.h"
31 #include "zypp/RepoManager.h"
33 #include "zypp/media/MediaManager.h"
34 #include "zypp/media/CredentialManager.h"
35 #include "zypp/MediaSetAccess.h"
36 #include "zypp/ExternalProgram.h"
37 #include "zypp/ManagedFile.h"
39 #include "zypp/parser/RepoFileReader.h"
40 #include "zypp/parser/ServiceFileReader.h"
41 #include "zypp/repo/ServiceRepos.h"
42 #include "zypp/repo/yum/Downloader.h"
43 #include "zypp/repo/susetags/Downloader.h"
44 #include "zypp/parser/plaindir/RepoParser.h"
45 #include "zypp/repo/PluginServices.h"
47 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
48 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance
49 #include "zypp/HistoryLog.h" // to write history :O)
51 #include "zypp/ZYppCallbacks.h"
57 using namespace zypp::repo;
59 ///////////////////////////////////////////////////////////////////
61 { /////////////////////////////////////////////////////////////////
65 /** Simple media mounter to access non-downloading URLs e.g. for non-local plaindir repos.
71 /** Ctor provides media access. */
72 MediaMounter( const Url & url_r )
74 media::MediaManager mediamanager;
75 _mid = mediamanager.open( url_r );
76 mediamanager.attach( _mid );
79 /** Ctor releases the media. */
82 media::MediaManager mediamanager;
83 mediamanager.release( _mid );
84 mediamanager.close( _mid );
87 /** Convert a path relative to the media into an absolute path.
89 * Called without argument it returns the path to the medias root directory.
91 Pathname getPathName( const Pathname & path_r = Pathname() ) const
93 media::MediaManager mediamanager;
94 return mediamanager.localPath( _mid, path_r );
98 media::MediaAccessId _mid;
101 /** Check if alias_r is present in repo/service container. */
102 template <class Iterator>
103 inline bool foundAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r )
105 for_( it, begin_r, end_r )
106 if ( it->alias() == alias_r )
111 template <class Container>
112 inline bool foundAliasIn( const std::string & alias_r, const Container & cont_r )
113 { return foundAliasIn( alias_r, cont_r.begin(), cont_r.end() ); }
115 /** Find alias_r in repo/service container. */
116 template <class Iterator>
117 inline Iterator findAlias( const std::string & alias_r, Iterator begin_r, Iterator end_r )
119 for_( it, begin_r, end_r )
120 if ( it->alias() == alias_r )
125 template <class Container>
126 inline typename Container::iterator findAlias( const std::string & alias_r, Container & cont_r )
127 { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
129 template <class Container>
130 inline typename Container::const_iterator findAlias( const std::string & alias_r, const Container & cont_r )
131 { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
134 ///////////////////////////////////////////////////////////////////
136 // CLASS NAME : RepoManagerOptions
138 ///////////////////////////////////////////////////////////////////
140 RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
142 repoCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
143 repoRawCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
144 repoSolvCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
145 repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
146 knownReposPath = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
147 knownServicesPath = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
148 pluginsPath = Pathname::assertprefix( root_r, ZConfig::instance().pluginsPath() );
149 probe = ZConfig::instance().repo_add_probe();
154 RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r )
156 RepoManagerOptions ret;
157 ret.repoCachePath = root_r;
158 ret.repoRawCachePath = root_r/"raw";
159 ret.repoSolvCachePath = root_r/"solv";
160 ret.repoPackagesCachePath = root_r/"packages";
161 ret.knownReposPath = root_r/"repos.d";
162 ret.knownServicesPath = root_r/"services.d";
163 ret.pluginsPath = root_r/"plugins";
164 ret.rootDir = root_r;
168 ////////////////////////////////////////////////////////////////////////////
171 * \short Simple callback to collect the results
173 * Classes like RepoFileParser call the callback
174 * once per each repo in a file.
176 * Passing this functor as callback, you can collect
177 * all results at the end, without dealing with async
180 * If targetDistro is set, all repos with non-empty RepoInfo::targetDistribution()
183 * \todo do this through a separate filter
185 struct RepoCollector : private base::NonCopyable
190 RepoCollector(const std::string & targetDistro_)
191 : targetDistro(targetDistro_)
194 bool collect( const RepoInfo &repo )
196 // skip repositories meant for other distros than specified
197 if (!targetDistro.empty()
198 && !repo.targetDistribution().empty()
199 && repo.targetDistribution() != targetDistro)
202 << "Skipping repository meant for '" << targetDistro
203 << "' distribution (current distro is '"
204 << repo.targetDistribution() << "')." << endl;
209 repos.push_back(repo);
214 std::string targetDistro;
217 ////////////////////////////////////////////////////////////////////////////
220 * Reads RepoInfo's from a repo file.
222 * \param file pathname of the file to read.
224 static std::list<RepoInfo> repositories_in_file( const Pathname & file )
226 MIL << "repo file: " << file << endl;
227 RepoCollector collector;
228 parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
229 return collector.repos;
232 ////////////////////////////////////////////////////////////////////////////
235 * \short List of RepoInfo's from a directory
237 * Goes trough every file ending with ".repo" in a directory and adds all
238 * RepoInfo's contained in that file.
240 * \param dir pathname of the directory to read.
242 static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
244 MIL << "directory " << dir << endl;
245 std::list<RepoInfo> repos;
246 std::list<Pathname> entries;
247 if ( filesystem::readdir( entries, dir, false ) != 0 )
249 // TranslatorExplanation '%s' is a pathname
250 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
253 str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
254 for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
256 if (str::regex_match(it->extension(), allowedRepoExt))
258 std::list<RepoInfo> tmp = repositories_in_file( *it );
259 repos.insert( repos.end(), tmp.begin(), tmp.end() );
261 //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
262 //MIL << "ok" << endl;
268 ////////////////////////////////////////////////////////////////////////////
270 std::list<RepoInfo> readRepoFile(const Url & repo_file)
272 // no interface to download a specific file, using workaround:
273 //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
275 Pathname path(url.getPathName());
276 url.setPathName ("/");
277 MediaSetAccess access(url);
278 Pathname local = access.provideFile(path);
280 DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
282 return repositories_in_file(local);
285 ////////////////////////////////////////////////////////////////////////////
287 inline void assert_alias( const RepoInfo & info )
289 if ( info.alias().empty() )
290 ZYPP_THROW( RepoNoAliasException() );
291 // bnc #473834. Maybe we can match the alias against a regex to define
292 // and check for valid aliases
293 if ( info.alias()[0] == '.')
294 ZYPP_THROW(RepoInvalidAliasException(
295 info, _("Repository alias cannot start with dot.")));
298 inline void assert_alias( const ServiceInfo & info )
300 if ( info.alias().empty() )
301 ZYPP_THROW( ServiceNoAliasException() );
302 // bnc #473834. Maybe we can match the alias against a regex to define
303 // and check for valid aliases
304 if ( info.alias()[0] == '.')
305 ZYPP_THROW(ServiceInvalidAliasException(
306 info, _("Service alias cannot start with dot.")));
309 ////////////////////////////////////////////////////////////////////////////
311 inline void assert_urls( const RepoInfo & info )
313 if ( info.baseUrlsEmpty() )
314 ZYPP_THROW( RepoNoUrlException( info ) );
317 inline void assert_url( const ServiceInfo & info )
319 if ( ! info.url().isValid() )
320 ZYPP_THROW( ServiceNoUrlException( info ) );
323 ////////////////////////////////////////////////////////////////////////////
326 * \short Calculates the raw cache path for a repository, this is usually
327 * /var/cache/zypp/alias
329 inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
332 return opt.repoRawCachePath / info.escaped_alias();
336 * \short Calculates the raw product metadata path for a repository, this is
337 * inside the raw cache dir, plus an optional path where the metadata is.
339 * It should be different only for repositories that are not in the root of
341 * for example /var/cache/zypp/alias/addondir
343 inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
346 return opt.repoRawCachePath / info.escaped_alias() / info.path();
351 * \short Calculates the packages cache path for a repository
353 inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
356 return opt.repoPackagesCachePath / info.escaped_alias();
360 * \short Calculates the solv cache path for a repository
362 inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
365 return opt.repoSolvCachePath / info.escaped_alias();
368 ////////////////////////////////////////////////////////////////////////////
370 /** Functor collecting ServiceInfos into a ServiceSet. */
371 class ServiceCollector
374 typedef std::set<ServiceInfo> ServiceSet;
376 ServiceCollector( ServiceSet & services_r )
377 : _services( services_r )
380 bool operator()( const ServiceInfo & service_r ) const
382 _services.insert( service_r );
387 ServiceSet & _services;
390 ////////////////////////////////////////////////////////////////////////////
392 ///////////////////////////////////////////////////////////////////
394 // CLASS NAME : RepoManager::Impl
396 ///////////////////////////////////////////////////////////////////
399 * \short RepoManager implementation.
401 struct RepoManager::Impl
403 Impl( const RepoManagerOptions &opt )
406 init_knownServices();
407 init_knownRepositories();
411 RepoManagerOptions options;
419 void saveService( ServiceInfo & service ) const;
421 Pathname generateNonExistingName( const Pathname &dir,
422 const std::string &basefilename ) const;
424 std::string generateFilename( const RepoInfo & info ) const;
425 std::string generateFilename( const ServiceInfo & info ) const;
429 void init_knownServices();
430 void init_knownRepositories();
433 friend Impl * rwcowClone<Impl>( const Impl * rhs );
434 /** clone for RWCOW_pointer */
436 { return new Impl( *this ); }
439 ///////////////////////////////////////////////////////////////////
441 /** \relates RepoManager::Impl Stream output */
442 inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
444 return str << "RepoManager::Impl";
447 ///////////////////////////////////////////////////////////////////
449 void RepoManager::Impl::saveService( ServiceInfo & service ) const
451 filesystem::assert_dir( options.knownServicesPath );
452 Pathname servfile = generateNonExistingName( options.knownServicesPath,
453 generateFilename( service ) );
454 service.setFilepath( servfile );
456 MIL << "saving service in " << servfile << endl;
458 std::ofstream file( servfile.c_str() );
461 // TranslatorExplanation '%s' is a filename
462 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() )));
464 service.dumpAsIniOn( file );
465 MIL << "done" << endl;
469 * Generate a non existing filename in a directory, using a base
470 * name. For example if a directory contains 3 files
476 * If you try to generate a unique filename for this directory,
477 * based on "ruu" you will get "ruu", but if you use the base
478 * "foo" you will get "foo_1"
480 * \param dir Directory where the file needs to be unique
481 * \param basefilename string to base the filename on.
483 Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir,
484 const std::string & basefilename ) const
486 std::string final_filename = basefilename;
488 while ( PathInfo(dir + final_filename).isExist() )
490 final_filename = basefilename + "_" + str::numstring(counter);
493 return dir + Pathname(final_filename);
496 ////////////////////////////////////////////////////////////////////////////
499 * \short Generate a related filename from a repo info
501 * From a repo info, it will try to use the alias as a filename
502 * escaping it if necessary. Other fallbacks can be added to
503 * this function in case there is no way to use the alias
505 std::string RepoManager::Impl::generateFilename( const RepoInfo & info ) const
507 std::string filename = info.alias();
508 // replace slashes with underscores
509 str::replaceAll( filename, "/", "_" );
511 filename = Pathname(filename).extend(".repo").asString();
512 MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
516 std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const
518 std::string filename = info.alias();
519 // replace slashes with underscores
520 str::replaceAll( filename, "/", "_" );
522 filename = Pathname(filename).extend(".service").asString();
523 MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl;
528 void RepoManager::Impl::init_knownServices()
530 Pathname dir = options.knownServicesPath;
531 std::list<Pathname> entries;
532 if (PathInfo(dir).isExist())
534 if ( filesystem::readdir( entries, dir, false ) != 0 )
536 // TranslatorExplanation '%s' is a pathname
537 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
540 //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
541 for_(it, entries.begin(), entries.end() )
543 parser::ServiceFileReader(*it, ServiceCollector(services));
547 repo::PluginServices(options.pluginsPath/"services", ServiceCollector(services));
550 void RepoManager::Impl::init_knownRepositories()
552 MIL << "start construct known repos" << endl;
554 if ( PathInfo(options.knownReposPath).isExist() )
556 RepoInfoList repol = repositories_in_dir(options.knownReposPath);
557 for ( RepoInfoList::iterator it = repol.begin();
561 // set the metadata path for the repo
562 Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it));
563 (*it).setMetadataPath(metadata_path);
565 // set the downloaded packages path for the repo
566 Pathname packages_path = packagescache_path_for_repoinfo(options, (*it));
567 (*it).setPackagesPath(packages_path);
573 MIL << "end construct known repos" << endl;
576 ///////////////////////////////////////////////////////////////////
578 // CLASS NAME : RepoManager
580 ///////////////////////////////////////////////////////////////////
582 RepoManager::RepoManager( const RepoManagerOptions &opt )
583 : _pimpl( new Impl(opt) )
586 ////////////////////////////////////////////////////////////////////////////
588 RepoManager::~RepoManager()
591 ////////////////////////////////////////////////////////////////////////////
593 bool RepoManager::repoEmpty() const
594 { return _pimpl->repos.empty(); }
596 RepoManager::RepoSizeType RepoManager::repoSize() const
597 { return _pimpl->repos.size(); }
599 RepoManager::RepoConstIterator RepoManager::repoBegin() const
600 { return _pimpl->repos.begin(); }
602 RepoManager::RepoConstIterator RepoManager::repoEnd() const
603 { return _pimpl->repos.end(); }
605 RepoInfo RepoManager::getRepo( const std::string & alias ) const
607 for_( it, repoBegin(), repoEnd() )
608 if ( it->alias() == alias )
610 return RepoInfo::noRepo;
613 bool RepoManager::hasRepo( const std::string & alias ) const
615 for_( it, repoBegin(), repoEnd() )
616 if ( it->alias() == alias )
621 std::string RepoManager::makeStupidAlias( const Url & url_r )
623 std::string ret( url_r.getScheme() );
629 std::string host( url_r.getHost() );
630 if ( ! host.empty() )
636 static Date::ValueType serial = Date::now();
637 ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8);
641 ////////////////////////////////////////////////////////////////////////////
643 Pathname RepoManager::metadataPath( const RepoInfo &info ) const
645 return rawcache_path_for_repoinfo(_pimpl->options, info );
648 Pathname RepoManager::packagesPath( const RepoInfo &info ) const
650 return packagescache_path_for_repoinfo(_pimpl->options, info );
653 ////////////////////////////////////////////////////////////////////////////
655 RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
657 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
658 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
659 RepoType repokind = info.type();
662 switch ( repokind.toEnum() )
664 case RepoType::NONE_e:
665 // unknown, probe the local metadata
666 repokind = probe( productdatapath.asUrl() );
672 switch ( repokind.toEnum() )
674 case RepoType::RPMMD_e :
676 status = RepoStatus( productdatapath + "/repodata/repomd.xml");
680 case RepoType::YAST2_e :
682 status = RepoStatus( productdatapath + "/content") && (RepoStatus( mediarootpath + "/media.1/media"));
686 case RepoType::RPMPLAINDIR_e :
688 if ( PathInfo(Pathname(productdatapath + "/cookie")).isExist() )
689 status = RepoStatus( productdatapath + "/cookie");
693 case RepoType::NONE_e :
694 // Return default RepoStatus in case of RepoType::NONE
695 // indicating it should be created?
696 // ZYPP_THROW(RepoUnknownTypeException());
702 void RepoManager::touchIndexFile(const RepoInfo & info)
704 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
706 RepoType repokind = info.type();
707 if ( repokind.toEnum() == RepoType::NONE_e )
708 // unknown, probe the local metadata
709 repokind = probe( productdatapath.asUrl() );
710 // if still unknown, just return
711 if (repokind == RepoType::NONE_e)
715 switch ( repokind.toEnum() )
717 case RepoType::RPMMD_e :
718 p = Pathname(productdatapath + "/repodata/repomd.xml");
721 case RepoType::YAST2_e :
722 p = Pathname(productdatapath + "/content");
725 case RepoType::RPMPLAINDIR_e :
726 p = Pathname(productdatapath + "/cookie");
729 case RepoType::NONE_e :
734 // touch the file, ignore error (they are logged anyway)
735 filesystem::touch(p);
738 RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata(
739 const RepoInfo &info,
741 RawMetadataRefreshPolicy policy )
745 RepoStatus oldstatus;
746 RepoStatus newstatus;
750 MIL << "Going to try to check whether refresh is needed for " << url << endl;
752 // first check old (cached) metadata
753 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
754 filesystem::assert_dir(mediarootpath);
755 oldstatus = metadataStatus(info);
757 if ( oldstatus.empty() )
759 MIL << "No cached metadata, going to refresh" << endl;
760 return REFRESH_NEEDED;
764 std::string scheme( url.getScheme() );
765 if ( scheme == "cd" || scheme == "dvd" )
767 MIL << "never refresh CD/DVD" << endl;
768 return REPO_UP_TO_DATE;
772 // now we've got the old (cached) status, we can decide repo.refresh.delay
773 if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
775 // difference in seconds
776 double diff = difftime(
777 (Date::ValueType)Date::now(),
778 (Date::ValueType)oldstatus.timestamp()) / 60;
780 DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
781 DBG << "current time: " << (Date::ValueType)Date::now() << endl;
782 DBG << "last refresh = " << diff << " minutes ago" << endl;
784 if ( diff < ZConfig::instance().repo_refresh_delay() )
788 WAR << "Repository '" << info.alias() << "' was refreshed in the future!" << endl;
792 MIL << "Repository '" << info.alias()
793 << "' has been refreshed less than repo.refresh.delay ("
794 << ZConfig::instance().repo_refresh_delay()
795 << ") minutes ago. Advising to skip refresh" << endl;
796 return REPO_CHECK_DELAYED;
801 // To test the new matadta create temp dir as sibling of mediarootpath
802 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
804 repo::RepoType repokind = info.type();
805 // if the type is unknown, try probing.
806 switch ( repokind.toEnum() )
808 case RepoType::NONE_e:
809 // unknown, probe it \todo respect productdir
810 repokind = probe( url, info.path() );
816 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
817 ( repokind.toEnum() == RepoType::YAST2_e ) )
819 MediaSetAccess media(url);
820 shared_ptr<repo::Downloader> downloader_ptr;
822 if ( repokind.toEnum() == RepoType::RPMMD_e )
823 downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
825 downloader_ptr.reset( new susetags::Downloader(info, mediarootpath));
827 RepoStatus newstatus = downloader_ptr->status(media);
828 bool refresh = false;
829 if ( oldstatus.checksum() == newstatus.checksum() )
831 MIL << "repo has not changed" << endl;
832 if ( policy == RefreshForced )
834 MIL << "refresh set to forced" << endl;
840 MIL << "repo has changed, going to refresh" << endl;
845 touchIndexFile(info);
847 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
849 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
851 MediaMounter media( url );
852 RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
853 bool refresh = false;
854 if ( oldstatus.checksum() == newstatus.checksum() )
856 MIL << "repo has not changed" << endl;
857 if ( policy == RefreshForced )
859 MIL << "refresh set to forced" << endl;
865 MIL << "repo has changed, going to refresh" << endl;
870 touchIndexFile(info);
872 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
876 ZYPP_THROW(RepoUnknownTypeException(info));
879 catch ( const Exception &e )
882 ERR << "refresh check failed for " << url << endl;
886 return REFRESH_NEEDED; // default
889 void RepoManager::refreshMetadata( const RepoInfo &info,
890 RawMetadataRefreshPolicy policy,
891 const ProgressData::ReceiverFnc & progress )
896 // we will throw this later if no URL checks out fine
897 RepoException rexception(_("Valid metadata not found at specified URL(s)"));
899 // try urls one by one
900 for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
906 // check whether to refresh metadata
907 // if the check fails for this url, it throws, so another url will be checked
908 if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
911 MIL << "Going to refresh metadata from " << url << endl;
913 repo::RepoType repokind = info.type();
915 // if the type is unknown, try probing.
916 switch ( repokind.toEnum() )
918 case RepoType::NONE_e:
920 repokind = probe( *it, info.path() );
922 if (repokind.toEnum() != RepoType::NONE_e)
924 // Adjust the probed type in RepoInfo
925 info.setProbedType( repokind ); // lazy init!
926 //save probed type only for repos in system
927 for_( it, repoBegin(), repoEnd() )
929 if ( info.alias() == (*it).alias() )
931 RepoInfo modifiedrepo = info;
932 modifiedrepo.setType( repokind );
933 modifyRepository( info.alias(), modifiedrepo );
943 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
944 filesystem::assert_dir(mediarootpath);
946 // create temp dir as sibling of mediarootpath
947 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
949 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
950 ( repokind.toEnum() == RepoType::YAST2_e ) )
952 MediaSetAccess media(url);
953 shared_ptr<repo::Downloader> downloader_ptr;
955 MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
957 if ( repokind.toEnum() == RepoType::RPMMD_e )
958 downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
960 downloader_ptr.reset( new susetags::Downloader(info, mediarootpath) );
963 * Given a downloader, sets the other repos raw metadata
964 * path as cache paths for the fetcher, so if another
965 * repo has the same file, it will not download it
966 * but copy it from the other repository
968 for_( it, repoBegin(), repoEnd() )
970 Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
971 if ( PathInfo(cachepath).isExist() )
972 downloader_ptr->addCachePath(cachepath);
975 downloader_ptr->download( media, tmpdir.path() );
977 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
979 MediaMounter media( url );
980 RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
982 Pathname productpath( tmpdir.path() / info.path() );
983 filesystem::assert_dir( productpath );
984 std::ofstream file( (productpath/"cookie").c_str() );
987 // TranslatorExplanation '%s' is a filename
988 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() )));
991 if ( ! info.path().empty() && info.path() != "/" )
992 file << " (" << info.path() << ")";
994 file << newstatus.checksum() << endl;
1000 ZYPP_THROW(RepoUnknownTypeException());
1003 // ok we have the metadata, now exchange
1005 filesystem::exchange( tmpdir.path(), mediarootpath );
1010 catch ( const Exception &e )
1013 ERR << "Trying another url..." << endl;
1015 // remember the exception caught for the *first URL*
1016 // if all other URLs fail, the rexception will be thrown with the
1017 // cause of the problem of the first URL remembered
1018 if (it == info.baseUrlsBegin())
1019 rexception.remember(e);
1022 ERR << "No more urls..." << endl;
1023 ZYPP_THROW(rexception);
1026 ////////////////////////////////////////////////////////////////////////////
1028 void RepoManager::cleanMetadata( const RepoInfo &info,
1029 const ProgressData::ReceiverFnc & progressfnc )
1031 ProgressData progress(100);
1032 progress.sendTo(progressfnc);
1034 filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
1038 void RepoManager::cleanPackages( const RepoInfo &info,
1039 const ProgressData::ReceiverFnc & progressfnc )
1041 ProgressData progress(100);
1042 progress.sendTo(progressfnc);
1044 filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
1048 void RepoManager::buildCache( const RepoInfo &info,
1049 CacheBuildPolicy policy,
1050 const ProgressData::ReceiverFnc & progressrcv )
1053 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
1054 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
1056 filesystem::assert_dir(_pimpl->options.repoCachePath);
1057 RepoStatus raw_metadata_status = metadataStatus(info);
1058 if ( raw_metadata_status.empty() )
1060 /* if there is no cache at this point, we refresh the raw
1061 in case this is the first time - if it's !autorefresh,
1062 we may still refresh */
1063 refreshMetadata(info, RefreshIfNeeded, progressrcv );
1064 raw_metadata_status = metadataStatus(info);
1067 bool needs_cleaning = false;
1068 if ( isCached( info ) )
1070 MIL << info.alias() << " is already cached." << endl;
1071 RepoStatus cache_status = cacheStatus(info);
1073 if ( cache_status.checksum() == raw_metadata_status.checksum() )
1075 MIL << info.alias() << " cache is up to date with metadata." << endl;
1076 if ( policy == BuildIfNeeded ) {
1080 MIL << info.alias() << " cache rebuild is forced" << endl;
1084 needs_cleaning = true;
1087 ProgressData progress(100);
1088 callback::SendReport<ProgressReport> report;
1089 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1090 progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str()));
1098 MIL << info.alias() << " building cache..." << info.type() << endl;
1100 Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
1101 filesystem::assert_dir(base);
1102 Pathname solvfile = base / "solv";
1105 repo::RepoType repokind = info.type();
1107 // if the type is unknown, try probing.
1108 switch ( repokind.toEnum() )
1110 case RepoType::NONE_e:
1111 // unknown, probe the local metadata
1112 repokind = probe( productdatapath.asUrl() );
1118 MIL << "repo type is " << repokind << endl;
1120 switch ( repokind.toEnum() )
1122 case RepoType::RPMMD_e :
1123 case RepoType::YAST2_e :
1124 case RepoType::RPMPLAINDIR_e :
1126 // Take care we unlink the solvfile on exception
1127 ManagedFile guard( solvfile, filesystem::unlink );
1128 scoped_ptr<MediaMounter> forPlainDirs;
1130 ExternalProgram::Arguments cmd;
1131 cmd.push_back( "repo2solv.sh" );
1133 // repo2solv expects -o as 1st arg!
1134 cmd.push_back( "-o" );
1135 cmd.push_back( solvfile.asString() );
1137 if ( repokind == RepoType::RPMPLAINDIR )
1139 forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
1140 // recusive for plaindir as 2nd arg!
1141 cmd.push_back( "-R" );
1142 // FIXME this does only work form dir: URLs
1143 cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
1146 cmd.push_back( productdatapath.asString() );
1148 ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
1149 std::string errdetail;
1151 for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1152 WAR << " " << output;
1153 if ( errdetail.empty() ) {
1154 errdetail = prog.command();
1157 errdetail += output;
1160 int ret = prog.close();
1163 RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
1164 ex.remember( errdetail );
1169 guard.resetDispose();
1173 ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
1176 // update timestamp and checksum
1177 setCacheStatus(info, raw_metadata_status);
1178 MIL << "Commit cache.." << endl;
1182 ////////////////////////////////////////////////////////////////////////////
1184 repo::RepoType RepoManager::probe( const Url & url ) const
1185 { return probe( url, Pathname() ); }
1187 repo::RepoType RepoManager::probe( const Url & url, const Pathname & path ) const
1189 MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
1191 if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
1193 // Handle non existing local directory in advance, as
1194 // MediaSetAccess does not support it.
1195 MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
1196 return repo::RepoType::NONE;
1199 // prepare exception to be thrown if the type could not be determined
1200 // due to a media exception. We can't throw right away, because of some
1201 // problems with proxy servers returning an incorrect error
1202 // on ftp file-not-found(bnc #335906). Instead we'll check another types
1205 // TranslatorExplanation '%s' is an URL
1206 RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
1207 bool gotMediaException = false;
1210 MediaSetAccess access(url);
1213 if ( access.doesFileExist(path/"/repodata/repomd.xml") )
1215 MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
1216 return repo::RepoType::RPMMD;
1219 catch ( const media::MediaException &e )
1222 DBG << "problem checking for repodata/repomd.xml file" << endl;
1224 gotMediaException = true;
1229 if ( access.doesFileExist(path/"/content") )
1231 MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
1232 return repo::RepoType::YAST2;
1235 catch ( const media::MediaException &e )
1238 DBG << "problem checking for content file" << endl;
1240 gotMediaException = true;
1243 // if it is a non-downloading URL denoting a directory
1244 if ( ! url.schemeIsDownloading() )
1246 MediaMounter media( url );
1247 if ( PathInfo(media.getPathName()/path).isDir() )
1249 // allow empty dirs for now
1250 MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
1251 return repo::RepoType::RPMPLAINDIR;
1255 catch ( const Exception &e )
1258 // TranslatorExplanation '%s' is an URL
1259 Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
1264 if (gotMediaException)
1267 MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
1268 return repo::RepoType::NONE;
1271 ////////////////////////////////////////////////////////////////////////////
1273 void RepoManager::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
1275 MIL << "Going to clean up garbage in cache dirs" << endl;
1277 ProgressData progress(300);
1278 progress.sendTo(progressrcv);
1281 std::list<Pathname> cachedirs;
1282 cachedirs.push_back(_pimpl->options.repoRawCachePath);
1283 cachedirs.push_back(_pimpl->options.repoPackagesCachePath);
1284 cachedirs.push_back(_pimpl->options.repoSolvCachePath);
1286 for_( dir, cachedirs.begin(), cachedirs.end() )
1288 if ( PathInfo(*dir).isExist() )
1290 std::list<Pathname> entries;
1291 if ( filesystem::readdir( entries, *dir, false ) != 0 )
1292 // TranslatorExplanation '%s' is a pathname
1293 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str())));
1295 unsigned sdircount = entries.size();
1296 unsigned sdircurrent = 1;
1297 for_( subdir, entries.begin(), entries.end() )
1299 // if it does not belong known repo, make it disappear
1301 for_( r, repoBegin(), repoEnd() )
1302 if ( subdir->basename() == r->escaped_alias() )
1303 { found = true; break; }
1306 filesystem::recursive_rmdir( *subdir );
1308 progress.set( progress.val() + sdircurrent * 100 / sdircount );
1313 progress.set( progress.val() + 100 );
1318 ////////////////////////////////////////////////////////////////////////////
1320 void RepoManager::cleanCache( const RepoInfo &info,
1321 const ProgressData::ReceiverFnc & progressrcv )
1323 ProgressData progress(100);
1324 progress.sendTo(progressrcv);
1327 MIL << "Removing raw metadata cache for " << info.alias() << endl;
1328 filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
1333 ////////////////////////////////////////////////////////////////////////////
1335 bool RepoManager::isCached( const RepoInfo &info ) const
1337 return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
1340 RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
1343 Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
1345 return RepoStatus::fromCookieFile(cookiefile);
1348 void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
1350 Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
1351 filesystem::assert_dir(base);
1352 Pathname cookiefile = base / "cookie";
1354 status.saveToCookieFile(cookiefile);
1357 void RepoManager::loadFromCache( const RepoInfo & info,
1358 const ProgressData::ReceiverFnc & progressrcv )
1361 Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
1363 if ( ! PathInfo(solvfile).isExist() )
1364 ZYPP_THROW(RepoNotCachedException(info));
1368 Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info );
1369 // test toolversion in order to rebuild solv file in case
1370 // it was written by an old satsolver-tool parser.
1372 // Known version strings used:
1376 sat::LookupRepoAttr toolversion( sat::SolvAttr::repositoryToolVersion, repo );
1377 if ( toolversion.begin().asString().empty() )
1379 repo.eraseFromPool();
1380 ZYPP_THROW(Exception("Solv-file was created by old parser."));
1382 // else: up-to-date (or even newer).
1384 catch ( const Exception & exp )
1387 MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1388 cleanCache( info, progressrcv );
1389 buildCache( info, BuildIfNeeded, progressrcv );
1391 sat::Pool::instance().addRepoSolv( solvfile, info );
1395 ////////////////////////////////////////////////////////////////////////////
1397 void RepoManager::addRepository( const RepoInfo &info,
1398 const ProgressData::ReceiverFnc & progressrcv )
1402 ProgressData progress(100);
1403 callback::SendReport<ProgressReport> report;
1404 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1405 progress.name(str::form(_("Adding repository '%s'"), info.label().c_str()));
1408 MIL << "Try adding repo " << info << endl;
1410 RepoInfo tosave = info;
1411 if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
1412 ZYPP_THROW(RepoAlreadyExistsException(info));
1414 // check the first url for now
1415 if ( _pimpl->options.probe )
1417 DBG << "unknown repository type, probing" << endl;
1419 RepoType probedtype;
1420 probedtype = probe( *tosave.baseUrlsBegin(), info.path() );
1421 if ( tosave.baseUrlsSize() > 0 )
1423 if ( probedtype == RepoType::NONE )
1424 ZYPP_THROW(RepoUnknownTypeException());
1426 tosave.setType(probedtype);
1432 // assert the directory exists
1433 filesystem::assert_dir(_pimpl->options.knownReposPath);
1435 Pathname repofile = _pimpl->generateNonExistingName(
1436 _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
1437 // now we have a filename that does not exists
1438 MIL << "Saving repo in " << repofile << endl;
1440 std::ofstream file(repofile.c_str());
1443 // TranslatorExplanation '%s' is a filename
1444 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1447 tosave.dumpAsIniOn(file);
1448 tosave.setFilepath(repofile);
1449 tosave.setMetadataPath( metadataPath( tosave ) );
1450 tosave.setPackagesPath( packagesPath( tosave ) );
1452 // We chould fix the API as we must injet those paths
1453 // into the repoinfo in order to keep it usable.
1454 RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
1455 oinfo.setMetadataPath( metadataPath( tosave ) );
1456 oinfo.setPackagesPath( packagesPath( tosave ) );
1458 _pimpl->repos.insert(tosave);
1462 // check for credentials in Urls
1463 bool havePasswords = false;
1464 for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
1465 if ( urlit->hasCredentialsInAuthority() )
1467 havePasswords = true;
1470 // save the credentials
1471 if ( havePasswords )
1473 media::CredentialManager cm(
1474 media::CredManagerOptions(_pimpl->options.rootDir) );
1476 for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
1477 if (urlit->hasCredentialsInAuthority())
1478 //! \todo use a method calling UI callbacks to ask where to save creds?
1479 cm.saveInUser(media::AuthData(*urlit));
1482 HistoryLog().addRepository(tosave);
1485 MIL << "done" << endl;
1488 void RepoManager::addRepositories( const Url &url,
1489 const ProgressData::ReceiverFnc & progressrcv )
1491 std::list<RepoInfo> repos = readRepoFile(url);
1492 for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1496 // look if the alias is in the known repos.
1497 for_ ( kit, repoBegin(), repoEnd() )
1499 if ( (*it).alias() == (*kit).alias() )
1501 ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1502 ZYPP_THROW(RepoAlreadyExistsException(*it));
1507 std::string filename = Pathname(url.getPathName()).basename();
1509 if ( filename == Pathname() )
1511 // TranslatorExplanation '%s' is an URL
1512 ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
1515 // assert the directory exists
1516 filesystem::assert_dir(_pimpl->options.knownReposPath);
1518 Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
1519 // now we have a filename that does not exists
1520 MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1522 std::ofstream file(repofile.c_str());
1525 // TranslatorExplanation '%s' is a filename
1526 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1529 for ( std::list<RepoInfo>::iterator it = repos.begin();
1533 MIL << "Saving " << (*it).alias() << endl;
1534 it->setFilepath(repofile.asString());
1535 it->dumpAsIniOn(file);
1536 _pimpl->repos.insert(*it);
1538 HistoryLog(_pimpl->options.rootDir).addRepository(*it);
1541 MIL << "done" << endl;
1544 ////////////////////////////////////////////////////////////////////////////
1546 void RepoManager::removeRepository( const RepoInfo & info,
1547 const ProgressData::ReceiverFnc & progressrcv)
1549 ProgressData progress;
1550 callback::SendReport<ProgressReport> report;
1551 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1552 progress.name(str::form(_("Removing repository '%s'"), info.label().c_str()));
1554 MIL << "Going to delete repo " << info.alias() << endl;
1556 for_( it, repoBegin(), repoEnd() )
1558 // they can be the same only if the provided is empty, that means
1559 // the provided repo has no alias
1561 if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1564 // TODO match by url
1566 // we have a matcing repository, now we need to know
1567 // where it does come from.
1568 RepoInfo todelete = *it;
1569 if (todelete.filepath().empty())
1571 ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1575 // figure how many repos are there in the file:
1576 std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1577 if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1579 // easy, only this one, just delete the file
1580 if ( filesystem::unlink(todelete.filepath()) != 0 )
1582 // TranslatorExplanation '%s' is a filename
1583 ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
1585 MIL << todelete.alias() << " sucessfully deleted." << endl;
1589 // there are more repos in the same file
1590 // write them back except the deleted one.
1592 //std::ofstream file(tmp.path().c_str());
1594 // assert the directory exists
1595 filesystem::assert_dir(todelete.filepath().dirname());
1597 std::ofstream file(todelete.filepath().c_str());
1600 // TranslatorExplanation '%s' is a filename
1601 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
1603 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1604 fit != filerepos.end();
1607 if ( (*fit).alias() != todelete.alias() )
1608 (*fit).dumpAsIniOn(file);
1612 CombinedProgressData subprogrcv(progress, 70);
1613 CombinedProgressData cleansubprogrcv(progress, 30);
1614 // now delete it from cache
1615 if ( isCached(todelete) )
1616 cleanCache( todelete, subprogrcv);
1617 // now delete metadata (#301037)
1618 cleanMetadata( todelete, cleansubprogrcv);
1619 _pimpl->repos.erase(todelete);
1620 MIL << todelete.alias() << " sucessfully deleted." << endl;
1621 HistoryLog(_pimpl->options.rootDir).removeRepository(todelete);
1623 } // else filepath is empty
1626 // should not be reached on a sucess workflow
1627 ZYPP_THROW(RepoNotFoundException(info));
1630 ////////////////////////////////////////////////////////////////////////////
1632 void RepoManager::modifyRepository( const std::string &alias,
1633 const RepoInfo & newinfo_r,
1634 const ProgressData::ReceiverFnc & progressrcv )
1636 RepoInfo toedit = getRepositoryInfo(alias);
1637 RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
1639 // check if the new alias already exists when renaming the repo
1640 if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
1642 ZYPP_THROW(RepoAlreadyExistsException(newinfo));
1645 if (toedit.filepath().empty())
1647 ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1651 // figure how many repos are there in the file:
1652 std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1654 // there are more repos in the same file
1655 // write them back except the deleted one.
1657 //std::ofstream file(tmp.path().c_str());
1659 // assert the directory exists
1660 filesystem::assert_dir(toedit.filepath().dirname());
1662 std::ofstream file(toedit.filepath().c_str());
1665 // TranslatorExplanation '%s' is a filename
1666 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
1668 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1669 fit != filerepos.end();
1672 // if the alias is different, dump the original
1673 // if it is the same, dump the provided one
1674 if ( (*fit).alias() != toedit.alias() )
1675 (*fit).dumpAsIniOn(file);
1677 newinfo.dumpAsIniOn(file);
1680 newinfo.setFilepath(toedit.filepath());
1681 _pimpl->repos.erase(toedit);
1682 _pimpl->repos.insert(newinfo);
1683 HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
1684 MIL << "repo " << alias << " modified" << endl;
1688 ////////////////////////////////////////////////////////////////////////////
1690 RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1691 const ProgressData::ReceiverFnc & progressrcv )
1694 info.setAlias(alias);
1695 RepoConstIterator it = _pimpl->repos.find( info );
1696 if( it == repoEnd() )
1697 ZYPP_THROW(RepoNotFoundException(info));
1702 ////////////////////////////////////////////////////////////////////////////
1704 RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1705 const url::ViewOption & urlview,
1706 const ProgressData::ReceiverFnc & progressrcv )
1708 for_( it, repoBegin(), repoEnd() )
1710 for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1711 urlit != (*it).baseUrlsEnd();
1714 if ((*urlit).asString(urlview) == url.asString(urlview))
1719 info.setBaseUrl(url);
1720 ZYPP_THROW(RepoNotFoundException(info));
1723 ////////////////////////////////////////////////////////////////////////////
1727 ////////////////////////////////////////////////////////////////////////////
1729 bool RepoManager::serviceEmpty() const
1730 { return _pimpl->services.empty(); }
1732 RepoManager::ServiceSizeType RepoManager::serviceSize() const
1733 { return _pimpl->services.size(); }
1735 RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
1736 { return _pimpl->services.begin(); }
1738 RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
1739 { return _pimpl->services.end(); }
1741 ServiceInfo RepoManager::getService( const std::string & alias ) const
1743 for_( it, serviceBegin(), serviceEnd() )
1744 if ( it->alias() == alias )
1746 return ServiceInfo::noService;
1749 bool RepoManager::hasService( const std::string & alias ) const
1751 for_( it, serviceBegin(), serviceEnd() )
1752 if ( it->alias() == alias )
1757 ////////////////////////////////////////////////////////////////////////////
1759 void RepoManager::addService( const std::string & alias, const Url & url )
1761 addService( ServiceInfo(alias, url) );
1764 void RepoManager::addService( const ServiceInfo & service )
1766 assert_alias( service );
1768 // check if service already exists
1769 if ( hasService( service.alias() ) )
1770 ZYPP_THROW( ServiceAlreadyExistsException( service ) );
1772 // Writable ServiceInfo is needed to save the location
1773 // of the .service file. Finaly insert into the service list.
1774 ServiceInfo toSave( service );
1775 _pimpl->saveService( toSave );
1776 _pimpl->services.insert( toSave );
1778 // check for credentials in Url (username:password, not ?credentials param)
1779 if ( toSave.url().hasCredentialsInAuthority() )
1781 media::CredentialManager cm(
1782 media::CredManagerOptions(_pimpl->options.rootDir) );
1784 //! \todo use a method calling UI callbacks to ask where to save creds?
1785 cm.saveInUser(media::AuthData(toSave.url()));
1788 MIL << "added service " << toSave.alias() << endl;
1791 ////////////////////////////////////////////////////////////////////////////
1793 void RepoManager::removeService( const std::string & alias )
1795 MIL << "Going to delete repo " << alias << endl;
1797 const ServiceInfo & service = getService( alias );
1799 Pathname location = service.filepath();
1800 if( location.empty() )
1802 ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1806 parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1808 // only one service definition in the file
1809 if ( tmpSet.size() == 1 )
1811 if ( filesystem::unlink(location) != 0 )
1813 // TranslatorExplanation '%s' is a filename
1814 ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
1816 MIL << alias << " sucessfully deleted." << endl;
1820 filesystem::assert_dir(location.dirname());
1822 std::ofstream file(location.c_str());
1825 // TranslatorExplanation '%s' is a filename
1826 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
1829 for_(it, tmpSet.begin(), tmpSet.end())
1831 if( it->alias() != alias )
1832 it->dumpAsIniOn(file);
1835 MIL << alias << " sucessfully deleted from file " << location << endl;
1838 // now remove all repositories added by this service
1839 RepoCollector rcollector;
1840 getRepositoriesInService( alias,
1841 boost::make_function_output_iterator(
1842 bind( &RepoCollector::collect, &rcollector, _1 ) ) );
1843 // cannot do this directly in getRepositoriesInService - would invalidate iterators
1844 for_(rit, rcollector.repos.begin(), rcollector.repos.end())
1845 removeRepository(*rit);
1848 void RepoManager::removeService( const ServiceInfo & service )
1849 { removeService(service.alias()); }
1851 ////////////////////////////////////////////////////////////////////////////
1853 void RepoManager::refreshServices()
1855 // copy the set of services since refreshService
1856 // can eventually invalidate the iterator
1857 ServiceSet services( serviceBegin(), serviceEnd() );
1858 for_( it, services.begin(), services.end() )
1860 if ( !it->enabled() )
1863 refreshService(*it);
1867 void RepoManager::refreshService( const ServiceInfo & service )
1868 { refreshService( service.alias() ); }
1870 void RepoManager::refreshService( const std::string & alias )
1872 ServiceInfo service( getService( alias ) );
1873 assert_alias( service );
1874 assert_url( service );
1875 // NOTE: It might be necessary to modify and rewrite the service info.
1876 // Either when probing the type, or when adjusting the repositories
1877 // enable/disable state.:
1878 bool serviceModified = false;
1879 MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
1881 //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
1883 // if the type is unknown, try probing.
1884 if ( service.type() == repo::ServiceType::NONE )
1886 repo::ServiceType type = probeService( service.url() );
1887 if ( type != ServiceType::NONE )
1889 service.setProbedType( type ); // lazy init!
1890 serviceModified = true;
1894 // get target distro identifier
1895 std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro;
1896 if ( servicesTargetDistro.empty() && getZYpp()->getTarget() )
1897 servicesTargetDistro = getZYpp()->target()->targetDistribution();
1898 DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
1901 RepoCollector collector(servicesTargetDistro);
1902 ServiceRepos repos(service, bind( &RepoCollector::collect, &collector, _1 ));
1904 // set service alias and base url for all collected repositories
1905 for_( it, collector.repos.begin(), collector.repos.end() )
1907 // if the repo url was not set by the repoindex parser, set service's url
1910 if ( it->baseUrlsEmpty() )
1911 url = service.url();
1914 // service repo can contain only one URL now, so no need to iterate.
1915 url = *it->baseUrlsBegin();
1918 // libzypp currently has problem with separate url + path handling
1919 // so just append the path to the baseurl
1920 if ( !it->path().empty() )
1922 Pathname path(url.getPathName());
1924 url.setPathName( path.asString() );
1928 // Prepend service alias:
1929 it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
1932 it->setBaseUrl( url );
1933 // set refrence to the parent service
1934 it->setService( service.alias() );
1937 ////////////////////////////////////////////////////////////////////////////
1938 // Now compare collected repos with the ones in the system...
1940 RepoInfoList oldRepos;
1941 getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
1943 // find old repositories to remove...
1944 for_( it, oldRepos.begin(), oldRepos.end() )
1946 if ( ! foundAliasIn( it->alias(), collector.repos ) )
1948 if ( it->enabled() && ! service.repoToDisableFind( it->alias() ) )
1950 DBG << "Service removes enabled repo " << it->alias() << endl;
1951 service.addRepoToEnable( it->alias() );
1952 serviceModified = true;
1956 DBG << "Service removes disabled repo " << it->alias() << endl;
1958 removeRepository( *it );
1962 ////////////////////////////////////////////////////////////////////////////
1963 // create missing repositories and modify exising ones if needed...
1964 for_( it, collector.repos.begin(), collector.repos.end() )
1966 // Service explicitly requests the repo being enabled?
1967 // Service explicitly requests the repo being disabled?
1968 // And hopefully not both ;) If so, enable wins.
1969 bool beEnabled = service.repoToEnableFind( it->alias() );
1970 bool beDisabled = service.repoToDisableFind( it->alias() );
1972 // Make sure the service repo is created with the
1973 // appropriate enable
1974 if ( beEnabled ) it->setEnabled(true);
1975 if ( beDisabled ) it->setEnabled(false);
1979 // Remove from enable request list.
1980 // NOTE: repoToDisable is handled differently.
1981 // It gets cleared on each refresh.
1982 service.delRepoToEnable( it->alias() );
1983 serviceModified = true;
1986 RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
1987 if ( oldRepo == oldRepos.end() )
1989 // Not found in oldRepos ==> a new repo to add
1991 // At that point check whether a repo with the same alias
1992 // exists outside this service. Maybe forcefully re-alias
1993 // the existing repo?
1994 DBG << "Service adds repo " << it->alias() << " " << (it->enabled()?"enabled":"disabled") << endl;
1995 addRepository( *it );
1997 // save repo credentials
1998 // ma@: task for modifyRepository?
2002 // ==> an exising repo to check
2003 bool oldRepoModified = false;
2008 if ( ! oldRepo->enabled() )
2010 DBG << "Service repo " << it->alias() << " gets enabled" << endl;
2011 oldRepo->setEnabled( true );
2012 oldRepoModified = true;
2016 DBG << "Service repo " << it->alias() << " stays enabled" << endl;
2019 else if ( beDisabled )
2021 if ( oldRepo->enabled() )
2023 DBG << "Service repo " << it->alias() << " gets disabled" << endl;
2024 oldRepo->setEnabled( false );
2025 oldRepoModified = true;
2029 DBG << "Service repo " << it->alias() << " stays disabled" << endl;
2034 DBG << "Service repo " << it->alias() << " stays " << (oldRepo->enabled()?"enabled":"disabled") << endl;
2038 // service repo can contain only one URL now, so no need to iterate.
2039 if ( oldRepo->url() != it->url() )
2041 DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
2042 oldRepo->setBaseUrl( it->url() );
2043 oldRepoModified = true;
2046 // save if modified:
2047 if ( oldRepoModified )
2049 modifyRepository( oldRepo->alias(), *oldRepo );
2054 // Unlike reposToEnable, reposToDisable is always cleared after refresh.
2055 if ( ! service.reposToDisableEmpty() )
2057 service.clearReposToDisable();
2058 serviceModified = true;
2061 ////////////////////////////////////////////////////////////////////////////
2062 // save service if modified:
2063 if ( serviceModified )
2065 // write out modified service file.
2066 modifyService( service.alias(), service );
2070 ////////////////////////////////////////////////////////////////////////////
2072 void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & newService)
2074 MIL << "Going to modify service " << oldAlias << endl;
2076 // we need a writable copy to link it to the file where
2077 // it is saved if we modify it
2078 ServiceInfo service(newService);
2080 if ( service.type() == ServiceType::PLUGIN )
2082 MIL << "Not modifying plugin service '" << oldAlias << "'" << endl;
2086 const ServiceInfo & oldService = getService(oldAlias);
2088 Pathname location = oldService.filepath();
2089 if( location.empty() )
2091 ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
2094 // remember: there may multiple services being defined in one file:
2096 parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
2098 filesystem::assert_dir(location.dirname());
2099 std::ofstream file(location.c_str());
2100 for_(it, tmpSet.begin(), tmpSet.end())
2102 if( *it != oldAlias )
2103 it->dumpAsIniOn(file);
2105 service.dumpAsIniOn(file);
2107 service.setFilepath(location);
2109 _pimpl->services.erase(oldAlias);
2110 _pimpl->services.insert(service);
2112 // changed properties affecting also repositories
2113 if( oldAlias != service.alias() // changed alias
2114 || oldService.enabled() != service.enabled() // changed enabled status
2117 std::vector<RepoInfo> toModify;
2118 getRepositoriesInService(oldAlias, std::back_inserter(toModify));
2119 for_( it, toModify.begin(), toModify.end() )
2121 if (oldService.enabled() && !service.enabled())
2122 it->setEnabled(false);
2123 else if (!oldService.enabled() && service.enabled())
2125 //! \todo do nothing? the repos will be enabled on service refresh
2126 //! \todo how to know the service needs a (auto) refresh????
2129 it->setService(service.alias());
2130 modifyRepository(it->alias(), *it);
2134 //! \todo refresh the service automatically if url is changed?
2137 ////////////////////////////////////////////////////////////////////////////
2139 repo::ServiceType RepoManager::probeService( const Url &url ) const
2143 MediaSetAccess access(url);
2144 if ( access.doesFileExist("/repo/repoindex.xml") )
2145 return repo::ServiceType::RIS;
2147 catch ( const media::MediaException &e )
2150 // TranslatorExplanation '%s' is an URL
2151 RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
2155 catch ( const Exception &e )
2158 // TranslatorExplanation '%s' is an URL
2159 Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
2164 return repo::ServiceType::NONE;
2167 ////////////////////////////////////////////////////////////////////////////
2169 std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
2171 return str << *obj._pimpl;
2174 /////////////////////////////////////////////////////////////////
2176 ///////////////////////////////////////////////////////////////////