Imported Upstream version 15.0.0
[platform/upstream/libzypp.git] / zypp / RepoManager.cc
index e2f81e8..d9ce8bf 100644 (file)
@@ -19,8 +19,9 @@
 #include <algorithm>
 
 #include "zypp/base/InputStream.h"
-#include "zypp/base/Logger.h"
+#include "zypp/base/LogTools.h"
 #include "zypp/base/Gettext.h"
+#include "zypp/base/DefaultIntegral.h"
 #include "zypp/base/Function.h"
 #include "zypp/base/Regex.h"
 #include "zypp/PathInfo.h"
 
 #include "zypp/parser/RepoFileReader.h"
 #include "zypp/parser/ServiceFileReader.h"
-#include "zypp/parser/RepoindexFileReader.h"
+#include "zypp/repo/ServiceRepos.h"
 #include "zypp/repo/yum/Downloader.h"
 #include "zypp/repo/susetags/Downloader.h"
-#include "zypp/parser/plaindir/RepoParser.h"
+#include "zypp/repo/PluginServices.h"
 
 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance
 #include "zypp/ZYppCallbacks.h"
 
 #include "sat/Pool.h"
-#include "satsolver/pool.h"
-#include "satsolver/repo.h"
-#include "satsolver/repo_solv.h"
 
 using std::endl;
+using std::string;
 using namespace zypp::repo;
 
+#define OPT_PROGRESS const ProgressData::ReceiverFnc & = ProgressData::ReceiverFnc()
+
 ///////////////////////////////////////////////////////////////////
 namespace zypp
-{ /////////////////////////////////////////////////////////////////
-
+{
+  ///////////////////////////////////////////////////////////////////
   namespace
   {
+    /** Simple media mounter to access non-downloading URLs e.g. for non-local plaindir repos.
+     * \ingroup g_RAII
+     */
+    class MediaMounter
+    {
+      public:
+        /** Ctor provides media access. */
+        MediaMounter( const Url & url_r )
+        {
+          media::MediaManager mediamanager;
+          _mid = mediamanager.open( url_r );
+          mediamanager.attach( _mid );
+        }
+
+        /** Ctor releases the media. */
+        ~MediaMounter()
+        {
+          media::MediaManager mediamanager;
+          mediamanager.release( _mid );
+          mediamanager.close( _mid );
+        }
+
+        /** Convert a path relative to the media into an absolute path.
+         *
+         * Called without argument it returns the path to the medias root directory.
+        */
+        Pathname getPathName( const Pathname & path_r = Pathname() ) const
+        {
+          media::MediaManager mediamanager;
+          return mediamanager.localPath( _mid, path_r );
+        }
+
+      private:
+        media::MediaAccessId _mid;
+    };
+    ///////////////////////////////////////////////////////////////////
+
     /** Check if alias_r is present in repo/service container. */
     template <class Iterator>
     inline bool foundAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r )
@@ -94,66 +132,35 @@ namespace zypp
     template <class Container>
     inline typename Container::const_iterator findAlias( const std::string & alias_r, const Container & cont_r )
     { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
-  }
 
-  ///////////////////////////////////////////////////////////////////
-  //
-  //   CLASS NAME : RepoManagerOptions
-  //
-  ///////////////////////////////////////////////////////////////////
-
-  RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
-  {
-    repoCachePath         = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
-    repoRawCachePath      = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
-    repoSolvCachePath     = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
-    repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
-    knownReposPath        = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
-    knownServicesPath     = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
-    probe                 = ZConfig::instance().repo_add_probe();
 
-    if ( getZYpp()->getTarget() )
-    {
-      servicesTargetDistro = getZYpp()->target()->targetDistribution();
-    }
-    else
+    /** \short Generate a related filename from a repo/service infos alias */
+    inline std::string filenameFromAlias( const std::string & alias_r, const std::string & stem_r )
     {
-      DBG << "Target not initialized, using an empty servicesTargetDistro." << endl;
-    }
-
-    rootDir = root_r;
-  }
-
-  RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r )
-  {
-    RepoManagerOptions ret;
-    ret.repoCachePath         = root_r;
-    ret.repoRawCachePath      = root_r/"raw";
-    ret.repoSolvCachePath     = root_r/"solv";
-    ret.repoPackagesCachePath = root_r/"packages";
-    ret.knownReposPath        = root_r/"repos.d";
-    ret.knownServicesPath     = root_r/"services.d";
-    ret.rootDir = root_r;
-    return ret;
-  }
+      std::string filename( alias_r );
+      // replace slashes with underscores
+      str::replaceAll( filename, "/", "_" );
 
-  ////////////////////////////////////////////////////////////////////////////
+      filename = Pathname(filename).extend("."+stem_r).asString();
+      MIL << "generating filename for " << stem_r << " [" << alias_r << "] : '" << filename << "'" << endl;
+      return filename;
+    }
 
-  /**
-    * \short Simple callback to collect the results
-    *
-    * Classes like RepoFileParser call the callback
-    * once per each repo in a file.
-    *
-    * Passing this functor as callback, you can collect
-    * all results at the end, without dealing with async
-    * code.
-    *
-    * If targetDistro is set, all repos with non-empty RepoInfo::targetDistribution()
-    * will be skipped.
-    *
-    * \todo do this through a separate filter
-    */
+    /**
+     * \short Simple callback to collect the results
+     *
+     * Classes like RepoFileReader call the callback
+     * once per each repo in a file.
+     *
+     * Passing this functor as callback, you can collect
+     * all results at the end, without dealing with async
+     * code.
+     *
+     * If targetDistro is set, all repos with non-empty RepoInfo::targetDistribution()
+     * will be skipped.
+     *
+     * \todo do this through a separate filter
+     */
     struct RepoCollector : private base::NonCopyable
     {
       RepoCollector()
@@ -171,9 +178,9 @@ namespace zypp
             && repo.targetDistribution() != targetDistro)
         {
           MIL
-            << "Skipping repository meant for '" << targetDistro
+            << "Skipping repository meant for '" << repo.targetDistribution()
             << "' distribution (current distro is '"
-            << repo.targetDistribution() << "')." << endl;
+            << targetDistro << "')." << endl;
 
           return true;
         }
@@ -185,134 +192,156 @@ namespace zypp
       RepoInfoList repos;
       std::string targetDistro;
     };
+    ////////////////////////////////////////////////////////////////////////////
 
-  ////////////////////////////////////////////////////////////////////////////
-
-  /**
-   * Reads RepoInfo's from a repo file.
-   *
-   * \param file pathname of the file to read.
-   */
-  static std::list<RepoInfo> repositories_in_file( const Pathname & file )
-  {
-    MIL << "repo file: " << file << endl;
-    RepoCollector collector;
-    parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
-    return collector.repos;
-  }
-
-  ////////////////////////////////////////////////////////////////////////////
+    /**
+     * Reads RepoInfo's from a repo file.
+     *
+     * \param file pathname of the file to read.
+     */
+    std::list<RepoInfo> repositories_in_file( const Pathname & file )
+    {
+      MIL << "repo file: " << file << endl;
+      RepoCollector collector;
+      parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
+      return std::move(collector.repos);
+    }
 
-  /**
-   * \short List of RepoInfo's from a directory
-   *
-   * Goes trough every file ending with ".repo" in a directory and adds all
-   * RepoInfo's contained in that file.
-   *
-   * \param dir pathname of the directory to read.
-   */
-  static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
-  {
-    MIL << "directory " << dir << endl;
-    std::list<RepoInfo> repos;
-    std::list<Pathname> entries;
-    if ( filesystem::readdir( entries, Pathname(dir), false ) != 0 )
-      ZYPP_THROW(Exception("failed to read directory"));
+    ////////////////////////////////////////////////////////////////////////////
 
-    str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
-    for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
+    /**
+     * \short List of RepoInfo's from a directory
+     *
+     * Goes trough every file ending with ".repo" in a directory and adds all
+     * RepoInfo's contained in that file.
+     *
+     * \param dir pathname of the directory to read.
+     */
+    std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
     {
-      if (str::regex_match(it->extension(), allowedRepoExt))
+      MIL << "directory " << dir << endl;
+      std::list<RepoInfo> repos;
+      bool nonroot( geteuid() != 0 );
+      if ( nonroot && ! PathInfo(dir).userMayRX() )
       {
-        std::list<RepoInfo> tmp = repositories_in_file( *it );
-        repos.insert( repos.end(), tmp.begin(), tmp.end() );
-
-        //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
-        //MIL << "ok" << endl;
+       JobReport::warning( formatNAC(_("Cannot read repo directory '%1%': Permission denied")) % dir );
+      }
+      else
+      {
+       std::list<Pathname> entries;
+       if ( filesystem::readdir( entries, dir, false ) != 0 )
+       {
+         // TranslatorExplanation '%s' is a pathname
+         ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
+       }
+
+       str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
+       for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
+       {
+         if ( str::regex_match(it->extension(), allowedRepoExt) )
+         {
+           if ( nonroot && ! PathInfo(*it).userMayR() )
+           {
+             JobReport::warning( formatNAC(_("Cannot read repo file '%1%': Permission denied")) % *it );
+           }
+           else
+           {
+             const std::list<RepoInfo> & tmp( repositories_in_file( *it ) );
+             repos.insert( repos.end(), tmp.begin(), tmp.end() );
+           }
+         }
+       }
       }
+      return repos;
     }
-    return repos;
-  }
-
-  ////////////////////////////////////////////////////////////////////////////
-
-   std::list<RepoInfo> readRepoFile(const Url & repo_file)
-   {
-     // no interface to download a specific file, using workaround:
-     //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
-     Url url(repo_file);
-     Pathname path(url.getPathName());
-     url.setPathName ("/");
-     MediaSetAccess access(url);
-     Pathname local = access.provideFile(path);
-
-     DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
 
-     return repositories_in_file(local);
-   }
+    ////////////////////////////////////////////////////////////////////////////
 
-  ////////////////////////////////////////////////////////////////////////////
+    inline void assert_alias( const RepoInfo & info )
+    {
+      if ( info.alias().empty() )
+       ZYPP_THROW( RepoNoAliasException( info ) );
+      // bnc #473834. Maybe we can match the alias against a regex to define
+      // and check for valid aliases
+      if ( info.alias()[0] == '.')
+       ZYPP_THROW(RepoInvalidAliasException(
+         info, _("Repository alias cannot start with dot.")));
+    }
 
-  inline void assert_alias( const RepoInfo & info )
-  {
-    if ( info.alias().empty() )
-      ZYPP_THROW( RepoNoAliasException() );
-  }
+    inline void assert_alias( const ServiceInfo & info )
+    {
+      if ( info.alias().empty() )
+       ZYPP_THROW( ServiceNoAliasException( info ) );
+      // bnc #473834. Maybe we can match the alias against a regex to define
+      // and check for valid aliases
+      if ( info.alias()[0] == '.')
+       ZYPP_THROW(ServiceInvalidAliasException(
+         info, _("Service alias cannot start with dot.")));
+    }
 
-  inline void assert_alias( const ServiceInfo & info )
-  {
-    if ( info.alias().empty() )
-      ZYPP_THROW( ServiceNoAliasException() );
-  }
+    ////////////////////////////////////////////////////////////////////////////
 
-  ////////////////////////////////////////////////////////////////////////////
+    inline void assert_urls( const RepoInfo & info )
+    {
+      if ( info.baseUrlsEmpty() )
+       ZYPP_THROW( RepoNoUrlException( info ) );
+    }
 
