+ void RepoManager::Impl::refreshServices( const RefreshServiceOptions & options_r )
+ {
+ // copy the set of services since refreshService
+ // can eventually invalidate the iterator
+ ServiceSet services( serviceBegin(), serviceEnd() );
+ for_( it, services.begin(), services.end() )
+ {
+ if ( !it->enabled() )
+ continue;
+
+ try {
+ refreshService(*it, options_r);
+ }
+ catch ( const repo::ServicePluginInformalException & e )
+ { ;/* ignore ServicePluginInformalException */ }
+ }
+ }
+
+ void RepoManager::Impl::refreshService( const std::string & alias, const RefreshServiceOptions & options_r )
+ {
+ ServiceInfo service( getService( alias ) );
+ assert_alias( service );
+ assert_url( service );
+ // NOTE: It might be necessary to modify and rewrite the service info.
+ // Either when probing the type, or when adjusting the repositories
+ // enable/disable state.:
+ bool serviceModified = false;
+ MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << ", opts: " << options_r << endl;
+
+ //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
+
+ // if the type is unknown, try probing.
+ if ( service.type() == repo::ServiceType::NONE )
+ {
+ repo::ServiceType type = probeService( service.url() );
+ if ( type != ServiceType::NONE )
+ {
+ service.setProbedType( type ); // lazy init!
+ serviceModified = true;
+ }
+ }
+
+ // get target distro identifier
+ std::string servicesTargetDistro = _options.servicesTargetDistro;
+ if ( servicesTargetDistro.empty() )
+ {
+ servicesTargetDistro = Target::targetDistribution( Pathname() );
+ }
+ DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
+
+ // parse it
+ RepoCollector collector(servicesTargetDistro);
+ // FIXME Ugly hack: ServiceRepos may throw ServicePluginInformalException
+ // which is actually a notification. Using an exception for this
+ // instead of signal/callback is bad. Needs to be fixed here, in refreshServices()
+ // and in zypper.
+ std::pair<DefaultIntegral<bool,false>, repo::ServicePluginInformalException> uglyHack;
+ try {
+ ServiceRepos repos(service, bind( &RepoCollector::collect, &collector, _1 ));
+ }
+ catch ( const repo::ServicePluginInformalException & e )
+ {
+ /* ignore ServicePluginInformalException and throw later */
+ uglyHack.first = true;
+ uglyHack.second = e;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // On the fly remember the new repo states as defined the reopoindex.xml.
+ // Move into ServiceInfo later.
+ ServiceInfo::RepoStates newRepoStates;
+
+ // set service alias and base url for all collected repositories
+ for_( it, collector.repos.begin(), collector.repos.end() )
+ {
+ // First of all: Prepend service alias:
+ it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
+ // set refrence to the parent service
+ it->setService( service.alias() );
+
+ // remember the new parsed repo state
+ newRepoStates[it->alias()] = *it;
+
+ // if the repo url was not set by the repoindex parser, set service's url
+ Url url;
+ if ( it->baseUrlsEmpty() )
+ url = service.url();
+ else
+ {
+ // service repo can contain only one URL now, so no need to iterate.
+ url = *it->baseUrlsBegin();
+ }
+
+ // libzypp currently has problem with separate url + path handling
+ // so just append the path to the baseurl
+ if ( !it->path().empty() )
+ {
+ Pathname path(url.getPathName());
+ path /= it->path();
+ url.setPathName( path.asString() );
+ it->setPath("");
+ }
+
+ // save the url
+ it->setBaseUrl( url );
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Now compare collected repos with the ones in the system...
+ //
+ RepoInfoList oldRepos;
+ getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
+
+ ////////////////////////////////////////////////////////////////////////////
+ // find old repositories to remove...
+ for_( oldRepo, oldRepos.begin(), oldRepos.end() )
+ {
+ if ( ! foundAliasIn( oldRepo->alias(), collector.repos ) )
+ {
+ if ( oldRepo->enabled() )
+ {
+ // Currently enabled. If this was a user modification remember the state.
+ const auto & last = service.repoStates().find( oldRepo->alias() );
+ if ( last != service.repoStates().end() && ! last->second.enabled )
+ {
+ DBG << "Service removes user enabled repo " << oldRepo->alias() << endl;
+ service.addRepoToEnable( oldRepo->alias() );
+ serviceModified = true;
+ }
+ else
+ DBG << "Service removes enabled repo " << oldRepo->alias() << endl;
+ }
+ else
+ DBG << "Service removes disabled repo " << oldRepo->alias() << endl;
+
+ removeRepository( *oldRepo );
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // create missing repositories and modify exising ones if needed...
+ for_( it, collector.repos.begin(), collector.repos.end() )
+ {
+ // User explicitly requested the repo being enabled?
+ // User explicitly requested the repo being disabled?
+ // And hopefully not both ;) If so, enable wins.
+
+ TriBool toBeEnabled( indeterminate ); // indeterminate - follow the service request
+ DBG << "Service request to " << (it->enabled()?"enable":"disable") << " service repo " << it->alias() << endl;
+
+ if ( options_r.testFlag( RefreshService_restoreStatus ) )
+ {
+ DBG << "Opt RefreshService_restoreStatus " << it->alias() << endl;
+ // this overrides any pending request!
+ // Remove from enable request list.
+ // NOTE: repoToDisable is handled differently.
+ // It gets cleared on each refresh.
+ service.delRepoToEnable( it->alias() );
+ // toBeEnabled stays indeterminate!
+ }
+ else
+ {
+ if ( service.repoToEnableFind( it->alias() ) )
+ {
+ DBG << "User request to enable service repo " << it->alias() << endl;
+ toBeEnabled = true;
+ // Remove from enable request list.
+ // NOTE: repoToDisable is handled differently.
+ // It gets cleared on each refresh.
+ service.delRepoToEnable( it->alias() );
+ serviceModified = true;
+ }
+ else if ( service.repoToDisableFind( it->alias() ) )
+ {
+ DBG << "User request to disable service repo " << it->alias() << endl;
+ toBeEnabled = false;
+ }
+ }
+
+ RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
+ if ( oldRepo == oldRepos.end() )
+ {
+ // Not found in oldRepos ==> a new repo to add
+
+ // Make sure the service repo is created with the appropriate enablement
+ if ( ! indeterminate(toBeEnabled) )
+ it->setEnabled( toBeEnabled );
+
+ DBG << "Service adds repo " << it->alias() << " " << (it->enabled()?"enabled":"disabled") << endl;
+ addRepository( *it );
+ }
+ else
+ {
+ // ==> an exising repo to check
+ bool oldRepoModified = false;
+
+ if ( indeterminate(toBeEnabled) )
+ {
+ // No user request: check for an old user modificaton otherwise follow service request.
+ // NOTE: Assert toBeEnabled is boolean afterwards!
+ if ( oldRepo->enabled() == it->enabled() )
+ toBeEnabled = it->enabled(); // service requests no change to the system
+ else if (options_r.testFlag( RefreshService_restoreStatus ) )
+ {
+ toBeEnabled = it->enabled(); // RefreshService_restoreStatus forced
+ DBG << "Opt RefreshService_restoreStatus " << it->alias() << " forces " << (toBeEnabled?"enabled":"disabled") << endl;
+ }
+ else
+ {
+ const auto & last = service.repoStates().find( oldRepo->alias() );
+ if ( last == service.repoStates().end() || last->second.enabled != it->enabled() )
+ toBeEnabled = it->enabled(); // service request has changed since last refresh -> follow
+ else
+ {
+ toBeEnabled = oldRepo->enabled(); // service request unchaned since last refresh -> keep user modification
+ DBG << "User modified service repo " << it->alias() << " may stay " << (toBeEnabled?"enabled":"disabled") << endl;
+ }
+ }
+ }
+
+ // changed enable?
+ if ( toBeEnabled == oldRepo->enabled() )
+ {
+ DBG << "Service repo " << it->alias() << " stays " << (oldRepo->enabled()?"enabled":"disabled") << endl;
+ }
+ else if ( toBeEnabled )
+ {
+ DBG << "Service repo " << it->alias() << " gets enabled" << endl;
+ oldRepo->setEnabled( true );
+ oldRepoModified = true;
+ }
+ else
+ {
+ DBG << "Service repo " << it->alias() << " gets disabled" << endl;
+ oldRepo->setEnabled( false );
+ oldRepoModified = true;
+ }
+
+ // all other attributes follow the service request:
+
+ // changed autorefresh
+ if ( oldRepo->autorefresh() != it->autorefresh() )
+ {
+ DBG << "Service repo " << it->alias() << " gets new AUTOREFRESH " << it->autorefresh() << endl;
+ oldRepo->setAutorefresh( it->autorefresh() );
+ oldRepoModified = true;
+ }
+
+ // changed priority?
+ if ( oldRepo->priority() != it->priority() )
+ {
+ DBG << "Service repo " << it->alias() << " gets new PRIORITY " << it->priority() << endl;
+ oldRepo->setPriority( it->priority() );
+ oldRepoModified = true;
+ }
+
+ // changed url?
+ // service repo can contain only one URL now, so no need to iterate.
+ if ( oldRepo->url() != it->url() )
+ {
+ DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
+ oldRepo->setBaseUrl( it->url() );
+ oldRepoModified = true;
+ }
+
+ // save if modified:
+ if ( oldRepoModified )
+ {
+ modifyRepository( oldRepo->alias(), *oldRepo );
+ }
+ }
+ }
+
+ // Unlike reposToEnable, reposToDisable is always cleared after refresh.
+ if ( ! service.reposToDisableEmpty() )
+ {
+ service.clearReposToDisable();
+ serviceModified = true;
+ }
+
+ // Remember original service request for next refresh
+ if ( service.repoStates() != newRepoStates )
+ {
+ service.setRepoStates( std::move(newRepoStates) );
+ serviceModified = true;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // save service if modified: (unless a plugin service)
+ if ( serviceModified && service.type() != ServiceType::PLUGIN )
+ {
+ // write out modified service file.
+ modifyService( service.alias(), service );
+ }
+
+ if ( uglyHack.first )
+ {
+ throw( uglyHack.second ); // intentionally not ZYPP_THROW
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ void RepoManager::Impl::modifyService( const std::string & oldAlias, const ServiceInfo & newService )
+ {
+ MIL << "Going to modify service " << oldAlias << endl;
+
+ // we need a writable copy to link it to the file where
+ // it is saved if we modify it
+ ServiceInfo service(newService);
+
+ if ( service.type() == ServiceType::PLUGIN )
+ {
+ ZYPP_THROW(ServicePluginImmutableException( service ));
+ }
+
+ const ServiceInfo & oldService = getService(oldAlias);
+
+ Pathname location = oldService.filepath();
+ if( location.empty() )
+ {
+ ZYPP_THROW(ServiceException( oldService, _("Can't figure out where the service is stored.") ));
+ }
+
+ // remember: there may multiple services being defined in one file:
+ ServiceSet tmpSet;
+ parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
+
+ filesystem::assert_dir(location.dirname());
+ std::ofstream file(location.c_str());
+ for_(it, tmpSet.begin(), tmpSet.end())
+ {
+ if( *it != oldAlias )
+ it->dumpAsIniOn(file);
+ }
+ service.dumpAsIniOn(file);
+ file.close();
+ service.setFilepath(location);
+
+ _services.erase(oldAlias);
+ _services.insert(service);
+
+ // changed properties affecting also repositories
+ if ( oldAlias != service.alias() // changed alias
+ || oldService.enabled() != service.enabled() ) // changed enabled status
+ {
+ std::vector<RepoInfo> toModify;
+ getRepositoriesInService(oldAlias, std::back_inserter(toModify));
+ for_( it, toModify.begin(), toModify.end() )
+ {
+ if ( oldService.enabled() != service.enabled() )
+ {
+ if ( service.enabled() )
+ {
+ // reset to last refreshs state
+ const auto & last = service.repoStates().find( it->alias() );
+ if ( last != service.repoStates().end() )
+ it->setEnabled( last->second.enabled );
+ }
+ else
+ it->setEnabled( false );
+ }
+
+ if ( oldAlias != service.alias() )
+ it->setService(service.alias());
+
+ modifyRepository(it->alias(), *it);
+ }
+ }
+
+ //! \todo refresh the service automatically if url is changed?
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ repo::ServiceType RepoManager::Impl::probeService( const Url & url ) const
+ {
+ try
+ {
+ MediaSetAccess access(url);
+ if ( access.doesFileExist("/repo/repoindex.xml") )
+ return repo::ServiceType::RIS;
+ }
+ catch ( const media::MediaException &e )
+ {
+ ZYPP_CAUGHT(e);
+ // TranslatorExplanation '%s' is an URL
+ RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
+ enew.remember(e);
+ ZYPP_THROW(enew);
+ }
+ catch ( const Exception &e )
+ {
+ ZYPP_CAUGHT(e);
+ // TranslatorExplanation '%s' is an URL
+ Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
+ enew.remember(e);
+ ZYPP_THROW(enew);
+ }
+
+ return repo::ServiceType::NONE;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ //
+ // CLASS NAME : RepoManager
+ //
+ ///////////////////////////////////////////////////////////////////
+
+ RepoManager::RepoManager( const RepoManagerOptions & opt )
+ : _pimpl( new Impl(opt) )
+ {}
+
+ RepoManager::~RepoManager()
+ {}
+
+ bool RepoManager::repoEmpty() const
+ { return _pimpl->repoEmpty(); }
+
+ RepoManager::RepoSizeType RepoManager::repoSize() const
+ { return _pimpl->repoSize(); }
+
+ RepoManager::RepoConstIterator RepoManager::repoBegin() const
+ { return _pimpl->repoBegin(); }
+
+ RepoManager::RepoConstIterator RepoManager::repoEnd() const
+ { return _pimpl->repoEnd(); }
+
+ RepoInfo RepoManager::getRepo( const std::string & alias ) const
+ { return _pimpl->getRepo( alias ); }
+
+ bool RepoManager::hasRepo( const std::string & alias ) const
+ { return _pimpl->hasRepo( alias ); }
+
+ std::string RepoManager::makeStupidAlias( const Url & url_r )