Imported Upstream version 14.30.0
[platform/upstream/libzypp.git] / zypp / RepoManager.cc
index 4f59189..4712f56 100644 (file)
 #include <fstream>
 #include <sstream>
 #include <list>
+#include <map>
 #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/TmpPath.h"
 
+#include "zypp/ServiceInfo.h"
 #include "zypp/repo/RepoException.h"
 #include "zypp/RepoManager.h"
 
-#include "zypp/cache/SolvStore.h"
-//#include "zypp/repo/cached/RepoImpl.h"
 #include "zypp/media/MediaManager.h"
+#include "zypp/media/CredentialManager.h"
 #include "zypp/MediaSetAccess.h"
 #include "zypp/ExternalProgram.h"
+#include "zypp/ManagedFile.h"
 
 #include "zypp/parser/RepoFileReader.h"
+#include "zypp/parser/ServiceFileReader.h"
+#include "zypp/repo/ServiceRepos.h"
 #include "zypp/repo/yum/Downloader.h"
-#include "zypp/parser/yum/RepoParser.h"
-//#include "zypp/parser/plaindir/RepoParser.h"
 #include "zypp/repo/susetags/Downloader.h"
-#include "zypp/parser/susetags/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/HistoryLog.h" // to write history :O)
 
 #include "zypp/ZYppCallbacks.h"
 
 #include "sat/Pool.h"
-#include "satsolver/pool.h"
-#include "satsolver/repo.h"
-#include "satsolver/repo_solv.h"
 
-using namespace std;
-using namespace zypp;
+using std::endl;
+using std::string;
 using namespace zypp::repo;
-using namespace zypp::filesystem;
 
-using namespace zypp::repo;
+#define OPT_PROGRESS const ProgressData::ReceiverFnc & = ProgressData::ReceiverFnc()
 
 ///////////////////////////////////////////////////////////////////
 namespace zypp
-{ /////////////////////////////////////////////////////////////////
-
+{
   ///////////////////////////////////////////////////////////////////
-  //
-  //   CLASS NAME : RepoManagerOptions
-  //
-  ///////////////////////////////////////////////////////////////////
-
-  RepoManagerOptions::RepoManagerOptions()
+  namespace
   {
-    repoCachePath    = ZConfig::instance().repoCachePath();
-    repoRawCachePath = ZConfig::instance().repoMetadataPath();
-    knownReposPath   = ZConfig::instance().knownReposPath();
-  }
+    /** 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 );
+        }
 
-  /**
-    * \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 resuls at the end, without dealing with async
-    * code.
-    */
-    struct RepoCollector
+        /** 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 )
+    {
+      for_( it, begin_r, end_r )
+        if ( it->alias() == alias_r )
+          return true;
+      return false;
+    }
+    /** \overload */
+    template <class Container>
+    inline bool foundAliasIn( const std::string & alias_r, const Container & cont_r )
+    { return foundAliasIn( alias_r, cont_r.begin(), cont_r.end() ); }
+
+    /** Find alias_r in repo/service container. */
+    template <class Iterator>
+    inline Iterator findAlias( const std::string & alias_r, Iterator begin_r, Iterator end_r )
+    {
+      for_( it, begin_r, end_r )
+        if ( it->alias() == alias_r )
+          return it;
+      return end_r;
+    }
+    /** \overload */
+    template <class Container>
+    inline typename Container::iterator findAlias( const std::string & alias_r, Container & cont_r )
+    { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
+    /** \overload */
+    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() ); }
+
+
+    /** \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 )
+    {
+      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 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()
-      {
-        MIL << endl;
-      }
+      {}
 
-      ~RepoCollector()
-      {
-        MIL << endl;
-      }
+      RepoCollector(const std::string & targetDistro_)
+        : targetDistro(targetDistro_)
+      {}
 
       bool collect( const RepoInfo &repo )
       {
-        //MIL << "here in collector: " << repo.alias() << endl;
+        // skip repositories meant for other distros than specified
+        if (!targetDistro.empty()
+            && !repo.targetDistribution().empty()
+            && repo.targetDistribution() != targetDistro)
+        {
+          MIL
+            << "Skipping repository meant for '" << repo.targetDistribution()
+            << "' distribution (current distro is '"
+            << targetDistro << "')." << endl;
+
+          return true;
+        }
+
         repos.push_back(repo);
-        //MIL << "added: " << repo.alias() << endl;
         return true;
       }
 
       RepoInfoList repos;
+      std::string targetDistro;
     };
+    ////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * 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 Internal version of clean cache
-    *
-    * Takes an extra SolvStore reference, so we avoid internally
-    * having 2 SolvStores writing to the same database.
-    */
-  static void cleanCacheInternal( cache::SolvStore &store,
-                                  const RepoInfo &info,
-                                  const ProgressData::ReceiverFnc & progressrcv = ProgressData::ReceiverFnc() )
-  {
-    ProgressData progress;
-    callback::SendReport<ProgressReport> report;
-    progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
-    progress.name(str::form(_("Cleaning repository '%s' cache"), info.name().c_str()));
+    ////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * \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 )
+    {
+      MIL << "directory " << dir << endl;
+      std::list<RepoInfo> repos;
+      bool nonroot( geteuid() != 0 );
+      if ( nonroot && ! PathInfo(dir).userMayRX() )
+      {
+       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;
+    }
 
-    if ( !store.isCached(info.alias()) )
-      return;
+    ////////////////////////////////////////////////////////////////////////////
 
-    MIL << info.alias() << " cleaning cache..." << endl;
-    data::RecordId id = store.lookupRepository(info.alias());
+    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.")));
+    }
 
-    CombinedProgressData subprogrcv(progress);
+    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.")));
+    }
 
-    store.cleanRepository(id, subprogrcv);
-  }
+    ////////////////////////////////////////////////////////////////////////////
 
-  ////////////////////////////////////////////////////////////////////////////
+    inline void assert_urls( const RepoInfo & info )
+    {
+      if ( info.baseUrlsEmpty() )
+       ZYPP_THROW( RepoNoUrlException( info ) );
+    }
 