-  inline void assert_urls( const RepoInfo & info )
-  {
-    if ( info.baseUrlsEmpty() )
-      ZYPP_THROW( RepoNoUrlException( info ) );
-  }
+    inline void assert_url( const ServiceInfo & info )
+    {
+      if ( ! info.url().isValid() )
+       ZYPP_THROW( ServiceNoUrlException( info ) );
+    }
 
-  inline void assert_url( const ServiceInfo & info )
-  {
-    if ( ! info.url().isValid() )
-      ZYPP_THROW( ServiceNoUrlException( info ) );
-  }
+    ////////////////////////////////////////////////////////////////////////////
 
-  ////////////////////////////////////////////////////////////////////////////
+    /**
+     * \short Calculates the raw cache path for a repository, this is usually
+     * /var/cache/zypp/alias
+     */
+    inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
+    {
+      assert_alias(info);
+      return opt.repoRawCachePath / info.escaped_alias();
+    }
 
-  /**
-   * \short Calculates the raw cache path for a repository
-   */
-  inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
-  {
-    assert_alias(info);
-    return opt.repoRawCachePath / info.escaped_alias();
-  }
+    /**
+     * \short Calculates the raw product metadata path for a repository, this is
+     * inside the raw cache dir, plus an optional path where the metadata is.
+     *
+     * It should be different only for repositories that are not in the root of
+     * the media.
+     * for example /var/cache/zypp/alias/addondir
+     */
+    inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
+    {
+      assert_alias(info);
+      return opt.repoRawCachePath / info.escaped_alias() / info.path();
+    }
 
-  /**
-   * \short Calculates the packages cache path for a repository
-   */
-  inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
-  {
-    assert_alias(info);
-    return opt.repoPackagesCachePath / info.escaped_alias();
-  }
+    /**
+     * \short Calculates the packages cache path for a repository
+     */
+    inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
+    {
+      assert_alias(info);
+      return opt.repoPackagesCachePath / info.escaped_alias();
+    }
 
-  /**
-   * \short Calculates the solv cache path for a repository
-   */
-  inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
-  {
-    assert_alias(info);
-    return opt.repoSolvCachePath / info.escaped_alias();
-  }
+    /**
+     * \short Calculates the solv cache path for a repository
+     */
+    inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
+    {
+      assert_alias(info);
+      return opt.repoSolvCachePath / info.escaped_alias();
+    }
 
-  ////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////
 
-  /** Functor collecting ServiceInfos into a ServiceSet. */
-  class ServiceCollector
-  {
+    /** Functor collecting ServiceInfos into a ServiceSet. */
+    class ServiceCollector
+    {
     public:
       typedef std::set<ServiceInfo> ServiceSet;
 
@@ -322,76 +351,294 @@ namespace zypp
 
       bool operator()( const ServiceInfo & service_r ) const
       {
-        _services.insert( service_r );
-        return true;
+       _services.insert( service_r );
+       return true;
       }
 
     private:
       ServiceSet & _services;
-  };
+    };
+    ////////////////////////////////////////////////////////////////////////////
 
-  ////////////////////////////////////////////////////////////////////////////
+  } // namespace
+  ///////////////////////////////////////////////////////////////////
+
+  std::list<RepoInfo> readRepoFile( const Url & repo_file )
+  {
+    // no interface to download a specific file, using workaround:
+    //! \todo add MediaManager::provideFile(Url file_url) to easily access any file URLs? (no need for media access id or media_nr)
+    Url url(repo_file);
+    Pathname path(url.getPathName());
+    url.setPathName ("/");
+    MediaSetAccess access(url);
+    Pathname local = access.provideFile(path);
+
+    DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
+
+    return repositories_in_file(local);
+  }
 
   ///////////////////////////////////////////////////////////////////
   //
-  //   CLASS NAME : RepoManager::Impl
+  //   class RepoManagerOptions
   //
-  ///////////////////////////////////////////////////////////////////
+  ////////////////////////////////////////////////////////////////////
 
-  /**
-   * \short RepoManager implementation.
-   */
+  RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
+  {
+    repoCachePath         = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
+    repoRawCachePath      = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
+    repoSolvCachePath     = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
+    repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
+    knownReposPath        = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
+    knownServicesPath     = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
+    pluginsPath           = Pathname::assertprefix( root_r, ZConfig::instance().pluginsPath() );
+    probe                 = ZConfig::instance().repo_add_probe();
+
+    rootDir = root_r;
+  }
+
+  RepoManagerOptions RepoManagerOptions::makeTestSetup( const Pathname & root_r )
+  {
+    RepoManagerOptions ret;
+    ret.repoCachePath         = root_r;
+    ret.repoRawCachePath      = root_r/"raw";
+    ret.repoSolvCachePath     = root_r/"solv";
+    ret.repoPackagesCachePath = root_r/"packages";
+    ret.knownReposPath        = root_r/"repos.d";
+    ret.knownServicesPath     = root_r/"services.d";
+    ret.pluginsPath           = root_r/"plugins";
+    ret.rootDir = root_r;
+    return ret;
+  }
+
+  std:: ostream & operator<<( std::ostream & str, const RepoManagerOptions & obj )
+  {
+#define OUTS(X) str << "  " #X "\t" << obj.X << endl
+    str << "RepoManagerOptions (" << obj.rootDir << ") {" << endl;
+    OUTS( repoRawCachePath );
+    OUTS( repoSolvCachePath );
+    OUTS( repoPackagesCachePath );
+    OUTS( knownReposPath );
+    OUTS( knownServicesPath );
+    OUTS( pluginsPath );
+    str << "}" << endl;
+#undef OUTS
+    return str;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  /// \class RepoManager::Impl
+  /// \brief RepoManager implementation.
+  ///
+  ///////////////////////////////////////////////////////////////////
   struct RepoManager::Impl
   {
+  public:
     Impl( const RepoManagerOptions &opt )
-      : options(opt)
+      : _options(opt)
     {
       init_knownServices();
       init_knownRepositories();
     }
 
-    RepoManagerOptions options;
+    ~Impl()
+    {
+      // trigger appdata refresh if some repos change
+      if ( _reposDirty && geteuid() == 0 && ( _options.rootDir.empty() || _options.rootDir == "/" ) )
+      {
+       try {
+         std::list<Pathname> entries;
+         filesystem::readdir( entries, _options.pluginsPath/"appdata", false );
+         if ( ! entries.empty() )
+         {
+           ExternalProgram::Arguments cmd;
+           cmd.push_back( "<" );               // discard stdin
+           cmd.push_back( ">" );               // discard stdout
+           cmd.push_back( "PROGRAM" );         // [2] - fix index below if changing!
+           for ( const auto & rinfo : repos() )
+           {
+             if ( ! rinfo.enabled() )
+               continue;
+             cmd.push_back( "-R" );
+             cmd.push_back( rinfo.alias() );
+             cmd.push_back( "-t" );
+             cmd.push_back( rinfo.type().asString() );
+             cmd.push_back( "-p" );
+             cmd.push_back( rinfo.metadataPath().asString() );
+           }
+
+           for_( it, entries.begin(), entries.end() )
+           {
+             PathInfo pi( *it );
+             //DBG << "/tmp/xx ->" << pi << endl;
+             if ( pi.isFile() && pi.userMayRX() )
+             {
+               // trigger plugin
+               cmd[2] = pi.asString();         // [2] - PROGRAM
+               ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
+             }
+           }
+         }
+       }
+       catch (...) {}  // no throw in dtor
+      }
+    }
+
+  public:
+    bool repoEmpty() const             { return repos().empty(); }
+    RepoSizeType repoSize() const      { return repos().size(); }
+    RepoConstIterator repoBegin() const        { return repos().begin(); }
+    RepoConstIterator repoEnd() const  { return repos().end(); }
+
+    bool hasRepo( const std::string & alias ) const
+    { return foundAliasIn( alias, repos() ); }
+
+    RepoInfo getRepo( const std::string & alias ) const
+    {
+      RepoConstIterator it( findAlias( alias, repos() ) );
+      return it == repos().end() ? RepoInfo::noRepo : *it;
+    }
+
+  public:
+    Pathname metadataPath( const RepoInfo & info ) const
+    { return rawcache_path_for_repoinfo( _options, info ); }
+
+    Pathname packagesPath( const RepoInfo & info ) const
+    { return packagescache_path_for_repoinfo( _options, info ); }
+
+    RepoStatus metadataStatus( const RepoInfo & info ) const;
+
+    RefreshCheckStatus checkIfToRefreshMetadata( const RepoInfo & info, const Url & url, RawMetadataRefreshPolicy policy );
+
+    void refreshMetadata( const RepoInfo & info, RawMetadataRefreshPolicy policy, OPT_PROGRESS );
+
+    void cleanMetadata( const RepoInfo & info, OPT_PROGRESS );
+
+    void cleanPackages( const RepoInfo & info, OPT_PROGRESS );
+
+    void buildCache( const RepoInfo & info, CacheBuildPolicy policy, OPT_PROGRESS );
+
+    repo::RepoType probe( const Url & url, const Pathname & path = Pathname() ) const;
+
+    void cleanCacheDirGarbage( OPT_PROGRESS );
+
+    void cleanCache( const RepoInfo & info, OPT_PROGRESS );
+
+    bool isCached( const RepoInfo & info ) const
+    { return PathInfo(solv_path_for_repoinfo( _options, info ) / "solv").isExist(); }
+
+    RepoStatus cacheStatus( const RepoInfo & info ) const
+    { return RepoStatus::fromCookieFile(solv_path_for_repoinfo(_options, info) / "cookie"); }
+
+    void loadFromCache( const RepoInfo & info, OPT_PROGRESS );
+
+    void addRepository( const RepoInfo & info, OPT_PROGRESS );
+
+    void addRepositories( const Url & url, OPT_PROGRESS );
+
+    void removeRepository( const RepoInfo & info, OPT_PROGRESS );
+
+    void modifyRepository( const std::string & alias, const RepoInfo & newinfo_r, OPT_PROGRESS );
+
+    RepoInfo getRepositoryInfo( const std::string & alias, OPT_PROGRESS );
+    RepoInfo getRepositoryInfo( const Url & url, const url::ViewOption & urlview, OPT_PROGRESS );
 
-    RepoSet repos;
+  public:
+    bool serviceEmpty() const                  { return _services.empty(); }
+    ServiceSizeType serviceSize() const                { return _services.size(); }
+    ServiceConstIterator serviceBegin() const  { return _services.begin(); }
+    ServiceConstIterator serviceEnd() const    { return _services.end(); }
+
+    bool hasService( const std::string & alias ) const
+    { return foundAliasIn( alias, _services ); }
 
-    ServiceSet services;
+    ServiceInfo getService( const std::string & alias ) const
+    {
+      ServiceConstIterator it( findAlias( alias, _services ) );
+      return it == _services.end() ? ServiceInfo::noService : *it;
+    }
 
   public:
+    void addService( const ServiceInfo & service );
+    void addService( const std::string & alias, const Url & url )
+    { addService( ServiceInfo( alias, url ) ); }
+
+    void removeService( const std::string & alias );
+    void removeService( const ServiceInfo & service )
+    { removeService( service.alias() ); }
+
+    void refreshServices( const RefreshServiceOptions & options_r );
 
+    void refreshService( const std::string & alias, const RefreshServiceOptions & options_r );
+    void refreshService( const ServiceInfo & service, const RefreshServiceOptions & options_r )
+    {  refreshService( service.alias(), options_r ); }
+
+    void modifyService( const std::string & oldAlias, const ServiceInfo & newService );
+
+    repo::ServiceType probeService( const Url & url ) const;
+
+  private:
     void saveService( ServiceInfo & service ) const;
 
-    Pathname generateNonExistingName( const Pathname &dir,
-                                      const std::string &basefilename ) const;
+    Pathname generateNonExistingName( const Pathname & dir, const std::string & basefilename ) const;
 
-    std::string generateFilename( const RepoInfo & info ) const;
-    std::string generateFilename( const ServiceInfo & info ) const;
+    std::string generateFilename( const RepoInfo & info ) const
+    { return filenameFromAlias( info.alias(), "repo" ); }
 
+    std::string generateFilename( const ServiceInfo & info ) const
+    { return filenameFromAlias( info.alias(), "service" ); }
+
+    void setCacheStatus( const RepoInfo & info, const RepoStatus & status )
+    {
+      Pathname base = solv_path_for_repoinfo( _options, info );
+      filesystem::assert_dir(base);
+      status.saveToCookieFile( base / "cookie" );
+    }
+
+    void touchIndexFile( const RepoInfo & info );
+
+    template<typename OutputIterator>
+    void getRepositoriesInService( const std::string & alias, OutputIterator out ) const
+    {
+      MatchServiceAlias filter( alias );
+      std::copy( boost::make_filter_iterator( filter, repos().begin(), repos().end() ),
+                 boost::make_filter_iterator( filter, repos().end(), repos().end() ),
+                 out);
+    }
 
   private:
     void init_knownServices();
     void init_knownRepositories();
 
+    const RepoSet & repos() const { return _reposX; }
+    RepoSet & reposManip()        { if ( ! _reposDirty ) _reposDirty = true; return _reposX; }
+
+  private:
+    RepoManagerOptions _options;
+    RepoSet            _reposX;
+    ServiceSet         _services;
+
+    DefaultIntegral<bool,false> _reposDirty;
+
   private:
     friend Impl * rwcowClone<Impl>( const Impl * rhs );
     /** clone for RWCOW_pointer */
     Impl * clone() const
     { return new Impl( *this ); }
   };
-
   ///////////////////////////////////////////////////////////////////
 
   /** \relates RepoManager::Impl Stream output */
   inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
-  {
-    return str << "RepoManager::Impl";
-  }
+  { return str << "RepoManager::Impl"; }
 
   ///////////////////////////////////////////////////////////////////
 
   void RepoManager::Impl::saveService( ServiceInfo & service ) const
   {
-    filesystem::assert_dir( options.knownServicesPath );
-    Pathname servfile = generateNonExistingName( options.knownServicesPath,
+    filesystem::assert_dir( _options.knownServicesPath );
+    Pathname servfile = generateNonExistingName( _options.knownServicesPath,
                                                  generateFilename( service ) );
     service.setFilepath( servfile );
 
@@ -400,7 +647,8 @@ namespace zypp
     std::ofstream file( servfile.c_str() );
     if ( !file )
     {
-      ZYPP_THROW( Exception( "Can't open " + servfile.asString() ) );
+      // TranslatorExplanation '%s' is a filename
+      ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() )));
     }
     service.dumpAsIniOn( file );
     MIL << "done" << endl;
