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/parser/RepoindexFileReader.h"
42 #include "zypp/repo/yum/Downloader.h"
43 #include "zypp/repo/susetags/Downloader.h"
44 #include "zypp/parser/plaindir/RepoParser.h"
46 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
47 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance
48 #include "zypp/HistoryLog.h" // to write history :O)
50 #include "zypp/ZYppCallbacks.h"
56 using namespace zypp::repo;
58 ///////////////////////////////////////////////////////////////////
60 { /////////////////////////////////////////////////////////////////
64 /** Simple media mounter to access non-downloading URLs e.g. for non-local plaindir repos.
70 /** Ctor provides media access. */
71 MediaMounter( const Url & url_r )
73 media::MediaManager mediamanager;
74 _mid = mediamanager.open( url_r );
75 mediamanager.attach( _mid );
78 /** Ctor releases the media. */
81 media::MediaManager mediamanager;
82 mediamanager.release( _mid );
83 mediamanager.close( _mid );
86 /** Convert a path relative to the media into an absolute path.
88 * Called without argument it returns the path to the medias root directory.
90 Pathname getPathName( const Pathname & path_r = Pathname() ) const
92 media::MediaManager mediamanager;
93 return mediamanager.localPath( _mid, path_r );
97 media::MediaAccessId _mid;
100 /** Check if alias_r is present in repo/service container. */
101 template <class Iterator>
102 inline bool foundAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r )
104 for_( it, begin_r, end_r )
105 if ( it->alias() == alias_r )
110 template <class Container>
111 inline bool foundAliasIn( const std::string & alias_r, const Container & cont_r )
112 { return foundAliasIn( alias_r, cont_r.begin(), cont_r.end() ); }
114 /** Find alias_r in repo/service container. */
115 template <class Iterator>
116 inline Iterator findAlias( const std::string & alias_r, Iterator begin_r, Iterator end_r )
118 for_( it, begin_r, end_r )
119 if ( it->alias() == alias_r )
124 template <class Container>
125 inline typename Container::iterator findAlias( const std::string & alias_r, Container & cont_r )
126 { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
128 template <class Container>
129 inline typename Container::const_iterator findAlias( const std::string & alias_r, const Container & cont_r )
130 { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
133 ///////////////////////////////////////////////////////////////////
135 // CLASS NAME : RepoManagerOptions
137 ///////////////////////////////////////////////////////////////////
139 RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
141 repoCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
142 repoRawCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
143 repoSolvCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
144 repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
145 knownReposPath = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
146 knownServicesPath = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
147 probe = ZConfig::instance().repo_add_probe();
152 RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r )
154 RepoManagerOptions ret;
155 ret.repoCachePath = root_r;
156 ret.repoRawCachePath = root_r/"raw";
157 ret.repoSolvCachePath = root_r/"solv";
158 ret.repoPackagesCachePath = root_r/"packages";
159 ret.knownReposPath = root_r/"repos.d";
160 ret.knownServicesPath = root_r/"services.d";
161 ret.rootDir = root_r;
165 ////////////////////////////////////////////////////////////////////////////
168 * \short Simple callback to collect the results
170 * Classes like RepoFileParser call the callback
171 * once per each repo in a file.
173 * Passing this functor as callback, you can collect
174 * all results at the end, without dealing with async
177 * If targetDistro is set, all repos with non-empty RepoInfo::targetDistribution()
180 * \todo do this through a separate filter
182 struct RepoCollector : private base::NonCopyable
187 RepoCollector(const std::string & targetDistro_)
188 : targetDistro(targetDistro_)
191 bool collect( const RepoInfo &repo )
193 // skip repositories meant for other distros than specified
194 if (!targetDistro.empty()
195 && !repo.targetDistribution().empty()
196 && repo.targetDistribution() != targetDistro)
199 << "Skipping repository meant for '" << targetDistro
200 << "' distribution (current distro is '"
201 << repo.targetDistribution() << "')." << endl;
206 repos.push_back(repo);
211 std::string targetDistro;
214 ////////////////////////////////////////////////////////////////////////////
217 * Reads RepoInfo's from a repo file.
219 * \param file pathname of the file to read.
221 static std::list<RepoInfo> repositories_in_file( const Pathname & file )
223 MIL << "repo file: " << file << endl;
224 RepoCollector collector;
225 parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
226 return collector.repos;
229 ////////////////////////////////////////////////////////////////////////////
232 * \short List of RepoInfo's from a directory
234 * Goes trough every file ending with ".repo" in a directory and adds all
235 * RepoInfo's contained in that file.
237 * \param dir pathname of the directory to read.
239 static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
241 MIL << "directory " << dir << endl;
242 std::list<RepoInfo> repos;
243 std::list<Pathname> entries;
244 if ( filesystem::readdir( entries, dir, false ) != 0 )
246 // TranslatorExplanation '%s' is a pathname
247 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
250 str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
251 for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
253 if (str::regex_match(it->extension(), allowedRepoExt))
255 std::list<RepoInfo> tmp = repositories_in_file( *it );
256 repos.insert( repos.end(), tmp.begin(), tmp.end() );
258 //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
259 //MIL << "ok" << endl;
265 ////////////////////////////////////////////////////////////////////////////
267 std::list<RepoInfo> readRepoFile(const Url & repo_file)
269 // no interface to download a specific file, using workaround:
270 //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
272 Pathname path(url.getPathName());
273 url.setPathName ("/");
274 MediaSetAccess access(url);
275 Pathname local = access.provideFile(path);
277 DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
279 return repositories_in_file(local);
282 ////////////////////////////////////////////////////////////////////////////
284 inline void assert_alias( const RepoInfo & info )
286 if ( info.alias().empty() )
287 ZYPP_THROW( RepoNoAliasException() );
288 // bnc #473834. Maybe we can match the alias against a regex to define
289 // and check for valid aliases
290 if ( info.alias()[0] == '.')
291 ZYPP_THROW(RepoInvalidAliasException(
292 info, _("Repository alias cannot start with dot.")));
295 inline void assert_alias( const ServiceInfo & info )
297 if ( info.alias().empty() )
298 ZYPP_THROW( ServiceNoAliasException() );
299 // bnc #473834. Maybe we can match the alias against a regex to define
300 // and check for valid aliases
301 if ( info.alias()[0] == '.')
302 ZYPP_THROW(ServiceInvalidAliasException(
303 info, _("Service alias cannot start with dot.")));
306 ////////////////////////////////////////////////////////////////////////////
308 inline void assert_urls( const RepoInfo & info )
310 if ( info.baseUrlsEmpty() )
311 ZYPP_THROW( RepoNoUrlException( info ) );
314 inline void assert_url( const ServiceInfo & info )
316 if ( ! info.url().isValid() )
317 ZYPP_THROW( ServiceNoUrlException( info ) );
320 ////////////////////////////////////////////////////////////////////////////
323 * \short Calculates the raw cache path for a repository, this is usually
324 * /var/cache/zypp/alias
326 inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
329 return opt.repoRawCachePath / info.escaped_alias();
333 * \short Calculates the raw product metadata path for a repository, this is
334 * inside the raw cache dir, plus an optional path where the metadata is.
336 * It should be different only for repositories that are not in the root of
338 * for example /var/cache/zypp/alias/addondir
340 inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
343 return opt.repoRawCachePath / info.escaped_alias() / info.path();
348 * \short Calculates the packages cache path for a repository
350 inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
353 return opt.repoPackagesCachePath / info.escaped_alias();
357 * \short Calculates the solv cache path for a repository
359 inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
362 return opt.repoSolvCachePath / info.escaped_alias();
365 ////////////////////////////////////////////////////////////////////////////
367 /** Functor collecting ServiceInfos into a ServiceSet. */
368 class ServiceCollector
371 typedef std::set<ServiceInfo> ServiceSet;
373 ServiceCollector( ServiceSet & services_r )
374 : _services( services_r )
377 bool operator()( const ServiceInfo & service_r ) const
379 _services.insert( service_r );
384 ServiceSet & _services;
387 ////////////////////////////////////////////////////////////////////////////
389 ///////////////////////////////////////////////////////////////////
391 // CLASS NAME : RepoManager::Impl
393 ///////////////////////////////////////////////////////////////////
396 * \short RepoManager implementation.
398 struct RepoManager::Impl
400 Impl( const RepoManagerOptions &opt )
403 init_knownServices();
404 init_knownRepositories();
407 RepoManagerOptions options;
415 void saveService( ServiceInfo & service ) const;
417 Pathname generateNonExistingName( const Pathname &dir,
418 const std::string &basefilename ) const;
420 std::string generateFilename( const RepoInfo & info ) const;
421 std::string generateFilename( const ServiceInfo & info ) const;
425 void init_knownServices();
426 void init_knownRepositories();
429 friend Impl * rwcowClone<Impl>( const Impl * rhs );
430 /** clone for RWCOW_pointer */
432 { return new Impl( *this ); }
435 ///////////////////////////////////////////////////////////////////
437 /** \relates RepoManager::Impl Stream output */
438 inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
440 return str << "RepoManager::Impl";
443 ///////////////////////////////////////////////////////////////////
445 void RepoManager::Impl::saveService( ServiceInfo & service ) const
447 filesystem::assert_dir( options.knownServicesPath );
448 Pathname servfile = generateNonExistingName( options.knownServicesPath,
449 generateFilename( service ) );
450 service.setFilepath( servfile );
452 MIL << "saving service in " << servfile << endl;
454 std::ofstream file( servfile.c_str() );
457 // TranslatorExplanation '%s' is a filename
458 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() )));
460 service.dumpAsIniOn( file );
461 MIL << "done" << endl;
465 * Generate a non existing filename in a directory, using a base
466 * name. For example if a directory contains 3 files
472 * If you try to generate a unique filename for this directory,
473 * based on "ruu" you will get "ruu", but if you use the base
474 * "foo" you will get "foo_1"
476 * \param dir Directory where the file needs to be unique
477 * \param basefilename string to base the filename on.
479 Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir,
480 const std::string & basefilename ) const
482 std::string final_filename = basefilename;
484 while ( PathInfo(dir + final_filename).isExist() )
486 final_filename = basefilename + "_" + str::numstring(counter);
489 return dir + Pathname(final_filename);
492 ////////////////////////////////////////////////////////////////////////////
495 * \short Generate a related filename from a repo info
497 * From a repo info, it will try to use the alias as a filename
498 * escaping it if necessary. Other fallbacks can be added to
499 * this function in case there is no way to use the alias
501 std::string RepoManager::Impl::generateFilename( const RepoInfo & info ) const
503 std::string filename = info.alias();
504 // replace slashes with underscores
505 str::replaceAll( filename, "/", "_" );
507 filename = Pathname(filename).extend(".repo").asString();
508 MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
512 std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const
514 std::string filename = info.alias();
515 // replace slashes with underscores
516 str::replaceAll( filename, "/", "_" );
518 filename = Pathname(filename).extend(".service").asString();
519 MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl;
524 void RepoManager::Impl::init_knownServices()
526 Pathname dir = options.knownServicesPath;
527 std::list<Pathname> entries;
528 if (PathInfo(dir).isExist())
530 if ( filesystem::readdir( entries, dir, false ) != 0 )
532 // TranslatorExplanation '%s' is a pathname
533 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
536 //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
537 for_(it, entries.begin(), entries.end() )
539 parser::ServiceFileReader(*it, ServiceCollector(services));
544 void RepoManager::Impl::init_knownRepositories()
546 MIL << "start construct known repos" << endl;
548 if ( PathInfo(options.knownReposPath).isExist() )
550 RepoInfoList repol = repositories_in_dir(options.knownReposPath);
551 for ( RepoInfoList::iterator it = repol.begin();
555 // set the metadata path for the repo
556 Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it));
557 (*it).setMetadataPath(metadata_path);
559 // set the downloaded packages path for the repo
560 Pathname packages_path = packagescache_path_for_repoinfo(options, (*it));
561 (*it).setPackagesPath(packages_path);
567 MIL << "end construct known repos" << endl;
570 ///////////////////////////////////////////////////////////////////
572 // CLASS NAME : RepoManager
574 ///////////////////////////////////////////////////////////////////
576 RepoManager::RepoManager( const RepoManagerOptions &opt )
577 : _pimpl( new Impl(opt) )
580 ////////////////////////////////////////////////////////////////////////////
582 RepoManager::~RepoManager()
585 ////////////////////////////////////////////////////////////////////////////
587 bool RepoManager::repoEmpty() const
588 { return _pimpl->repos.empty(); }
590 RepoManager::RepoSizeType RepoManager::repoSize() const
591 { return _pimpl->repos.size(); }
593 RepoManager::RepoConstIterator RepoManager::repoBegin() const
594 { return _pimpl->repos.begin(); }
596 RepoManager::RepoConstIterator RepoManager::repoEnd() const
597 { return _pimpl->repos.end(); }
599 RepoInfo RepoManager::getRepo( const std::string & alias ) const
601 for_( it, repoBegin(), repoEnd() )
602 if ( it->alias() == alias )
604 return RepoInfo::noRepo;
607 bool RepoManager::hasRepo( const std::string & alias ) const
609 for_( it, repoBegin(), repoEnd() )
610 if ( it->alias() == alias )
615 std::string RepoManager::makeStupidAlias( const Url & url_r )
617 std::string ret( url_r.getScheme() );
623 std::string host( url_r.getHost() );
624 if ( ! host.empty() )
630 static Date::ValueType serial = Date::now();
631 ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8);
635 ////////////////////////////////////////////////////////////////////////////
637 Pathname RepoManager::metadataPath( const RepoInfo &info ) const
639 return rawcache_path_for_repoinfo(_pimpl->options, info );
642 Pathname RepoManager::packagesPath( const RepoInfo &info ) const
644 return packagescache_path_for_repoinfo(_pimpl->options, info );
647 ////////////////////////////////////////////////////////////////////////////
649 RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
651 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
652 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
653 RepoType repokind = info.type();
656 switch ( repokind.toEnum() )
658 case RepoType::NONE_e:
659 // unknown, probe the local metadata
660 repokind = probe( productdatapath.asUrl() );
666 switch ( repokind.toEnum() )
668 case RepoType::RPMMD_e :
670 status = RepoStatus( productdatapath + "/repodata/repomd.xml");
674 case RepoType::YAST2_e :
676 status = RepoStatus( productdatapath + "/content") && (RepoStatus( mediarootpath + "/media.1/media"));
680 case RepoType::RPMPLAINDIR_e :
682 if ( PathInfo(Pathname(productdatapath + "/cookie")).isExist() )
683 status = RepoStatus( productdatapath + "/cookie");
687 case RepoType::NONE_e :
688 // Return default RepoStatus in case of RepoType::NONE
689 // indicating it should be created?
690 // ZYPP_THROW(RepoUnknownTypeException());
696 void RepoManager::touchIndexFile(const RepoInfo & info)
698 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
700 RepoType repokind = info.type();
701 if ( repokind.toEnum() == RepoType::NONE_e )
702 // unknown, probe the local metadata
703 repokind = probe( productdatapath.asUrl() );
704 // if still unknown, just return
705 if (repokind == RepoType::NONE_e)
709 switch ( repokind.toEnum() )
711 case RepoType::RPMMD_e :
712 p = Pathname(productdatapath + "/repodata/repomd.xml");
715 case RepoType::YAST2_e :
716 p = Pathname(productdatapath + "/content");
719 case RepoType::RPMPLAINDIR_e :
720 p = Pathname(productdatapath + "/cookie");
723 case RepoType::NONE_e :
728 // touch the file, ignore error (they are logged anyway)
729 filesystem::touch(p);
732 RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata(
733 const RepoInfo &info,
735 RawMetadataRefreshPolicy policy )
739 RepoStatus oldstatus;
740 RepoStatus newstatus;
744 MIL << "Going to try to check whether refresh is needed for " << url << endl;
746 // first check old (cached) metadata
747 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
748 filesystem::assert_dir(mediarootpath);
749 oldstatus = metadataStatus(info);
751 if ( oldstatus.empty() )
753 MIL << "No cached metadata, going to refresh" << endl;
754 return REFRESH_NEEDED;
758 std::string scheme( url.getScheme() );
759 if ( scheme == "cd" || scheme == "dvd" )
761 MIL << "never refresh CD/DVD" << endl;
762 return REPO_UP_TO_DATE;
766 // now we've got the old (cached) status, we can decide repo.refresh.delay
767 if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
769 // difference in seconds
770 double diff = difftime(
771 (Date::ValueType)Date::now(),
772 (Date::ValueType)oldstatus.timestamp()) / 60;
774 DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
775 DBG << "current time: " << (Date::ValueType)Date::now() << endl;
776 DBG << "last refresh = " << diff << " minutes ago" << endl;
778 if ( diff < ZConfig::instance().repo_refresh_delay() )
782 WAR << "Repository '" << info.alias() << "' was refreshed in the future!" << endl;
786 MIL << "Repository '" << info.alias()
787 << "' has been refreshed less than repo.refresh.delay ("
788 << ZConfig::instance().repo_refresh_delay()
789 << ") minutes ago. Advising to skip refresh" << endl;
790 return REPO_CHECK_DELAYED;
795 // To test the new matadta create temp dir as sibling of mediarootpath
796 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
798 repo::RepoType repokind = info.type();
799 // if the type is unknown, try probing.
800 switch ( repokind.toEnum() )
802 case RepoType::NONE_e:
803 // unknown, probe it \todo respect productdir
804 repokind = probe( url, info.path() );
810 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
811 ( repokind.toEnum() == RepoType::YAST2_e ) )
813 MediaSetAccess media(url);
814 shared_ptr<repo::Downloader> downloader_ptr;
816 if ( repokind.toEnum() == RepoType::RPMMD_e )
817 downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
819 downloader_ptr.reset( new susetags::Downloader(info, mediarootpath));
821 RepoStatus newstatus = downloader_ptr->status(media);
822 bool refresh = false;
823 if ( oldstatus.checksum() == newstatus.checksum() )
825 MIL << "repo has not changed" << endl;
826 if ( policy == RefreshForced )
828 MIL << "refresh set to forced" << endl;
834 MIL << "repo has changed, going to refresh" << endl;
839 touchIndexFile(info);
841 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
843 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
845 MediaMounter media( url );
846 RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
847 bool refresh = false;
848 if ( oldstatus.checksum() == newstatus.checksum() )
850 MIL << "repo has not changed" << endl;
851 if ( policy == RefreshForced )
853 MIL << "refresh set to forced" << endl;
859 MIL << "repo has changed, going to refresh" << endl;
864 touchIndexFile(info);
866 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
870 ZYPP_THROW(RepoUnknownTypeException(info));
873 catch ( const Exception &e )
876 ERR << "refresh check failed for " << url << endl;
880 return REFRESH_NEEDED; // default
883 void RepoManager::refreshMetadata( const RepoInfo &info,
884 RawMetadataRefreshPolicy policy,
885 const ProgressData::ReceiverFnc & progress )
890 // we will throw this later if no URL checks out fine
891 RepoException rexception(_("Valid metadata not found at specified URL(s)"));
893 // try urls one by one
894 for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
900 // check whether to refresh metadata
901 // if the check fails for this url, it throws, so another url will be checked
902 if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
905 MIL << "Going to refresh metadata from " << url << endl;
907 repo::RepoType repokind = info.type();
909 // if the type is unknown, try probing.
910 switch ( repokind.toEnum() )
912 case RepoType::NONE_e:
914 repokind = probe( *it, info.path() );
916 if (repokind.toEnum() != RepoType::NONE_e)
918 // Adjust the probed type in RepoInfo
919 info.setProbedType( repokind ); // lazy init!
920 //save probed type only for repos in system
921 for_( it, repoBegin(), repoEnd() )
923 if ( info.alias() == (*it).alias() )
925 RepoInfo modifiedrepo = info;
926 modifiedrepo.setType( repokind );
927 modifyRepository( info.alias(), modifiedrepo );
937 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
938 filesystem::assert_dir(mediarootpath);
940 // create temp dir as sibling of mediarootpath
941 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
943 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
944 ( repokind.toEnum() == RepoType::YAST2_e ) )
946 MediaSetAccess media(url);
947 shared_ptr<repo::Downloader> downloader_ptr;
949 MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
951 if ( repokind.toEnum() == RepoType::RPMMD_e )
952 downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
954 downloader_ptr.reset( new susetags::Downloader(info, mediarootpath) );
957 * Given a downloader, sets the other repos raw metadata
958 * path as cache paths for the fetcher, so if another
959 * repo has the same file, it will not download it
960 * but copy it from the other repository
962 for_( it, repoBegin(), repoEnd() )
964 Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
965 if ( PathInfo(cachepath).isExist() )
966 downloader_ptr->addCachePath(cachepath);
969 downloader_ptr->download( media, tmpdir.path() );
971 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
973 MediaMounter media( url );
974 RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
976 Pathname productpath( tmpdir.path() / info.path() );
977 filesystem::assert_dir( productpath );
978 std::ofstream file( (productpath/"cookie").c_str() );
981 // TranslatorExplanation '%s' is a filename
982 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() )));
985 if ( ! info.path().empty() && info.path() != "/" )
986 file << " (" << info.path() << ")";
988 file << newstatus.checksum() << endl;
994 ZYPP_THROW(RepoUnknownTypeException());
997 // ok we have the metadata, now exchange
999 filesystem::exchange( tmpdir.path(), mediarootpath );
1004 catch ( const Exception &e )
1007 ERR << "Trying another url..." << endl;
1009 // remember the exception caught for the *first URL*
1010 // if all other URLs fail, the rexception will be thrown with the
1011 // cause of the problem of the first URL remembered
1012 if (it == info.baseUrlsBegin())
1013 rexception.remember(e);
1016 ERR << "No more urls..." << endl;
1017 ZYPP_THROW(rexception);
1020 ////////////////////////////////////////////////////////////////////////////
1022 void RepoManager::cleanMetadata( const RepoInfo &info,
1023 const ProgressData::ReceiverFnc & progressfnc )
1025 ProgressData progress(100);
1026 progress.sendTo(progressfnc);
1028 filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
1032 void RepoManager::cleanPackages( const RepoInfo &info,
1033 const ProgressData::ReceiverFnc & progressfnc )
1035 ProgressData progress(100);
1036 progress.sendTo(progressfnc);
1038 filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
1042 void RepoManager::buildCache( const RepoInfo &info,
1043 CacheBuildPolicy policy,
1044 const ProgressData::ReceiverFnc & progressrcv )
1047 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
1048 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
1050 filesystem::assert_dir(_pimpl->options.repoCachePath);
1051 RepoStatus raw_metadata_status = metadataStatus(info);
1052 if ( raw_metadata_status.empty() )
1054 /* if there is no cache at this point, we refresh the raw
1055 in case this is the first time - if it's !autorefresh,
1056 we may still refresh */
1057 refreshMetadata(info, RefreshIfNeeded, progressrcv );
1058 raw_metadata_status = metadataStatus(info);
1061 bool needs_cleaning = false;
1062 if ( isCached( info ) )
1064 MIL << info.alias() << " is already cached." << endl;
1065 RepoStatus cache_status = cacheStatus(info);
1067 if ( cache_status.checksum() == raw_metadata_status.checksum() )
1069 MIL << info.alias() << " cache is up to date with metadata." << endl;
1070 if ( policy == BuildIfNeeded ) {
1074 MIL << info.alias() << " cache rebuild is forced" << endl;
1078 needs_cleaning = true;
1081 ProgressData progress(100);
1082 callback::SendReport<ProgressReport> report;
1083 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1084 progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str()));
1092 MIL << info.alias() << " building cache..." << info.type() << endl;
1094 Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
1095 filesystem::assert_dir(base);
1096 Pathname solvfile = base / "solv";
1099 repo::RepoType repokind = info.type();
1101 // if the type is unknown, try probing.
1102 switch ( repokind.toEnum() )
1104 case RepoType::NONE_e:
1105 // unknown, probe the local metadata
1106 repokind = probe( productdatapath.asUrl() );
1112 MIL << "repo type is " << repokind << endl;
1114 switch ( repokind.toEnum() )
1116 case RepoType::RPMMD_e :
1117 case RepoType::YAST2_e :
1118 case RepoType::RPMPLAINDIR_e :
1120 // Take care we unlink the solvfile on exception
1121 ManagedFile guard( solvfile, filesystem::unlink );
1122 scoped_ptr<MediaMounter> forPlainDirs;
1124 ExternalProgram::Arguments cmd;
1125 cmd.push_back( "repo2solv.sh" );
1127 // repo2solv expects -o as 1st arg!
1128 cmd.push_back( "-o" );
1129 cmd.push_back( solvfile.asString() );
1131 if ( repokind == RepoType::RPMPLAINDIR )
1133 forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
1134 // recusive for plaindir as 2nd arg!
1135 cmd.push_back( "-R" );
1136 // FIXME this does only work form dir: URLs
1137 cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
1140 cmd.push_back( productdatapath.asString() );
1142 ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
1143 std::string errdetail;
1145 for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1146 WAR << " " << output;
1147 if ( errdetail.empty() ) {
1148 errdetail = prog.command();
1151 errdetail += output;
1154 int ret = prog.close();
1157 RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
1158 ex.remember( errdetail );
1163 guard.resetDispose();
1167 ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
1170 // update timestamp and checksum
1171 setCacheStatus(info, raw_metadata_status);
1172 MIL << "Commit cache.." << endl;
1176 ////////////////////////////////////////////////////////////////////////////
1178 repo::RepoType RepoManager::probe( const Url & url ) const
1179 { return probe( url, Pathname() ); }
1181 repo::RepoType RepoManager::probe( const Url & url, const Pathname & path ) const
1183 MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
1185 if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
1187 // Handle non existing local directory in advance, as
1188 // MediaSetAccess does not support it.
1189 MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
1190 return repo::RepoType::NONE;
1193 // prepare exception to be thrown if the type could not be determined
1194 // due to a media exception. We can't throw right away, because of some
1195 // problems with proxy servers returning an incorrect error
1196 // on ftp file-not-found(bnc #335906). Instead we'll check another types
1199 // TranslatorExplanation '%s' is an URL
1200 RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
1201 bool gotMediaException = false;
1204 MediaSetAccess access(url);
1207 if ( access.doesFileExist(path/"/repodata/repomd.xml") )
1209 MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
1210 return repo::RepoType::RPMMD;
1213 catch ( const media::MediaException &e )
1216 DBG << "problem checking for repodata/repomd.xml file" << endl;
1218 gotMediaException = true;
1223 if ( access.doesFileExist(path/"/content") )
1225 MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
1226 return repo::RepoType::YAST2;
1229 catch ( const media::MediaException &e )
1232 DBG << "problem checking for content file" << endl;
1234 gotMediaException = true;
1237 // if it is a non-downloading URL denoting a directory
1238 if ( ! url.schemeIsDownloading() )
1240 MediaMounter media( url );
1241 if ( PathInfo(media.getPathName()/path).isDir() )
1243 // allow empty dirs for now
1244 MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
1245 return repo::RepoType::RPMPLAINDIR;
1249 catch ( const Exception &e )
1252 // TranslatorExplanation '%s' is an URL
1253 Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
1258 if (gotMediaException)
1261 MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
1262 return repo::RepoType::NONE;
1265 ////////////////////////////////////////////////////////////////////////////
1267 void RepoManager::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
1269 MIL << "Going to clean up garbage in cache dirs" << endl;
1271 ProgressData progress(300);
1272 progress.sendTo(progressrcv);
1275 std::list<Pathname> cachedirs;
1276 cachedirs.push_back(_pimpl->options.repoRawCachePath);
1277 cachedirs.push_back(_pimpl->options.repoPackagesCachePath);
1278 cachedirs.push_back(_pimpl->options.repoSolvCachePath);
1280 for_( dir, cachedirs.begin(), cachedirs.end() )
1282 if ( PathInfo(*dir).isExist() )
1284 std::list<Pathname> entries;
1285 if ( filesystem::readdir( entries, *dir, false ) != 0 )
1286 // TranslatorExplanation '%s' is a pathname
1287 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str())));
1289 unsigned sdircount = entries.size();
1290 unsigned sdircurrent = 1;
1291 for_( subdir, entries.begin(), entries.end() )
1293 // if it does not belong known repo, make it disappear
1295 for_( r, repoBegin(), repoEnd() )
1296 if ( subdir->basename() == r->escaped_alias() )
1297 { found = true; break; }
1300 filesystem::recursive_rmdir( *subdir );
1302 progress.set( progress.val() + sdircurrent * 100 / sdircount );
1307 progress.set( progress.val() + 100 );
1312 ////////////////////////////////////////////////////////////////////////////
1314 void RepoManager::cleanCache( const RepoInfo &info,
1315 const ProgressData::ReceiverFnc & progressrcv )
1317 ProgressData progress(100);
1318 progress.sendTo(progressrcv);
1321 MIL << "Removing raw metadata cache for " << info.alias() << endl;
1322 filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
1327 ////////////////////////////////////////////////////////////////////////////
1329 bool RepoManager::isCached( const RepoInfo &info ) const
1331 return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
1334 RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
1337 Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
1339 return RepoStatus::fromCookieFile(cookiefile);
1342 void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
1344 Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
1345 filesystem::assert_dir(base);
1346 Pathname cookiefile = base / "cookie";
1348 status.saveToCookieFile(cookiefile);
1351 void RepoManager::loadFromCache( const RepoInfo & info,
1352 const ProgressData::ReceiverFnc & progressrcv )
1355 Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
1357 if ( ! PathInfo(solvfile).isExist() )
1358 ZYPP_THROW(RepoNotCachedException(info));
1362 Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info );
1363 // test toolversion in order to rebuild solv file in case
1364 // it was written by an old satsolver-tool parser.
1366 // Known version strings used:
1370 sat::LookupRepoAttr toolversion( sat::SolvAttr::repositoryToolVersion, repo );
1371 if ( toolversion.begin().asString().empty() )
1373 repo.eraseFromPool();
1374 ZYPP_THROW(Exception("Solv-file was created by old parser."));
1376 // else: up-to-date (or even newer).
1378 catch ( const Exception & exp )
1381 MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1382 cleanCache( info, progressrcv );
1383 buildCache( info, BuildIfNeeded, progressrcv );
1385 sat::Pool::instance().addRepoSolv( solvfile, info );
1389 ////////////////////////////////////////////////////////////////////////////
1391 void RepoManager::addRepository( const RepoInfo &info,
1392 const ProgressData::ReceiverFnc & progressrcv )
1396 ProgressData progress(100);
1397 callback::SendReport<ProgressReport> report;
1398 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1399 progress.name(str::form(_("Adding repository '%s'"), info.label().c_str()));
1402 MIL << "Try adding repo " << info << endl;
1404 RepoInfo tosave = info;
1405 if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
1406 ZYPP_THROW(RepoAlreadyExistsException(info));
1408 // check the first url for now
1409 if ( _pimpl->options.probe )
1411 DBG << "unknown repository type, probing" << endl;
1413 RepoType probedtype;
1414 probedtype = probe( *tosave.baseUrlsBegin(), info.path() );
1415 if ( tosave.baseUrlsSize() > 0 )
1417 if ( probedtype == RepoType::NONE )
1418 ZYPP_THROW(RepoUnknownTypeException());
1420 tosave.setType(probedtype);
1426 // assert the directory exists
1427 filesystem::assert_dir(_pimpl->options.knownReposPath);
1429 Pathname repofile = _pimpl->generateNonExistingName(
1430 _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
1431 // now we have a filename that does not exists
1432 MIL << "Saving repo in " << repofile << endl;
1434 std::ofstream file(repofile.c_str());
1437 // TranslatorExplanation '%s' is a filename
1438 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1441 tosave.dumpAsIniOn(file);
1442 tosave.setFilepath(repofile);
1443 tosave.setMetadataPath( metadataPath( tosave ) );
1444 tosave.setPackagesPath( packagesPath( tosave ) );
1446 // We chould fix the API as we must injet those paths
1447 // into the repoinfo in order to keep it usable.
1448 RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
1449 oinfo.setMetadataPath( metadataPath( tosave ) );
1450 oinfo.setPackagesPath( packagesPath( tosave ) );
1452 _pimpl->repos.insert(tosave);
1456 // check for credentials in Urls
1457 bool havePasswords = false;
1458 for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
1459 if ( urlit->hasCredentialsInAuthority() )
1461 havePasswords = true;
1464 // save the credentials
1465 if ( havePasswords )
1467 media::CredentialManager cm(
1468 media::CredManagerOptions(_pimpl->options.rootDir) );
1470 for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
1471 if (urlit->hasCredentialsInAuthority())
1472 //! \todo use a method calling UI callbacks to ask where to save creds?
1473 cm.saveInUser(media::AuthData(*urlit));
1476 HistoryLog().addRepository(tosave);
1479 MIL << "done" << endl;
1482 void RepoManager::addRepositories( const Url &url,
1483 const ProgressData::ReceiverFnc & progressrcv )
1485 std::list<RepoInfo> repos = readRepoFile(url);
1486 for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1490 // look if the alias is in the known repos.
1491 for_ ( kit, repoBegin(), repoEnd() )
1493 if ( (*it).alias() == (*kit).alias() )
1495 ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1496 ZYPP_THROW(RepoAlreadyExistsException(*it));
1501 std::string filename = Pathname(url.getPathName()).basename();
1503 if ( filename == Pathname() )
1505 // TranslatorExplanation '%s' is an URL
1506 ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
1509 // assert the directory exists
1510 filesystem::assert_dir(_pimpl->options.knownReposPath);
1512 Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
1513 // now we have a filename that does not exists
1514 MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1516 std::ofstream file(repofile.c_str());
1519 // TranslatorExplanation '%s' is a filename
1520 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1523 for ( std::list<RepoInfo>::iterator it = repos.begin();
1527 MIL << "Saving " << (*it).alias() << endl;
1528 it->setFilepath(repofile.asString());
1529 it->dumpAsIniOn(file);
1530 _pimpl->repos.insert(*it);
1532 HistoryLog(_pimpl->options.rootDir).addRepository(*it);
1535 MIL << "done" << endl;
1538 ////////////////////////////////////////////////////////////////////////////
1540 void RepoManager::removeRepository( const RepoInfo & info,
1541 const ProgressData::ReceiverFnc & progressrcv)
1543 ProgressData progress;
1544 callback::SendReport<ProgressReport> report;
1545 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1546 progress.name(str::form(_("Removing repository '%s'"), info.label().c_str()));
1548 MIL << "Going to delete repo " << info.alias() << endl;
1550 for_( it, repoBegin(), repoEnd() )
1552 // they can be the same only if the provided is empty, that means
1553 // the provided repo has no alias
1555 if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1558 // TODO match by url
1560 // we have a matcing repository, now we need to know
1561 // where it does come from.
1562 RepoInfo todelete = *it;
1563 if (todelete.filepath().empty())
1565 ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1569 // figure how many repos are there in the file:
1570 std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1571 if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1573 // easy, only this one, just delete the file
1574 if ( filesystem::unlink(todelete.filepath()) != 0 )
1576 // TranslatorExplanation '%s' is a filename
1577 ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
1579 MIL << todelete.alias() << " sucessfully deleted." << endl;
1583 // there are more repos in the same file
1584 // write them back except the deleted one.
1586 //std::ofstream file(tmp.path().c_str());
1588 // assert the directory exists
1589 filesystem::assert_dir(todelete.filepath().dirname());
1591 std::ofstream file(todelete.filepath().c_str());
1594 // TranslatorExplanation '%s' is a filename
1595 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
1597 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1598 fit != filerepos.end();
1601 if ( (*fit).alias() != todelete.alias() )
1602 (*fit).dumpAsIniOn(file);
1606 CombinedProgressData subprogrcv(progress, 70);
1607 CombinedProgressData cleansubprogrcv(progress, 30);
1608 // now delete it from cache
1609 if ( isCached(todelete) )
1610 cleanCache( todelete, subprogrcv);
1611 // now delete metadata (#301037)
1612 cleanMetadata( todelete, cleansubprogrcv);
1613 _pimpl->repos.erase(todelete);
1614 MIL << todelete.alias() << " sucessfully deleted." << endl;
1615 HistoryLog(_pimpl->options.rootDir).removeRepository(todelete);
1617 } // else filepath is empty
1620 // should not be reached on a sucess workflow
1621 ZYPP_THROW(RepoNotFoundException(info));
1624 ////////////////////////////////////////////////////////////////////////////
1626 void RepoManager::modifyRepository( const std::string &alias,
1627 const RepoInfo & newinfo_r,
1628 const ProgressData::ReceiverFnc & progressrcv )
1630 RepoInfo toedit = getRepositoryInfo(alias);
1631 RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
1633 // check if the new alias already exists when renaming the repo
1634 if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
1636 ZYPP_THROW(RepoAlreadyExistsException(newinfo));
1639 if (toedit.filepath().empty())
1641 ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1645 // figure how many repos are there in the file:
1646 std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1648 // there are more repos in the same file
1649 // write them back except the deleted one.
1651 //std::ofstream file(tmp.path().c_str());
1653 // assert the directory exists
1654 filesystem::assert_dir(toedit.filepath().dirname());
1656 std::ofstream file(toedit.filepath().c_str());
1659 // TranslatorExplanation '%s' is a filename
1660 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
1662 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1663 fit != filerepos.end();
1666 // if the alias is different, dump the original
1667 // if it is the same, dump the provided one
1668 if ( (*fit).alias() != toedit.alias() )
1669 (*fit).dumpAsIniOn(file);
1671 newinfo.dumpAsIniOn(file);
1674 newinfo.setFilepath(toedit.filepath());
1675 _pimpl->repos.erase(toedit);
1676 _pimpl->repos.insert(newinfo);
1677 HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
1678 MIL << "repo " << alias << " modified" << endl;
1682 ////////////////////////////////////////////////////////////////////////////
1684 RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1685 const ProgressData::ReceiverFnc & progressrcv )
1688 info.setAlias(alias);
1689 RepoConstIterator it = _pimpl->repos.find( info );
1690 if( it == repoEnd() )
1691 ZYPP_THROW(RepoNotFoundException(info));
1696 ////////////////////////////////////////////////////////////////////////////
1698 RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1699 const url::ViewOption & urlview,
1700 const ProgressData::ReceiverFnc & progressrcv )
1702 for_( it, repoBegin(), repoEnd() )
1704 for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1705 urlit != (*it).baseUrlsEnd();
1708 if ((*urlit).asString(urlview) == url.asString(urlview))
1713 info.setBaseUrl(url);
1714 ZYPP_THROW(RepoNotFoundException(info));
1717 ////////////////////////////////////////////////////////////////////////////
1721 ////////////////////////////////////////////////////////////////////////////
1723 bool RepoManager::serviceEmpty() const
1724 { return _pimpl->services.empty(); }
1726 RepoManager::ServiceSizeType RepoManager::serviceSize() const
1727 { return _pimpl->services.size(); }
1729 RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
1730 { return _pimpl->services.begin(); }
1732 RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
1733 { return _pimpl->services.end(); }
1735 ServiceInfo RepoManager::getService( const std::string & alias ) const
1737 for_( it, serviceBegin(), serviceEnd() )
1738 if ( it->alias() == alias )
1740 return ServiceInfo::noService;
1743 bool RepoManager::hasService( const std::string & alias ) const
1745 for_( it, serviceBegin(), serviceEnd() )
1746 if ( it->alias() == alias )
1751 ////////////////////////////////////////////////////////////////////////////
1753 void RepoManager::addService( const std::string & alias, const Url & url )
1755 addService( ServiceInfo(alias, url) );
1758 void RepoManager::addService( const ServiceInfo & service )
1760 assert_alias( service );
1762 // check if service already exists
1763 if ( hasService( service.alias() ) )
1764 ZYPP_THROW( ServiceAlreadyExistsException( service ) );
1766 // Writable ServiceInfo is needed to save the location
1767 // of the .service file. Finaly insert into the service list.
1768 ServiceInfo toSave( service );
1769 _pimpl->saveService( toSave );
1770 _pimpl->services.insert( toSave );
1772 // check for credentials in Url (username:password, not ?credentials param)
1773 if ( toSave.url().hasCredentialsInAuthority() )
1775 media::CredentialManager cm(
1776 media::CredManagerOptions(_pimpl->options.rootDir) );
1778 //! \todo use a method calling UI callbacks to ask where to save creds?
1779 cm.saveInUser(media::AuthData(toSave.url()));
1782 MIL << "added service " << toSave.alias() << endl;
1785 ////////////////////////////////////////////////////////////////////////////
1787 void RepoManager::removeService( const std::string & alias )
1789 MIL << "Going to delete repo " << alias << endl;
1791 const ServiceInfo & service = getService( alias );
1793 Pathname location = service.filepath();
1794 if( location.empty() )
1796 ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1800 parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1802 // only one service definition in the file
1803 if ( tmpSet.size() == 1 )
1805 if ( filesystem::unlink(location) != 0 )
1807 // TranslatorExplanation '%s' is a filename
1808 ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
1810 MIL << alias << " sucessfully deleted." << endl;
1814 filesystem::assert_dir(location.dirname());
1816 std::ofstream file(location.c_str());
1819 // TranslatorExplanation '%s' is a filename
1820 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
1823 for_(it, tmpSet.begin(), tmpSet.end())
1825 if( it->alias() != alias )
1826 it->dumpAsIniOn(file);
1829 MIL << alias << " sucessfully deleted from file " << location << endl;
1832 // now remove all repositories added by this service
1833 RepoCollector rcollector;
1834 getRepositoriesInService( alias,
1835 boost::make_function_output_iterator(
1836 bind( &RepoCollector::collect, &rcollector, _1 ) ) );
1837 // cannot do this directly in getRepositoriesInService - would invalidate iterators
1838 for_(rit, rcollector.repos.begin(), rcollector.repos.end())
1839 removeRepository(*rit);
1842 void RepoManager::removeService( const ServiceInfo & service )
1843 { removeService(service.alias()); }
1845 ////////////////////////////////////////////////////////////////////////////
1847 void RepoManager::refreshServices()
1849 // copy the set of services since refreshService
1850 // can eventually invalidate the iterator
1851 ServiceSet services( serviceBegin(), serviceEnd() );
1852 for_( it, services.begin(), services.end() )
1854 if ( !it->enabled() )
1857 refreshService(*it);
1861 void RepoManager::refreshService( const ServiceInfo & service )
1862 { refreshService( service.alias() ); }
1864 void RepoManager::refreshService( const std::string & alias )
1866 ServiceInfo service( getService( alias ) );
1867 assert_alias( service );
1868 assert_url( service );
1869 // NOTE: It might be necessary to modify and rewrite the service info.
1870 // Either when probing the type, or when adjusting the repositories
1871 // enable/disable state.:
1872 bool serviceModified = false;
1873 MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
1875 //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
1877 // if the type is unknown, try probing.
1878 if ( service.type() == repo::ServiceType::NONE )
1880 repo::ServiceType type = probeService( service.url() );
1881 if ( type != ServiceType::NONE )
1883 service.setProbedType( type ); // lazy init!
1884 serviceModified = true;
1888 // repoindex.xml must be fetched always without using cookies (bnc #573897)
1889 Url serviceUrl( service.url() );
1890 serviceUrl.setQueryParam( "cookies", "0" );
1892 // download the repo index file
1893 media::MediaManager mediamanager;
1894 media::MediaAccessId mid = mediamanager.open( serviceUrl );
1895 mediamanager.attach( mid );
1896 mediamanager.provideFile( mid, "repo/repoindex.xml" );
1897 Pathname path = mediamanager.localPath(mid, "repo/repoindex.xml" );
1899 // get target distro identifier
1900 std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro;
1901 if ( servicesTargetDistro.empty() && getZYpp()->getTarget() )
1902 servicesTargetDistro = getZYpp()->target()->targetDistribution();
1903 DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
1906 RepoCollector collector(servicesTargetDistro);
1907 parser::RepoindexFileReader reader( path, bind( &RepoCollector::collect, &collector, _1 ) );
1908 mediamanager.release( mid );
1909 mediamanager.close( mid );
1912 // set service alias and base url for all collected repositories
1913 for_( it, collector.repos.begin(), collector.repos.end() )
1915 // if the repo url was not set by the repoindex parser, set service's url
1918 if ( it->baseUrlsEmpty() )
1919 url = service.url();
1922 // service repo can contain only one URL now, so no need to iterate.
1923 url = *it->baseUrlsBegin();
1926 // libzypp currently has problem with separate url + path handling
1927 // so just append the path to the baseurl
1928 if ( !it->path().empty() )
1930 Pathname path(url.getPathName());
1932 url.setPathName( path.asString() );
1936 // Prepend service alias:
1937 it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
1940 it->setBaseUrl( url );
1941 // set refrence to the parent service
1942 it->setService( service.alias() );
1945 ////////////////////////////////////////////////////////////////////////////
1946 // Now compare collected repos with the ones in the system...
1948 RepoInfoList oldRepos;
1949 getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
1951 // find old repositories to remove...
1952 for_( it, oldRepos.begin(), oldRepos.end() )
1954 if ( ! foundAliasIn( it->alias(), collector.repos ) )
1956 if ( it->enabled() && ! service.repoToDisableFind( it->alias() ) )
1958 DBG << "Service removes enabled repo " << it->alias() << endl;
1959 service.addRepoToEnable( it->alias() );
1960 serviceModified = true;
1964 DBG << "Service removes disabled repo " << it->alias() << endl;
1966 removeRepository( *it );
1970 ////////////////////////////////////////////////////////////////////////////
1971 // create missing repositories and modify exising ones if needed...
1972 for_( it, collector.repos.begin(), collector.repos.end() )
1974 // Service explicitly requests the repo being enabled?
1975 // Service explicitly requests the repo being disabled?
1976 // And hopefully not both ;) If so, enable wins.
1977 bool beEnabled = service.repoToEnableFind( it->alias() );
1978 bool beDisabled = service.repoToDisableFind( it->alias() );
1982 // Remove from enable request list.
1983 // NOTE: repoToDisable is handled differently.
1984 // It gets cleared on each refresh.
1985 service.delRepoToEnable( it->alias() );
1986 serviceModified = true;
1989 RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
1990 if ( oldRepo == oldRepos.end() )
1992 // Not found in oldRepos ==> a new repo to add
1994 // Make sure the service repo is created with the
1995 // appropriate enable and autorefresh true.
1996 it->setEnabled( beEnabled );
1997 it->setAutorefresh( true );
1999 // At that point check whether a repo with the same alias
2000 // exists outside this service. Maybe forcefully re-alias
2001 // the existing repo?
2002 DBG << "Service adds repo " << it->alias() << " " << (beEnabled?"enabled":"disabled") << endl;
2003 addRepository( *it );
2005 // save repo credentials
2006 // ma@: task for modifyRepository?
2010 // ==> an exising repo to check
2011 bool oldRepoModified = false;
2016 if ( ! oldRepo->enabled() )
2018 DBG << "Service repo " << it->alias() << " gets enabled" << endl;
2019 oldRepo->setEnabled( true );
2020 oldRepoModified = true;
2024 DBG << "Service repo " << it->alias() << " stays enabled" << endl;
2027 else if ( beDisabled )
2029 if ( oldRepo->enabled() )
2031 DBG << "Service repo " << it->alias() << " gets disabled" << endl;
2032 oldRepo->setEnabled( false );
2033 oldRepoModified = true;
2037 DBG << "Service repo " << it->alias() << " stays disabled" << endl;
2042 DBG << "Service repo " << it->alias() << " stays " << (oldRepo->enabled()?"enabled":"disabled") << endl;
2046 // service repo can contain only one URL now, so no need to iterate.
2047 if ( oldRepo->url() != it->url() )
2049 DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
2050 oldRepo->setBaseUrl( it->url() );
2051 oldRepoModified = true;
2054 // save if modified:
2055 if ( oldRepoModified )
2057 modifyRepository( oldRepo->alias(), *oldRepo );
2062 // Unlike reposToEnable, reposToDisable is always cleared after refresh.
2063 if ( ! service.reposToDisableEmpty() )
2065 service.clearReposToDisable();
2066 serviceModified = true;
2069 ////////////////////////////////////////////////////////////////////////////
2070 // save service if modified:
2071 if ( serviceModified )
2073 // write out modified service file.
2074 modifyService( service.alias(), service );
2078 ////////////////////////////////////////////////////////////////////////////
2080 void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & service)
2082 MIL << "Going to modify service " << oldAlias << endl;
2084 const ServiceInfo & oldService = getService(oldAlias);
2086 Pathname location = oldService.filepath();
2087 if( location.empty() )
2089 ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
2092 // remember: there may multiple services being defined in one file:
2094 parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
2096 filesystem::assert_dir(location.dirname());
2097 std::ofstream file(location.c_str());
2098 for_(it, tmpSet.begin(), tmpSet.end())
2100 if( *it != oldAlias )
2101 it->dumpAsIniOn(file);
2103 service.dumpAsIniOn(file);
2106 _pimpl->services.erase(oldAlias);
2107 _pimpl->services.insert(service);
2109 // changed properties affecting also repositories
2110 if( oldAlias != service.alias() // changed alias
2111 || oldService.enabled() != service.enabled() // changed enabled status
2114 std::vector<RepoInfo> toModify;
2115 getRepositoriesInService(oldAlias, std::back_inserter(toModify));
2116 for_( it, toModify.begin(), toModify.end() )
2118 if (oldService.enabled() && !service.enabled())
2119 it->setEnabled(false);
2120 else if (!oldService.enabled() && service.enabled())
2122 //! \todo do nothing? the repos will be enabled on service refresh
2123 //! \todo how to know the service needs a (auto) refresh????
2126 it->setService(service.alias());
2127 modifyRepository(it->alias(), *it);
2131 //! \todo refresh the service automatically if url is changed?
2134 ////////////////////////////////////////////////////////////////////////////
2136 repo::ServiceType RepoManager::probeService( const Url &url ) const
2140 MediaSetAccess access(url);
2141 if ( access.doesFileExist("/repo/repoindex.xml") )
2142 return repo::ServiceType::RIS;
2144 catch ( const media::MediaException &e )
2147 // TranslatorExplanation '%s' is an URL
2148 RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
2152 catch ( const Exception &e )
2155 // TranslatorExplanation '%s' is an URL
2156 Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
2161 return repo::ServiceType::NONE;
2164 ////////////////////////////////////////////////////////////////////////////
2166 std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
2168 return str << *obj._pimpl;
2171 /////////////////////////////////////////////////////////////////
2173 ///////////////////////////////////////////////////////////////////