-  /**
-   * 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;
-  }
+    inline void assert_url( const ServiceInfo & info )
+    {
+      if ( ! info.url().isValid() )
+       ZYPP_THROW( ServiceNoUrlException( info ) );
+    }
 
-  ////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////
 
-  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);
+    /**
+     * \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();
+    }
 
-     DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
+    /**
+     * \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();
+    }
 
-     return repositories_in_file(local);
-   }
+    /**
+     * \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 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;
-    list<RepoInfo> repos;
-    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 ( list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
+    /** Functor collecting ServiceInfos into a ServiceSet. */
+    class ServiceCollector
     {
-      if (str::regex_match(it->extension(), allowedRepoExt))
-      {
-        list<RepoInfo> tmp = repositories_in_file( *it );
-        repos.insert( repos.end(), tmp.begin(), tmp.end() );
+    public:
+      typedef std::set<ServiceInfo> ServiceSet;
 
-        //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
-        //MIL << "ok" << endl;
+      ServiceCollector( ServiceSet & services_r )
+      : _services( services_r )
+      {}
+
+      bool operator()( const ServiceInfo & service_r ) const
+      {
+       _services.insert( service_r );
+       return true;
       }
-    }
-    return repos;
-  }
 
-  ////////////////////////////////////////////////////////////////////////////
+    private:
+      ServiceSet & _services;
+    };
+    ////////////////////////////////////////////////////////////////////////////
+
+  } // namespace
+  ///////////////////////////////////////////////////////////////////
 
-  static void assert_alias( const RepoInfo &info )
+  std::list<RepoInfo> readRepoFile( const Url & repo_file )
   {
-    if (info.alias().empty())
-        ZYPP_THROW(RepoNoAliasException());
+    // 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 RepoManagerOptions
+  //
+  ////////////////////////////////////////////////////////////////////
 
-  static void assert_urls( const RepoInfo &info )
+  RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
   {
-    if (info.baseUrlsEmpty())
-        ZYPP_THROW(RepoNoUrlException());
+    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;
+  }
 
-  /**
-   * \short Calculates the raw cache path for a repository
-   */
-  static Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
+  std:: ostream & operator<<( std::ostream & str, const RepoManagerOptions & obj )
   {
-    assert_alias(info);
-    return opt.repoRawCachePath + info.alias();
+#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 NAME : RepoManager::Impl
-  //
+  /// \class RepoManager::Impl
+  /// \brief RepoManager implementation.
+  ///
   ///////////////////////////////////////////////////////////////////
-
-  /**
-   * \short RepoManager implementation.
-   */
   struct RepoManager::Impl
   {
+  public:
     Impl( const RepoManagerOptions &opt )
-      : options(opt)
+      : _options(opt)
     {
-
+      init_knownServices();
+      init_knownRepositories();
     }
 
-    Impl()
+    ~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;
     }
 
-    RepoManagerOptions options;
+  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 );
 
   public:
-    /** Offer default Impl. */
-    static shared_ptr<Impl> nullimpl()
+    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 ); }
+
+    ServiceInfo getService( const std::string & alias ) const
     {
-      static shared_ptr<Impl> _nullimpl( new Impl );
-      return _nullimpl;
+      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;
+
+    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"; }
 
   ///////////////////////////////////////////////////////////////////
-  //
-  //   CLASS NAME : RepoManager
-  //
-  ///////////////////////////////////////////////////////////////////
 
-  RepoManager::RepoManager( const RepoManagerOptions &opt )
-  : _pimpl( new Impl(opt) )
-  {}
+  void RepoManager::Impl::saveService( ServiceInfo & service ) const
+  {
+    filesystem::assert_dir( _options.knownServicesPath );
+    Pathname servfile = generateNonExistingName( _options.knownServicesPath,
+                                                 generateFilename( service ) );
+    service.setFilepath( servfile );
 
-  ////////////////////////////////////////////////////////////////////////////
+    MIL << "saving service in " << servfile << endl;
 
-  RepoManager::~RepoManager()
-  {}
+    std::ofstream file( servfile.c_str() );
+    if ( !file )
+    {
+      // 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;
+  }
+
+  /**
+   * Generate a non existing filename in a directory, using a base
+   * name. For example if a directory contains 3 files
+   *
+   * |-- bar
+   * |-- foo
+   * `-- moo
+   *
+   * If you try to generate a unique filename for this directory,
+   * based on "ruu" you will get "ruu", but if you use the base
+   * "foo" you will get "foo_1"
+   *
+   * \param dir Directory where the file needs to be unique
+   * \param basefilename string to base the filename on.
+   */
+  Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir,
+                                                       const std::string & basefilename ) const
+  {
+    std::string final_filename = basefilename;
+    int counter = 1;
+    while ( PathInfo(dir + final_filename).isExist() )
+    {
+      final_filename = basefilename + "_" + str::numstring(counter);
+      ++counter;
+    }
+    return dir + Pathname(final_filename);
+  }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  std::list<RepoInfo> RepoManager::knownRepositories() const
+  void RepoManager::Impl::init_knownServices()
   {
-    MIL << endl;
-
-    if ( PathInfo(_pimpl->options.knownReposPath).isExist() )
+    Pathname dir = _options.knownServicesPath;
+    std::list<Pathname> entries;
+    if (PathInfo(dir).isExist())
     {
-      RepoInfoList repos = repositories_in_dir(_pimpl->options.knownReposPath);
-      for ( RepoInfoList::iterator it = repos.begin();
-            it != repos.end();
-            ++it )
+      if ( filesystem::readdir( entries, dir, false ) != 0 )
       {
-        // set the metadata path for the repo
-        Pathname metadata_path = rawcache_path_for_repoinfo(_pimpl->options, (*it));
-        (*it).setMetadataPath(metadata_path);
+        // 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));
       }
-      return repos;
     }
-    else
-      return std::list<RepoInfo>();
 
-    MIL << endl;
+    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;
 
-  Pathname RepoManager::metadataPath( const RepoInfo &info ) const
+      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()
   {
-    return rawcache_path_for_repoinfo(_pimpl->options, info );
+    MIL << "start construct known repos" << endl;
+
+    if ( PathInfo(_options.knownReposPath).isExist() )
+    {
+      std::list<std::string> repoEscAliases;
+      std::list<RepoInfo> orphanedRepos;
+      for ( RepoInfo & repoInfo : repositories_in_dir(_options.knownReposPath) )
+      {
+        // set the metadata path for the repo
+        repoInfo.setMetadataPath( rawcache_path_for_repoinfo(_options, repoInfo) );
+       // set the downloaded packages path for the repo
+       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());
+      }
+
+      // 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;
   }
 
-  ////////////////////////////////////////////////////////////////////////////
+  ///////////////////////////////////////////////////////////////////
 
-  RepoStatus RepoManager::metadataStatus( const RepoInfo &info ) const
+  RepoStatus RepoManager::Impl::metadataStatus( const RepoInfo & info ) const
   {
-    Pathname rawpath = rawcache_path_for_repoinfo( _pimpl->options, info );
-    RepoType repokind = info.type();
-    RepoStatus status;
+    Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
+    Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
 
-    switch ( repokind.toEnum() )
-    {
-      case RepoType::NONE_e:
-      // unknown, probe the local metadata
-        repokind = probe(rawpath.asUrl());
-      break;
-      default:
-      break;
-    }
+    RepoType repokind = info.type();
+    // 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 :
-      {
-        // the order of RepoStatus && RepoStatus matters! (#304310)
-        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
@@ -369,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;
@@ -385,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 :
@@ -405,38 +875,39 @@ namespace zypp
     filesystem::touch(p);
   }
 
-  bool 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)
+      if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
       {
         // difference in seconds
         double diff = difftime(
@@ -447,82 +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 false;
+         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.path()));
-        else
-          downloader_ptr.reset( new susetags::Downloader(info.path()));
+       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;
+       default:
+       case RepoType::NONE_e:
+         ZYPP_THROW( RepoUnknownTypeException( info ) );
+         break;
       }
-#if 0
-      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;
+      // 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;
+       }
       }