@@ -429,181 +677,157 @@ namespace zypp
     while ( PathInfo(dir + final_filename).isExist() )
     {
       final_filename = basefilename + "_" + str::numstring(counter);
-      counter++;
+      ++counter;
     }
     return dir + Pathname(final_filename);
   }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  /**
-   * \short Generate a related filename from a repo info
-   *
-   * From a repo info, it will try to use the alias as a filename
-   * escaping it if necessary. Other fallbacks can be added to
-   * this function in case there is no way to use the alias
-   */
-  std::string RepoManager::Impl::generateFilename( const RepoInfo & info ) const
-  {
-    std::string filename = info.alias();
-    // replace slashes with underscores
-    str::replaceAll( filename, "/", "_" );
-
-    filename = Pathname(filename).extend(".repo").asString();
-    MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
-    return filename;
-  }
-
-  std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const
-  {
-    std::string filename = info.alias();
-    // replace slashes with underscores
-    str::replaceAll( filename, "/", "_" );
-
-    filename = Pathname(filename).extend(".service").asString();
-    MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl;
-    return filename;
-  }
-
-
   void RepoManager::Impl::init_knownServices()
   {
-    Pathname dir = options.knownServicesPath;
+    Pathname dir = _options.knownServicesPath;
     std::list<Pathname> entries;
     if (PathInfo(dir).isExist())
     {
-      if ( filesystem::readdir( entries, Pathname(dir), false ) != 0 )
-          ZYPP_THROW(Exception("failed to read directory"));
+      if ( filesystem::readdir( entries, dir, false ) != 0 )
+      {
+        // TranslatorExplanation '%s' is a pathname
+        ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
+      }
 
       //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
       for_(it, entries.begin(), entries.end() )
       {
-        parser::ServiceFileReader(*it, ServiceCollector(services));
+        parser::ServiceFileReader(*it, ServiceCollector(_services));
       }
     }
+
+    repo::PluginServices(_options.pluginsPath/"services", ServiceCollector(_services));
   }
 
+  ///////////////////////////////////////////////////////////////////
+  namespace {
+    /** Delete \a cachePath_r subdirs not matching known aliases in \a repoEscAliases_r (must be sorted!)
+     * \note bnc#891515: Auto-cleanup only zypp.conf default locations. Otherwise
+     * we'd need some magic file to identify zypp cache directories. Without this
+     * we may easily remove user data (zypper --pkg-cache-dir . download ...)
+     */
+    inline void cleanupNonRepoMetadtaFolders( const Pathname & cachePath_r,
+                                             const Pathname & defaultCachePath_r,
+                                             const std::list<std::string> & repoEscAliases_r )
+    {
+      if ( cachePath_r != defaultCachePath_r )
+       return;
+
+      std::list<std::string> entries;
+      if ( filesystem::readdir( entries, cachePath_r, false ) == 0 )
+      {
+       entries.sort();
+       std::set<std::string> oldfiles;
+       set_difference( entries.begin(), entries.end(), repoEscAliases_r.begin(), repoEscAliases_r.end(),
+                       std::inserter( oldfiles, oldfiles.end() ) );
+       for ( const std::string & old : oldfiles )
+       {
+         if ( old == Repository::systemRepoAlias() )   // don't remove the @System solv file
+           continue;
+         filesystem::recursive_rmdir( cachePath_r / old );
+       }
+      }
+    }
+  } // namespace
+  ///////////////////////////////////////////////////////////////////
   void RepoManager::Impl::init_knownRepositories()
   {
     MIL << "start construct known repos" << endl;
 
-    if ( PathInfo(options.knownReposPath).isExist() )
+    if ( PathInfo(_options.knownReposPath).isExist() )
     {
-      RepoInfoList repol = repositories_in_dir(options.knownReposPath);
-      for ( RepoInfoList::iterator it = repol.begin();
-            it != repol.end();
-            ++it )
+      std::list<std::string> repoEscAliases;
+      std::list<RepoInfo> orphanedRepos;
+      for ( RepoInfo & repoInfo : repositories_in_dir(_options.knownReposPath) )
       {
         // set the metadata path for the repo
-        Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it));
-        (*it).setMetadataPath(metadata_path);
-
+        repoInfo.setMetadataPath( rawcache_path_for_repoinfo(_options, repoInfo) );
        // set the downloaded packages path for the repo
-       Pathname packages_path = packagescache_path_for_repoinfo(options, (*it));
-       (*it).setPackagesPath(packages_path);
+       repoInfo.setPackagesPath( packagescache_path_for_repoinfo(_options, repoInfo) );
+       // remember it
+        _reposX.insert( repoInfo );    // direct access via _reposX in ctor! no reposManip.
+
+       // detect orphaned repos belonging to a deleted service
+       const std::string & serviceAlias( repoInfo.service() );
+       if ( ! ( serviceAlias.empty() || hasService( serviceAlias ) ) )
+       {
+         WAR << "Schedule orphaned service repo for deletion: " << repoInfo << endl;
+         orphanedRepos.push_back( repoInfo );
+         continue;     // don't remember it in repoEscAliases
+       }
+
+        repoEscAliases.push_back(repoInfo.escaped_alias());
+      }
 
-        repos.insert(*it);
+      // Cleanup orphanded service repos:
+      if ( ! orphanedRepos.empty() )
+      {
+       for ( auto & repoInfo : orphanedRepos )
+       {
+         MIL << "Delete orphaned service repo " << repoInfo.alias() << endl;
+         // translators: Cleanup a repository previously owned by a meanwhile unknown (deleted) service.
+         //   %1% = service name
+         //   %2% = repository name
+         JobReport::warning( formatNAC(_("Unknown service '%1%': Removing orphaned service repository '%2%'" ))
+                             % repoInfo.service()
+                             % repoInfo.alias() );
+         try {
+           removeRepository( repoInfo );
+         }
+         catch ( const Exception & caugth )
+         {
+           JobReport::error( caugth.asUserHistory() );
+         }
+       }
       }
-    }
 
+      // delete metadata folders without corresponding repo (e.g. old tmp directories)
+      //
+      // bnc#891515: Auto-cleanup only zypp.conf default locations. Otherwise
+      // we'd need somemagic file to identify zypp cache directories. Without this
+      // we may easily remove user data (zypper --pkg-cache-dir . download ...)
+      repoEscAliases.sort();
+      RepoManagerOptions defaultCache( _options.rootDir );
+      cleanupNonRepoMetadtaFolders( _options.repoRawCachePath,         defaultCache.repoRawCachePath,          repoEscAliases );
+      cleanupNonRepoMetadtaFolders( _options.repoSolvCachePath,                defaultCache.repoSolvCachePath,         repoEscAliases );
+      cleanupNonRepoMetadtaFolders( _options.repoPackagesCachePath,    defaultCache.repoPackagesCachePath,     repoEscAliases );
+    }
     MIL << "end construct known repos" << endl;
   }
 
   ///////////////////////////////////////////////////////////////////
-  //
-  //   CLASS NAME : RepoManager
-  //
-  ///////////////////////////////////////////////////////////////////
-
-  RepoManager::RepoManager( const RepoManagerOptions &opt )
-  : _pimpl( new Impl(opt) )
-  {}
-
-  ////////////////////////////////////////////////////////////////////////////
-
-  RepoManager::~RepoManager()
-  {}
-
-  ////////////////////////////////////////////////////////////////////////////
-
-  bool RepoManager::repoEmpty() const
-  { return _pimpl->repos.empty(); }
-
-  RepoManager::RepoSizeType RepoManager::repoSize() const
-  { return _pimpl->repos.size(); }
 
-  RepoManager::RepoConstIterator RepoManager::repoBegin() const
-  { return _pimpl->repos.begin(); }
-
-  RepoManager::RepoConstIterator RepoManager::repoEnd() const
-  { return _pimpl->repos.end(); }
-
-  RepoInfo RepoManager::getRepo( const std::string & alias ) const
-  {
-    for_( it, repoBegin(), repoEnd() )
-      if ( it->alias() == alias )
-        return *it;
-    return RepoInfo::noRepo;
-  }
-
-  bool RepoManager::hasRepo( const std::string & alias ) const
+  RepoStatus RepoManager::Impl::metadataStatus( const RepoInfo & info ) const
   {
-    for_( it, repoBegin(), repoEnd() )
-      if ( it->alias() == alias )
-        return true;
-    return false;
-  }
-
-  ////////////////////////////////////////////////////////////////////////////
-
-  Pathname RepoManager::metadataPath( const RepoInfo &info ) const
-  {
-    return rawcache_path_for_repoinfo(_pimpl->options, info );
-  }
-
-  Pathname RepoManager::packagesPath( const RepoInfo &info ) const
-  {
-    return packagescache_path_for_repoinfo(_pimpl->options, info );
-  }
+    Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
+    Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
 
