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() );
290 inline void assert_alias( const ServiceInfo & info )
292 if ( info.alias().empty() )
293 ZYPP_THROW( ServiceNoAliasException() );
296 ////////////////////////////////////////////////////////////////////////////
298 inline void assert_urls( const RepoInfo & info )
300 if ( info.baseUrlsEmpty() )
301 ZYPP_THROW( RepoNoUrlException( info ) );
304 inline void assert_url( const ServiceInfo & info )
306 if ( ! info.url().isValid() )
307 ZYPP_THROW( ServiceNoUrlException( info ) );
310 ////////////////////////////////////////////////////////////////////////////
313 * \short Calculates the raw cache path for a repository, this is usually
314 * /var/cache/zypp/alias
316 inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
319 return opt.repoRawCachePath / info.escaped_alias();
323 * \short Calculates the raw product metadata path for a repository, this is
324 * inside the raw cache dir, plus an optional path where the metadata is.
326 * It should be different only for repositories that are not in the root of
328 * for example /var/cache/zypp/alias/addondir
330 inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
333 return opt.repoRawCachePath / info.escaped_alias() / info.path();
338 * \short Calculates the packages cache path for a repository
340 inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
343 return opt.repoPackagesCachePath / info.escaped_alias();
347 * \short Calculates the solv cache path for a repository
349 inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
352 return opt.repoSolvCachePath / info.escaped_alias();
355 ////////////////////////////////////////////////////////////////////////////
357 /** Functor collecting ServiceInfos into a ServiceSet. */
358 class ServiceCollector
361 typedef std::set<ServiceInfo> ServiceSet;
363 ServiceCollector( ServiceSet & services_r )
364 : _services( services_r )
367 bool operator()( const ServiceInfo & service_r ) const
369 _services.insert( service_r );
374 ServiceSet & _services;
377 ////////////////////////////////////////////////////////////////////////////
379 ///////////////////////////////////////////////////////////////////
381 // CLASS NAME : RepoManager::Impl
383 ///////////////////////////////////////////////////////////////////
386 * \short RepoManager implementation.
388 struct RepoManager::Impl
390 Impl( const RepoManagerOptions &opt )
393 init_knownServices();
394 init_knownRepositories();
397 RepoManagerOptions options;
405 void saveService( ServiceInfo & service ) const;
407 Pathname generateNonExistingName( const Pathname &dir,
408 const std::string &basefilename ) const;
410 std::string generateFilename( const RepoInfo & info ) const;
411 std::string generateFilename( const ServiceInfo & info ) const;
415 void init_knownServices();
416 void init_knownRepositories();
419 friend Impl * rwcowClone<Impl>( const Impl * rhs );
420 /** clone for RWCOW_pointer */
422 { return new Impl( *this ); }
425 ///////////////////////////////////////////////////////////////////
427 /** \relates RepoManager::Impl Stream output */
428 inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
430 return str << "RepoManager::Impl";
433 ///////////////////////////////////////////////////////////////////
435 void RepoManager::Impl::saveService( ServiceInfo & service ) const
437 filesystem::assert_dir( options.knownServicesPath );
438 Pathname servfile = generateNonExistingName( options.knownServicesPath,
439 generateFilename( service ) );
440 service.setFilepath( servfile );
442 MIL << "saving service in " << servfile << endl;
444 std::ofstream file( servfile.c_str() );
447 // TranslatorExplanation '%s' is a filename
448 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() )));
450 service.dumpAsIniOn( file );
451 MIL << "done" << endl;
455 * Generate a non existing filename in a directory, using a base
456 * name. For example if a directory contains 3 files
462 * If you try to generate a unique filename for this directory,
463 * based on "ruu" you will get "ruu", but if you use the base
464 * "foo" you will get "foo_1"
466 * \param dir Directory where the file needs to be unique
467 * \param basefilename string to base the filename on.
469 Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir,
470 const std::string & basefilename ) const
472 std::string final_filename = basefilename;
474 while ( PathInfo(dir + final_filename).isExist() )
476 final_filename = basefilename + "_" + str::numstring(counter);
479 return dir + Pathname(final_filename);
482 ////////////////////////////////////////////////////////////////////////////
485 * \short Generate a related filename from a repo info
487 * From a repo info, it will try to use the alias as a filename
488 * escaping it if necessary. Other fallbacks can be added to
489 * this function in case there is no way to use the alias
491 std::string RepoManager::Impl::generateFilename( const RepoInfo & info ) const
493 std::string filename = info.alias();
494 // replace slashes with underscores
495 str::replaceAll( filename, "/", "_" );
497 filename = Pathname(filename).extend(".repo").asString();
498 MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
502 std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const
504 std::string filename = info.alias();
505 // replace slashes with underscores
506 str::replaceAll( filename, "/", "_" );
508 filename = Pathname(filename).extend(".service").asString();
509 MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl;
514 void RepoManager::Impl::init_knownServices()
516 Pathname dir = options.knownServicesPath;
517 std::list<Pathname> entries;
518 if (PathInfo(dir).isExist())
520 if ( filesystem::readdir( entries, dir, false ) != 0 )
522 // TranslatorExplanation '%s' is a pathname
523 ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
526 //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
527 for_(it, entries.begin(), entries.end() )
529 parser::ServiceFileReader(*it, ServiceCollector(services));
534 void RepoManager::Impl::init_knownRepositories()
536 MIL << "start construct known repos" << endl;
538 if ( PathInfo(options.knownReposPath).isExist() )
540 RepoInfoList repol = repositories_in_dir(options.knownReposPath);
541 for ( RepoInfoList::iterator it = repol.begin();
545 // set the metadata path for the repo
546 Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it));
547 (*it).setMetadataPath(metadata_path);
549 // set the downloaded packages path for the repo
550 Pathname packages_path = packagescache_path_for_repoinfo(options, (*it));
551 (*it).setPackagesPath(packages_path);
557 MIL << "end construct known repos" << endl;
560 ///////////////////////////////////////////////////////////////////
562 // CLASS NAME : RepoManager
564 ///////////////////////////////////////////////////////////////////
566 RepoManager::RepoManager( const RepoManagerOptions &opt )
567 : _pimpl( new Impl(opt) )
570 ////////////////////////////////////////////////////////////////////////////
572 RepoManager::~RepoManager()
575 ////////////////////////////////////////////////////////////////////////////
577 bool RepoManager::repoEmpty() const
578 { return _pimpl->repos.empty(); }
580 RepoManager::RepoSizeType RepoManager::repoSize() const
581 { return _pimpl->repos.size(); }
583 RepoManager::RepoConstIterator RepoManager::repoBegin() const
584 { return _pimpl->repos.begin(); }
586 RepoManager::RepoConstIterator RepoManager::repoEnd() const
587 { return _pimpl->repos.end(); }
589 RepoInfo RepoManager::getRepo( const std::string & alias ) const
591 for_( it, repoBegin(), repoEnd() )
592 if ( it->alias() == alias )
594 return RepoInfo::noRepo;
597 bool RepoManager::hasRepo( const std::string & alias ) const
599 for_( it, repoBegin(), repoEnd() )
600 if ( it->alias() == alias )
605 std::string RepoManager::makeStupidAlias( const Url & url_r )
607 std::string ret( url_r.getScheme() );
613 std::string host( url_r.getHost() );
614 if ( ! host.empty() )
620 static Date::ValueType serial = Date::now();
621 ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8);
625 ////////////////////////////////////////////////////////////////////////////
627 Pathname RepoManager::metadataPath( const RepoInfo &info ) const
629 return rawcache_path_for_repoinfo(_pimpl->options, info );
632 Pathname RepoManager::packagesPath( const RepoInfo &info ) const
634 return packagescache_path_for_repoinfo(_pimpl->options, info );
637 ////////////////////////////////////////////////////////////////////////////
639 RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
641 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
642 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
643 RepoType repokind = info.type();
646 switch ( repokind.toEnum() )
648 case RepoType::NONE_e:
649 // unknown, probe the local metadata
650 repokind = probe( productdatapath.asUrl() );
656 switch ( repokind.toEnum() )
658 case RepoType::RPMMD_e :
660 status = RepoStatus( productdatapath + "/repodata/repomd.xml");
664 case RepoType::YAST2_e :
666 status = RepoStatus( productdatapath + "/content") && (RepoStatus( mediarootpath + "/media.1/media"));
670 case RepoType::RPMPLAINDIR_e :
672 if ( PathInfo(Pathname(productdatapath + "/cookie")).isExist() )
673 status = RepoStatus( productdatapath + "/cookie");
677 case RepoType::NONE_e :
678 // Return default RepoStatus in case of RepoType::NONE
679 // indicating it should be created?
680 // ZYPP_THROW(RepoUnknownTypeException());
686 void RepoManager::touchIndexFile(const RepoInfo & info)
688 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
690 RepoType repokind = info.type();
691 if ( repokind.toEnum() == RepoType::NONE_e )
692 // unknown, probe the local metadata
693 repokind = probe( productdatapath.asUrl() );
694 // if still unknown, just return
695 if (repokind == RepoType::NONE_e)
699 switch ( repokind.toEnum() )
701 case RepoType::RPMMD_e :
702 p = Pathname(productdatapath + "/repodata/repomd.xml");
705 case RepoType::YAST2_e :
706 p = Pathname(productdatapath + "/content");
709 case RepoType::RPMPLAINDIR_e :
710 p = Pathname(productdatapath + "/cookie");
713 case RepoType::NONE_e :
718 // touch the file, ignore error (they are logged anyway)
719 filesystem::touch(p);
722 RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata(
723 const RepoInfo &info,
725 RawMetadataRefreshPolicy policy )
729 RepoStatus oldstatus;
730 RepoStatus newstatus;
734 MIL << "Going to try to check whether refresh is needed for " << url << endl;
736 // first check old (cached) metadata
737 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
738 filesystem::assert_dir(mediarootpath);
739 oldstatus = metadataStatus(info);
741 if ( oldstatus.empty() )
743 MIL << "No cached metadata, going to refresh" << endl;
744 return REFRESH_NEEDED;
748 std::string scheme( url.getScheme() );
749 if ( scheme == "cd" || scheme == "dvd" )
751 MIL << "never refresh CD/DVD" << endl;
752 return REPO_UP_TO_DATE;
756 // now we've got the old (cached) status, we can decide repo.refresh.delay
757 if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
759 // difference in seconds
760 double diff = difftime(
761 (Date::ValueType)Date::now(),
762 (Date::ValueType)oldstatus.timestamp()) / 60;
764 DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
765 DBG << "current time: " << (Date::ValueType)Date::now() << endl;
766 DBG << "last refresh = " << diff << " minutes ago" << endl;
768 if (diff < ZConfig::instance().repo_refresh_delay())
770 MIL << "Repository '" << info.alias()
771 << "' has been refreshed less than repo.refresh.delay ("
772 << ZConfig::instance().repo_refresh_delay()
773 << ") minutes ago. Advising to skip refresh" << endl;
774 return REPO_CHECK_DELAYED;
778 // To test the new matadta create temp dir as sibling of mediarootpath
779 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
781 repo::RepoType repokind = info.type();
782 // if the type is unknown, try probing.
783 switch ( repokind.toEnum() )
785 case RepoType::NONE_e:
786 // unknown, probe it \todo respect productdir
787 repokind = probe( url, info.path() );
793 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
794 ( repokind.toEnum() == RepoType::YAST2_e ) )
796 MediaSetAccess media(url);
797 shared_ptr<repo::Downloader> downloader_ptr;
799 if ( repokind.toEnum() == RepoType::RPMMD_e )
800 downloader_ptr.reset(new yum::Downloader(info));
802 downloader_ptr.reset( new susetags::Downloader(info));
804 RepoStatus newstatus = downloader_ptr->status(media);
805 bool refresh = false;
806 if ( oldstatus.checksum() == newstatus.checksum() )
808 MIL << "repo has not changed" << endl;
809 if ( policy == RefreshForced )
811 MIL << "refresh set to forced" << endl;
817 MIL << "repo has changed, going to refresh" << endl;
822 touchIndexFile(info);
824 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
826 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
828 MediaMounter media( url );
829 RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
830 bool refresh = false;
831 if ( oldstatus.checksum() == newstatus.checksum() )
833 MIL << "repo has not changed" << endl;
834 if ( policy == RefreshForced )
836 MIL << "refresh set to forced" << endl;
842 MIL << "repo has changed, going to refresh" << endl;
847 touchIndexFile(info);
849 return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
853 ZYPP_THROW(RepoUnknownTypeException(info));
856 catch ( const Exception &e )
859 ERR << "refresh check failed for " << url << endl;
863 return REFRESH_NEEDED; // default
866 void RepoManager::refreshMetadata( const RepoInfo &info,
867 RawMetadataRefreshPolicy policy,
868 const ProgressData::ReceiverFnc & progress )
873 // we will throw this later if no URL checks out fine
874 RepoException rexception(_("Valid metadata not found at specified URL(s)"));
876 // try urls one by one
877 for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
883 // check whether to refresh metadata
884 // if the check fails for this url, it throws, so another url will be checked
885 if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
888 MIL << "Going to refresh metadata from " << url << endl;
890 repo::RepoType repokind = info.type();
892 // if the type is unknown, try probing.
893 switch ( repokind.toEnum() )
895 case RepoType::NONE_e:
897 repokind = probe( *it, info.path() );
899 if (repokind.toEnum() != RepoType::NONE_e)
901 // Adjust the probed type in RepoInfo
902 info.setProbedType( repokind ); // lazy init!
903 //save probed type only for repos in system
904 for_( it, repoBegin(), repoEnd() )
906 if ( info.alias() == (*it).alias() )
908 RepoInfo modifiedrepo = info;
909 modifiedrepo.setType( repokind );
910 modifyRepository( info.alias(), modifiedrepo );
920 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
921 filesystem::assert_dir(mediarootpath);
923 // create temp dir as sibling of mediarootpath
924 filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
926 if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
927 ( repokind.toEnum() == RepoType::YAST2_e ) )
929 MediaSetAccess media(url);
930 shared_ptr<repo::Downloader> downloader_ptr;
932 MIL << "Creating downloader for [ " << info.name() << " ]" << endl;
934 if ( repokind.toEnum() == RepoType::RPMMD_e )
935 downloader_ptr.reset(new yum::Downloader(info));
937 downloader_ptr.reset( new susetags::Downloader(info) );
940 * Given a downloader, sets the other repos raw metadata
941 * path as cache paths for the fetcher, so if another
942 * repo has the same file, it will not download it
943 * but copy it from the other repository
945 for_( it, repoBegin(), repoEnd() )
947 Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
948 if ( PathInfo(cachepath).isExist() )
949 downloader_ptr->addCachePath(cachepath);
952 downloader_ptr->download( media, tmpdir.path() );
954 else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
956 MediaMounter media( url );
957 RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
959 Pathname productpath( tmpdir.path() / info.path() );
960 filesystem::assert_dir( productpath );
961 std::ofstream file( (productpath/"cookie").c_str() );
964 // TranslatorExplanation '%s' is a filename
965 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() )));
968 if ( ! info.path().empty() && info.path() != "/" )
969 file << " (" << info.path() << ")";
971 file << newstatus.checksum() << endl;
977 ZYPP_THROW(RepoUnknownTypeException());
980 // ok we have the metadata, now exchange
982 filesystem::exchange( tmpdir.path(), mediarootpath );
987 catch ( const Exception &e )
990 ERR << "Trying another url..." << endl;
992 // remember the exception caught for the *first URL*
993 // if all other URLs fail, the rexception will be thrown with the
994 // cause of the problem of the first URL remembered
995 if (it == info.baseUrlsBegin())
996 rexception.remember(e);
999 ERR << "No more urls..." << endl;
1000 ZYPP_THROW(rexception);
1003 ////////////////////////////////////////////////////////////////////////////
1005 void RepoManager::cleanMetadata( const RepoInfo &info,
1006 const ProgressData::ReceiverFnc & progressfnc )
1008 ProgressData progress(100);
1009 progress.sendTo(progressfnc);
1011 filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
1015 void RepoManager::cleanPackages( const RepoInfo &info,
1016 const ProgressData::ReceiverFnc & progressfnc )
1018 ProgressData progress(100);
1019 progress.sendTo(progressfnc);
1021 filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
1025 void RepoManager::buildCache( const RepoInfo &info,
1026 CacheBuildPolicy policy,
1027 const ProgressData::ReceiverFnc & progressrcv )
1030 Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
1031 Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
1033 filesystem::assert_dir(_pimpl->options.repoCachePath);
1034 RepoStatus raw_metadata_status = metadataStatus(info);
1035 if ( raw_metadata_status.empty() )
1037 /* if there is no cache at this point, we refresh the raw
1038 in case this is the first time - if it's !autorefresh,
1039 we may still refresh */
1040 refreshMetadata(info, RefreshIfNeeded, progressrcv );
1041 raw_metadata_status = metadataStatus(info);
1044 bool needs_cleaning = false;
1045 if ( isCached( info ) )
1047 MIL << info.alias() << " is already cached." << endl;
1048 RepoStatus cache_status = cacheStatus(info);
1050 if ( cache_status.checksum() == raw_metadata_status.checksum() )
1052 MIL << info.alias() << " cache is up to date with metadata." << endl;
1053 if ( policy == BuildIfNeeded ) {
1057 MIL << info.alias() << " cache rebuild is forced" << endl;
1061 needs_cleaning = true;
1064 ProgressData progress(100);
1065 callback::SendReport<ProgressReport> report;
1066 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1067 progress.name(str::form(_("Building repository '%s' cache"), info.name().c_str()));
1075 MIL << info.alias() << " building cache..." << info.type() << endl;
1077 Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
1078 filesystem::assert_dir(base);
1079 Pathname solvfile = base / "solv";
1082 repo::RepoType repokind = info.type();
1084 // if the type is unknown, try probing.
1085 switch ( repokind.toEnum() )
1087 case RepoType::NONE_e:
1088 // unknown, probe the local metadata
1089 repokind = probe( productdatapath.asUrl() );
1095 MIL << "repo type is " << repokind << endl;
1097 switch ( repokind.toEnum() )
1099 case RepoType::RPMMD_e :
1100 case RepoType::YAST2_e :
1101 case RepoType::RPMPLAINDIR_e :
1103 // Take care we unlink the solvfile on exception
1104 ManagedFile guard( solvfile, filesystem::unlink );
1105 scoped_ptr<MediaMounter> forPlainDirs;
1107 ExternalProgram::Arguments cmd;
1108 cmd.push_back( "repo2solv.sh" );
1110 // repo2solv expects -o as 1st arg!
1111 cmd.push_back( "-o" );
1112 cmd.push_back( solvfile.asString() );
1114 if ( repokind == RepoType::RPMPLAINDIR )
1116 forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
1117 // recusive for plaindir as 2nd arg!
1118 cmd.push_back( "-R" );
1119 // FIXME this does only work form dir: URLs
1120 cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
1123 cmd.push_back( productdatapath.asString() );
1125 ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
1126 std::string errdetail;
1128 for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1129 WAR << " " << output;
1130 if ( errdetail.empty() ) {
1131 errdetail = prog.command();
1134 errdetail += output;
1137 int ret = prog.close();
1140 RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
1141 ex.remember( errdetail );
1146 guard.resetDispose();
1150 ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
1153 // update timestamp and checksum
1154 setCacheStatus(info, raw_metadata_status);
1155 MIL << "Commit cache.." << endl;
1159 ////////////////////////////////////////////////////////////////////////////
1161 repo::RepoType RepoManager::probe( const Url & url ) const
1162 { return probe( url, Pathname() ); }
1164 repo::RepoType RepoManager::probe( const Url & url, const Pathname & path ) const
1166 MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
1168 if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
1170 // Handle non existing local directory in advance, as
1171 // MediaSetAccess does not support it.
1172 MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
1173 return repo::RepoType::NONE;
1176 // prepare exception to be thrown if the type could not be determined
1177 // due to a media exception. We can't throw right away, because of some
1178 // problems with proxy servers returning an incorrect error
1179 // on ftp file-not-found(bnc #335906). Instead we'll check another types
1182 // TranslatorExplanation '%s' is an URL
1183 RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
1184 bool gotMediaException = false;
1187 MediaSetAccess access(url);
1190 if ( access.doesFileExist(path/"/repodata/repomd.xml") )
1192 MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
1193 return repo::RepoType::RPMMD;
1196 catch ( const media::MediaException &e )
1199 DBG << "problem checking for repodata/repomd.xml file" << endl;
1201 gotMediaException = true;
1206 if ( access.doesFileExist(path/"/content") )
1208 MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
1209 return repo::RepoType::YAST2;
1212 catch ( const media::MediaException &e )
1215 DBG << "problem checking for content file" << endl;
1217 gotMediaException = true;
1220 // if it is a non-downloading URL denoting a directory
1221 if ( ! url.schemeIsDownloading() )
1223 MediaMounter media( url );
1224 if ( PathInfo(media.getPathName()/path).isDir() )
1226 // allow empty dirs for now
1227 MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
1228 return repo::RepoType::RPMPLAINDIR;
1232 catch ( const Exception &e )
1235 // TranslatorExplanation '%s' is an URL
1236 Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
1241 if (gotMediaException)
1244 MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
1245 return repo::RepoType::NONE;
1248 ////////////////////////////////////////////////////////////////////////////
1250 void RepoManager::cleanCache( const RepoInfo &info,
1251 const ProgressData::ReceiverFnc & progressrcv )
1253 ProgressData progress(100);
1254 progress.sendTo(progressrcv);
1257 filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
1262 ////////////////////////////////////////////////////////////////////////////
1264 bool RepoManager::isCached( const RepoInfo &info ) const
1266 return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
1269 RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
1272 Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
1274 return RepoStatus::fromCookieFile(cookiefile);
1277 void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
1279 Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
1280 filesystem::assert_dir(base);
1281 Pathname cookiefile = base / "cookie";
1283 status.saveToCookieFile(cookiefile);
1286 void RepoManager::loadFromCache( const RepoInfo & info,
1287 const ProgressData::ReceiverFnc & progressrcv )
1290 Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
1292 if ( ! PathInfo(solvfile).isExist() )
1293 ZYPP_THROW(RepoNotCachedException(info));
1297 sat::Pool::instance().addRepoSolv( solvfile, info );
1299 catch ( const Exception & exp )
1302 MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1303 cleanCache( info, progressrcv );
1304 buildCache( info, BuildIfNeeded, progressrcv );
1306 sat::Pool::instance().addRepoSolv( solvfile, info );
1310 ////////////////////////////////////////////////////////////////////////////
1312 void RepoManager::addRepository( const RepoInfo &info,
1313 const ProgressData::ReceiverFnc & progressrcv )
1317 ProgressData progress(100);
1318 callback::SendReport<ProgressReport> report;
1319 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1320 progress.name(str::form(_("Adding repository '%s'"), info.name().c_str()));
1323 MIL << "Try adding repo " << info << endl;
1325 RepoInfo tosave = info;
1326 if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
1327 ZYPP_THROW(RepoAlreadyExistsException(info));
1329 // check the first url for now
1330 if ( _pimpl->options.probe )
1332 DBG << "unknown repository type, probing" << endl;
1334 RepoType probedtype;
1335 probedtype = probe( *tosave.baseUrlsBegin(), info.path() );
1336 if ( tosave.baseUrlsSize() > 0 )
1338 if ( probedtype == RepoType::NONE )
1339 ZYPP_THROW(RepoUnknownTypeException());
1341 tosave.setType(probedtype);
1347 // assert the directory exists
1348 filesystem::assert_dir(_pimpl->options.knownReposPath);
1350 Pathname repofile = _pimpl->generateNonExistingName(
1351 _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
1352 // now we have a filename that does not exists
1353 MIL << "Saving repo in " << repofile << endl;
1355 std::ofstream file(repofile.c_str());
1358 // TranslatorExplanation '%s' is a filename
1359 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1362 tosave.dumpAsIniOn(file);
1363 tosave.setFilepath(repofile);
1364 tosave.setMetadataPath( metadataPath( tosave ) );
1365 tosave.setPackagesPath( packagesPath( tosave ) );
1367 // We chould fix the API as we must injet those paths
1368 // into the repoinfo in order to keep it usable.
1369 RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
1370 oinfo.setMetadataPath( metadataPath( tosave ) );
1371 oinfo.setPackagesPath( packagesPath( tosave ) );
1373 _pimpl->repos.insert(tosave);
1377 // check for credentials in Urls
1378 bool havePasswords = false;
1379 for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
1380 if ( urlit->hasCredentialsInAuthority() )
1382 havePasswords = true;
1385 // save the credentials
1386 if ( havePasswords )
1388 media::CredentialManager cm(
1389 media::CredManagerOptions(_pimpl->options.rootDir) );
1391 for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
1392 if (urlit->hasCredentialsInAuthority())
1393 //! \todo use a method calling UI callbacks to ask where to save creds?
1394 cm.saveInUser(media::AuthData(*urlit));
1397 HistoryLog().addRepository(tosave);
1400 MIL << "done" << endl;
1403 void RepoManager::addRepositories( const Url &url,
1404 const ProgressData::ReceiverFnc & progressrcv )
1406 std::list<RepoInfo> repos = readRepoFile(url);
1407 for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1411 // look if the alias is in the known repos.
1412 for_ ( kit, repoBegin(), repoEnd() )
1414 if ( (*it).alias() == (*kit).alias() )
1416 ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1417 ZYPP_THROW(RepoAlreadyExistsException(*it));
1422 std::string filename = Pathname(url.getPathName()).basename();
1424 if ( filename == Pathname() )
1426 // TranslatorExplanation '%s' is an URL
1427 ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
1430 // assert the directory exists
1431 filesystem::assert_dir(_pimpl->options.knownReposPath);
1433 Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
1434 // now we have a filename that does not exists
1435 MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1437 std::ofstream file(repofile.c_str());
1440 // TranslatorExplanation '%s' is a filename
1441 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1444 for ( std::list<RepoInfo>::iterator it = repos.begin();
1448 MIL << "Saving " << (*it).alias() << endl;
1449 it->setFilepath(repofile.asString());
1450 it->dumpAsIniOn(file);
1451 _pimpl->repos.insert(*it);
1453 HistoryLog(_pimpl->options.rootDir).addRepository(*it);
1456 MIL << "done" << endl;
1459 ////////////////////////////////////////////////////////////////////////////
1461 void RepoManager::removeRepository( const RepoInfo & info,
1462 const ProgressData::ReceiverFnc & progressrcv)
1464 ProgressData progress;
1465 callback::SendReport<ProgressReport> report;
1466 progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1467 progress.name(str::form(_("Removing repository '%s'"), info.name().c_str()));
1469 MIL << "Going to delete repo " << info.alias() << endl;
1471 for_( it, repoBegin(), repoEnd() )
1473 // they can be the same only if the provided is empty, that means
1474 // the provided repo has no alias
1476 if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1479 // TODO match by url
1481 // we have a matcing repository, now we need to know
1482 // where it does come from.
1483 RepoInfo todelete = *it;
1484 if (todelete.filepath().empty())
1486 ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1490 // figure how many repos are there in the file:
1491 std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1492 if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1494 // easy, only this one, just delete the file
1495 if ( filesystem::unlink(todelete.filepath()) != 0 )
1497 // TranslatorExplanation '%s' is a filename
1498 ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
1500 MIL << todelete.alias() << " sucessfully deleted." << endl;
1504 // there are more repos in the same file
1505 // write them back except the deleted one.
1507 //std::ofstream file(tmp.path().c_str());
1509 // assert the directory exists
1510 filesystem::assert_dir(todelete.filepath().dirname());
1512 std::ofstream file(todelete.filepath().c_str());
1515 // TranslatorExplanation '%s' is a filename
1516 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
1518 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1519 fit != filerepos.end();
1522 if ( (*fit).alias() != todelete.alias() )
1523 (*fit).dumpAsIniOn(file);
1527 CombinedProgressData subprogrcv(progress, 70);
1528 CombinedProgressData cleansubprogrcv(progress, 30);
1529 // now delete it from cache
1530 if ( isCached(todelete) )
1531 cleanCache( todelete, subprogrcv);
1532 // now delete metadata (#301037)
1533 cleanMetadata( todelete, cleansubprogrcv);
1534 _pimpl->repos.erase(todelete);
1535 MIL << todelete.alias() << " sucessfully deleted." << endl;
1536 HistoryLog(_pimpl->options.rootDir).removeRepository(todelete);
1538 } // else filepath is empty
1541 // should not be reached on a sucess workflow
1542 ZYPP_THROW(RepoNotFoundException(info));
1545 ////////////////////////////////////////////////////////////////////////////
1547 void RepoManager::modifyRepository( const std::string &alias,
1548 const RepoInfo & newinfo_r,
1549 const ProgressData::ReceiverFnc & progressrcv )
1551 RepoInfo toedit = getRepositoryInfo(alias);
1552 RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
1554 // check if the new alias already exists when renaming the repo
1555 if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
1557 ZYPP_THROW(RepoAlreadyExistsException(newinfo));
1560 if (toedit.filepath().empty())
1562 ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1566 // figure how many repos are there in the file:
1567 std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1569 // there are more repos in the same file
1570 // write them back except the deleted one.
1572 //std::ofstream file(tmp.path().c_str());
1574 // assert the directory exists
1575 filesystem::assert_dir(toedit.filepath().dirname());
1577 std::ofstream file(toedit.filepath().c_str());
1580 // TranslatorExplanation '%s' is a filename
1581 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
1583 for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1584 fit != filerepos.end();
1587 // if the alias is different, dump the original
1588 // if it is the same, dump the provided one
1589 if ( (*fit).alias() != toedit.alias() )
1590 (*fit).dumpAsIniOn(file);
1592 newinfo.dumpAsIniOn(file);
1595 newinfo.setFilepath(toedit.filepath());
1596 _pimpl->repos.erase(toedit);
1597 _pimpl->repos.insert(newinfo);
1598 HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
1599 MIL << "repo " << alias << " modified" << endl;
1603 ////////////////////////////////////////////////////////////////////////////
1605 RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1606 const ProgressData::ReceiverFnc & progressrcv )
1609 info.setAlias(alias);
1610 RepoConstIterator it = _pimpl->repos.find( info );
1611 if( it == repoEnd() )
1612 ZYPP_THROW(RepoNotFoundException(info));
1617 ////////////////////////////////////////////////////////////////////////////
1619 RepoInfo RepoManager::getRepositoryInfo( const Url & url,
1620 const url::ViewOption & urlview,
1621 const ProgressData::ReceiverFnc & progressrcv )
1623 for_( it, repoBegin(), repoEnd() )
1625 for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1626 urlit != (*it).baseUrlsEnd();
1629 if ((*urlit).asString(urlview) == url.asString(urlview))
1634 info.setBaseUrl(url);
1635 ZYPP_THROW(RepoNotFoundException(info));
1638 ////////////////////////////////////////////////////////////////////////////
1642 ////////////////////////////////////////////////////////////////////////////
1644 bool RepoManager::serviceEmpty() const
1645 { return _pimpl->services.empty(); }
1647 RepoManager::ServiceSizeType RepoManager::serviceSize() const
1648 { return _pimpl->services.size(); }
1650 RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
1651 { return _pimpl->services.begin(); }
1653 RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
1654 { return _pimpl->services.end(); }
1656 ServiceInfo RepoManager::getService( const std::string & alias ) const
1658 for_( it, serviceBegin(), serviceEnd() )
1659 if ( it->alias() == alias )
1661 return ServiceInfo::noService;
1664 bool RepoManager::hasService( const std::string & alias ) const
1666 for_( it, serviceBegin(), serviceEnd() )
1667 if ( it->alias() == alias )
1672 ////////////////////////////////////////////////////////////////////////////
1674 void RepoManager::addService( const std::string & alias, const Url & url )
1676 addService( ServiceInfo(alias, url) );
1679 void RepoManager::addService( const ServiceInfo & service )
1681 assert_alias( service );
1683 // check if service already exists
1684 if ( hasService( service.alias() ) )
1685 ZYPP_THROW( ServiceAlreadyExistsException( service ) );
1687 // Writable ServiceInfo is needed to save the location
1688 // of the .service file. Finaly insert into the service list.
1689 ServiceInfo toSave( service );
1690 _pimpl->saveService( toSave );
1691 _pimpl->services.insert( toSave );
1693 // check for credentials in Url (username:password, not ?credentials param)
1694 if ( toSave.url().hasCredentialsInAuthority() )
1696 media::CredentialManager cm(
1697 media::CredManagerOptions(_pimpl->options.rootDir) );
1699 //! \todo use a method calling UI callbacks to ask where to save creds?
1700 cm.saveInUser(media::AuthData(toSave.url()));
1703 MIL << "added service " << toSave.alias() << endl;
1706 ////////////////////////////////////////////////////////////////////////////
1708 void RepoManager::removeService( const std::string & alias )
1710 MIL << "Going to delete repo " << alias << endl;
1712 const ServiceInfo & service = getService( alias );
1714 Pathname location = service.filepath();
1715 if( location.empty() )
1717 ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1721 parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1723 // only one service definition in the file
1724 if ( tmpSet.size() == 1 )
1726 if ( filesystem::unlink(location) != 0 )
1728 // TranslatorExplanation '%s' is a filename
1729 ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
1731 MIL << alias << " sucessfully deleted." << endl;
1735 filesystem::assert_dir(location.dirname());
1737 std::ofstream file(location.c_str());
1740 // TranslatorExplanation '%s' is a filename
1741 ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
1744 for_(it, tmpSet.begin(), tmpSet.end())
1746 if( it->alias() != alias )
1747 it->dumpAsIniOn(file);
1750 MIL << alias << " sucessfully deleted from file " << location << endl;
1753 // now remove all repositories added by this service
1754 RepoCollector rcollector;
1755 getRepositoriesInService( alias,
1756 boost::make_function_output_iterator(
1757 bind( &RepoCollector::collect, &rcollector, _1 ) ) );
1758 // cannot do this directly in getRepositoriesInService - would invalidate iterators
1759 for_(rit, rcollector.repos.begin(), rcollector.repos.end())
1760 removeRepository(*rit);
1763 void RepoManager::removeService( const ServiceInfo & service )
1764 { removeService(service.alias()); }
1766 ////////////////////////////////////////////////////////////////////////////
1768 void RepoManager::refreshServices()
1770 // copy the set of services since refreshService
1771 // can eventually invalidate the iterator
1772 ServiceSet services( serviceBegin(), serviceEnd() );
1773 for_( it, services.begin(), services.end() )
1775 if ( !it->enabled() )
1778 refreshService(*it);
1782 void RepoManager::refreshService( const ServiceInfo & service )
1783 { refreshService( service.alias() ); }
1785 void RepoManager::refreshService( const std::string & alias )
1787 ServiceInfo service( getService( alias ) );
1788 assert_alias( service );
1789 assert_url( service );
1790 // NOTE: It might be necessary to modify and rewrite the service info.
1791 // Either when probing the type, or when adjusting the repositories
1792 // enable/disable state.:
1793 bool serviceModified = false;
1794 MIL << "going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
1796 //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
1798 // if the type is unknown, try probing.
1799 if ( service.type() == repo::ServiceType::NONE )
1801 repo::ServiceType type = probeService( service.url() );
1802 if ( type != ServiceType::NONE )
1804 service.setProbedType( type ); // lazy init!
1805 serviceModified = true;
1809 // download the repo index file
1810 media::MediaManager mediamanager;
1811 media::MediaAccessId mid = mediamanager.open( service.url() );
1812 mediamanager.attach( mid );
1813 mediamanager.provideFile( mid, "repo/repoindex.xml" );
1814 Pathname path = mediamanager.localPath(mid, "repo/repoindex.xml" );
1816 // get target distro identifier
1817 std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro;
1818 if ( servicesTargetDistro.empty() && getZYpp()->getTarget() )
1819 servicesTargetDistro = getZYpp()->target()->targetDistribution();
1820 DBG << "servicesTargetDistro: " << servicesTargetDistro << endl;
1823 RepoCollector collector(servicesTargetDistro);
1824 parser::RepoindexFileReader reader( path, bind( &RepoCollector::collect, &collector, _1 ) );
1825 mediamanager.release( mid );
1826 mediamanager.close( mid );
1829 // set service alias and base url for all collected repositories
1830 for_( it, collector.repos.begin(), collector.repos.end() )
1832 // if the repo url was not set by the repoindex parser, set service's url
1835 if ( it->baseUrlsEmpty() )
1836 url = service.url();
1839 // service repo can contain only one URL now, so no need to iterate.
1840 url = *it->baseUrlsBegin();
1843 // libzypp currently has problem with separate url + path handling
1844 // so just append the path to the baseurl
1845 if ( !it->path().empty() )
1847 Pathname path(url.getPathName());
1849 url.setPathName( path.asString() );
1853 // Prepend service alias:
1854 it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
1857 it->setBaseUrl( url );
1858 // set refrence to the parent service
1859 it->setService( service.alias() );
1862 ////////////////////////////////////////////////////////////////////////////
1863 // Now compare collected repos with the ones in the system...
1865 RepoInfoList oldRepos;
1866 getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
1868 // find old repositories to remove...
1869 for_( it, oldRepos.begin(), oldRepos.end() )
1871 if ( ! foundAliasIn( it->alias(), collector.repos ) )
1873 removeRepository( *it );
1877 ////////////////////////////////////////////////////////////////////////////
1878 // create missing repositories and modify exising ones if needed...
1879 for_( it, collector.repos.begin(), collector.repos.end() )
1881 // Service explicitly requests the repo being enabled?
1882 // Service explicitly requests the repo being disabled?
1883 // And hopefully not both ;) If so, enable wins.
1884 bool beEnabled = service.repoToEnableFind( it->alias() );
1885 bool beDisabled = service.repoToDisableFind( it->alias() );
1889 // Remove from enable request list.
1890 // NOTE: repoToDisable is handled differently.
1891 // It gets cleared on each refresh.
1892 service.delRepoToEnable( it->alias() );
1893 serviceModified = true;
1896 RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
1897 if ( oldRepo == oldRepos.end() )
1899 // Not found in oldRepos ==> a new repo to add
1901 // Make sure the service repo is created with the
1902 // appropriate enable and autorefresh true.
1903 it->setEnabled( beEnabled );
1904 it->setAutorefresh( true );
1906 // At that point check whether a repo with the same alias
1907 // exists outside this service. Maybe forcefully re-alias
1908 // the existing repo?
1909 addRepository( *it );
1911 // save repo credentials
1912 // ma@: task for modifyRepository?
1916 // ==> an exising repo to check
1917 bool oldRepoModified = false;
1922 if ( ! oldRepo->enabled() )
1924 oldRepo->setEnabled( true );
1925 oldRepoModified = true;
1928 else if ( beDisabled )
1930 if ( oldRepo->enabled() )
1932 oldRepo->setEnabled( false );
1933 oldRepoModified = true;
1938 // service repo can contain only one URL now, so no need to iterate.
1939 if ( oldRepo->url() != it->url() )
1941 oldRepo->setBaseUrl( it->url() );
1942 oldRepoModified = true;
1945 // save if modified:
1946 if ( oldRepoModified )
1948 modifyRepository( oldRepo->alias(), *oldRepo );
1953 // Unlike reposToEnable, reposToDisable is always cleared after refresh.
1954 if ( ! service.reposToDisableEmpty() )
1956 service.clearReposToDisable();
1957 serviceModified = true;
1960 ////////////////////////////////////////////////////////////////////////////
1961 // save service if modified:
1962 if ( serviceModified )
1964 // write out modified service file.
1965 modifyService( service.alias(), service );
1969 ////////////////////////////////////////////////////////////////////////////
1971 void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & service)
1973 MIL << "Going to modify service " << oldAlias << endl;
1975 const ServiceInfo & oldService = getService(oldAlias);
1977 Pathname location = oldService.filepath();
1978 if( location.empty() )
1980 ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1983 // remember: there may multiple services being defined in one file:
1985 parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1987 filesystem::assert_dir(location.dirname());
1988 std::ofstream file(location.c_str());
1989 for_(it, tmpSet.begin(), tmpSet.end())
1991 if( *it != oldAlias )
1992 it->dumpAsIniOn(file);
1994 service.dumpAsIniOn(file);
1997 _pimpl->services.erase(oldAlias);
1998 _pimpl->services.insert(service);
2000 // changed properties affecting also repositories
2001 if( oldAlias != service.alias() // changed alias
2002 || oldService.enabled() != service.enabled() // changed enabled status
2005 std::vector<RepoInfo> toModify;
2006 getRepositoriesInService(oldAlias, std::back_inserter(toModify));
2007 for_( it, toModify.begin(), toModify.end() )
2009 if (oldService.enabled() && !service.enabled())
2010 it->setEnabled(false);
2011 else if (!oldService.enabled() && service.enabled())
2013 //! \todo do nothing? the repos will be enabled on service refresh
2014 //! \todo how to know the service needs a (auto) refresh????
2017 it->setService(service.alias());
2018 modifyRepository(it->alias(), *it);
2022 //! \todo refresh the service automatically if url is changed?
2025 ////////////////////////////////////////////////////////////////////////////
2027 repo::ServiceType RepoManager::probeService( const Url &url ) const
2031 MediaSetAccess access(url);
2032 if ( access.doesFileExist("/repo/repoindex.xml") )
2033 return repo::ServiceType::RIS;
2035 catch ( const media::MediaException &e )
2038 // TranslatorExplanation '%s' is an URL
2039 RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
2043 catch ( const Exception &e )
2046 // TranslatorExplanation '%s' is an URL
2047 Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
2052 return repo::ServiceType::NONE;
2055 ////////////////////////////////////////////////////////////////////////////
2057 std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
2059 return str << *obj._pimpl;
2062 /////////////////////////////////////////////////////////////////
2064 ///////////////////////////////////////////////////////////////////