-#endif
       else
       {
-        ZYPP_THROW(RepoUnknownTypeException());
+       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 )
     {
@@ -531,18 +998,19 @@ namespace zypp
       ZYPP_RETHROW(e);
     }
 
-    return true; // default
+    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() ) );
 
     // try urls one by one
     for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
@@ -553,7 +1021,7 @@ namespace zypp
 
         // check whether to refresh metadata
         // if the check fails for this url, it throws, so another url will be checked
-        if (!checkIfToRefreshMetadata(info, url, policy))
+        if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
           return;
 
         MIL << "Going to refresh metadata from " << url << endl;
@@ -561,21 +1029,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);
-          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 ) )
@@ -583,10 +1073,12 @@ namespace zypp
           MediaSetAccess media(url);
           shared_ptr<repo::Downloader> downloader_ptr;
 
+          MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
+
           if ( repokind.toEnum() == RepoType::RPMMD_e )
-            downloader_ptr.reset(new yum::Downloader(info.path()));
+            downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
           else
-            downloader_ptr.reset( new susetags::Downloader(info.path()));
+            downloader_ptr.reset( new susetags::Downloader(info, mediarootpath) );
 
           /**
            * Given a downloader, sets the other repos raw metadata
@@ -594,42 +1086,34 @@ namespace zypp
            * repo has the same file, it will not download it
            * but copy it from the other repository
            */
-          std::list<RepoInfo> repos = knownRepositories();
-          for ( std::list<RepoInfo>::const_iterator it = repos.begin();
-                it != repos.end();
-                ++it )
+          for_( it, repoBegin(), repoEnd() )
           {
-            downloader_ptr->addCachePath(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() );
         }
-#if 0
         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" );
         }
-#endif
         else
         {
-          ZYPP_THROW(RepoUnknownTypeException());
+          ZYPP_THROW(RepoUnknownTypeException( info ));
         }
 
         // ok we have the metadata, now exchange
         // the contents
-        TmpDir oldmetadata( 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;
       }
@@ -651,42 +1135,54 @@ 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::buildCache( const RepoInfo &info,
-                                CacheBuildPolicy policy,
-                                const ProgressData::ReceiverFnc & progressrcv )
+
+  void RepoManager::Impl::cleanPackages( const RepoInfo & info, const ProgressData::ReceiverFnc & progressfnc )
   {
-    assert_alias(info);
-    Pathname rawpath = rawcache_path_for_repoinfo(_pimpl->options, info);
+    ProgressData progress(100);
+    progress.sendTo(progressfnc);
 
-    Pathname base = _pimpl->options.repoCachePath + info.alias();
-    Pathname solvfile = base.extend(".solv");
+    filesystem::recursive_rmdir(packagescache_path_for_repoinfo(_options, info));
+    progress.toMax();
+  }
 
-    //cache::SolvStore store(_pimpl->options.repoCachePath);
 
+  void RepoManager::Impl::buildCache( const RepoInfo & info, CacheBuildPolicy policy, const ProgressData::ReceiverFnc & progressrcv )
+  {
+    assert_alias(info);
+    Pathname mediarootpath = rawcache_path_for_repoinfo( _options, info );
+    Pathname productdatapath = rawproductdata_path_for_repoinfo( _options, info );
+
+    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() )
     {
-      ZYPP_THROW(RepoMetadataException(info));
+       /* if there is no cache at this point, we refresh the raw
+          in case this is the first time - if it's !autorefresh,
+          we may still refresh */
+      refreshMetadata(info, RefreshIfNeeded, progressrcv );
+      raw_metadata_status = metadataStatus(info);
     }
 
     bool needs_cleaning = false;
     if ( isCached( info ) )
     {
       MIL << info.alias() << " is already cached." << endl;
-      //data::RecordId id = store.lookupRepository(info.alias());
       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 ) {
@@ -703,23 +1199,31 @@ 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)
     {
-//       Pathname name = _pimpl->options.repoCachePath;
-//       //data::RecordId id = store.lookupRepository(info.alias());
-//       ostringstream os;
-//       os << id.get();
-//       name += os.str() + ".solv";
-//       unlink (name);
-//       cleanCacheInternal( store, info);
       cleanCache(info);
     }
 
-    MIL << info.alias() << " building cache..." << endl;
-    //data::RecordId id = store.lookupOrAppendRepository(info.alias());
+    MIL << info.alias() << " building cache..." << info.type() << endl;
+
+    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?
     repo::RepoType repokind = info.type();
 
@@ -728,325 +1232,292 @@ namespace zypp
     {
       case RepoType::NONE_e:
         // unknown, probe the local metadata
-        repokind = probe(rawpath.asUrl());
+        repokind = probe( productdatapath.asUrl() );
       break;
       default:
       break;
     }
 
     MIL << "repo type is " << repokind << endl;