-  ////////////////////////////////////////////////////////////////////////////
-
-  RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
-  {
-    Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
     RepoType repokind = info.type();
-    RepoStatus status;
-
-    switch ( repokind.toEnum() )
-    {
-      case RepoType::NONE_e:
-      // unknown, probe the local metadata
-        repokind = probe(rawpath.asUrl());
-      break;
-      default:
-      break;
-    }
+    // If unknown, probe the local metadata
+    if ( repokind == RepoType::NONE )
+      repokind = probe( productdatapath.asUrl() );
 
+    RepoStatus status;
     switch ( repokind.toEnum() )
     {
       case RepoType::RPMMD_e :
-      {
-        status = RepoStatus( rawpath + "/repodata/repomd.xml");
-      }
-      break;
+        status = RepoStatus( productdatapath/"repodata/repomd.xml");
+       break;
 
       case RepoType::YAST2_e :
-      {
-        status = RepoStatus( rawpath + "/content") && (RepoStatus( rawpath + "/media.1/media"));
-      }
-      break;
+        status = RepoStatus( productdatapath/"content" ) && RepoStatus( mediarootpath/"media.1/media" );
+       break;
 
       case RepoType::RPMPLAINDIR_e :
-      {
-        if ( PathInfo(Pathname(rawpath + "/cookie")).isExist() )
-          status = RepoStatus( rawpath + "/cookie");
-      }
-      break;
+       status = RepoStatus::fromCookieFile( productdatapath/"cookie" );
+       break;
 
       case RepoType::NONE_e :
        // Return default RepoStatus in case of RepoType::NONE
@@ -614,14 +838,15 @@ namespace zypp
     return status;
   }
 
-  void RepoManager::touchIndexFile(const RepoInfo & info)
+
+  void RepoManager::Impl::touchIndexFile( const RepoInfo & info )
   {
-    Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
+    Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
 
     RepoType repokind = info.type();
     if ( repokind.toEnum() == RepoType::NONE_e )
       // unknown, probe the local metadata
-      repokind = probe(rawpath.asUrl());
+      repokind = probe( productdatapath.asUrl() );
     // if still unknown, just return
     if (repokind == RepoType::NONE_e)
       return;
@@ -630,15 +855,15 @@ namespace zypp
     switch ( repokind.toEnum() )
     {
       case RepoType::RPMMD_e :
-        p = Pathname(rawpath + "/repodata/repomd.xml");
+        p = Pathname(productdatapath + "/repodata/repomd.xml");
         break;
 
       case RepoType::YAST2_e :
-        p = Pathname(rawpath + "/content");
+        p = Pathname(productdatapath + "/content");
         break;
 
       case RepoType::RPMPLAINDIR_e :
-        p = Pathname(rawpath + "/cookie");
+        p = Pathname(productdatapath + "/cookie");
         break;
 
       case RepoType::NONE_e :
@@ -650,36 +875,36 @@ namespace zypp
     filesystem::touch(p);
   }
 
-  RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata(
-                                              const RepoInfo &info,
-                                              const Url &url,
-                                              RawMetadataRefreshPolicy policy )
+
+  RepoManager::RefreshCheckStatus RepoManager::Impl::checkIfToRefreshMetadata( const RepoInfo & info, const Url & url, RawMetadataRefreshPolicy policy )
   {
     assert_alias(info);
-
-    RepoStatus oldstatus;
-    RepoStatus newstatus;
-
     try
     {
       MIL << "Going to try to check whether refresh is needed for " << url << endl;
 
-      repo::RepoType repokind = info.type();
+      // first check old (cached) metadata
+      Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
+      filesystem::assert_dir( mediarootpath );
+      RepoStatus oldstatus = metadataStatus( info );
 
-      // if the type is unknown, try probing.
-      switch ( repokind.toEnum() )
+      if ( oldstatus.empty() )
       {
-        case RepoType::NONE_e:
-          // unknown, probe it
-          repokind = probe(url);
-        break;
-        default:
-        break;
+        MIL << "No cached metadata, going to refresh" << endl;
+        return REFRESH_NEEDED;
       }
 
-      Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
-      filesystem::assert_dir(rawpath);
-      oldstatus = metadataStatus(info);
+      {
+        if ( url.schemeIsVolatile() )
+       {
+         MIL << "never refresh CD/DVD" << endl;
+          return REPO_UP_TO_DATE;
+       }
+       if ( url.schemeIsLocal() )
+       {
+         policy = RefreshIfNeededIgnoreDelay;
+       }
+      }
 
       // now we've got the old (cached) status, we can decide repo.refresh.delay
       if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
@@ -693,80 +918,78 @@ namespace zypp
         DBG << "current time: " << (Date::ValueType)Date::now() << endl;
         DBG << "last refresh = " << diff << " minutes ago" << endl;
 
-        if (diff < ZConfig::instance().repo_refresh_delay())
+        if ( diff < ZConfig::instance().repo_refresh_delay() )
         {
-          MIL << "Repository '" << info.alias()
-              << "' has been refreshed less than repo.refresh.delay ("
-              << ZConfig::instance().repo_refresh_delay()
-              << ") minutes ago. Advising to skip refresh" << endl;
-          return REPO_CHECK_DELAYED;
+         if ( diff < 0 )
+         {
+           WAR << "Repository '" << info.alias() << "' was refreshed in the future!" << endl;
+         }
+         else
+         {
+           MIL << "Repository '" << info.alias()
+               << "' has been refreshed less than repo.refresh.delay ("
+               << ZConfig::instance().repo_refresh_delay()
+               << ") minutes ago. Advising to skip refresh" << endl;
+           return REPO_CHECK_DELAYED;
+         }
         }
       }
 
-      // create temp dir as sibling of rawpath
-      filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( rawpath ) );
+      repo::RepoType repokind = info.type();
+      // if unknown: probe it
+      if ( repokind == RepoType::NONE )
+       repokind = probe( url, info.path() );
 
-      if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
-           ( repokind.toEnum() == RepoType::YAST2_e ) )
+      // retrieve newstatus
+      RepoStatus newstatus;
+      switch ( repokind.toEnum() )
       {
-        MediaSetAccess media(url);
-        shared_ptr<repo::Downloader> downloader_ptr;
-
-        if ( repokind.toEnum() == RepoType::RPMMD_e )
-          downloader_ptr.reset(new yum::Downloader(info));
-        else
-          downloader_ptr.reset( new susetags::Downloader(info));
+       case RepoType::RPMMD_e:
+       {
+         MediaSetAccess media( url );
+         newstatus = yum::Downloader( info, mediarootpath ).status( media );
+       }
+       break;
 
-        RepoStatus newstatus = downloader_ptr->status(media);
-        bool refresh = false;
-        if ( oldstatus.checksum() == newstatus.checksum() )
-        {
-          MIL << "repo has not changed" << endl;
-          if ( policy == RefreshForced )
-          {
-            MIL << "refresh set to forced" << endl;
-            refresh = true;
-          }
-        }
-        else
-        {
-          MIL << "repo has changed, going to refresh" << endl;
-          refresh = true;
-        }
+       case RepoType::YAST2_e:
+       {
+         MediaSetAccess media( url );
+         newstatus = susetags::Downloader( info, mediarootpath ).status( media );
+       }
+       break;
 
-        if (!refresh)
-          touchIndexFile(info);
+       case RepoType::RPMPLAINDIR_e:
+         newstatus = RepoStatus( MediaMounter(url).getPathName(info.path()) ); // dir status
+         break;
 
-        return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
+       default:
+       case RepoType::NONE_e:
+         ZYPP_THROW( RepoUnknownTypeException( info ) );
+         break;
       }
-      else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
-      {
-        RepoStatus newstatus = parser::plaindir::dirStatus(url.getPathName());
-        bool refresh = false;
-        if ( oldstatus.checksum() == newstatus.checksum() )
-        {
-          MIL << "repo has not changed" << endl;
-          if ( policy == RefreshForced )
-          {
-            MIL << "refresh set to forced" << endl;
-            refresh = true;
-          }
-        }
-        else
-        {
-          MIL << "repo has changed, going to refresh" << endl;
-          refresh = true;
-        }
 
-        if (!refresh)
-          touchIndexFile(info);
-
-        return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
+      // check status
+      bool refresh = false;
+      if ( oldstatus == newstatus )
+      {
+       MIL << "repo has not changed" << endl;
+       if ( policy == RefreshForced )
+       {
+         MIL << "refresh set to forced" << endl;
+         refresh = true;
+       }
       }
       else
       {
-        ZYPP_THROW(RepoUnknownTypeException(info));
+       MIL << "repo has changed, going to refresh" << endl;
+       refresh = true;
       }
+
+      if (!refresh)
+       touchIndexFile(info);
+
+      return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
+
     }
     catch ( const Exception &e )
     {
@@ -778,15 +1001,19 @@ namespace zypp
     return REFRESH_NEEDED; // default
   }
 
-  void RepoManager::refreshMetadata( const RepoInfo &info,
-                                     RawMetadataRefreshPolicy policy,
-                                     const ProgressData::ReceiverFnc & progress )
+
+  void RepoManager::Impl::refreshMetadata( const RepoInfo & info, RawMetadataRefreshPolicy policy, const ProgressData::ReceiverFnc & progress )
   {
     assert_alias(info);
     assert_urls(info);
 
     // we will throw this later if no URL checks out fine
-    RepoException rexception(_("Valid metadata not found at specified URL(s)"));
+    RepoException rexception( info, _PL("Valid metadata not found at specified URL",
+                                       "Valid metadata not found at specified URLs",
+                                       info.baseUrlsSize() ) );
+
+    // Suppress (interactive) media::MediaChangeReport if we in have multiple basurls (>1)
+    media::ScopedDisableMediaChangeReport guard( info.baseUrlsSize() > 1 );
 
     // try urls one by one
     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
@@ -805,38 +1032,43 @@ namespace zypp
         repo::RepoType repokind = info.type();
 
         // if the type is unknown, try probing.
-        switch ( repokind.toEnum() )
+       if ( repokind == RepoType::NONE )
+       {
+         // unknown, probe it
+         repokind = probe( *it, info.path() );
+
+         if (repokind.toEnum() != RepoType::NONE_e)
+         {
+           // Adjust the probed type in RepoInfo
+           info.setProbedType( repokind ); // lazy init!
+           //save probed type only for repos in system
+           for_( it, repoBegin(), repoEnd() )
+           {
+             if ( info.alias() == (*it).alias() )
+             {
+               RepoInfo modifiedrepo = info;
+               modifiedrepo.setType( repokind );
+               modifyRepository( info.alias(), modifiedrepo );
+               break;
+             }
+           }
+         }
+       }
+
+        Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
+        if( filesystem::assert_dir(mediarootpath) )
         {
-          case RepoType::NONE_e:
-            // unknown, probe it
-            repokind = probe(*it);
-
-            if (repokind.toEnum() != RepoType::NONE_e)
-            {
-              // Adjust the probed type in RepoInfo
-              info.setProbedType( repokind ); // lazy init!
-              //save probed type only for repos in system
-              for_( it, repoBegin(), repoEnd() )
-              {
-                if ( info.alias() == (*it).alias() )
-                {
-                  RepoInfo modifiedrepo = info;
-                  modifiedrepo.setType( repokind );
-                  modifyRepository( info.alias(), modifiedrepo );
-                  break;
-                }
-              }
-            }
-          break;
-          default:
-          break;
+          Exception ex(str::form( _("Can't create %s"), mediarootpath.c_str()) );
+          ZYPP_THROW(ex);
         }
 
-        Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
-        filesystem::assert_dir(rawpath);
-
-        // create temp dir as sibling of rawpath
-        filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( rawpath ) );
+        // create temp dir as sibling of mediarootpath
+        filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
+        if( tmpdir.path().empty() )
+        {
+          Exception ex(_("Can't create metadata cache directory."));
+          ZYPP_THROW(ex);
+        }
 
         if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
              ( repokind.toEnum() == RepoType::YAST2_e ) )
@@ -844,12 +1076,12 @@ namespace zypp
           MediaSetAccess media(url);
           shared_ptr<repo::Downloader> downloader_ptr;
 
-          MIL << "Creating downloader for [ " << info.name() << " ]" << endl;
+          MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
 
           if ( repokind.toEnum() == RepoType::RPMMD_e )
-            downloader_ptr.reset(new yum::Downloader(info));
+            downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
           else
-            downloader_ptr.reset( new susetags::Downloader(info) );
+            downloader_ptr.reset( new susetags::Downloader(info, mediarootpath) );
 
           /**
            * Given a downloader, sets the other repos raw metadata
@@ -859,38 +1091,31 @@ namespace zypp
            */
           for_( it, repoBegin(), repoEnd() )
           {
-            Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
+            Pathname cachepath(rawcache_path_for_repoinfo( _options, *it ));
             if ( PathInfo(cachepath).isExist() )
               downloader_ptr->addCachePath(cachepath);
           }
 
-          downloader_ptr->download( media, tmpdir.path());
+          downloader_ptr->download( media, tmpdir.path() );
         }
         else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
         {
-          RepoStatus newstatus = parser::plaindir::dirStatus(url.getPathName());
-
-          std::ofstream file(( tmpdir.path() + "/cookie").c_str());
-          if (!file) {
-            ZYPP_THROW (Exception( "Can't open " + tmpdir.path().asString() + "/cookie" ) );
-          }
-          file << url << endl;
-          file << newstatus.checksum() << endl;
+          MediaMounter media( url );
+          RepoStatus newstatus = RepoStatus( media.getPathName( info.path() ) );       // dir status
 
-          file.close();
+          Pathname productpath( tmpdir.path() / info.path() );
+          filesystem::assert_dir( productpath );
+         newstatus.saveToCookieFile( productpath/"cookie" );
         }
         else
         {
-          ZYPP_THROW(RepoUnknownTypeException());
+          ZYPP_THROW(RepoUnknownTypeException( info ));
         }
 
         // ok we have the metadata, now exchange
         // the contents
-
-        filesystem::TmpDir oldmetadata( filesystem::TmpDir::makeSibling( rawpath ) );
-        filesystem::rename( rawpath, oldmetadata.path() );
-        // move the just downloaded there
-        filesystem::rename( tmpdir.path(), rawpath );
+       filesystem::exchange( tmpdir.path(), mediarootpath );
+       reposManip();   // remember to trigger appdata refresh
 
         // we are done.
         return;
@@ -913,34 +1138,37 @@ namespace zypp
 
   ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::cleanMetadata( const RepoInfo &info,
-                                   const ProgressData::ReceiverFnc & progressfnc )
+  void RepoManager::Impl::cleanMetadata( const RepoInfo & info, const ProgressData::ReceiverFnc & progressfnc )
   {
     ProgressData progress(100);
     progress.sendTo(progressfnc);
 
-    filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_pimpl->options, info));
+    filesystem::recursive_rmdir(rawcache_path_for_repoinfo(_options, info));
     progress.toMax();
   }
 
-  void RepoManager::cleanPackages( const RepoInfo &info,
-                                   const ProgressData::ReceiverFnc & progressfnc )
+
+  void RepoManager::Impl::cleanPackages( const RepoInfo & info, const ProgressData::ReceiverFnc & progressfnc )
   {
     ProgressData progress(100);
     progress.sendTo(progressfnc);
 
-    filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_pimpl->options, info));
+    filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_options, info));
     progress.toMax();
   }
 
-  void RepoManager::buildCache( const RepoInfo &info,
-                                CacheBuildPolicy policy,
-                                const ProgressData::ReceiverFnc & progressrcv )
+
+  void RepoManager::Impl::buildCache( const RepoInfo & info, CacheBuildPolicy policy, const ProgressData::ReceiverFnc & progressrcv )
   {
     assert_alias(info);
-    Pathname rawpath = rawcache_path_for_repoinfo(_pimpl->options, info);
+    Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
+    Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
 
-    filesystem::assert_dir(_pimpl->options.repoCachePath);
+    if( filesystem::assert_dir(_options.repoCachePath) )
+    {
+      Exception ex(str::form( _("Can't create %s"), _options.repoCachePath.c_str()) );
+      ZYPP_THROW(ex);
+    }
     RepoStatus raw_metadata_status = metadataStatus(info);
     if ( raw_metadata_status.empty() )
     {
@@ -957,7 +1185,7 @@ namespace zypp
       MIL << info.alias() << " is already cached." << endl;
       RepoStatus cache_status = cacheStatus(info);
 
-      if ( cache_status.checksum() == raw_metadata_status.checksum() )
+      if ( cache_status == raw_metadata_status )
       {
         MIL << info.alias() << " cache is up to date with metadata." << endl;
         if ( policy == BuildIfNeeded ) {
@@ -974,7 +1202,7 @@ namespace zypp
     ProgressData progress(100);
     callback::SendReport<ProgressReport> report;
     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
-    progress.name(str::form(_("Building repository '%s' cache"), info.name().c_str()));
+    progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str()));
     progress.toMin();
 
     if (needs_cleaning)
@@ -982,10 +1210,21 @@ namespace zypp
       cleanCache(info);
     }
 
-    MIL << info.alias() << " building cache..." << endl;
+    MIL << info.alias() << " building cache..." << info.type() << endl;
 
-    Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
-    filesystem::assert_dir(base);
+    Pathname base = solv_path_for_repoinfo( _options, info);
+
+    if( filesystem::assert_dir(base) )
+    {
+      Exception ex(str::form( _("Can't create %s"), base.c_str()) );
+      ZYPP_THROW(ex);
+    }
+
+    if( ! PathInfo(base).userMayW() )
+    {
+      Exception ex(str::form( _("Can't create cache at %s - no writing permissions."), base.c_str()) );
+      ZYPP_THROW(ex);
+    }
     Pathname solvfile = base / "solv";
 
     // do we have type?
@@ -996,7 +1235,7 @@ namespace zypp
     {
       case RepoType::NONE_e:
         // unknown, probe the local metadata
-        repokind = probe(rawpath.asUrl());
+        repokind = probe( productdatapath.asUrl() );
       break;
       default:
       break;
@@ -1012,36 +1251,43 @@ namespace zypp
       {
         // Take care we unlink the solvfile on exception
         ManagedFile guard( solvfile, filesystem::unlink );
+        scoped_ptr<MediaMounter> forPlainDirs;
 
-        std::ostringstream cmd;
-        std::string toFile( str::gsub(solvfile.asString(),"\"","\\\"") );
-        if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
+        ExternalProgram::Arguments cmd;
+        cmd.push_back( "repo2solv.sh" );
+        // repo2solv expects -o as 1st arg!
+        cmd.push_back( "-o" );
+        cmd.push_back( solvfile.asString() );
+       cmd.push_back( "-X" );  // autogenerate pattern from pattern-package
+
+        if ( repokind == RepoType::RPMPLAINDIR )
         {
+          forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
+          // recusive for plaindir as 2nd arg!
+          cmd.push_back( "-R" );
           // FIXME this does only work form dir: URLs
-          cmd << str::form( "repo2solv.sh \"%s\" > \"%s\"",
-                            str::gsub( info.baseUrlsBegin()->getPathName(),"\"","\\\"" ).c_str(),
-                            toFile.c_str() );
+          cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
         }
         else
-        {
-          cmd << str::form( "repo2solv.sh \"%s\" > \"%s\"",
-                            str::gsub( rawpath.asString(),"\"","\\\"" ).c_str(),
-                            toFile.c_str() );
-        }
-        MIL << "Executing: " << cmd.str() << endl;
-        ExternalProgram prog( cmd.str(), ExternalProgram::Stderr_To_Stdout );
+          cmd.push_back( productdatapath.asString() );
+
+        ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
+        std::string errdetail;
 
-        cmd << endl;
         for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
           WAR << "  " << output;
-          cmd << "     " << output;
+          if ( errdetail.empty() ) {
+            errdetail = prog.command();
+            errdetail += '\n';
+          }
+          errdetail += output;
         }
 
         int ret = prog.close();
         if ( ret != 0 )
         {
-          RepoException ex(str::form("Failed to cache repo (%d).", ret));
-          ex.remember( cmd.str() );
+          RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
+          ex.remember( errdetail );
           ZYPP_THROW(ex);
         }
 
@@ -1050,7 +1296,7 @@ namespace zypp
       }
       break;
       default:
-        ZYPP_THROW(RepoUnknownTypeException("Unhandled repository type"));
+        ZYPP_THROW(RepoUnknownTypeException( info, _("Unhandled repository type") ));
       break;
     }
     // update timestamp and checksum
@@ -1061,104 +1307,179 @@ namespace zypp
 
   ////////////////////////////////////////////////////////////////////////////
 
-  repo::RepoType RepoManager::probe( const Url &url ) const
+  repo::RepoType RepoManager::Impl::probe( const Url & url, const Pathname & path  ) const
   {
-    MIL << "going to probe the type of the repo " << endl;
+    MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
 
-    if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName() ).isDir() )
+    if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
     {
       // Handle non existing local directory in advance, as
       // MediaSetAccess does not support it.
+      MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
       return repo::RepoType::NONE;
     }
 
+    // prepare exception to be thrown if the type could not be determined
+    // due to a media exception. We can't throw right away, because of some
+    // problems with proxy servers returning an incorrect error
+    // on ftp file-not-found(bnc #335906). Instead we'll check another types
+    // before throwing.
+
+    // TranslatorExplanation '%s' is an URL
+    RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
+    bool gotMediaException = false;
     try
     {
       MediaSetAccess access(url);
-      if ( access.doesFileExist("/repodata/repomd.xml") )
-        return repo::RepoType::RPMMD;
-      if ( access.doesFileExist("/content") )
-        return repo::RepoType::YAST2;
+      try
+      {
+        if ( access.doesFileExist(path/"/repodata/repomd.xml") )
+        {
+          MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
+          return repo::RepoType::RPMMD;
+        }
+      }
+      catch ( const media::MediaException &e )
+      {
+        ZYPP_CAUGHT(e);
+        DBG << "problem checking for repodata/repomd.xml file" << endl;
+        enew.remember(e);
+        gotMediaException = true;
+      }
+
+      try
+      {
+        if ( access.doesFileExist(path/"/content") )
+        {
+          MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
+          return repo::RepoType::YAST2;
+        }
+      }
+      catch ( const media::MediaException &e )
+      {
+        ZYPP_CAUGHT(e);
+        DBG << "problem checking for content file" << endl;
+        enew.remember(e);
+        gotMediaException = true;
+      }
 
-      // if it is a local url of type dir
-      if ( (! media::MediaManager::downloads(url)) && ( url.getScheme() == "dir" ) )
+      // if it is a non-downloading URL denoting a directory
+      if ( ! url.schemeIsDownloading() )
       {
-        Pathname path = Pathname(url.getPathName());
-        if ( PathInfo(path).isDir() )
+        MediaMounter media( url );
+        if ( PathInfo(media.getPathName()/path).isDir() )
         {
           // allow empty dirs for now
+          MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
           return repo::RepoType::RPMPLAINDIR;
         }
       }
     }