-    
+
     switch ( repokind.toEnum() )
     {
       case RepoType::RPMMD_e :
       case RepoType::YAST2_e :
+      case RepoType::RPMPLAINDIR_e :
       {
-//         string cmd = "repo2solv.sh \"";
-//     cmd += rawpath.asString() + "\" > " + solvfile.asString();
-//     int ret = system (cmd.c_str());
-//         if (WIFEXITED (ret) && WEXITSTATUS (ret) != 0)
-//       ZYPP_THROW(RepoUnknownTypeException());
-        MIL << "Executing solv converter" << endl;
-        string cmd( str::form( "/usr/bin/repo2solv.sh \"%s\" > %s", rawpath.asString().c_str(), solvfile.asString().c_str() ) );
+        // Take care we unlink the solvfile on exception
+        ManagedFile guard( solvfile, filesystem::unlink );
+        scoped_ptr<MediaMounter> forPlainDirs;
+
+        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.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
+        }
+        else
+          cmd.push_back( productdatapath.asString() );
+
         ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
-        for ( string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
-          MIL << "  " << output;
+        std::string errdetail;
+
+        for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
+          WAR << "  " << 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( errdetail );
+          ZYPP_THROW(ex);
+        }
+
+        // We keep it.
+        guard.resetDispose();
       }
       break;
       default:
-        ZYPP_THROW(Exception("Unhandled repostory type"));
+        ZYPP_THROW(RepoUnknownTypeException( info, _("Unhandled repository type") ));
       break;
     }
-#if 0
-    switch ( repokind.toEnum() )
-    {
-      case RepoType::RPMMD_e :
-      if (0)
-      {
-        CombinedProgressData subprogrcv( progress, 100);
-        parser::yum::RepoParser parser(id, store, parser::yum::RepoParserOpts(), subprogrcv);
-        parser.parse(rawpath);
-          // no error
-      }
-      break;
-      case RepoType::YAST2_e :
-      if (0)
-      {
-        CombinedProgressData subprogrcv( progress, 100);
-        parser::susetags::RepoParser parser(id, store, subprogrcv);
-        parser.parse(rawpath);
-        // no error
-      }
-      break;
-#endif
-#if 0
-      case RepoType::RPMPLAINDIR_e :
-      {
-        CombinedProgressData subprogrcv( progress, 100);
-        InputStream is(rawpath + "cookie");
-        string buffer;
-        getline( is.stream(), buffer);
-        Url url(buffer);
-        parser::plaindir::RepoParser parser(id, store, subprogrcv);
-        parser.parse(url.getPathName());
-      }
-      break;
-
-      default:
-        ZYPP_THROW(RepoUnknownTypeException());
-    }
-#endif
     // update timestamp and checksum
-    //store.updateRepositoryStatus(id, raw_metadata_status);
-    setCacheStatus(info.alias(), raw_metadata_status);
+    setCacheStatus(info, raw_metadata_status);
     MIL << "Commit cache.." << endl;
-    //store.commit();
-    //progress.toMax();
+    progress.toMax();
   }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  repo::RepoType RepoManager::probe( const Url &url ) const
+  repo::RepoType RepoManager::Impl::probe( const Url & url, const Pathname & path  ) const
   {
-    if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName() ).isDir() )
+    MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
+
+    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 )
   {
-    Pathname name = _pimpl->options.repoCachePath;
-    name += info.alias() + ".solv";
-    unlink (name);
-  }
+    MIL << "Going to clean up garbage in cache dirs" << endl;
 
-  ////////////////////////////////////////////////////////////////////////////
+    ProgressData progress(300);
+    progress.sendTo(progressrcv);
+    progress.toMin();
 
-  bool RepoManager::isCached( const RepoInfo &info ) const
-  {
-    Pathname name = _pimpl->options.repoCachePath;
-    return PathInfo(name + Pathname(info.alias()).extend(".solv")).isExist();
-  }
+    std::list<Pathname> cachedirs;
+    cachedirs.push_back(_options.repoRawCachePath);
+    cachedirs.push_back(_options.repoPackagesCachePath);
+    cachedirs.push_back(_options.repoSolvCachePath);
 
-  RepoStatus RepoManager::cacheStatus( const RepoInfo &info ) const
-  {
-    RepoStatus status;
-    Pathname base = _pimpl->options.repoCachePath + info.alias();
-    Pathname solvfile = base.extend(".solv");
-    Pathname cookiefile = base.extend(".cookie");
+    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; }
 
-    std::ifstream file(cookiefile.c_str());
-    if (!file) {
-      ZYPP_THROW (Exception( "Can't open " + cookiefile.asString() ) );
-    }
+          if ( ! found && ( Date::now()-PathInfo(*subdir).mtime() > Date::day ) )
+            filesystem::recursive_rmdir( *subdir );
 
-    std::string buffer;
-    while(file && !file.eof()) {
-      getline(file, buffer);
+          progress.set( progress.val() + sdircurrent * 100 / sdircount );
+          ++sdircurrent;
+        }
+      }
+      else
+        progress.set( progress.val() + 100 );
     }
-
-    std::vector<std::string> words;
-    if ( str::split( buffer, std::back_inserter(words) ) != 2 )
-      ZYPP_THROW (Exception( "corrupt file " + cookiefile.asString() ) );
-
-    status.setTimestamp(Date(str::strtonum<time_t>(words[1])));
-    status.setChecksum(words[0]);
-    return status;
+    progress.toMax();
   }
 
-  void RepoManager::setCacheStatus( const string &alias, const RepoStatus &status )
+  ////////////////////////////////////////////////////////////////////////////
+
+  void RepoManager::Impl::cleanCache( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
   {
-    Pathname base = _pimpl->options.repoCachePath + alias;
-    Pathname cookiefile = base.extend(".cookie");
+    ProgressData progress(100);
+    progress.sendTo(progressrcv);
+    progress.toMin();
 
-    std::ofstream file(cookiefile.c_str());
-    if (!file) {
-      ZYPP_THROW (Exception( "Can't open " + cookiefile.asString() ) );
-    }
-    file << status;
-    file.close();
+    MIL << "Removing raw metadata cache for " << info.alias() << endl;
+    filesystem::recursive_rmdir(solv_path_for_repoinfo(_options, info));
+
+    progress.toMax();
   }
 
-  map<data::RecordId, Repo *> repo2solv;
+  ////////////////////////////////////////////////////////////////////////////
 
-  Repository RepoManager::createFromCache( const RepoInfo &info,
-                                           const ProgressData::ReceiverFnc & progressrcv )
+  void RepoManager::Impl::loadFromCache( const RepoInfo & info, const ProgressData::ReceiverFnc & progressrcv )
   {
-    callback::SendReport<ProgressReport> report;
-    ProgressData progress;
-    progress.sendTo(ProgressReportAdaptor( progressrcv, report ));
-    //progress.sendTo( progressrcv );
-    progress.name(str::form(_("Reading repository '%s' cache"), info.name().c_str()));
-
-    //_pimpl->options.repoCachePath
-    if ( ! isCached( info ) )
-      ZYPP_THROW(RepoNotCachedException());
-
-    MIL << "Repository " << info.alias() << " is cached" << endl;
+    assert_alias(info);
+    Pathname solvfile = solv_path_for_repoinfo(_options, info) / "solv";
 
-    sat::Pool satpool( sat::Pool::instance() );
+    if ( ! PathInfo(solvfile).isExist() )
+      ZYPP_THROW(RepoNotCachedException(info));
 
-    Pathname solvfile = (_pimpl->options.repoCachePath + info.alias()).extend(".solv");
-      
+    sat::Pool::instance().reposErase( info.alias() );
     try
     {
-      satpool.addRepoSolv(solvfile, info.alias());
+      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 &e )
+    catch ( const Exception & exp )
     {
-      ZYPP_RETHROW(e);
-    }
+      ZYPP_CAUGHT( exp );
+      MIL << "Try to handle exception by rebuilding the solv-file" << endl;
+      cleanCache( info, progressrcv );
+      buildCache( info, BuildIfNeeded, progressrcv );
 
-    CombinedProgressData subprogrcv(progress);
-
-//     repo::cached::RepoOptions opts( info, _pimpl->options.repoCachePath, id );
-//     opts.readingResolvablesProgress = subprogrcv;
-//     //opts.repo = repo;
-//     repo::cached::RepoImpl::Ptr repoimpl =
-//         new repo::cached::RepoImpl( opts );
-//
-//     repoimpl->resolvables();
-    // read the resolvables from cache
-    return Repository::noRepository;
-  }
-
-  ////////////////////////////////////////////////////////////////////////////
-
-  /**
-   * Generate a non existing filename in a directory, using a base
-   * name. For example if a directory contains 3 files
-   *
-   * |-- bar
-   * |-- foo
-   * `-- moo
-   *
-   * If you try to generate a unique filename for this directory,
-   * based on "ruu" you will get "ruu", but if you use the base
-   * "foo" you will get "foo_1"
-   *
-   * \param dir Directory where the file needs to be unique
-   * \param basefilename string to base the filename on.
-   */
-  static Pathname generate_non_existing_name( const Pathname &dir,
-                                              const std::string &basefilename )
-  {
-    string final_filename = basefilename;
-    int counter = 1;
-    while ( PathInfo(dir + final_filename).isExist() )
-    {
-      final_filename = basefilename + "_" + str::numstring(counter);
-      counter++;
+      sat::Pool::instance().addRepoSolv( solvfile, info );
     }
-    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
-   */
-  static std::string generate_filename( const RepoInfo &info )
-  {
-    std::string fnd="/";
-    std::string rep="_";
-    std::string filename = info.alias();
-    // replace slashes with underscores
-    size_t pos = filename.find(fnd);
-    while(pos!=string::npos)
-    {
-      filename.replace(pos,fnd.length(),rep);
-      pos = filename.find(fnd,pos+rep.length());
-    }
-    filename = Pathname(filename).extend(".repo").asString();
-    MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
-    return filename;
-  }
-
-
-  ////////////////////////////////////////////////////////////////////////////
-
-  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();
 
-    std::list<RepoInfo> repos = knownRepositories();
-    for ( std::list<RepoInfo>::const_iterator it = repos.begin();
-          it != repos.end();
-          ++it )
-    {
-      if ( info.alias() == (*it).alias() )
-        ZYPP_THROW(RepoAlreadyExistsException(info.alias()));
-    }
+    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 ( ZConfig::instance().repo_add_probe()
-        || ( tosave.type() == RepoType::NONE && tosave.enabled()) )
+    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);
       }
@@ -1055,88 +1526,129 @@ namespace zypp
     progress.set(50);
 
     // assert the directory exists
-    filesystem::assert_dir(_pimpl->options.knownReposPath);
+    filesystem::assert_dir(_options.knownReposPath);
 
-    Pathname repofile = generate_non_existing_name(_pimpl->options.knownReposPath,
-                                                    generate_filename(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);
+    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);
+
+    // check for credentials in Urls
+    bool havePasswords = false;
+    for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
+      if ( urlit->hasCredentialsInAuthority() )
+      {
+        havePasswords = true;
+        break;
+      }
+    // save the credentials
+    if ( havePasswords )
+    {
+      media::CredentialManager cm(
+          media::CredManagerOptions(_options.rootDir) );
+
+      for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
+        if (urlit->hasCredentialsInAuthority())
+          //! \todo use a method calling UI callbacks to ask where to save creds?
+          cm.saveInUser(media::AuthData(*urlit));
     }
 
-    tosave.dumpRepoOn(file);
+    HistoryLog().addRepository(tosave);
+
     progress.toMax();
     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> knownrepos = knownRepositories();
     std::list<RepoInfo> repos = readRepoFile(url);
     for ( std::list<RepoInfo>::const_iterator it = repos.begin();
           it != repos.end();
           ++it )
     {
       // look if the alias is in the known repos.
-      for ( std::list<RepoInfo>::const_iterator kit = knownrepos.begin();
-          kit != knownrepos.end();
-          ++kit )
+      for_ ( kit, repoBegin(), repoEnd() )
       {
         if ( (*it).alias() == (*kit).alias() )
         {
           ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
-          ZYPP_THROW(RepoAlreadyExistsException((*it).alias()));
+          ZYPP_THROW(RepoAlreadyExistsException(*it));
         }
       }
     }
 
-    string filename = Pathname(url.getPathName()).basename();
+    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 = generate_non_existing_name(_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>::const_iterator it = repos.begin();
+    for ( std::list<RepoInfo>::iterator it = repos.begin();
           it != repos.end();
           ++it )
     {
       MIL << "Saving " << (*it).alias() << endl;
-      (*it).dumpRepoOn(file);
+      it->setFilepath(repofile.asString());
+      it->dumpAsIniOn(file);
+      reposManip().insert(*it);
+
+      HistoryLog(_options.rootDir).addRepository(*it);
     }
+
     MIL << "done" << endl;
   }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  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;
 
-    std::list<RepoInfo> repos = knownRepositories();
-    for ( std::list<RepoInfo>::const_iterator it = repos.begin();
-          it != repos.end();
-          ++it )
+    for_( it, repoBegin(), repoEnd() )
     {
       // they can be the same only if the provided is empty, that means
       // the provided repo has no alias
@@ -1151,7 +1663,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
       {
@@ -1162,9 +1674,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
         {
@@ -1177,27 +1690,32 @@ 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();
                 ++fit )
           {
             if ( (*fit).alias() != todelete.alias() )
-              (*fit).dumpRepoOn(file);
+              (*fit).dumpAsIniOn(file);
           }
         }
 
-        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);
-        MIL << todelete.alias() << " sucessfully deleted." << endl;
+        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
 