-    catch ( const media::MediaException &e )
-    {
-      ZYPP_CAUGHT(e);
-      RepoException enew("Error trying to read from " + url.asString());
-      enew.remember(e);
-      ZYPP_THROW(enew);
-    }
     catch ( const Exception &e )
     {
       ZYPP_CAUGHT(e);
-      Exception enew("Unknown error reading from " + url.asString());
+      // TranslatorExplanation '%s' is an URL
+      Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
       enew.remember(e);
       ZYPP_THROW(enew);
     }
 
+    if (gotMediaException)
+      ZYPP_THROW(enew);
+
+    MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
     return repo::RepoType::NONE;
   }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::cleanCache( const RepoInfo &info,
-                                const ProgressData::ReceiverFnc & progressrcv )
+  void RepoManager::Impl::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
   {
-    ProgressData progress(100);
+    MIL << "Going to clean up garbage in cache dirs" << endl;
+
+    ProgressData progress(300);
     progress.sendTo(progressrcv);
     progress.toMin();
 
-    filesystem::recursive_rmdir(solv_path_for_repoinfo(_pimpl->options, info));
+    std::list<Pathname> cachedirs;
+    cachedirs.push_back(_options.repoRawCachePath);
+    cachedirs.push_back(_options.repoPackagesCachePath);
+    cachedirs.push_back(_options.repoSolvCachePath);
+
+    for_( dir, cachedirs.begin(), cachedirs.end() )
+    {
+      if ( PathInfo(*dir).isExist() )
+      {
+        std::list<Pathname> entries;
+        if ( filesystem::readdir( entries, *dir, false ) != 0 )
+          // TranslatorExplanation '%s' is a pathname
+          ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str())));
+
+        unsigned sdircount   = entries.size();
+        unsigned sdircurrent = 1;
+        for_( subdir, entries.begin(), entries.end() )
+        {
+          // if it does not belong known repo, make it disappear
+          bool found = false;
+          for_( r, repoBegin(), repoEnd() )
+            if ( subdir->basename() == r->escaped_alias() )
+            { found = true; break; }
+
+          if ( ! found && ( Date::now()-PathInfo(*subdir).mtime() > Date::day ) )
+            filesystem::recursive_rmdir( *subdir );
 
+          progress.set( progress.val() + sdircurrent * 100 / sdircount );
+          ++sdircurrent;
+        }
+      }
+      else
+        progress.set( progress.val() + 100 );
+    }
     progress.toMax();
   }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  bool RepoManager::isCached( const RepoInfo &info ) const
-  {
-    return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
-  }
-
-  RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
+  void RepoManager::Impl::cleanCache( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
   {
+    ProgressData progress(100);
+    progress.sendTo(progressrcv);
+    progress.toMin();
 
-    Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
+    MIL << "Removing raw metadata cache for " << info.alias() << endl;
+    filesystem::recursive_rmdir(solv_path_for_repoinfo(_options, info));
 
-    return RepoStatus::fromCookieFile(cookiefile);
+    progress.toMax();
   }
 
-  void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
-  {
-    Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
-    filesystem::assert_dir(base);
-    Pathname cookiefile = base / "cookie";
-
-    status.saveToCookieFile(cookiefile);
-  }
+  ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::loadFromCache( const RepoInfo & info,
-                                   const ProgressData::ReceiverFnc & progressrcv )
+  void RepoManager::Impl::loadFromCache( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
   {
     assert_alias(info);
-    Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
+    Pathname solvfile = solv_path_for_repoinfo(_options, info) / "solv";
 
     if ( ! PathInfo(solvfile).isExist() )
       ZYPP_THROW(RepoNotCachedException(info));
 
+    sat::Pool::instance().reposErase( info.alias() );
     try
     {
-      sat::Pool::instance().addRepoSolv( solvfile, info );
+      Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info );
+      // test toolversion in order to rebuild solv file in case
+      // it was written by an old libsolv-tool parser.
+      //
+      // Known version strings used:
+      //  - <no string>
+      //  - "1.0"
+      //
+      sat::LookupRepoAttr toolversion( sat::SolvAttr::repositoryToolVersion, repo );
+      if ( toolversion.begin().asString().empty() )
+      {
+        repo.eraseFromPool();
+        ZYPP_THROW(Exception("Solv-file was created by old parser."));
+      }
+      // else: up-to-date (or even newer).
     }
     catch ( const Exception & exp )
     {
@@ -1173,33 +1494,33 @@ namespace zypp
 
   ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::addRepository( const RepoInfo &info,
-                                   const ProgressData::ReceiverFnc & progressrcv )
+  void RepoManager::Impl::addRepository( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
   {
     assert_alias(info);
 
     ProgressData progress(100);
     callback::SendReport<ProgressReport> report;
     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
-    progress.name(str::form(_("Adding repository '%s'"), info.name().c_str()));
+    progress.name(str::form(_("Adding repository '%s'"), info.label().c_str()));
     progress.toMin();
 
-    RepoInfo tosave = info;
-    if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
-        ZYPP_THROW(RepoAlreadyExistsException(info));
+    MIL << "Try adding repo " << info << endl;
 
+    RepoInfo tosave = info;
+    if ( repos().find(tosave) != repos().end() )
+      ZYPP_THROW(RepoAlreadyExistsException(info));
 
     // check the first url for now
-    if ( _pimpl->options.probe )
+    if ( _options.probe )
     {
       DBG << "unknown repository type, probing" << endl;
 
       RepoType probedtype;
-      probedtype = probe(*tosave.baseUrlsBegin());
+      probedtype = probe( *tosave.baseUrlsBegin(), info.path() );
       if ( tosave.baseUrlsSize() > 0 )
       {
         if ( probedtype == RepoType::NONE )
-          ZYPP_THROW(RepoUnknownTypeException());
+          ZYPP_THROW(RepoUnknownTypeException(info));
         else
           tosave.setType(probedtype);
       }
@@ -1208,21 +1529,32 @@ namespace zypp
     progress.set(50);
 
     // assert the directory exists
-    filesystem::assert_dir(_pimpl->options.knownReposPath);
+    filesystem::assert_dir(_options.knownReposPath);
 
-    Pathname repofile = _pimpl->generateNonExistingName(
-        _pimpl->options.knownReposPath, _pimpl->generateFilename(tosave));
+    Pathname repofile = generateNonExistingName(
+        _options.knownReposPath, generateFilename(tosave));
     // now we have a filename that does not exists
     MIL << "Saving repo in " << repofile << endl;
 
     std::ofstream file(repofile.c_str());
-    if (!file) {
-      ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
+    if (!file)
+    {
+      // TranslatorExplanation '%s' is a filename
+      ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
     }
 
     tosave.dumpAsIniOn(file);
     tosave.setFilepath(repofile);
-    _pimpl->repos.insert(tosave);
+    tosave.setMetadataPath( metadataPath( tosave ) );
+    tosave.setPackagesPath( packagesPath( tosave ) );
+    {
+      // We chould fix the API as we must injet those paths
+      // into the repoinfo in order to keep it usable.
+      RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
+      oinfo.setMetadataPath( metadataPath( tosave ) );
+      oinfo.setPackagesPath( packagesPath( tosave ) );
+    }
+    reposManip().insert(tosave);
 
     progress.set(90);
 
@@ -1238,7 +1570,7 @@ namespace zypp
     if ( havePasswords )
     {
       media::CredentialManager cm(
-          media::CredManagerOptions(_pimpl->options.rootDir) );
+          media::CredManagerOptions(_options.rootDir) );
 
       for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
         if (urlit->hasCredentialsInAuthority())
@@ -1252,8 +1584,8 @@ namespace zypp
     MIL << "done" << endl;
   }
 
-  void RepoManager::addRepositories( const Url &url,
-                                     const ProgressData::ReceiverFnc & progressrcv )
+
+  void RepoManager::Impl::addRepositories( const Url & url, const ProgressData::ReceiverFnc & progressrcv )
   {
     std::list<RepoInfo> repos = readRepoFile(url);
     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
@@ -1274,18 +1606,23 @@ namespace zypp
     std::string filename = Pathname(url.getPathName()).basename();
 
     if ( filename == Pathname() )
-      ZYPP_THROW(RepoException("Invalid repo file name at " + url.asString() ));
+    {
+      // TranslatorExplanation '%s' is an URL
+      ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
+    }
 
     // assert the directory exists
-    filesystem::assert_dir(_pimpl->options.knownReposPath);
+    filesystem::assert_dir(_options.knownReposPath);
 
-    Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
+    Pathname repofile = generateNonExistingName(_options.knownReposPath, filename);
     // now we have a filename that does not exists
     MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
 
     std::ofstream file(repofile.c_str());
-    if (!file) {
-      ZYPP_THROW (Exception( "Can't open " + repofile.asString() ) );
+    if (!file)
+    {
+      // TranslatorExplanation '%s' is a filename
+      ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
     }
 
     for ( std::list<RepoInfo>::iterator it = repos.begin();
@@ -1295,9 +1632,9 @@ namespace zypp
       MIL << "Saving " << (*it).alias() << endl;
       it->setFilepath(repofile.asString());
       it->dumpAsIniOn(file);
-      _pimpl->repos.insert(*it);
+      reposManip().insert(*it);
 
-      HistoryLog(_pimpl->options.rootDir).addRepository(*it);
+      HistoryLog(_options.rootDir).addRepository(*it);
     }
 
     MIL << "done" << endl;
@@ -1305,13 +1642,12 @@ namespace zypp
 
   ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::removeRepository( const RepoInfo & info,
-                                      const ProgressData::ReceiverFnc & progressrcv)
+  void RepoManager::Impl::removeRepository( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
   {
     ProgressData progress;
     callback::SendReport<ProgressReport> report;
     progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
-    progress.name(str::form(_("Removing repository '%s'"), info.name().c_str()));
+    progress.name(str::form(_("Removing repository '%s'"), info.label().c_str()));
 
     MIL << "Going to delete repo " << info.alias() << endl;
 
@@ -1330,7 +1666,7 @@ namespace zypp
       RepoInfo todelete = *it;
       if (todelete.filepath().empty())
       {
-        ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
+        ZYPP_THROW(RepoException( todelete, _("Can't figure out where the repo is stored.") ));
       }
       else
       {
@@ -1341,9 +1677,10 @@ namespace zypp
           // easy, only this one, just delete the file
           if ( filesystem::unlink(todelete.filepath()) != 0 )
           {
-            ZYPP_THROW(RepoException("Can't delete " + todelete.filepath().asString()));
+            // TranslatorExplanation '%s' is a filename
+            ZYPP_THROW(RepoException( todelete, str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
           }
-          MIL << todelete.alias() << " sucessfully deleted." << endl;
+          MIL << todelete.alias() << " successfully deleted." << endl;
         }
         else
         {
@@ -1356,9 +1693,10 @@ namespace zypp
           filesystem::assert_dir(todelete.filepath().dirname());
 
           std::ofstream file(todelete.filepath().c_str());
-          if (!file) {
-            //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
-            ZYPP_THROW (Exception( "Can't open " + todelete.filepath().asString() ) );
+          if (!file)
+          {
+            // TranslatorExplanation '%s' is a filename
+            ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
           }
           for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
                 fit != filerepos.end();
@@ -1369,16 +1707,18 @@ namespace zypp
           }
         }
 
-        CombinedProgressData subprogrcv(progress, 70);
-        CombinedProgressData cleansubprogrcv(progress, 30);
+        CombinedProgressData cSubprogrcv(progress, 20);
+        CombinedProgressData mSubprogrcv(progress, 40);
+        CombinedProgressData pSubprogrcv(progress, 40);
         // now delete it from cache
         if ( isCached(todelete) )
-          cleanCache( todelete, subprogrcv);
+          cleanCache( todelete, cSubprogrcv);
         // now delete metadata (#301037)
-        cleanMetadata( todelete, cleansubprogrcv);
-        _pimpl->repos.erase(todelete);
-        MIL << todelete.alias() << " sucessfully deleted." << endl;
-        HistoryLog(_pimpl->options.rootDir).removeRepository(todelete);
+        cleanMetadata( todelete, mSubprogrcv );
+       cleanPackages( todelete, pSubprogrcv );
+        reposManip().erase(todelete);
+        MIL << todelete.alias() << " successfully deleted." << endl;
+        HistoryLog(_options.rootDir).removeRepository(todelete);
         return;
       } // else filepath is empty
 
@@ -1389,25 +1729,20 @@ namespace zypp
 
   ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::modifyRepository( const std::string &alias,
-                                      const RepoInfo & newinfo,
-                                      const ProgressData::ReceiverFnc & progressrcv )
+  void RepoManager::Impl::modifyRepository( const std::string & alias, const RepoInfo & newinfo_r, const ProgressData::ReceiverFnc & progressrcv )
   {
     RepoInfo toedit = getRepositoryInfo(alias);
+    RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
 
     // check if the new alias already exists when renaming the repo
-    if (alias != newinfo.alias())
+    if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
     {
-      for_( it, repoBegin(), repoEnd() )
-      {
-        if ( newinfo.alias() == (*it).alias() )
-          ZYPP_THROW(RepoAlreadyExistsException(newinfo));
-      }
+      ZYPP_THROW(RepoAlreadyExistsException(newinfo));
     }
 
     if (toedit.filepath().empty())
     {
-      ZYPP_THROW(RepoException("Can't figure where the repo is stored"));
+      ZYPP_THROW(RepoException( toedit, _("Can't figure out where the repo is stored.") ));
     }
     else
     {
@@ -1423,9 +1758,10 @@ namespace zypp
       filesystem::assert_dir(toedit.filepath().dirname());
 
       std::ofstream file(toedit.filepath().c_str());
-      if (!file) {
-        //ZYPP_THROW (Exception( "Can't open " + tmp.path().asString() ) );
-        ZYPP_THROW (Exception( "Can't open " + toedit.filepath().asString() ) );
+      if (!file)
+      {
+        // TranslatorExplanation '%s' is a filename
+        ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
       }
       for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
             fit != filerepos.end();
@@ -1439,46 +1775,40 @@ namespace zypp
             newinfo.dumpAsIniOn(file);
       }
 
-      _pimpl->repos.erase(toedit);
-      _pimpl->repos.insert(newinfo);
-      HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
+      newinfo.setFilepath(toedit.filepath());
+      reposManip().erase(toedit);
+      reposManip().insert(newinfo);
+      HistoryLog(_options.rootDir).modifyRepository(toedit, newinfo);
       MIL << "repo " << alias << " modified" << endl;
     }
   }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
-                                           const ProgressData::ReceiverFnc & progressrcv )
+  RepoInfo RepoManager::Impl::getRepositoryInfo( const std::string & alias, const ProgressData::ReceiverFnc & progressrcv )
   {
-    RepoInfo info;
-    info.setAlias(alias);
-    RepoConstIterator it = _pimpl->repos.find( info );
-    if( it == repoEnd() )
-      ZYPP_THROW(RepoNotFoundException(info));
-    else
+    RepoConstIterator it( findAlias( alias, repos() ) );
+    if ( it != repos().end() )
       return *it;
+    RepoInfo info;
+    info.setAlias( alias );
+    ZYPP_THROW( RepoNotFoundException(info) );
   }
 
-  ////////////////////////////////////////////////////////////////////////////
 
-  RepoInfo RepoManager::getRepositoryInfo( const Url & url,
-                                           const url::ViewOption & urlview,
-                                           const ProgressData::ReceiverFnc & progressrcv )
+  RepoInfo RepoManager::Impl::getRepositoryInfo( const Url & url, const url::ViewOption & urlview, const ProgressData::ReceiverFnc & progressrcv )
   {
     for_( it, repoBegin(), repoEnd() )
     {
-      for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
-          urlit != (*it).baseUrlsEnd();
-          ++urlit)
+      for_( urlit, (*it).baseUrlsBegin(), (*it).baseUrlsEnd() )
       {
-        if ((*urlit).asString(urlview) == url.asString(urlview))
-          return *it;
+        if ( (*urlit).asString(urlview) == url.asString(urlview) )
+         return *it;
       }
     }
     RepoInfo info;
-    info.setBaseUrl(url);
-    ZYPP_THROW(RepoNotFoundException(info));
+    info.setBaseUrl( url );
+    ZYPP_THROW( RepoNotFoundException(info) );
   }
 
   ////////////////////////////////////////////////////////////////////////////
@@ -1487,42 +1817,7 @@ namespace zypp
   //
   ////////////////////////////////////////////////////////////////////////////
 
-  bool RepoManager::serviceEmpty() const
-  { return _pimpl->services.empty(); }
-
-  RepoManager::ServiceSizeType RepoManager::serviceSize() const
-  { return _pimpl->services.size(); }
-
-  RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
-  { return _pimpl->services.begin(); }
-
-  RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
-  { return _pimpl->services.end(); }
-
-  ServiceInfo RepoManager::getService( const std::string & alias ) const
-  {
-    for_( it, serviceBegin(), serviceEnd() )
-      if ( it->alias() == alias )
-        return *it;
-    return ServiceInfo::noService;
-  }
-
-  bool RepoManager::hasService( const std::string & alias ) const
-  {
-    for_( it, serviceBegin(), serviceEnd() )
-      if ( it->alias() == alias )
-        return true;
-    return false;
-  }
-
-  ////////////////////////////////////////////////////////////////////////////
-
-  void RepoManager::addService( const std::string & alias, const Url & url )
-  {
-    addService( ServiceInfo(alias, url) );
-  }
-
-  void RepoManager::addService( const ServiceInfo & service )
+  void RepoManager::Impl::addService( const ServiceInfo & service )
   {
     assert_alias( service );
 
@@ -1533,14 +1828,14 @@ namespace zypp
     // Writable ServiceInfo is needed to save the location
     // of the .service file. Finaly insert into the service list.
     ServiceInfo toSave( service );
-    _pimpl->saveService( toSave );
-    _pimpl->services.insert( toSave );
+    saveService( toSave );
+    _services.insert( toSave );
 
     // check for credentials in Url (username:password, not ?credentials param)
     if ( toSave.url().hasCredentialsInAuthority() )
     {
       media::CredentialManager cm(
-          media::CredManagerOptions(_pimpl->options.rootDir) );
+          media::CredManagerOptions(_options.rootDir) );
 
       //! \todo use a method calling UI callbacks to ask where to save creds?
       cm.saveInUser(media::AuthData(toSave.url()));
@@ -1551,16 +1846,16 @@ namespace zypp
 
   ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::removeService( const std::string & alias )
+  void RepoManager::Impl::removeService( const std::string & alias )
   {
-    MIL << "Going to delete repo " << alias << endl;
+    MIL << "Going to delete service " << alias << endl;
 
     const ServiceInfo & service = getService( alias );
 
     Pathname location = service.filepath();
     if( location.empty() )
     {
-      ZYPP_THROW(RepoException("Can't figure where the service is stored"));
+      ZYPP_THROW(ServiceException( service, _("Can't figure out where the service is stored.") ));
     }
 
     ServiceSet tmpSet;
@@ -1571,17 +1866,21 @@ namespace zypp
     {
       if ( filesystem::unlink(location) != 0 )
       {
-        ZYPP_THROW(RepoException("Can't delete " + location.asString()));
+        // TranslatorExplanation '%s' is a filename
+        ZYPP_THROW(ServiceException( service, str::form( _("Can't delete '%s'"), location.c_str() ) ));
       }
-      MIL << alias << " sucessfully deleted." << endl;
+      MIL << alias << " successfully deleted." << endl;
     }
     else
     {
       filesystem::assert_dir(location.dirname());
 
       std::ofstream file(location.c_str());
-      if( file.fail() )
-        ZYPP_THROW(Exception("failed open file to write"));
+      if( !file )
+      {
+        // TranslatorExplanation '%s' is a filename
+        ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
+      }
 
       for_(it, tmpSet.begin(), tmpSet.end())
       {
@@ -1589,24 +1888,21 @@ namespace zypp
           it->dumpAsIniOn(file);
       }
 
-      MIL << alias << " sucessfully deleted from file " << location <<  endl;
+      MIL << alias << " successfully deleted from file " << location <<  endl;
     }
 
     // now remove all repositories added by this service
     RepoCollector rcollector;
     getRepositoriesInService( alias,
-      boost::make_function_output_iterator(
-          bind( &RepoCollector::collect, &rcollector, _1 ) ) );
+                             boost::make_function_output_iterator( bind( &RepoCollector::collect, &rcollector, _1 ) ) );
     // cannot do this directly in getRepositoriesInService - would invalidate iterators
     for_(rit, rcollector.repos.begin(), rcollector.repos.end())
       removeRepository(*rit);
   }
 
+  ////////////////////////////////////////////////////////////////////////////
 
-  void RepoManager::removeService( const ServiceInfo & service )
-  { removeService(service.alias()); }
-
-  void RepoManager::refreshServices()
+  void RepoManager::Impl::refreshServices( const RefreshServiceOptions & options_r )
   {
     // copy the set of services since refreshService
     // can eventually invalidate the iterator
@@ -1616,21 +1912,24 @@ namespace zypp
       if ( !it->enabled() )
         continue;
 
-      refreshService(*it);
+      try {
+       refreshService(*it, options_r);
+      }
+      catch ( const repo::ServicePluginInformalException & e )
+      { ;/* ignore ServicePluginInformalException */ }
     }
   }
 
-  void RepoManager::refreshService( const ServiceInfo & dont_use_service_r )
+  void RepoManager::Impl::refreshService( const std::string & alias, const RefreshServiceOptions & options_r )
   {
-    assert_alias( dont_use_service_r );
-    assert_url( dont_use_service_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. Thus 'dont_use_service_r' but 'service':
-    ServiceInfo service( dont_use_service_r );
+    // enable/disable state.:
     bool serviceModified = false;
-    MIL << "going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
+    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)
 
@@ -1645,32 +1944,55 @@ namespace zypp
       }
     }
 
-    // download the repo index file
-    media::MediaManager mediamanager;
-    media::MediaAccessId mid = mediamanager.open( service.url() );
-    mediamanager.attachDesiredMedia( mid );
-    mediamanager.provideFile( mid, "repo/repoindex.xml" );
-    Pathname path = mediamanager.localPath(mid, "repo/repoindex.xml" );
+    // get target distro identifier
+    std::string servicesTargetDistro = _options.servicesTargetDistro;
+    if ( servicesTargetDistro.empty() )
+    {
+      servicesTargetDistro = Target::targetDistribution( Pathname() );
+    }
+    DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
 
     // parse it
-    RepoCollector collector(_pimpl->options.servicesTargetDistro);
-    parser::RepoindexFileReader reader( path, bind( &RepoCollector::collect, &collector, _1 ) );
-    mediamanager.release( mid );
-    mediamanager.close( mid );
+    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();
+        url = service.rawUrl();
       else
       {
         // service repo can contain only one URL now, so no need to iterate.
-        url = *it->baseUrlsBegin();
+        url = it->rawUrl();    // raw!
       }
 
       // libzypp currently has problem with separate url + path handling
@@ -1683,13 +2005,8 @@ namespace zypp
         it->setPath("");
       }
 
-      // Prepend service alias:
-      it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
-
       // save the url
       it->setBaseUrl( url );
-      // set refrence to the parent service
-      it->setService( service.alias() );
     }
 
     ////////////////////////////////////////////////////////////////////////////
@@ -1698,12 +2015,29 @@ namespace zypp
     RepoInfoList oldRepos;
     getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
 
+    ////////////////////////////////////////////////////////////////////////////
     // find old repositories to remove...
-    for_( it, oldRepos.begin(), oldRepos.end() )
+    for_( oldRepo, oldRepos.begin(), oldRepos.end() )
     {
-      if ( ! foundAliasIn( it->alias(), collector.repos ) )
+      if ( ! foundAliasIn( oldRepo->alias(), collector.repos ) )
       {
-        removeRepository( *it );
+       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 );
       }
     }
 
@@ -1711,19 +2045,40 @@ namespace zypp
     // create missing repositories and modify exising ones if needed...
     for_( it, collector.repos.begin(), collector.repos.end() )
     {
-      // Service explicitly requests the repo being enabled?
-      // Service explicitly requests the repo being disabled?
+      // User explicitly requested the repo being enabled?
+      // User explicitly requested the repo being disabled?
       // And hopefully not both ;) If so, enable wins.
-      bool beEnabled = service.repoToEnableFind( it->alias() );
-      bool beDisabled = service.repoToDisableFind( it->alias() );
 
-      if ( beEnabled )
+      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 ) )
       {
-        // Remove from enable request list.
-        // NOTE: repoToDisable is handled differently.
-        //       It gets cleared on each refresh.
-        service.delRepoToEnable( it->alias() );
-        serviceModified = true;
+       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 ) );
@@ -1731,44 +2086,95 @@ namespace zypp
       {
         // Not found in oldRepos ==> a new repo to add
 
-        // Make sure the service repo is created with the
-        // appropriate enable and autorefresh true.
-        it->setEnabled( beEnabled );
-        it->setAutorefresh( true );
+       // Make sure the service repo is created with the appropriate enablement
+       if ( ! indeterminate(toBeEnabled) )
+         it->setEnabled( toBeEnabled );
 
-        // At that point check whether a repo with the same alias
-        // exists outside this service. Maybe forcefully re-alias
-        // the existing repo?
+        DBG << "Service adds repo " << it->alias() << " " << (it->enabled()?"enabled":"disabled") << endl;
         addRepository( *it );
-
-        // save repo credentials
-        // ma@: task for modifyRepository?
       }
       else
       {
         // ==> an exising repo to check
         bool oldRepoModified = false;
 
-        if ( beEnabled )
+       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
         {
-          if ( ! oldRepo->enabled() )
-          {
-            oldRepo->setEnabled( true );
-            oldRepoModified = true;
-          }
-        }
-        else if ( beDisabled )
+         DBG << "Service repo " << it->alias() << " gets disabled" << endl;
+         oldRepo->setEnabled( false );
+         oldRepoModified = true;
+       }
+
+       // all other attributes follow the service request:
+
+       // changed name (raw!)
+       if ( oldRepo->rawName() != it->rawName() )
+       {
+         DBG << "Service repo " << it->alias() << " gets new NAME " << it->rawName() << endl;
+         oldRepo->setName( it->rawName() );
+         oldRepoModified = true;
+       }
+
+       // 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->rawUrl() != it->rawUrl() )
         {
-          if ( oldRepo->enabled() )
-          {
-            oldRepo->setEnabled( false );
-            oldRepoModified = true;
-          }
+          DBG << "Service repo " << it->alias() << " gets new URL " << it->rawUrl() << endl;
+          oldRepo->setBaseUrl( it->rawUrl() );
+          oldRepoModified = true;
         }
 
-#warning also check changed URL due to PATH/URL change in service, but ignore ?credentials param!
-// ma@: task for modifyRepository?
-
         // save if modified:
         if ( oldRepoModified )
         {
@@ -1784,27 +2190,48 @@ namespace zypp
       serviceModified = true;
     }
 
+    // Remember original service request for next refresh
+    if ( service.repoStates() != newRepoStates )
+    {
+      service.setRepoStates( std::move(newRepoStates) );
+      serviceModified = true;
+    }
+
     ////////////////////////////////////////////////////////////////////////////
-    // save service if modified:
-    if ( serviceModified )
+    // 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::modifyService(const std::string & oldAlias, const ServiceInfo & service)
+  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(RepoException(
-          "Cannot figure out where the service file is stored."));
+      ZYPP_THROW(ServiceException( oldService, _("Can't figure out where the service is stored.") ));
     }
 
     // remember: there may multiple services being defined in one file:
@@ -1820,28 +2247,35 @@ namespace zypp
     }
     service.dumpAsIniOn(file);
     file.close();