@@ -1208,15 +1726,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() && hasRepo( newinfo.alias() ) )
+    {
+      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
     {
@@ -1232,9 +1755,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();
@@ -1243,63 +1767,705 @@ namespace zypp
           // if the alias is different, dump the original
           // if it is the same, dump the provided one
           if ( (*fit).alias() != toedit.alias() )
-            (*fit).dumpRepoOn(file);
+            (*fit).dumpAsIniOn(file);
           else
-            newinfo.dumpRepoOn(file);
+            newinfo.dumpAsIniOn(file);
       }
+
+      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 )
   {
-    std::list<RepoInfo> repos = knownRepositories();
-    for ( std::list<RepoInfo>::const_iterator it = repos.begin();
-          it != repos.end();
-          ++it )
+    RepoConstIterator it( findAlias( alias, repos() ) );
+    if ( it != repos().end() )
+      return *it;
+    RepoInfo info;
+    info.setAlias( alias );
+    ZYPP_THROW( RepoNotFoundException(info) );
+  }
+
+
+  RepoInfo RepoManager::Impl::getRepositoryInfo( const Url & url, const url::ViewOption & urlview, const ProgressData::ReceiverFnc & progressrcv )
+  {
+    for_( it, repoBegin(), repoEnd() )
     {
-      if ( (*it).alias() == alias )
-        return *it;
+      for_( urlit, (*it).baseUrlsBegin(), (*it).baseUrlsEnd() )
+      {
+        if ( (*urlit).asString(urlview) == url.asString(urlview) )
+         return *it;
+      }
     }
     RepoInfo info;
-    info.setAlias(info.alias());
-    ZYPP_THROW(RepoNotFoundException(info));
+    info.setBaseUrl( url );
+    ZYPP_THROW( RepoNotFoundException(info) );
   }
 
   ////////////////////////////////////////////////////////////////////////////
+  //
+  // Services
+  //
+  ////////////////////////////////////////////////////////////////////////////
 
-  RepoInfo RepoManager::getRepositoryInfo( const Url & url,
-                                           const url::ViewOption & urlview,
-                                           const ProgressData::ReceiverFnc & progressrcv )
+  void RepoManager::Impl::addService( const ServiceInfo & service )
   {
-    std::list<RepoInfo> repos = knownRepositories();
-    for ( std::list<RepoInfo>::const_iterator it = repos.begin();
-          it != repos.end();
-          ++it )
+    assert_alias( service );
+
+    // check if service already exists
+    if ( hasService( service.alias() ) )
+      ZYPP_THROW( ServiceAlreadyExistsException( service ) );
+
+    // Writable ServiceInfo is needed to save the location
+    // of the .service file. Finaly insert into the service list.
+    ServiceInfo toSave( service );
+    saveService( toSave );
+    _services.insert( toSave );
+
+    // check for credentials in Url (username:password, not ?credentials param)
+    if ( toSave.url().hasCredentialsInAuthority() )
+    {
+      media::CredentialManager cm(
+          media::CredManagerOptions(_options.rootDir) );
+
+      //! \todo use a method calling UI callbacks to ask where to save creds?
+      cm.saveInUser(media::AuthData(toSave.url()));
+    }
+
+    MIL << "added service " << toSave.alias() << endl;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  void RepoManager::Impl::removeService( const std::string & alias )
+  {
+    MIL << "Going to delete service " << alias << endl;
+
+    const ServiceInfo & service = getService( alias );
+
+    Pathname location = service.filepath();
+    if( location.empty() )
     {
-      for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
-          urlit != (*it).baseUrlsEnd();
-          ++urlit)
+      ZYPP_THROW(ServiceException( service, _("Can't figure out where the service is stored.") ));
+    }
+
+    ServiceSet tmpSet;
+    parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
+
+    // only one service definition in the file
+    if ( tmpSet.size() == 1 )
+    {
+      if ( filesystem::unlink(location) != 0 )
       {
-        if ((*urlit).asString(urlview) == url.asString(urlview))
-          return *it;
+        // TranslatorExplanation '%s' is a filename
+        ZYPP_THROW(ServiceException( service, str::form( _("Can't delete '%s'"), location.c_str() ) ));
       }
+      MIL << alias << " successfully deleted." << endl;
     }
-    RepoInfo info;
-    info.setAlias(info.alias());
-    info.setBaseUrl(url);
-    ZYPP_THROW(RepoNotFoundException(info));
+    else
+    {
+      filesystem::assert_dir(location.dirname());
+
+      std::ofstream file(location.c_str());
+      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())
+      {
+        if( it->alias() != alias )
+          it->dumpAsIniOn(file);
+      }
+
+      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 ) ) );
+    // cannot do this directly in getRepositoriesInService - would invalidate iterators
+    for_(rit, rcollector.repos.begin(), rcollector.repos.end())
+      removeRepository(*rit);
   }
 
   ////////////////////////////////////////////////////////////////////////////
 