+    service.setFilepath(location);
 
-    _pimpl->services.erase(oldAlias);
-    _pimpl->services.insert(service);
+    _services.erase(oldAlias);
+    _services.insert(service);
 
     // changed properties affecting also repositories
-    if( oldAlias != service.alias()                    // changed alias
-        || oldService.enabled() != service.enabled()   // changed enabled status
-      )
+    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())
-          it->setEnabled(false);
-        else if (!oldService.enabled() && service.enabled())
-        {
-          //! \todo do nothing? the repos will be enabled on service refresh
-          //! \todo how to know the service needs a (auto) refresh????
-        }
-        else
+       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);
       }
     }
@@ -1849,7 +2283,9 @@ namespace zypp
     //! \todo refresh the service automatically if url is changed?
   }
 
-  repo::ServiceType RepoManager::probeService( const Url &url ) const
+  ////////////////////////////////////////////////////////////////////////////
+
+  repo::ServiceType RepoManager::Impl::probeService( const Url & url ) const
   {
     try
     {
@@ -1860,14 +2296,16 @@ namespace zypp
     catch ( const media::MediaException &e )
     {
       ZYPP_CAUGHT(e);
-      RepoException enew("Error trying to read from " + url.asString());
+      // 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);
-      Exception enew("Unknown error reading from " + url.asString());
+      // TranslatorExplanation '%s' is an URL
+      Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
       enew.remember(e);
       ZYPP_THROW(enew);
     }
@@ -1875,13 +2313,170 @@ namespace zypp
     return repo::ServiceType::NONE;
   }
 
-  ////////////////////////////////////////////////////////////////////////////
+  ///////////////////////////////////////////////////////////////////
+  //
+  //   CLASS NAME : RepoManager
+  //
+  ///////////////////////////////////////////////////////////////////
 
-  std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
+  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 )
   {
-    return str << *obj._pimpl;
+    std::string ret( url_r.getScheme() );
+    if ( ret.empty() )
+      ret = "repo-";
+    else
+      ret += "-";
+
+    std::string host( url_r.getHost() );
+    if ( ! host.empty() )
+    {
+      ret += host;
+      ret += "-";
+    }
+
+    static Date::ValueType serial = Date::now();
+    ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8);
+    return ret;
   }
 
+  RepoStatus RepoManager::metadataStatus( const RepoInfo & info ) const
+  { return _pimpl->metadataStatus( info ); }
+
+  RepoManager::RefreshCheckStatus RepoManager::checkIfToRefreshMetadata( const RepoInfo &info, const Url &url, RawMetadataRefreshPolicy policy )
+  { return _pimpl->checkIfToRefreshMetadata( info, url, policy ); }
+
+  Pathname RepoManager::metadataPath( const RepoInfo &info ) const
+  { return _pimpl->metadataPath( info ); }
+
+  Pathname RepoManager::packagesPath( const RepoInfo &info ) const
+  { return _pimpl->packagesPath( info ); }
+
+  void RepoManager::refreshMetadata( const RepoInfo &info, RawMetadataRefreshPolicy policy, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->refreshMetadata( info, policy, progressrcv ); }
+
+  void RepoManager::cleanMetadata( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->cleanMetadata( info, progressrcv ); }
+
+  void RepoManager::cleanPackages( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->cleanPackages( info, progressrcv ); }
+
+  RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
+  { return _pimpl->cacheStatus( info ); }
+
+  void RepoManager::buildCache( const RepoInfo &info, CacheBuildPolicy policy, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->buildCache( info, policy, progressrcv ); }
+
+  void RepoManager::cleanCache( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->cleanCache( info, progressrcv ); }
+
+  bool RepoManager::isCached( const RepoInfo &info ) const
+  { return _pimpl->isCached( info ); }
+
+  void RepoManager::loadFromCache( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->loadFromCache( info, progressrcv ); }
+
+  void RepoManager::cleanCacheDirGarbage( const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->cleanCacheDirGarbage( progressrcv ); }
+
+  repo::RepoType RepoManager::probe( const Url & url, const Pathname & path ) const
+  { return _pimpl->probe( url, path ); }
+
+  repo::RepoType RepoManager::probe( const Url & url ) const
+  { return _pimpl->probe( url ); }
+
+  void RepoManager::addRepository( const RepoInfo &info, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->addRepository( info, progressrcv ); }
+
+  void RepoManager::addRepositories( const Url &url, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->addRepositories( url, progressrcv ); }
+
+  void RepoManager::removeRepository( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->removeRepository( info, progressrcv ); }
+
+  void RepoManager::modifyRepository( const std::string &alias, const RepoInfo & newinfo, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->modifyRepository( alias, newinfo, progressrcv ); }
+
+  RepoInfo RepoManager::getRepositoryInfo( const std::string &alias, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->getRepositoryInfo( alias, progressrcv ); }
+
+  RepoInfo RepoManager::getRepositoryInfo( const Url & url, const url::ViewOption & urlview, const ProgressData::ReceiverFnc & progressrcv )
+  { return _pimpl->getRepositoryInfo( url, urlview, progressrcv ); }
+
+  bool RepoManager::serviceEmpty() const
+  { return _pimpl->serviceEmpty(); }
+
+  RepoManager::ServiceSizeType RepoManager::serviceSize() const
+  { return _pimpl->serviceSize(); }
+
+  RepoManager::ServiceConstIterator RepoManager::serviceBegin() const
+  { return _pimpl->serviceBegin(); }
+
+  RepoManager::ServiceConstIterator RepoManager::serviceEnd() const
+  { return _pimpl->serviceEnd(); }
+
+  ServiceInfo RepoManager::getService( const std::string & alias ) const
+  { return _pimpl->getService( alias ); }
+
+  bool RepoManager::hasService( const std::string & alias ) const
+  { return _pimpl->hasService( alias ); }
+
+  repo::ServiceType RepoManager::probeService( const Url &url ) const
+  { return _pimpl->probeService( url ); }
+
+  void RepoManager::addService( const std::string & alias, const Url& url )
+  { return _pimpl->addService( alias, url ); }
+
+  void RepoManager::addService( const ServiceInfo & service )
+  { return _pimpl->addService( service ); }
+
+  void RepoManager::removeService( const std::string & alias )
+  { return _pimpl->removeService( alias ); }
+
+  void RepoManager::removeService( const ServiceInfo & service )
+  { return _pimpl->removeService( service ); }
+
+  void RepoManager::refreshServices( const RefreshServiceOptions & options_r )
+  { return _pimpl->refreshServices( options_r ); }
+
+  void RepoManager::refreshService( const std::string & alias, const RefreshServiceOptions & options_r )
+  { return _pimpl->refreshService( alias, options_r ); }
+
+  void RepoManager::refreshService( const ServiceInfo & service, const RefreshServiceOptions & options_r )
+  { return _pimpl->refreshService( service, options_r ); }
+
+  void RepoManager::modifyService( const std::string & oldAlias, const ServiceInfo & service )
+  { return _pimpl->modifyService( oldAlias, service ); }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
+  { return str << *obj._pimpl; }
+
   /////////////////////////////////////////////////////////////////
 } // namespace zypp
 ///////////////////////////////////////////////////////////////////