-  std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
+  void RepoManager::Impl::refreshServices( const RefreshServiceOptions & options_r )
+  {
+    // copy the set of services since refreshService
+    // can eventually invalidate the iterator
+    ServiceSet services( serviceBegin(), serviceEnd() );
+    for_( it, services.begin(), services.end() )
+    {
+      if ( !it->enabled() )
+        continue;
+
+      try {
+       refreshService(*it, options_r);
+      }
+      catch ( const repo::ServicePluginInformalException & e )
+      { ;/* ignore ServicePluginInformalException */ }
+    }
+  }
+
+  void RepoManager::Impl::refreshService( const std::string & alias, const RefreshServiceOptions & options_r )
+  {
+    ServiceInfo service( getService( alias ) );
+    assert_alias( service );
+    assert_url( service );
+    // NOTE: It might be necessary to modify and rewrite the service info.
+    // Either when probing the type, or when adjusting the repositories
+    // enable/disable state.:
+    bool serviceModified = false;
+    MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << ", opts: " << options_r << endl;
+
+    //! \todo add callbacks for apps (start, end, repo removed, repo added, repo changed)
+
+    // if the type is unknown, try probing.
+    if ( service.type() == repo::ServiceType::NONE )
+    {
+      repo::ServiceType type = probeService( service.url() );
+      if ( type != ServiceType::NONE )
+      {
+        service.setProbedType( type ); // lazy init!
+        serviceModified = true;
+      }
+    }
+
+    // get target distro identifier
+    std::string servicesTargetDistro = _options.servicesTargetDistro;
+    if ( servicesTargetDistro.empty() )
+    {
+      servicesTargetDistro = Target::targetDistribution( Pathname() );
+    }
+    DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
+
+    // parse it
+    RepoCollector collector(servicesTargetDistro);
+    // FIXME Ugly hack: ServiceRepos may throw ServicePluginInformalException
+    // which is actually a notification. Using an exception for this
+    // instead of signal/callback is bad. Needs to be fixed here, in refreshServices()
+    // and in zypper.
+    std::pair<DefaultIntegral<bool,false>, repo::ServicePluginInformalException> uglyHack;
+    try {
+      ServiceRepos repos(service, bind( &RepoCollector::collect, &collector, _1 ));
+    }
+    catch ( const repo::ServicePluginInformalException & e )
+    {
+      /* ignore ServicePluginInformalException and throw later */
+      uglyHack.first = true;
+      uglyHack.second = e;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // On the fly remember the new repo states as defined the reopoindex.xml.
+    // Move into ServiceInfo later.
+    ServiceInfo::RepoStates newRepoStates;
+
+    // set service alias and base url for all collected repositories
+    for_( it, collector.repos.begin(), collector.repos.end() )
+    {
+      // First of all: Prepend service alias:
+      it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
+      // set refrence to the parent service
+      it->setService( service.alias() );
+
+      // remember the new parsed repo state
+      newRepoStates[it->alias()] = *it;
+
+      // if the repo url was not set by the repoindex parser, set service's url
+      Url url;
+      if ( it->baseUrlsEmpty() )
+        url = service.url();
+      else
+      {
+        // service repo can contain only one URL now, so no need to iterate.
+        url = *it->baseUrlsBegin();
+      }
+
+      // libzypp currently has problem with separate url + path handling
+      // so just append the path to the baseurl
+      if ( !it->path().empty() )
+      {
+        Pathname path(url.getPathName());
+        path /= it->path();
+        url.setPathName( path.asString() );
+        it->setPath("");
+      }
+
+      // save the url
+      it->setBaseUrl( url );
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Now compare collected repos with the ones in the system...
+    //
+    RepoInfoList oldRepos;
+    getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
+
+    ////////////////////////////////////////////////////////////////////////////
+    // find old repositories to remove...
+    for_( oldRepo, oldRepos.begin(), oldRepos.end() )
+    {
+      if ( ! foundAliasIn( oldRepo->alias(), collector.repos ) )
+      {
+       if ( oldRepo->enabled() )
+       {
+         // Currently enabled. If this was a user modification remember the state.
+         const auto & last = service.repoStates().find( oldRepo->alias() );
+         if ( last != service.repoStates().end() && ! last->second.enabled )
+         {
+           DBG << "Service removes user enabled repo " << oldRepo->alias() << endl;
+           service.addRepoToEnable( oldRepo->alias() );
+           serviceModified = true;
+         }
+         else
+           DBG << "Service removes enabled repo " << oldRepo->alias() << endl;
+       }
+       else
+         DBG << "Service removes disabled repo " << oldRepo->alias() << endl;
+
+        removeRepository( *oldRepo );
+      }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // create missing repositories and modify exising ones if needed...
+    for_( it, collector.repos.begin(), collector.repos.end() )
+    {
+      // User explicitly requested the repo being enabled?
+      // User explicitly requested the repo being disabled?
+      // And hopefully not both ;) If so, enable wins.
+
+      TriBool toBeEnabled( indeterminate );    // indeterminate - follow the service request
+      DBG << "Service request to " << (it->enabled()?"enable":"disable") << " service repo " << it->alias() << endl;
+
+      if ( options_r.testFlag( RefreshService_restoreStatus ) )
+      {
+       DBG << "Opt RefreshService_restoreStatus " << it->alias() << endl;
+       // this overrides any pending request!
+       // Remove from enable request list.
+       // NOTE: repoToDisable is handled differently.
+       //       It gets cleared on each refresh.
+       service.delRepoToEnable( it->alias() );
+       // toBeEnabled stays indeterminate!
+      }
+      else
+      {
+       if ( service.repoToEnableFind( it->alias() ) )
+       {
+         DBG << "User request to enable service repo " << it->alias() << endl;
+         toBeEnabled = true;
+         // Remove from enable request list.
+         // NOTE: repoToDisable is handled differently.
+         //       It gets cleared on each refresh.
+         service.delRepoToEnable( it->alias() );
+         serviceModified = true;
+       }
+       else if ( service.repoToDisableFind( it->alias() ) )
+       {
+         DBG << "User request to disable service repo " << it->alias() << endl;
+         toBeEnabled = false;
+       }
+      }
+
+      RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
+      if ( oldRepo == oldRepos.end() )
+      {
+        // Not found in oldRepos ==> a new repo to add
+
+       // Make sure the service repo is created with the appropriate enablement
+       if ( ! indeterminate(toBeEnabled) )
+         it->setEnabled( toBeEnabled );
+
+        DBG << "Service adds repo " << it->alias() << " " << (it->enabled()?"enabled":"disabled") << endl;
+        addRepository( *it );
+      }
+      else
+      {
+        // ==> an exising repo to check
+        bool oldRepoModified = false;
+
+       if ( indeterminate(toBeEnabled) )
+       {
+         // No user request: check for an old user modificaton otherwise follow service request.
+         // NOTE: Assert toBeEnabled is boolean afterwards!
+         if ( oldRepo->enabled() == it->enabled() )
+           toBeEnabled = it->enabled();        // service requests no change to the system
+         else if (options_r.testFlag( RefreshService_restoreStatus ) )
+         {
+           toBeEnabled = it->enabled();        // RefreshService_restoreStatus forced
+           DBG << "Opt RefreshService_restoreStatus " << it->alias() <<  " forces " << (toBeEnabled?"enabled":"disabled") << endl;
+         }
+         else
+         {
+           const auto & last = service.repoStates().find( oldRepo->alias() );
+           if ( last == service.repoStates().end() || last->second.enabled != it->enabled() )
+             toBeEnabled = it->enabled();      // service request has changed since last refresh -> follow
+           else
+           {
+             toBeEnabled = oldRepo->enabled(); // service request unchaned since last refresh -> keep user modification
+             DBG << "User modified service repo " << it->alias() <<  " may stay " << (toBeEnabled?"enabled":"disabled") << endl;
+           }
+         }
+       }
+
+        // changed enable?
+       if ( toBeEnabled == oldRepo->enabled() )
+       {
+         DBG << "Service repo " << it->alias() << " stays " <<  (oldRepo->enabled()?"enabled":"disabled") << endl;
+       }
+       else if ( toBeEnabled )
+       {
+         DBG << "Service repo " << it->alias() << " gets enabled" << endl;
+         oldRepo->setEnabled( true );
+         oldRepoModified = true;
+       }
+       else
+        {
+         DBG << "Service repo " << it->alias() << " gets disabled" << endl;
+         oldRepo->setEnabled( false );
+         oldRepoModified = true;
+       }
+
+       // all other attributes follow the service request:
+
+       // changed autorefresh
+       if ( oldRepo->autorefresh() != it->autorefresh() )
+       {
+         DBG << "Service repo " << it->alias() << " gets new AUTOREFRESH " << it->autorefresh() << endl;
+         oldRepo->setAutorefresh( it->autorefresh() );
+         oldRepoModified = true;
+       }
+
+       // changed priority?
+       if ( oldRepo->priority() != it->priority() )
+       {
+         DBG << "Service repo " << it->alias() << " gets new PRIORITY " << it->priority() << endl;
+         oldRepo->setPriority( it->priority() );
+         oldRepoModified = true;
+       }
+
+        // changed url?
+        // service repo can contain only one URL now, so no need to iterate.
+        if ( oldRepo->url() != it->url() )
+        {
+          DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
+          oldRepo->setBaseUrl( it->url() );
+          oldRepoModified = true;
+        }
+
+        // save if modified:
+        if ( oldRepoModified )
+        {
+          modifyRepository( oldRepo->alias(), *oldRepo );
+        }
+      }
+    }
+
+    // Unlike reposToEnable, reposToDisable is always cleared after refresh.
+    if ( ! service.reposToDisableEmpty() )
+    {
+      service.clearReposToDisable();
+      serviceModified = true;
+    }
+
+    // Remember original service request for next refresh
+    if ( service.repoStates() != newRepoStates )
+    {
+      service.setRepoStates( std::move(newRepoStates) );
+      serviceModified = true;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // save service if modified: (unless a plugin service)
+    if ( serviceModified && service.type() != ServiceType::PLUGIN )
+    {
+      // write out modified service file.
+      modifyService( service.alias(), service );
+    }
+
+    if ( uglyHack.first )
+    {
+      throw( uglyHack.second ); // intentionally not ZYPP_THROW
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  void RepoManager::Impl::modifyService( const std::string & oldAlias, const ServiceInfo & newService )
+  {
+    MIL << "Going to modify service " << oldAlias << endl;
+
+    // we need a writable copy to link it to the file where
+    // it is saved if we modify it
+    ServiceInfo service(newService);
+
+    if ( service.type() == ServiceType::PLUGIN )
+    {
+      ZYPP_THROW(ServicePluginImmutableException( service ));
+    }
+
+    const ServiceInfo & oldService = getService(oldAlias);
+
+    Pathname location = oldService.filepath();
+    if( location.empty() )
+    {
+      ZYPP_THROW(ServiceException( oldService, _("Can't figure out where the service is stored.") ));
+    }
+
+    // remember: there may multiple services being defined in one file:
+    ServiceSet tmpSet;
+    parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
+
+    filesystem::assert_dir(location.dirname());
+    std::ofstream file(location.c_str());
+    for_(it, tmpSet.begin(), tmpSet.end())
+    {
+      if( *it != oldAlias )
+        it->dumpAsIniOn(file);
+    }
+    service.dumpAsIniOn(file);
+    file.close();
+    service.setFilepath(location);
+
+    _services.erase(oldAlias);
+    _services.insert(service);
+
+    // changed properties affecting also repositories
+    if ( oldAlias != service.alias()                   // changed alias
+      || oldService.enabled() != service.enabled() )   // changed enabled status
+    {
+      std::vector<RepoInfo> toModify;
+      getRepositoriesInService(oldAlias, std::back_inserter(toModify));
+      for_( it, toModify.begin(), toModify.end() )
+      {
+       if ( oldService.enabled() != service.enabled() )
+       {
+         if ( service.enabled() )
+         {
+           // reset to last refreshs state
+           const auto & last = service.repoStates().find( it->alias() );
+           if ( last != service.repoStates().end() )
+             it->setEnabled( last->second.enabled );
+         }
+         else
+           it->setEnabled( false );
+       }
+
+        if ( oldAlias != service.alias() )
+          it->setService(service.alias());
+
+        modifyRepository(it->alias(), *it);
+      }
+    }
+
+    //! \todo refresh the service automatically if url is changed?
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  repo::ServiceType RepoManager::Impl::probeService( const Url & url ) const
+  {
+    try
+    {
+      MediaSetAccess access(url);
+      if ( access.doesFileExist("/repo/repoindex.xml") )
+        return repo::ServiceType::RIS;
+    }
+    catch ( const media::MediaException &e )
+    {
+      ZYPP_CAUGHT(e);
+      // TranslatorExplanation '%s' is an URL
+      RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
+      enew.remember(e);
+      ZYPP_THROW(enew);
+    }
+    catch ( const Exception &e )
+    {
+      ZYPP_CAUGHT(e);
+      // TranslatorExplanation '%s' is an URL
+      Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
+      enew.remember(e);
+      ZYPP_THROW(enew);
+    }
+
+    return repo::ServiceType::NONE;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  //
+  //   CLASS NAME : RepoManager
+  //
+  ///////////////////////////////////////////////////////////////////
+
+  RepoManager::RepoManager( const RepoManagerOptions & opt )
+  : _pimpl( new Impl(opt) )
+  {}
+
+  RepoManager::~RepoManager()
+  {}
+
+  bool RepoManager::repoEmpty() const
+  { return _pimpl->repoEmpty(); }
+
+  RepoManager::RepoSizeType RepoManager::repoSize() const
+  { return _pimpl->repoSize(); }
+
+  RepoManager::RepoConstIterator RepoManager::repoBegin() const
+  { return _pimpl->repoBegin(); }
+
+  RepoManager::RepoConstIterator RepoManager::repoEnd() const
+  { return _pimpl->repoEnd(); }
+
+  RepoInfo RepoManager::getRepo( const std::string & alias ) const
+  { return _pimpl->getRepo( alias ); }
+
+  bool RepoManager::hasRepo( const std::string & alias ) const
+  { return _pimpl->hasRepo( alias ); }
+
+  std::string RepoManager::makeStupidAlias( const Url & url_r )
   {
-    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
 ///////////////////////////////////////////////////////////////////