Repository: conan-io/conan
Pull request #3246 (2018-07-20 15:26:20)

Prepare for revisions
- ConanFileReferences from conan_api (that can receive str or reference) to rest_client.
- Renamed remote manager method `get_ref`
- Prepared rest client to implement V2
- Refactor in "client api" (remote_manager, auth_manager, proxy) to implement "get_recipe" and "get_package" (instead of get_recipe_urls and get_package_urls etc) being the rest_client the one filtering the files. This is to get prepare to V2 where the files are downloaded directly without requesting URLs.
- Solved a couple of bugs detected in `uploader_downloader.py`


Patch:
diff --git a/conans/client/cmd/copy.py b/conans/client/cmd/copy.py
index 69660d81e..becb37d23 100644
--- a/conans/client/cmd/copy.py
+++ b/conans/client/cmd/copy.py
@@ -32,11 +32,9 @@ def cmd_copy(reference, user_channel, package_ids, client_cache, user_io, remote
     """
     param package_ids: Falsey=do not copy binaries. True=All existing. []=list of ids
     """
-    src_ref = ConanFileReference.loads(reference)
-
-    short_paths = _prepare_sources(client_cache, src_ref, remote_manager, registry)
-    package_ids = _get_package_ids(client_cache, src_ref, package_ids)
-    package_copy(src_ref, user_channel, package_ids, client_cache, user_io,
+    short_paths = _prepare_sources(client_cache, reference, remote_manager, registry)
+    package_ids = _get_package_ids(client_cache, reference, package_ids)
+    package_copy(reference, user_channel, package_ids, client_cache, user_io,
                  short_paths, force)
 
 
diff --git a/conans/client/cmd/uploader.py b/conans/client/cmd/uploader.py
index c5b08bb27..4f3725680 100644
--- a/conans/client/cmd/uploader.py
+++ b/conans/client/cmd/uploader.py
@@ -72,7 +72,7 @@ class CmdUpload(object):
                 integrity_check, no_overwrite, remote_name, recorder):
         """Uploads the recipes and binaries identified by conan_ref"""
 
-        defined_remote = self._registry.get_ref(conan_ref)
+        defined_remote = self._registry.get_recipe_remote(conan_ref)
         if remote_name:  # If remote_name is given, use it
             upload_remote = self._registry.remote(remote_name)
         elif defined_remote:  # Else, if the package had defined a remote, use it
@@ -108,7 +108,7 @@ class CmdUpload(object):
 
     def _upload_recipe(self, conan_reference, retry, retry_wait, skip_upload, no_overwrite, remote):
         conan_file_path = self._client_cache.conanfile(conan_reference)
-        current_remote = self._registry.get_ref(conan_reference)
+        current_remote = self._registry.get_recipe_remote(conan_reference)
         if remote != current_remote:
             conanfile = load_conanfile_class(conan_file_path)
             complete_recipe_sources(self._remote_manager, self._client_cache, self._registry,
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
index dd2e3844b..021737bd0 100644
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -657,6 +657,7 @@ class ConanAPIV1(object):
         """
         from conans.client.cmd.copy import cmd_copy
         # FIXME: conan copy does not support short-paths in Windows
+        reference = ConanFileReference.loads(str(reference))
         cmd_copy(reference, user_channel, packages, self._client_cache,
                  self._user_io, self._remote_manager, self._registry, force=force)
 
@@ -710,6 +711,7 @@ class ConanAPIV1(object):
         search = Search(self._client_cache, self._remote_manager, self._registry)
 
         try:
+            reference = ConanFileReference.loads(str(reference))
             references = search.search_packages(reference, remote,
                                                 query=query,
                                                 outdated=outdated)
@@ -719,11 +721,11 @@ class ConanAPIV1(object):
             raise
 
         for remote, remote_ref in references.items():
-            recorder.add_recipe(str(remote), str(reference))
+            recorder.add_recipe(str(remote), reference)
             if remote_ref.ordered_packages:
                 for package_id, properties in remote_ref.ordered_packages.items():
                     package_recipe_hash = properties.get("recipe_hash", None)
-                    recorder.add_package(str(remote), str(reference), package_id,
+                    recorder.add_package(str(remote), reference, package_id,
                                          properties.get("options", []),
                                          properties.get("settings", []),
                                          properties.get("full_requires", []),
@@ -782,14 +784,17 @@ class ConanAPIV1(object):
 
     @api_method
     def remote_add_ref(self, reference, remote):
+        reference = ConanFileReference.loads(str(reference))
         return self._registry.add_ref(reference, remote)
 
     @api_method
     def remote_remove_ref(self, reference):
+        reference = ConanFileReference.loads(str(reference))
         return self._registry.remove_ref(reference)
 
     @api_method
     def remote_update_ref(self, reference, remote):
+        reference = ConanFileReference.loads(str(reference))
         return self._registry.update_ref(reference, remote)
 
     @api_method
@@ -833,8 +838,8 @@ class ConanAPIV1(object):
 
     @api_method
     def export_alias(self, reference, target_reference):
-        reference = ConanFileReference.loads(str(reference))
-        target_reference = ConanFileReference.loads(str(target_reference))
+        reference = ConanFileReference.loads(reference)
+        target_reference = ConanFileReference.loads(target_reference)
         return export_alias(reference, target_reference, self._client_cache)
 
     @api_method
diff --git a/conans/client/graph/graph_binaries.py b/conans/client/graph/graph_binaries.py
index 13638381b..f17519e20 100644
--- a/conans/client/graph/graph_binaries.py
+++ b/conans/client/graph/graph_binaries.py
@@ -80,7 +80,7 @@ class GraphBinariesAnalyzer(object):
         if remote_name:
             remote = self._registry.remote(remote_name)
         else:
-            remote = self._registry.get_ref(conan_ref)
+            remote = self._registry.get_recipe_remote(conan_ref)
         remotes = self._registry.remotes
 
         if os.path.exists(package_folder):
diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py
index fbe8d3cda..5d5ea032b 100644
--- a/conans/client/graph/graph_builder.py
+++ b/conans/client/graph/graph_builder.py
@@ -212,7 +212,7 @@ class DepsGraphBuilder(object):
                 self._output.error("Failed requirement '%s' from '%s'" % (requirement.conan_reference,
                                                                           base_ref))
                 raise e
-            conanfile_path, recipe_status, remote = result
+            conanfile_path, recipe_status, remote, _ = result
 
         output = ScopedOutput(str(requirement.conan_reference), self._output)
         dep_conanfile = self._loader.load_conan(conanfile_path, output,
diff --git a/conans/client/graph/proxy.py b/conans/client/graph/proxy.py
index edbb569c5..d0682dcef 100644
--- a/conans/client/graph/proxy.py
+++ b/conans/client/graph/proxy.py
@@ -35,26 +35,26 @@ class ConanProxy(object):
 
         # NOT in disk, must be retrieved from remotes
         if not os.path.exists(conanfile_path):
-            ref_remote = self._download_recipe(reference, output, remote_name)
+            remote, new_ref = self._download_recipe(reference, output, remote_name)
             status = RECIPE_DOWNLOADED
-            return conanfile_path, status, ref_remote
+            return conanfile_path, status, remote, new_ref
 
-        ref_remote = self._registry.get_ref(reference)
+        remote = self._registry.get_recipe_remote(reference)
         check_updates = check_updates or update
         # Recipe exists in disk, but no need to check updates
         if not check_updates:
             status = RECIPE_INCACHE
             log_recipe_got_from_local_cache(reference)
             self._recorder.recipe_fetched_from_cache(reference)
-            return conanfile_path, status, ref_remote
+            return conanfile_path, status, remote, reference
 
         named_remote = self._registry.remote(remote_name) if remote_name else None
-        update_remote = named_remote or ref_remote
+        update_remote = named_remote or remote
         if not update_remote:
             status = RECIPE_NO_REMOTE
             log_recipe_got_from_local_cache(reference)
             self._recorder.recipe_fetched_from_cache(reference)
-            return conanfile_path, status, None
+            return conanfile_path, status, None, reference
 
         try:  # get_conan_manifest can fail, not in server
             upstream_manifest = self._remote_manager.get_conan_manifest(reference, update_remote)
@@ -62,7 +62,7 @@ class ConanProxy(object):
             status = RECIPE_NOT_IN_REMOTE
             log_recipe_got_from_local_cache(reference)
             self._recorder.recipe_fetched_from_cache(reference)
-            return conanfile_path, status, update_remote
+            return conanfile_path, status, update_remote, reference
 
         export = self._client_cache.export(reference)
         read_manifest = FileTreeManifest.load(export)
@@ -71,7 +71,7 @@ class ConanProxy(object):
                 if update:
                     DiskRemover(self._client_cache).remove_recipe(reference)
                     output.info("Retrieving from remote '%s'..." % update_remote.name)
-                    self._remote_manager.get_recipe(reference, update_remote)
+                    reference = self._remote_manager.get_recipe(reference, update_remote)
                     self._registry.set_ref(reference, update_remote)
                     status = RECIPE_UPDATED
                 else:
@@ -83,35 +83,36 @@ class ConanProxy(object):
 
         log_recipe_got_from_local_cache(reference)
         self._recorder.recipe_fetched_from_cache(reference)
-        return conanfile_path, status, update_remote
+        return conanfile_path, status, update_remote, reference
 
     def _download_recipe(self, conan_reference, output, remote_name):
         def _retrieve_from_remote(the_remote):
             output.info("Trying with '%s'..." % the_remote.name)
-            self._remote_manager.get_recipe(conan_reference, the_remote)
-            self._registry.set_ref(conan_reference, the_remote)
+            new_reference = self._remote_manager.get_recipe(conan_reference, the_remote)
+            self._registry.set_ref(new_reference, the_remote)
             self._recorder.recipe_downloaded(conan_reference, the_remote.url)
+            return new_reference
 
         if remote_name:
             output.info("Not found, retrieving from server '%s' " % remote_name)
-            ref_remote = self._registry.remote(remote_name)
+            remote = self._registry.remote(remote_name)
         else:
-            ref_remote = self._registry.get_ref(conan_reference)
-            if ref_remote:
-                output.info("Retrieving from predefined remote '%s'" % ref_remote.name)
+            remote = self._registry.get_recipe_remote(conan_reference)
+            if remote:
+                output.info("Retrieving from predefined remote '%s'" % remote.name)
 
-        if ref_remote:
+        if remote:
             try:
-                _retrieve_from_remote(ref_remote)
-                return ref_remote
+                new_ref = _retrieve_from_remote(remote)
+                return remote, new_ref
             except NotFoundException:
-                msg = "%s was not found in remote '%s'" % (str(conan_reference), ref_remote.name)
+                msg = "%s was not found in remote '%s'" % (str(conan_reference), remote.name)
                 self._recorder.recipe_install_error(conan_reference, INSTALL_ERROR_MISSING,
-                                                    msg, ref_remote.url)
+                                                    msg, remote.url)
                 raise NotFoundException(msg)
             except RequestException as exc:
                 self._recorder.recipe_install_error(conan_reference, INSTALL_ERROR_NETWORK,
-                                                    str(exc), ref_remote.url)
+                                                    str(exc), remote.url)
                 raise exc
 
         output.info("Not found in local cache, looking in remotes...")
@@ -121,10 +122,10 @@ class ConanProxy(object):
         for remote in remotes:
             logger.debug("Trying with remote %s" % remote.name)
             try:
-                _retrieve_from_remote(remote)
-                return remote
+                new_ref = _retrieve_from_remote(remote)
+                return remote, new_ref
             # If not found continue with the next, else raise
-            except NotFoundException as exc:
+            except NotFoundException:
                 pass
         else:
             msg = "Unable to find '%s' in remotes" % str(conan_reference)
diff --git a/conans/client/printer.py b/conans/client/printer.py
index 8b640c0b3..564a6f0a2 100644
--- a/conans/client/printer.py
+++ b/conans/client/printer.py
@@ -7,7 +7,6 @@ from conans.model.ref import ConanFileReference
 from conans.model.ref import PackageReference
 from conans.client.installer import build_id
 import fnmatch
-from platform import node
 
 
 class Printer(object):
@@ -85,7 +84,13 @@ class Printer(object):
                 continue
 
             self._out.writeln("%s" % str(ref), Color.BRIGHT_CYAN)
-            reg_remote = registry.get_ref(ref)
+            try:
+                # Excludes PROJECT fake reference
+                reg_remote = registry.get_recipe_remote(ref)
+            except:
+                # Excludes PROJECT fake reference
+                reg_remote = None
+
             # Excludes PROJECT fake reference
 
             if show("id"):
@@ -173,6 +178,7 @@ class Printer(object):
                     self._out.writeln(conan_item["recipe"]["id"])
 
     def print_search_packages(self, search_info, reference, packages_query):
+        assert(isinstance(reference, ConanFileReference))
         self._out.info("Existing packages for recipe %s:\n" % str(reference))
         for remote_info in search_info:
             if remote_info["remote"] != 'None':
diff --git a/conans/client/recorder/action_recorder.py b/conans/client/recorder/action_recorder.py
index d771bf075..d7988a7a0 100644
--- a/conans/client/recorder/action_recorder.py
+++ b/conans/client/recorder/action_recorder.py
@@ -41,11 +41,13 @@ class ActionRecorder(object):
         self._inst_recipes_develop.add(reference)
 
     def _add_recipe_action(self, reference, action):
+        assert(isinstance(reference, ConanFileReference))
         if reference not in self._inst_recipes_actions:
             self._inst_recipes_actions[reference] = []
         self._inst_recipes_actions[reference].append(action)
 
     def _add_package_action(self, reference, action):
+        assert(isinstance(reference, PackageReference))
         if reference not in self._inst_packages_actions:
             self._inst_packages_actions[reference] = []
         self._inst_packages_actions[reference].append(action)
@@ -72,6 +74,7 @@ class ActionRecorder(object):
         self._add_package_action(reference, Action(INSTALL_DOWNLOADED, {"remote": remote}))
 
     def package_install_error(self, reference, error_type, description, remote=None):
+        assert(isinstance(reference, PackageReference))
         if reference not in self._inst_packages_actions:
             self._inst_packages_actions[reference] = []
         doc = {"type": error_type, "description": description, "remote": remote}
@@ -87,6 +90,7 @@ class ActionRecorder(object):
         return False
 
     def _get_installed_packages(self, reference):
+        assert(isinstance(reference, ConanFileReference))
         ret = []
         for _package_ref, _package_actions in self._inst_packages_actions.items():
             # Could be a download and then an access to cache, we want the first one
diff --git a/conans/client/recorder/search_recorder.py b/conans/client/recorder/search_recorder.py
index e4597ddeb..c8d4880b7 100644
--- a/conans/client/recorder/search_recorder.py
+++ b/conans/client/recorder/search_recorder.py
@@ -5,7 +5,7 @@ class _SearchRecipe(namedtuple("SearchRecipe", "reference")):
     with_packages = True
 
     def to_dict(self):
-        data = {"id": self.reference}
+        data = {"id": str(self.reference)}
         return data
 
 
diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py
index 929174655..e9da23eef 100644
--- a/conans/client/remote_manager.py
+++ b/conans/client/remote_manager.py
@@ -1,18 +1,20 @@
 import os
-import shutil
+import stat
 import tarfile
-import time
 import traceback
-import stat
 
+import shutil
+import time
 from requests.exceptions import ConnectionError
 
 from conans.errors import ConanException, ConanConnectionError, NotFoundException
 from conans.model.manifest import gather_files
 from conans.paths import PACKAGE_TGZ_NAME, CONANINFO, CONAN_MANIFEST, CONANFILE, EXPORT_TGZ_NAME, \
     rm_conandir, EXPORT_SOURCES_TGZ_NAME, EXPORT_SOURCES_DIR_OLD
+
 from conans.util.files import gzopen_without_timestamps, is_dirty,\
     make_read_only, set_dirty, clean_dirty
+
 from conans.util.files import tar_extract, rmdir, exception_message_safe, mkdir
 from conans.util.files import touch_folder
 from conans.util.log import logger
@@ -21,13 +23,14 @@ from conans.util.tracer import (log_package_upload, log_recipe_upload,
                                 log_recipe_sources_download,
                                 log_uncompressed_file, log_compressed_files, log_recipe_download,
                                 log_package_download)
+
 from conans.client.source import merge_directories
 from conans.util.env_reader import get_env
 from conans.search.search import filter_packages
 
 
 class RemoteManager(object):
-    """ Will handle the remotes to get conans, packages etc """
+    """ Will handle the remotes to get recipes, packages etc """
 
     def __init__(self, client_cache, auth_manager, output):
         self._client_cache = client_cache
@@ -58,18 +61,18 @@ class RemoteManager(object):
         if skip_upload:
             return None
 
-        ret = self._call_remote(remote, "upload_recipe", conan_reference, the_files,
-                                retry, retry_wait, ignore_deleted_file, no_overwrite)
+        ret, new_ref = self._call_remote(remote, "upload_recipe", conan_reference, the_files, retry, retry_wait,
+                                         ignore_deleted_file, no_overwrite)
         duration = time.time() - t1
-        log_recipe_upload(conan_reference, duration, the_files, remote)
+        log_recipe_upload(new_ref, duration, the_files, remote)
         if ret:
-            msg = "Uploaded conan recipe '%s' to '%s'" % (str(conan_reference), remote.name)
+            msg = "Uploaded conan recipe '%s' to '%s'" % (str(new_ref), remote.name)
             url = remote.url.replace("https://api.bintray.com/conan", "https://bintray.com")
             msg += ": %s" % url
         else:
             msg = "Recipe is up to date, upload skipped"
         self._output.info(msg)
-        return ret
+        return new_ref
 
     def _package_integrity_check(self, package_reference, files, package_folder):
         # If package has been modified remove tgz to regenerate it
@@ -175,21 +178,9 @@ class RemoteManager(object):
         dest_folder = self._client_cache.export(conan_reference)
         rmdir(dest_folder)
 
-        def filter_function(urls):
-            if CONANFILE not in list(urls.keys()):
-                raise NotFoundException("Conan '%s' doesn't have a %s!"
-                                        % (conan_reference, CONANFILE))
-            urls.pop(EXPORT_SOURCES_TGZ_NAME, None)
-            return urls
-
         t1 = time.time()
-        urls = self._call_remote(remote, "get_recipe_urls", conan_reference)
-        urls = filter_function(urls)
-        if not urls:
-            return conan_reference
-
-        zipped_files = self._call_remote(remote, "download_files_to_folder", urls, dest_folder)
-
+        zipped_files, conan_reference = self._call_remote(remote, "get_recipe", conan_reference,
+                                                          dest_folder)
         duration = time.time() - t1
         log_recipe_download(conan_reference, duration, remote, zipped_files)
 
@@ -197,39 +188,27 @@ class RemoteManager(object):
         # Make sure that the source dir is deleted
         rm_conandir(self._client_cache.source(conan_reference))
         touch_folder(dest_folder)
+        return conan_reference
 
     def get_recipe_sources(self, conan_reference, export_folder, export_sources_folder, remote):
         t1 = time.time()
 
-        def filter_function(urls):
-            file_url = urls.pop(EXPORT_SOURCES_TGZ_NAME, None)
-            check_compressed_files(EXPORT_SOURCES_TGZ_NAME, urls)
-            if file_url:
-                urls = {EXPORT_SOURCES_TGZ_NAME: file_url}
-            else:
-                return None
-            return urls
-
-        urls = self._call_remote(remote, "get_recipe_urls", conan_reference)
-        urls = filter_function(urls)
-        if not urls:
+        zipped_files = self._call_remote(remote, "get_recipe_sources", conan_reference,
+                                         export_folder)
+        if not zipped_files:
+            mkdir(export_sources_folder)  # create the folder even if no source files
             return conan_reference
 
-        zipped_files = self._call_remote(remote, "download_files_to_folder", urls, export_folder)
-
         duration = time.time() - t1
         log_recipe_sources_download(conan_reference, duration, remote, zipped_files)
 
-        if not zipped_files:
-            mkdir(export_sources_folder)  # create the folder even if no source files
-            return
-
         unzip_and_get_files(zipped_files, export_sources_folder, EXPORT_SOURCES_TGZ_NAME)
         c_src_path = os.path.join(export_sources_folder, EXPORT_SOURCES_DIR_OLD)
         if os.path.exists(c_src_path):
             merge_directories(c_src_path, export_sources_folder)
             rmdir(c_src_path)
         touch_folder(export_sources_folder)
+        return conan_reference
 
     def get_package(self, package_reference, dest_folder, remote, output, recorder):
         package_id = package_reference.package_id
@@ -237,8 +216,7 @@ class RemoteManager(object):
         rm_conandir(dest_folder)  # Remove first the destination folder
         t1 = time.time()
         try:
-            urls = self._call_remote(remote, "get_package_urls", package_reference)
-            zipped_files = self._call_remote(remote, "download_files_to_folder", urls, dest_folder)
+            zipped_files = self._call_remote(remote, "get_package", package_reference, dest_folder)
             duration = time.time() - t1
             log_package_download(package_reference, duration, remote, zipped_files)
             unzip_and_get_files(zipped_files, dest_folder, PACKAGE_TGZ_NAME)
@@ -380,7 +358,7 @@ def compress_files(files, symlinks, name, dest_dir):
 def check_compressed_files(tgz_name, files):
     bare_name = os.path.splitext(tgz_name)[0]
     for f in files:
-        if bare_name == os.path.splitext(f)[0]:
+        if bare_name == os.path.splitext(f)[0] and f != tgz_name:
             raise ConanException("This Conan version is not prepared to handle '%s' file format. "
                                  "Please upgrade conan client." % f)
 
@@ -408,7 +386,7 @@ def uncompress_file(src_path, dest_folder):
             if os.path.exists(dest_folder):
                 shutil.rmtree(dest_folder)
                 error_msg += "Folder removed"
-        except Exception as e:
+        except Exception:
             error_msg += "Folder not removed, files/package might be damaged, remove manually"
         raise ConanException(error_msg)
 
diff --git a/conans/client/remote_registry.py b/conans/client/remote_registry.py
index bf6df2bab..5622bdc56 100644
--- a/conans/client/remote_registry.py
+++ b/conans/client/remote_registry.py
@@ -4,6 +4,7 @@ import fasteners
 from collections import OrderedDict, namedtuple
 
 from conans.errors import ConanException, NoRemoteAvailable
+from conans.model.ref import ConanFileReference
 from conans.util.files import load, save
 from conans.util.config_parser import get_bool_from_text_value
 from conans.util.log import logger
@@ -107,7 +108,8 @@ class RemoteRegistry(object):
             _, refs = self._load()
             return refs
 
-    def get_ref(self, conan_reference):
+    def get_recipe_remote(self, conan_reference):
+        assert(isinstance(conan_reference, ConanFileReference))
         with fasteners.InterProcessLock(self._filename + ".lock", logger=logger):
             remotes, refs = self._load()
             remote_name = refs.get(str(conan_reference))
@@ -117,44 +119,44 @@ class RemoteRegistry(object):
                 return None
 
     def remove_ref(self, conan_reference, quiet=False):
+        assert(isinstance(conan_reference, ConanFileReference))
         with fasteners.InterProcessLock(self._filename + ".lock", logger=logger):
-            conan_reference = str(conan_reference)
             remotes, refs = self._load()
             try:
-                del refs[conan_reference]
+                del refs[str(conan_reference)]
                 self._save(remotes, refs)
             except:
                 if not quiet:
                     self._output.warn("Couldn't delete '%s' from remote registry"
-                                      % conan_reference)
+                                      % str(conan_reference))
 
     def set_ref(self, conan_reference, remote):
+        assert(isinstance(conan_reference, ConanFileReference))
         with fasteners.InterProcessLock(self._filename + ".lock", logger=logger):
-            conan_reference = str(conan_reference)
             remotes, refs = self._load()
-            refs[conan_reference] = remote.name
+            refs[str(conan_reference)] = remote.name
             self._save(remotes, refs)
 
     def add_ref(self, conan_reference, remote):
+        assert(isinstance(conan_reference, ConanFileReference))
         with fasteners.InterProcessLock(self._filename + ".lock", logger=logger):
-            conan_reference = str(conan_reference)
             remotes, refs = self._load()
             if conan_reference in refs:
                 raise ConanException("%s already exists. Use update" % conan_reference)
             if remote not in remotes:
                 raise ConanException("%s not in remotes" % remote)
-            refs[conan_reference] = remote
+            refs[str(conan_reference)] = remote
             self._save(remotes, refs)
 
     def update_ref(self, conan_reference, remote):
+        assert(isinstance(conan_reference, ConanFileReference))
         with fasteners.InterProcessLock(self._filename + ".lock", logger=logger):
-            conan_reference = str(conan_reference)
             remotes, refs = self._load()
-            if conan_reference not in refs:
-                raise ConanException("%s does not exist. Use add" % conan_reference)
+            if str(conan_reference) not in refs:
+                raise ConanException("%s does not exist. Use add" % str(conan_reference))
             if remote not in remotes:
                 raise ConanException("%s not in remotes" % remote)
-            refs[conan_reference] = remote
+            refs[str(conan_reference)] = remote
             self._save(remotes, refs)
 
     def _upsert(self, remote_name, url, verify_ssl, insert):
diff --git a/conans/client/remover.py b/conans/client/remover.py
index e69f6eccc..83443ef13 100644
--- a/conans/client/remover.py
+++ b/conans/client/remover.py
@@ -93,7 +93,7 @@ class ConanRemover(object):
     def _remote_remove(self, reference, package_ids, remote):
         if package_ids is None:
             result = self._remote_manager.remove(reference, remote)
-            current_remote = self._registry.get_ref(reference)
+            current_remote = self._registry.get_recipe_remote(reference)
             if current_remote == remote:
                 self._registry.remove_ref(reference)
             return result
diff --git a/conans/client/rest/auth_manager.py b/conans/client/rest/auth_manager.py
index 8f46ca4e8..b60f64663 100644
--- a/conans/client/rest/auth_manager.py
+++ b/conans/client/rest/auth_manager.py
@@ -144,12 +144,16 @@ class ConanApiAuthManager(object):
         return self._rest_client.get_package_manifest(package_reference)
 
     @input_credentials_if_unauthorized
-    def get_recipe_urls(self, conan_reference):
-        return self._rest_client.get_recipe_urls(conan_reference)
+    def get_package(self, package_reference, dest_folder):
+        return self._rest_client.get_package(package_reference, dest_folder)
 
     @input_credentials_if_unauthorized
-    def get_package_urls(self, package_reference):
-        return self._rest_client.get_package_urls(package_reference)
+    def get_recipe(self, reference, dest_folder):
+        return self._rest_client.get_recipe(reference, dest_folder)
+
+    @input_credentials_if_unauthorized
+    def get_recipe_sources(self, reference, dest_folder):
+        return self._rest_client.get_recipe_sources(reference, dest_folder)
 
     @input_credentials_if_unauthorized
     def download_files_to_folder(self, urls, dest_folder):
diff --git a/conans/client/rest/rest_client.py b/conans/client/rest/rest_client.py
index 2df2dc673..a254d2cef 100644
--- a/conans/client/rest/rest_client.py
+++ b/conans/client/rest/rest_client.py
@@ -1,66 +1,5 @@
-from conans.errors import EXCEPTION_CODE_MAPPING, NotFoundException, ConanException, \
-    AuthenticationException
-from requests.auth import AuthBase, HTTPBasicAuth
-from conans.util.log import logger
-import json
-from conans.paths import CONAN_MANIFEST, CONANINFO
-import time
-from conans.client.rest.differ import diff_snapshots
-from conans.util.files import decode_text, md5sum
-import os
-from conans.model.manifest import FileTreeManifest
-from conans.client.rest.uploader_downloader import Uploader, Downloader
-from conans.model.ref import ConanFileReference
-from six.moves.urllib.parse import urlsplit, parse_qs, urlencode, urlparse, urljoin
-from conans import COMPLEX_SEARCH_CAPABILITY
-from conans.search.search import filter_packages
-from conans.model.info import ConanInfo
-from conans.util.tracer import log_client_rest_api_call
-
-
-def handle_return_deserializer(deserializer=None):
-    """Decorator for rest api methods.
-    Map exceptions and http return codes and deserialize if needed.
-
-    deserializer: Function for deserialize values"""
-    def handle_return(method):
-        def inner(*argc, **argv):
-            ret = method(*argc, **argv)
-            if ret.status_code != 200:
-                ret.charset = "utf-8"  # To be able to access ret.text (ret.content are bytes)
-                text = ret.text if ret.status_code != 404 else "404 Not found"
-                raise get_exception_from_error(ret.status_code)(text)
-            return deserializer(ret.content) if deserializer else decode_text(ret.content)
-        return inner
-    return handle_return
-
-
-def get_exception_from_error(error_code):
-    try:
-        tmp = {value: key for key, value in EXCEPTION_CODE_MAPPING.items()}
-        if error_code in tmp:
-            logger.debug("From server: %s" % str(tmp[error_code]))
-            return tmp[error_code]
-        else:
-            logger.debug("From server: %s" % str(_base_error(error_code)))
-            return tmp[_base_error(error_code)]
-    except KeyError:
-        return None
-
-
-def _base_error(error_code):
-    return int(str(error_code)[0] + "00")
-
-
-class JWTAuth(AuthBase):
-    """Attaches JWT Authentication to the given Request object."""
-    def __init__(self, token):
-        self.token = token
-
-    def __call__(self, request):
-        if self.token:
-            request.headers['Authorization'] = "Bearer %s" % str(self.token)
-        return request
+from collections import defaultdict
+from conans.client.rest.rest_client_v1 import RestV1Methods
 
 
 class RestApiClient(object):
@@ -80,465 +19,65 @@ class RestApiClient(object):
         self.verify_ssl = True
         self._put_headers = put_headers
 
-    @property
-    def auth(self):
-        return JWTAuth(self.token)
+        self._capabilities = defaultdict(list)
 
-    def get_conan_manifest(self, conan_reference):
-        """Gets a FileTreeManifest from conans"""
+    def _get_api(self):
+        if self.remote_url not in self._capabilities:
+            tmp = RestV1Methods(self.remote_url, self.token, self.custom_headers, self._output,
+                                self.requester, self.verify_ssl, self._put_headers)
+            _, _, cap = tmp.server_info()
+            self._capabilities[self.remote_url] = cap
 
-        # Obtain the URLs
-        url = "%s/conans/%s/digest" % (self._remote_api_url, "/".join(conan_reference))
-        urls = self._get_file_to_url_dict(url)
+        return RestV1Methods(self.remote_url, self.token, self.custom_headers, self._output,
+                             self.requester, self.verify_ssl, self._put_headers)
 
-        # Get the digest
-        contents = self.download_files(urls)
-        # Unroll generator and decode shas (plain text)
-        contents = {key: decode_text(value) for key, value in dict(contents).items()}
-        return FileTreeManifest.loads(contents[CONAN_MANIFEST])
+    def get_conan_manifest(self, conan_reference):
+        return self._get_api().get_conan_manifest(conan_reference)
 
     def get_package_manifest(self, package_reference):
-        """Gets a FileTreeManifest from a package"""
-
-        # Obtain the URLs
-        url = "%s/conans/%s/packages/%s/digest" % (self._remote_api_url,
-                                                   "/".join(package_reference.conan),
-                                                   package_reference.package_id)
-        urls = self._get_file_to_url_dict(url)
-
-        # Get the digest
-        contents = self.download_files(urls)
-        # Unroll generator and decode shas (plain text)
-        contents = {key: decode_text(value) for key, value in dict(contents).items()}
-        return FileTreeManifest.loads(contents[CONAN_MANIFEST])
+        return self._get_api().get_package_manifest(package_reference)
 
     def get_package_info(self, package_reference):
-        """Gets a ConanInfo file from a package"""
-
-        url = "%s/conans/%s/packages/%s/download_urls" % (self._remote_api_url,
-                                                          "/".join(package_reference.conan),
-                                                          package_reference.package_id)
-        urls = self._get_file_to_url_dict(url)
-        if not urls:
-            raise NotFoundException("Package not found!")
+        return self._get_api().get_package_info(package_reference)
 
-        if CONANINFO not in urls:
-            raise NotFoundException("Package %s doesn't have the %s file!" % (package_reference,
-                                                                              CONANINFO))
-        # Get the info (in memory)
-        contents = self.download_files({CONANINFO: urls[CONANINFO]})
-        # Unroll generator and decode shas (plain text)
-        contents = {key: decode_text(value) for key, value in dict(contents).items()}
-        return ConanInfo.loads(contents[CONANINFO])
+    def get_recipe(self, conan_reference, dest_folder):
+        return self._get_api().get_recipe(conan_reference, dest_folder)
 
-    def get_recipe_urls(self, conan_reference):
-        """Gets a dict of filename:contents from conans"""
-        # Get the conanfile snapshot first
-        url = "%s/conans/%s/download_urls" % (self._remote_api_url, "/".join(conan_reference))
-        urls = self._get_file_to_url_dict(url)
+    def get_recipe_sources(self, conan_reference, dest_folder):
+        return self._get_api().get_recipe_sources(conan_reference, dest_folder)
 
-        return urls
+    def get_package(self, package_reference, dest_folder):
+        return self._get_api().get_package(package_reference, dest_folder)
 
-    def get_package_urls(self, package_reference):
-        """Gets a dict of filename:contents from package"""
-        url = "%s/conans/%s/packages/%s/download_urls" % (self._remote_api_url,
-                                                          "/".join(package_reference.conan),
-                                                          package_reference.package_id)
-        urls = self._get_file_to_url_dict(url)
-        if not urls:
-            raise NotFoundException("Package not found!")
-
-        return urls
+    def get_path(self, conan_reference, package_id, path):
+        return self._get_api().get_path(conan_reference, package_id, path)
 
     def upload_recipe(self, conan_reference, the_files, retry, retry_wait, ignore_deleted_file,
                       no_overwrite):
-        """
-        the_files: dict with relative_path: content
-        """
-        self.check_credentials()
-
-        # Get the remote snapshot
-        remote_snapshot = self._get_conan_snapshot(conan_reference)
-        local_snapshot = {filename: md5sum(abs_path) for filename, abs_path in the_files.items()}
-
-        # Get the diff
-        new, modified, deleted = diff_snapshots(local_snapshot, remote_snapshot)
-        if ignore_deleted_file and ignore_deleted_file in deleted:
-            deleted.remove(ignore_deleted_file)
-
-        if not new and not deleted and modified in (["conanmanifest.txt"], []):
-            return False
-
-        if no_overwrite and remote_snapshot:
-            if no_overwrite in ("all", "recipe"):
-                raise ConanException("Local recipe is different from the remote recipe. "
-                                     "Forbidden overwrite")
-        files_to_upload = {filename.replace("\\", "/"): the_files[filename]
-                           for filename in new + modified}
-
-        if files_to_upload:
-            # Get the upload urls
-            url = "%s/conans/%s/upload_urls" % (self._remote_api_url, "/".join(conan_reference))
-            filesizes = {filename.replace("\\", "/"): os.stat(abs_path).st_size
-                         for filename, abs_path in files_to_upload.items()}
-            urls = self._get_file_to_url_dict(url, data=filesizes)
-            self.upload_files(urls, files_to_upload, self._output, retry, retry_wait)
-        if deleted:
-            self._remove_conanfile_files(conan_reference, deleted)
-
-        return files_to_upload or deleted
+        return self._get_api().upload_recipe(conan_reference, the_files, retry, retry_wait,
+                                             ignore_deleted_file, no_overwrite)
 
     def upload_package(self, package_reference, the_files, retry, retry_wait, no_overwrite):
-        """
-        basedir: Base directory with the files to upload (for read the files in disk)
-        relative_files: relative paths to upload
-        """
-        self.check_credentials()
-
-        t1 = time.time()
-        # Get the remote snapshot
-        remote_snapshot = self._get_package_snapshot(package_reference)
-        local_snapshot = {filename: md5sum(abs_path) for filename, abs_path in the_files.items()}
-
-        # Get the diff
-        new, modified, deleted = diff_snapshots(local_snapshot, remote_snapshot)
-        if not new and not deleted and modified in (["conanmanifest.txt"], []):
-            return False
-
-        if no_overwrite and remote_snapshot:
-            if no_overwrite in ("all", ):
-                raise ConanException("Local package is different from the remote package. "
-                                     "Forbidden overwrite")
+        return self._get_api().upload_package(package_reference, the_files, retry, retry_wait,
+                                              no_overwrite)
 
-        files_to_upload = {filename: the_files[filename] for filename in new + modified}
-        if files_to_upload:        # Obtain upload urls
-            url = "%s/conans/%s/packages/%s/upload_urls" % (self._remote_api_url,
-                                                            "/".join(package_reference.conan),
-                                                            package_reference.package_id)
-            filesizes = {filename: os.stat(abs_path).st_size for filename,
-                         abs_path in files_to_upload.items()}
-            self._output.rewrite_line("Requesting upload permissions...")
-            urls = self._get_file_to_url_dict(url, data=filesizes)
-            self._output.rewrite_line("Requesting upload permissions...Done!")
-            self._output.writeln("")
-            self.upload_files(urls, files_to_upload, self._output, retry, retry_wait)
-        if deleted:
-            self._remove_package_files(package_reference, deleted)
-
-        logger.debug("====> Time rest client upload_package: %f" % (time.time() - t1))
-        return files_to_upload or deleted
-
-    @handle_return_deserializer()
     def authenticate(self, user, password):
-        """Sends user + password to get a token"""
-        auth = HTTPBasicAuth(user, password)
-        url = "%s/users/authenticate" % self._remote_api_url
-        t1 = time.time()
-        ret = self.requester.get(url, auth=auth, headers=self.custom_headers,
-                                 verify=self.verify_ssl)
-        if ret.status_code == 401:
-            raise AuthenticationException("Wrong user or password")
-        # Cannot check content-type=text/html, conan server is doing it wrong
-        if not ret.ok or "html>" in str(ret.content):
-            raise ConanException("%s\n\nInvalid server response, check remote URL and "
-                                 "try again" % str(ret.content))
-        duration = time.time() - t1
-        log_client_rest_api_call(url, "GET", duration, self.custom_headers)
-        return ret
+        return self._get_api().authenticate(user, password)
 
-    @handle_return_deserializer()
     def check_credentials(self):
-        """If token is not valid will raise AuthenticationException.
-        User will be asked for new user/pass"""
-        url = "%s/users/check_credentials" % self._remote_api_url
-        t1 = time.time()
-        ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,
-                                 verify=self.verify_ssl)
-        duration = time.time() - t1
-        log_client_rest_api_call(url, "GET", duration, self.custom_headers)
-        return ret
+        return self._get_api().check_credentials()
 
     def search(self, pattern=None, ignorecase=True):
-        """
-        the_files: dict with relative_path: content
-        """
-        query = ''
-        if pattern:
-            params = {"q": pattern}
-            if not ignorecase:
-                params["ignorecase"] = "False"
-            query = "?%s" % urlencode(params)
-
-        url = "%s/conans/search%s" % (self._remote_api_url, query)
-        response = self._get_json(url)["results"]
-        return [ConanFileReference.loads(ref) for ref in response]
+        return self._get_api().search(pattern, ignorecase)
 
     def search_packages(self, reference, query):
+        return self._get_api().search_packages(reference, query)
 
-        url = "%s/conans/%s/search?" % (self._remote_api_url, "/".join(reference))
-        if not query:
-            package_infos = self._get_json(url)
-            return package_infos
-
-        # Read capabilities
-        try:
-            _, _, capabilities = self.server_info()
-        except NotFoundException:
-            capabilities = []
-
-        if COMPLEX_SEARCH_CAPABILITY in capabilities:
-            url += urlencode({"q": query})
-            package_infos = self._get_json(url)
-            return package_infos
-        else:
-            package_infos = self._get_json(url)
-            return filter_packages(query, package_infos)
-
-    @handle_return_deserializer()
     def remove_conanfile(self, conan_reference):
-        """ Remove a recipe and packages """
-        self.check_credentials()
-        url = "%s/conans/%s" % (self._remote_api_url, '/'.join(conan_reference))
-        response = self.requester.delete(url,
-                                         auth=self.auth,
-                                         headers=self.custom_headers,
-                                         verify=self.verify_ssl)
-        return response
+        return self._get_api().remove_conanfile(conan_reference)
 
-    @handle_return_deserializer()
     def remove_packages(self, conan_reference, package_ids=None):
-        """ Remove any packages specified by package_ids"""
-        self.check_credentials()
-        payload = {"package_ids": package_ids}
-        url = "%s/conans/%s/packages/delete" % (self._remote_api_url, '/'.join(conan_reference))
-        return self._post_json(url, payload)
-
-    @handle_return_deserializer()
-    def _remove_conanfile_files(self, conan_reference, files):
-        """ Remove recipe files """
-        self.check_credentials()
-        payload = {"files": [filename.replace("\\", "/") for filename in files]}
-        url = "%s/conans/%s/remove_files" % (self._remote_api_url, '/'.join(conan_reference))
-        return self._post_json(url, payload)
-
-    @handle_return_deserializer()
-    def _remove_package_files(self, package_reference, files):
-        """ Remove package files """
-        self.check_credentials()
-        payload = {"files": [filename.replace("\\", "/") for filename in files]}
-        url = "%s/conans/%s/packages/%s/remove_files" % (self._remote_api_url,
-                                                         "/".join(package_reference.conan),
-                                                         package_reference.package_id)
-        return self._post_json(url, payload)
+        return self._get_api().remove_packages(conan_reference, package_ids)
 
     def server_info(self):
-        """Get information about the server: status, version, type and capabilities"""
-        url = "%s/ping" % self._remote_api_url
-        ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,
-                                 verify=self.verify_ssl)
-        if ret.status_code == 404:
-            raise NotFoundException("Not implemented endpoint")
-
-        version_check = ret.headers.get('X-Conan-Client-Version-Check', None)
-        server_version = ret.headers.get('X-Conan-Server-Version', None)
-        server_capabilities = ret.headers.get('X-Conan-Server-Capabilities', "")
-        server_capabilities = [cap.strip() for cap in server_capabilities.split(",") if cap]
-
-        return version_check, server_version, server_capabilities
-
-    def _get_conan_snapshot(self, reference):
-        url = "%s/conans/%s" % (self._remote_api_url, '/'.join(reference))
-        try:
-            snapshot = self._get_json(url)
-        except NotFoundException:
-            snapshot = {}
-        norm_snapshot = {os.path.normpath(filename): the_md5
-                         for filename, the_md5 in snapshot.items()}
-        return norm_snapshot
-
-    def _get_package_snapshot(self, package_reference):
-        url = "%s/conans/%s/packages/%s" % (self._remote_api_url,
-                                            "/".join(package_reference.conan),
-                                            package_reference.package_id)
-        try:
-            snapshot = self._get_json(url)
-        except NotFoundException:
-            snapshot = {}
-        norm_snapshot = {os.path.normpath(filename): the_md5
-                         for filename, the_md5 in snapshot.items()}
-        return norm_snapshot
-
-    def _post_json(self, url, payload):
-        response = self.requester.post(url,
-                                       auth=self.auth,
-                                       headers=self.custom_headers,
-                                       verify=self.verify_ssl,
-                                       json=payload)
-        return response
-
-    def _complete_url(self, url):
-        """ Ensures that an url is absolute by completing relative urls with
-            the remote url. urls that are already absolute are not modified.
-        """
-        if bool(urlparse(url).netloc):
-            return url
-        return urljoin(self.remote_url, url)
-
-    def _get_file_to_url_dict(self, url, data=None):
-        """Call to url and decode the json returning a dict of {filepath: url} dict
-        converting the url to a complete url when needed"""
-        urls = self._get_json(url, data=data)
-        return {filepath: self._complete_url(url) for filepath, url in urls.items()}
-
-    def _get_json(self, url, data=None):
-        t1 = time.time()
-        headers = self.custom_headers
-        if data:  # POST request
-            headers.update({'Content-type': 'application/json',
-                            'Accept': 'text/plain',
-                            'Accept': 'application/json'})
-            response = self.requester.post(url, auth=self.auth, headers=headers,
-                                           verify=self.verify_ssl,
-                                           stream=True,
-                                           data=json.dumps(data))
-        else:
-            response = self.requester.get(url, auth=self.auth, headers=headers,
-                                          verify=self.verify_ssl,
-                                          stream=True)
-
-        duration = time.time() - t1
-        method = "POST" if data else "GET"
-        log_client_rest_api_call(url, method, duration, headers)
-        if response.status_code != 200:  # Error message is text
-            response.charset = "utf-8"  # To be able to access ret.text (ret.content are bytes)
-            raise get_exception_from_error(response.status_code)(response.text)
-
-        result = json.loads(decode_text(response.content))
-        if not isinstance(result, dict):
-            raise ConanException("Unexpected server response %s" % result)
-        return result
-
-    @property
-    def _remote_api_url(self):
-        return "%s/v1" % self.remote_url.rstrip("/")
-
-    def _file_server_capabilities(self, resource_url):
-        auth = None
-        dedup = False
-        urltokens = urlsplit(resource_url)
-        query_string = urltokens[3]
-        parsed_string_dict = parse_qs(query_string)
-        if "signature" not in parsed_string_dict and "Signature" not in parsed_string_dict:
-            # If monolithic server, we can use same auth, and server understand dedup
-            auth = self.auth
-            dedup = True
-        return auth, dedup
-
-    def download_files(self, file_urls, output=None):
-        """
-        :param: file_urls is a dict with {filename: url}
-
-        Its a generator, so it yields elements for memory performance
-        """
-        downloader = Downloader(self.requester, output, self.verify_ssl)
-        # Take advantage of filenames ordering, so that conan_package.tgz and conan_export.tgz
-        # can be < conanfile, conaninfo, and sent always the last, so smaller files go first
-        for filename, resource_url in sorted(file_urls.items(), reverse=True):
-            if output:
-                output.writeln("Downloading %s" % filename)
-            auth, _ = self._file_server_capabilities(resource_url)
-            contents = downloader.download(resource_url, auth=auth)
-            if output:
-                output.writeln("")
-            yield os.path.normpath(filename), contents
-
-    def download_files_to_folder(self, file_urls, to_folder):
-        """
-        :param: file_urls is a dict with {filename: abs_path}
-
-        It writes downloaded files to disk (appending to file, only keeps chunks in memory)
-        """
-        downloader = Downloader(self.requester, self._output, self.verify_ssl)
-        ret = {}
-        # Take advantage of filenames ordering, so that conan_package.tgz and conan_export.tgz
-        # can be < conanfile, conaninfo, and sent always the last, so smaller files go first
-        for filename, resource_url in sorted(file_urls.items(), reverse=True):
-            if self._output:
-                self._output.writeln("Downloading %s" % filename)
-            auth, _ = self._file_server_capabilities(resource_url)
-            abs_path = os.path.join(to_folder, filename)
-            downloader.download(resource_url, abs_path, auth=auth)
-            if self._output:
-                self._output.writeln("")
-            ret[filename] = abs_path
-        return ret
-
-    def upload_files(self, file_urls, files, output, retry, retry_wait):
-        t1 = time.time()
-        failed = []
-        uploader = Uploader(self.requester, output, self.verify_ssl)
-        # Take advantage of filenames ordering, so that conan_package.tgz and conan_export.tgz
-        # can be < conanfile, conaninfo, and sent always the last, so smaller files go first
-        for filename, resource_url in sorted(file_urls.items(), reverse=True):
-            output.rewrite_line("Uploading %s" % filename)
-            auth, dedup = self._file_server_capabilities(resource_url)
-            try:
-                response = uploader.upload(resource_url, files[filename], auth=auth, dedup=dedup,
-                                           retry=retry, retry_wait=retry_wait, headers=self._put_headers)
-                output.writeln("")
-                if not response.ok:
-                    output.error("\nError uploading file: %s, '%s'" % (filename, response.content))
-                    failed.append(filename)
-                else:
-                    pass
-            except Exception as exc:
-                output.error("\nError uploading file: %s, '%s'" % (filename, exc))
-                failed.append(filename)
-
-        if failed:
-            raise ConanException("Execute upload again to retry upload the failed files: %s"
-                                 % ", ".join(failed))
-        else:
-            logger.debug("\nAll uploaded! Total time: %s\n" % str(time.time() - t1))
-
-    def get_path(self, conan_reference, package_id, path):
-        """Gets a file content or a directory list"""
-
-        if not package_id:
-            url = "%s/conans/%s/download_urls" % (self._remote_api_url, "/".join(conan_reference))
-        else:
-            url = "%s/conans/%s/packages/%s/download_urls" % (self._remote_api_url,
-                                                              "/".join(conan_reference),
-                                                              package_id)
-        try:
-            urls = self._get_file_to_url_dict(url)
-        except NotFoundException:
-            if package_id:
-                raise NotFoundException("Package %s:%s not found" % (conan_reference, package_id))
-            else:
-                raise NotFoundException("Recipe %s not found" % str(conan_reference))
-
-        def is_dir(the_path):
-            if the_path == ".":
-                return True
-            for the_file in urls.keys():
-                if the_path == the_file:
-                    return False
-                elif the_file.startswith(the_path):
-                    return True
-            raise NotFoundException("The specified path doesn't exist")
-
-        if is_dir(path):
-            ret = []
-            for the_file in urls.keys():
-                if path == "." or the_file.startswith(path):
-                    tmp = the_file[len(path)-1:].split("/", 1)[0]
-                    if tmp not in ret:
-                        ret.append(tmp)
-            return sorted(ret)
-        else:
-            downloader = Downloader(self.requester, None, self.verify_ssl)
-            auth, _ = self._file_server_capabilities(urls[path])
-            content = downloader.download(urls[path], auth=auth)
-
-            return decode_text(content)
+        return self._get_api().server_info()
diff --git a/conans/client/rest/rest_client_common.py b/conans/client/rest/rest_client_common.py
new file mode 100644
index 000000000..a63170599
--- /dev/null
+++ b/conans/client/rest/rest_client_common.py
@@ -0,0 +1,241 @@
+import json
+import time
+
+import os
+from requests.auth import AuthBase, HTTPBasicAuth
+from six.moves.urllib.parse import urlencode
+
+from conans import COMPLEX_SEARCH_CAPABILITY
+from conans.errors import (EXCEPTION_CODE_MAPPING, NotFoundException, ConanException,
+                           AuthenticationException)
+from conans.model.ref import ConanFileReference
+from conans.search.search import filter_packages
+from conans.util.files import decode_text
+from conans.util.log import logger
+from conans.util.tracer import log_client_rest_api_call
+
+
+class JWTAuth(AuthBase):
+    """Attaches JWT Authentication to the given Request object."""
+    def __init__(self, token):
+        self.token = token
+
+    def __call__(self, request):
+        if self.token:
+            request.headers['Authorization'] = "Bearer %s" % str(self.token)
+        return request
+
+
+def _base_error(error_code):
+    return int(str(error_code)[0] + "00")
+
+
+def get_exception_from_error(error_code):
+    try:
+        tmp = {value: key for key, value in EXCEPTION_CODE_MAPPING.items()}
+        if error_code in tmp:
+            logger.debug("From server: %s" % str(tmp[error_code]))
+            return tmp[error_code]
+        else:
+            logger.debug("From server: %s" % str(_base_error(error_code)))
+            return tmp[_base_error(error_code)]
+    except KeyError:
+        return None
+
+
+def handle_return_deserializer(deserializer=None):
+    """Decorator for rest api methods.
+    Map exceptions and http return codes and deserialize if needed.
+
+    deserializer: Function for deserialize values"""
+    def handle_return(method):
+        def inner(*argc, **argv):
+            ret = method(*argc, **argv)
+            if ret.status_code != 200:
+                ret.charset = "utf-8"  # To be able to access ret.text (ret.content are bytes)
+                text = ret.text if ret.status_code != 404 else "404 Not found"
+                raise get_exception_from_error(ret.status_code)(text)
+            return deserializer(ret.content) if deserializer else decode_text(ret.content)
+        return inner
+    return handle_return
+
+
+class RestCommonMethods(object):
+
+    def __init__(self, remote_url, token, custom_headers, output, requester, verify_ssl,
+                 put_headers=None):
+
+        self.token = token
+        self.remote_url = remote_url
+        self.custom_headers = custom_headers
+        self._output = output
+        self.requester = requester
+        self.verify_ssl = verify_ssl
+        self._put_headers = put_headers
+
+    @property
+    def auth(self):
+        return JWTAuth(self.token)
+
+    @handle_return_deserializer()
+    def authenticate(self, user, password):
+        """Sends user + password to get a token"""
+        auth = HTTPBasicAuth(user, password)
+        url = "%s/users/authenticate" % self.remote_api_url
+        t1 = time.time()
+        ret = self.requester.get(url, auth=auth, headers=self.custom_headers,
+                                 verify=self.verify_ssl)
+        if ret.status_code == 401:
+            raise AuthenticationException("Wrong user or password")
+        # Cannot check content-type=text/html, conan server is doing it wrong
+        if not ret.ok or "html>" in str(ret.content):
+            raise ConanException("%s\n\nInvalid server response, check remote URL and "
+                                 "try again" % str(ret.content))
+        duration = time.time() - t1
+        log_client_rest_api_call(url, "GET", duration, self.custom_headers)
+        return ret
+
+    @handle_return_deserializer()
+    def check_credentials(self):
+        """If token is not valid will raise AuthenticationException.
+        User will be asked for new user/pass"""
+        url = "%s/users/check_credentials" % self.remote_api_url
+        t1 = time.time()
+        ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,
+                                 verify=self.verify_ssl)
+        duration = time.time() - t1
+        log_client_rest_api_call(url, "GET", duration, self.custom_headers)
+        return ret
+
+    def server_info(self):
+        """Get information about the server: status, version, type and capabilities"""
+        url = "%s/ping" % self.remote_api_url
+        ret = self.requester.get(url, auth=self.auth, headers=self.custom_headers,
+                                 verify=self.verify_ssl)
+        if ret.status_code == 404:
+            raise NotFoundException("Not implemented endpoint")
+
+        version_check = ret.headers.get('X-Conan-Client-Version-Check', None)
+        server_version = ret.headers.get('X-Conan-Server-Version', None)
+        server_capabilities = ret.headers.get('X-Conan-Server-Capabilities', "")
+        server_capabilities = [cap.strip() for cap in server_capabilities.split(",") if cap]
+
+        return version_check, server_version, server_capabilities
+
+    def get_json(self, url, data=None):
+        t1 = time.time()
+        headers = self.custom_headers
+        if data:  # POST request
+            headers.update({'Content-type': 'application/json',
+                            'Accept': 'text/plain',
+                            'Accept': 'application/json'})
+            response = self.requester.post(url, auth=self.auth, headers=headers,
+                                           verify=self.verify_ssl,
+                                           stream=True,
+                                           data=json.dumps(data))
+        else:
+            response = self.requester.get(url, auth=self.auth, headers=headers,
+                                          verify=self.verify_ssl,
+                                          stream=True)
+
+        duration = time.time() - t1
+        method = "POST" if data else "GET"
+        log_client_rest_api_call(url, method, duration, headers)
+        if response.status_code != 200:  # Error message is text
+            response.charset = "utf-8"  # To be able to access ret.text (ret.content are bytes)
+            raise get_exception_from_error(response.status_code)(response.text)
+
+        result = json.loads(decode_text(response.content))
+        if not isinstance(result, dict):
+            raise ConanException("Unexpected server response %s" % result)
+        return result
+
+    def search(self, pattern=None, ignorecase=True):
+        """
+        the_files: dict with relative_path: content
+        """
+        query = ''
+        if pattern:
+            params = {"q": pattern}
+            if not ignorecase:
+                params["ignorecase"] = "False"
+            query = "?%s" % urlencode(params)
+
+        url = "%s/conans/search%s" % (self.remote_api_url, query)
+        response = self.get_json(url)["results"]
+        return [ConanFileReference.loads(ref) for ref in response]
+
+    def search_packages(self, reference, query):
+        url = "%s/search?" % self._recipe_url(reference)
+
+        if not query:
+            package_infos = self.get_json(url)
+            return package_infos
+
+        # Read capabilities
+        try:
+            _, _, capabilities = self.server_info()
+        except NotFoundException:
+            capabilities = []
+
+        if COMPLEX_SEARCH_CAPABILITY in capabilities:
+            url += urlencode({"q": query})
+            package_infos = self.get_json(url)
+            return package_infos
+        else:
+            package_infos = self.get_json(url)
+            return filter_packages(query, package_infos)
+
+    @handle_return_deserializer()
+    def remove_conanfile(self, conan_reference):
+        """ Remove a recipe and packages """
+        self.check_credentials()
+        url = "%s/conans/%s" % (self.remote_api_url, '/'.join(conan_reference))
+        response = self.requester.delete(url,
+                                         auth=self.auth,
+                                         headers=self.custom_headers,
+                                         verify=self.verify_ssl)
+        return response
+
+    @handle_return_deserializer()
+    def remove_packages(self, conan_reference, package_ids=None):
+        """ Remove any packages specified by package_ids"""
+        self.check_credentials()
+        payload = {"package_ids": package_ids}
+        url = "%s/conans/%s/packages/delete" % (self.remote_api_url, '/'.join(conan_reference))
+        return self._post_json(url, payload)
+
+    @handle_return_deserializer()
+    def _remove_conanfile_files(self, conan_reference, files):
+        """ Remove recipe files """
+        self.check_credentials()
+        payload = {"files": [filename.replace("\\", "/") for filename in files]}
+        url = "%s/conans/%s/remove_files" % (self.remote_api_url, '/'.join(conan_reference))
+        return self._post_json(url, payload)
+
+    @handle_return_deserializer()
+    def _remove_package_files(self, package_reference, files):
+        """ Remove package files """
+        self.check_credentials()
+        payload = {"files": [filename.replace("\\", "/") for filename in files]}
+        url = "%s/conans/%s/packages/%s/remove_files" % (self.remote_api_url,
+                                                         "/".join(package_reference.conan),
+                                                         package_reference.package_id)
+        return self._post_json(url, payload)
+
+    def _post_json(self, url, payload):
+        response = self.requester.post(url,
+                                       auth=self.auth,
+                                       headers=self.custom_headers,
+                                       verify=self.verify_ssl,
+                                       json=payload)
+        return response
+
+    def _recipe_url(self, conan_reference):
+        url = "%s/conans/%s" % (self.remote_api_url, "/".join(conan_reference))
+        return url.replace("#", "%23")
+
+    def _package_url(self, p_reference):
+        url = self._recipe_url(p_reference.conan)
+        url += "/packages/%s" % p_reference.package_id
+        return url.replace("#", "%23")
diff --git a/conans/client/rest/rest_client_v1.py b/conans/client/rest/rest_client_v1.py
new file mode 100644
index 000000000..af2677592
--- /dev/null
+++ b/conans/client/rest/rest_client_v1.py
@@ -0,0 +1,346 @@
+import os
+import time
+
+from six.moves.urllib.parse import urlparse, urljoin, urlsplit, parse_qs
+
+from conans.client.remote_manager import check_compressed_files
+from conans.client.rest.differ import diff_snapshots
+from conans.client.rest.rest_client_common import RestCommonMethods
+from conans.client.rest.uploader_downloader import Downloader, Uploader
+from conans.errors import NotFoundException, ConanException
+from conans.model.info import ConanInfo
+from conans.model.manifest import FileTreeManifest
+from conans.paths import CONAN_MANIFEST, CONANINFO, EXPORT_SOURCES_TGZ_NAME, EXPORT_TGZ_NAME, \
+    PACKAGE_TGZ_NAME
+from conans.util.files import decode_text, md5sum
+from conans.util.log import logger
+
+
+def complete_url(base_url, url):
+    """ Ensures that an url is absolute by completing relative urls with
+        the remote url. urls that are already absolute are not modified.
+    """
+    if bool(urlparse(url).netloc):
+        return url
+    return urljoin(base_url, url)
+
+
+class RestV1Methods(RestCommonMethods):
+
+    @property
+    def remote_api_url(self):
+        return "%s/v1" % self.remote_url.rstrip("/")
+
+    def _download_files(self, file_urls, output=None):
+        """
+        :param: file_urls is a dict with {filename: url}
+
+        Its a generator, so it yields elements for memory performance
+        """
+        downloader = Downloader(self.requester, output, self.verify_ssl)
+        # Take advantage of filenames ordering, so that conan_package.tgz and conan_export.tgz
+        # can be < conanfile, conaninfo, and sent always the last, so smaller files go first
+        for filename, resource_url in sorted(file_urls.items(), reverse=True):
+            if output:
+                output.writeln("Downloading %s" % filename)
+            auth, _ = self._file_server_capabilities(resource_url)
+            contents = downloader.download(resource_url, auth=auth)
+            if output:
+                output.writeln("")
+            yield os.path.normpath(filename), contents
+
+    def _file_server_capabilities(self, resource_url):
+        auth = None
+        dedup = False
+        urltokens = urlsplit(resource_url)
+        query_string = urltokens[3]
+        parsed_string_dict = parse_qs(query_string)
+        if "signature" not in parsed_string_dict and "Signature" not in parsed_string_dict:
+            # If monolithic server, we can use same auth, and server understand dedup
+            auth = self.auth
+            dedup = True
+        return auth, dedup
+
+    def get_conan_manifest(self, conan_reference):
+        """Gets a FileTreeManifest from conans"""
+
+        # Obtain the URLs
+        url = "%s/conans/%s/digest" % (self.remote_api_url, "/".join(conan_reference))
+        urls = self._get_file_to_url_dict(url)
+
+        # Get the digest
+        contents = self._download_files(urls)
+        # Unroll generator and decode shas (plain text)
+        contents = {key: decode_text(value) for key, value in dict(contents).items()}
+        return FileTreeManifest.loads(contents[CONAN_MANIFEST])
+
+    def get_package_manifest(self, package_reference):
+        """Gets a FileTreeManifest from a package"""
+
+        # Obtain the URLs
+        url = "%s/conans/%s/packages/%s/digest" % (self.remote_api_url,
+                                                   "/".join(package_reference.conan),
+                                                   package_reference.package_id)
+        urls = self._get_file_to_url_dict(url)
+
+        # Get the digest
+        contents = self._download_files(urls)
+        # Unroll generator and decode shas (plain text)
+        contents = {key: decode_text(value) for key, value in dict(contents).items()}
+        return FileTreeManifest.loads(contents[CONAN_MANIFEST])
+
+    def get_package_info(self, package_reference):
+        """Gets a ConanInfo file from a package"""
+
+        url = "%s/conans/%s/packages/%s/download_urls" % (self.remote_api_url,
+                                                          "/".join(package_reference.conan),
+                                                          package_reference.package_id)
+        urls = self._get_file_to_url_dict(url)
+        if not urls:
+            raise NotFoundException("Package not found!")
+
+        if CONANINFO not in urls:
+            raise NotFoundException("Package %s doesn't have the %s file!" % (package_reference,
+                                                                              CONANINFO))
+        # Get the info (in memory)
+        contents = self._download_files({CONANINFO: urls[CONANINFO]})
+        # Unroll generator and decode shas (plain text)
+        contents = {key: decode_text(value) for key, value in dict(contents).items()}
+        return ConanInfo.loads(contents[CONANINFO])
+
+    def _get_file_to_url_dict(self, url, data=None):
+        """Call to url and decode the json returning a dict of {filepath: url} dict
+        converting the url to a complete url when needed"""
+        urls = self.get_json(url, data=data)
+        return {filepath: complete_url(self.remote_url, url) for filepath, url in urls.items()}
+
+    def _upload_files(self, file_urls, files, output, retry, retry_wait):
+        t1 = time.time()
+        failed = []
+        uploader = Uploader(self.requester, output, self.verify_ssl)
+        # Take advantage of filenames ordering, so that conan_package.tgz and conan_export.tgz
+        # can be < conanfile, conaninfo, and sent always the last, so smaller files go first
+        for filename, resource_url in sorted(file_urls.items(), reverse=True):
+            output.rewrite_line("Uploading %s" % filename)
+            auth, dedup = self._file_server_capabilities(resource_url)
+            try:
+                response = uploader.upload(resource_url, files[filename], auth=auth, dedup=dedup,
+                                           retry=retry, retry_wait=retry_wait, headers=self._put_headers)
+                output.writeln("")
+                if not response.ok:
+                    output.error("\nError uploading file: %s, '%s'" % (filename, response.content))
+                    failed.append(filename)
+                else:
+                    pass
+            except Exception as exc:
+                output.error("\nError uploading file: %s, '%s'" % (filename, exc))
+                failed.append(filename)
+
+        if failed:
+            raise ConanException("Execute upload again to retry upload the failed files: %s"
+                                 % ", ".join(failed))
+        else:
+            logger.debug("\nAll uploaded! Total time: %s\n" % str(time.time() - t1))
+
+    def _download_files_to_folder(self, file_urls, to_folder):
+        """
+        :param: file_urls is a dict with {filename: abs_path}
+
+        It writes downloaded files to disk (appending to file, only keeps chunks in memory)
+        """
+        downloader = Downloader(self.requester, self._output, self.verify_ssl)
+        ret = {}
+        # Take advantage of filenames ordering, so that conan_package.tgz and conan_export.tgz
+        # can be < conanfile, conaninfo, and sent always the last, so smaller files go first
+        for filename, resource_url in sorted(file_urls.items(), reverse=True):
+            if self._output:
+                self._output.writeln("Downloading %s" % filename)
+            auth, _ = self._file_server_capabilities(resource_url)
+            abs_path = os.path.join(to_folder, filename)
+            downloader.download(resource_url, abs_path, auth=auth)
+            if self._output:
+                self._output.writeln("")
+            ret[filename] = abs_path
+        return ret
+
+    def get_recipe(self, conan_reference, dest_folder):
+        urls = self._get_recipe_urls(conan_reference)
+        urls.pop(EXPORT_SOURCES_TGZ_NAME, None)
+        check_compressed_files(EXPORT_TGZ_NAME, urls)
+        zipped_files = self._download_files_to_folder(urls, dest_folder)
+        return zipped_files, conan_reference
+
+    def get_recipe_sources(self, conan_reference, dest_folder):
+        urls = self._get_recipe_urls(conan_reference)
+        check_compressed_files(EXPORT_SOURCES_TGZ_NAME, urls)
+        if EXPORT_SOURCES_TGZ_NAME not in urls:
+            return None
+        urls = {EXPORT_SOURCES_TGZ_NAME: urls[EXPORT_SOURCES_TGZ_NAME]}
+        zipped_files = self._download_files_to_folder(urls, dest_folder)
+        return zipped_files
+
+    def _get_recipe_urls(self, conan_reference):
+        """Gets a dict of filename:contents from conans"""
+        # Get the conanfile snapshot first
+        url = "%s/conans/%s/download_urls" % (self.remote_api_url, "/".join(conan_reference))
+        urls = self._get_file_to_url_dict(url)
+        return urls
+
+    def get_package(self, package_reference, dest_folder):
+        urls = self._get_package_urls(package_reference)
+        check_compressed_files(PACKAGE_TGZ_NAME, urls)
+        zipped_files = self._download_files_to_folder(urls, dest_folder)
+        return zipped_files
+
+    def _get_package_urls(self, package_reference):
+        """Gets a dict of filename:contents from package"""
+        url = "%s/conans/%s/packages/%s/download_urls" % (self.remote_api_url,
+                                                          "/".join(package_reference.conan),
+                                                          package_reference.package_id)
+        urls = self._get_file_to_url_dict(url)
+        if not urls:
+            raise NotFoundException("Package not found!")
+
+        return urls
+
+    def upload_recipe(self, conan_reference, the_files, retry, retry_wait, ignore_deleted_file,
+                      no_overwrite):
+        """
+        the_files: dict with relative_path: content
+        """
+        self.check_credentials()
+
+        # Get the remote snapshot
+        remote_snapshot = self._get_conan_snapshot(conan_reference)
+        local_snapshot = {filename: md5sum(abs_path) for filename, abs_path in the_files.items()}
+
+        # Get the diff
+        new, modified, deleted = diff_snapshots(local_snapshot, remote_snapshot)
+        if ignore_deleted_file and ignore_deleted_file in deleted:
+            deleted.remove(ignore_deleted_file)
+
+        if not new and not deleted and modified in (["conanmanifest.txt"], []):
+            return False, conan_reference
+
+        if no_overwrite and remote_snapshot:
+            if no_overwrite in ("all", "recipe"):
+                raise ConanException("Local recipe is different from the remote recipe. "
+                                     "Forbidden overwrite")
+        files_to_upload = {filename.replace("\\", "/"): the_files[filename]
+                           for filename in new + modified}
+
+        if files_to_upload:
+            # Get the upload urls
+            url = "%s/conans/%s/upload_urls" % (self.remote_api_url, "/".join(conan_reference))
+            filesizes = {filename.replace("\\", "/"): os.stat(abs_path).st_size
+                         for filename, abs_path in files_to_upload.items()}
+            urls = self._get_file_to_url_dict(url, data=filesizes)
+            self._upload_files(urls, files_to_upload, self._output, retry, retry_wait)
+        if deleted:
+            self._remove_conanfile_files(conan_reference, deleted)
+
+        return (files_to_upload or deleted), conan_reference
+
+    def upload_package(self, package_reference, the_files, retry, retry_wait, no_overwrite):
+        """
+        basedir: Base directory with the files to upload (for read the files in disk)
+        relative_files: relative paths to upload
+        """
+        self.check_credentials()
+
+        t1 = time.time()
+        # Get the remote snapshot
+        remote_snapshot = self._get_package_snapshot(package_reference)
+        local_snapshot = {filename: md5sum(abs_path) for filename, abs_path in the_files.items()}
+
+        # Get the diff
+        new, modified, deleted = diff_snapshots(local_snapshot, remote_snapshot)
+        if not new and not deleted and modified in (["conanmanifest.txt"], []):
+            return False
+
+        if no_overwrite and remote_snapshot:
+            if no_overwrite == "all":
+                raise ConanException("Local package is different from the remote package. "
+                                     "Forbidden overwrite")
+
+        files_to_upload = {filename: the_files[filename] for filename in new + modified}
+        if files_to_upload:        # Obtain upload urls
+            url = "%s/conans/%s/packages/%s/upload_urls" % (self.remote_api_url,
+                                                            "/".join(package_reference.conan),
+                                                            package_reference.package_id)
+            filesizes = {filename: os.stat(abs_path).st_size for filename,
+                         abs_path in files_to_upload.items()}
+            self._output.rewrite_line("Requesting upload permissions...")
+            urls = self._get_file_to_url_dict(url, data=filesizes)
+            self._output.rewrite_line("Requesting upload permissions...Done!")
+            self._output.writeln("")
+            self._upload_files(urls, files_to_upload, self._output, retry, retry_wait)
+        if deleted:
+            self._remove_package_files(package_reference, deleted)
+
+        logger.debug("====> Time rest client upload_package: %f" % (time.time() - t1))
+        return files_to_upload or deleted
+
+    def _get_conan_snapshot(self, reference):
+        url = "%s/conans/%s" % (self.remote_api_url, '/'.join(reference))
+        try:
+            snapshot = self.get_json(url)
+        except NotFoundException:
+            snapshot = {}
+        norm_snapshot = {os.path.normpath(filename): the_md5
+                         for filename, the_md5 in snapshot.items()}
+        return norm_snapshot
+
+    def _get_package_snapshot(self, package_reference):
+        url = "%s/conans/%s/packages/%s" % (self.remote_api_url,
+                                            "/".join(package_reference.conan),
+                                            package_reference.package_id)
+        try:
+            snapshot = self.get_json(url)
+        except NotFoundException:
+            snapshot = {}
+        norm_snapshot = {os.path.normpath(filename): the_md5
+                         for filename, the_md5 in snapshot.items()}
+        return norm_snapshot
+
+    def get_path(self, conan_reference, package_id, path):
+        """Gets a file content or a directory list"""
+
+        if not package_id:
+            url = "%s/conans/%s/download_urls" % (self.remote_api_url, "/".join(conan_reference))
+        else:
+            url = "%s/conans/%s/packages/%s/download_urls" % (self.remote_api_url,
+                                                              "/".join(conan_reference),
+                                                              package_id)
+        try:
+            urls = self._get_file_to_url_dict(url)
+        except NotFoundException:
+            if package_id:
+                raise NotFoundException("Package %s:%s not found" % (conan_reference, package_id))
+            else:
+                raise NotFoundException("Recipe %s not found" % str(conan_reference))
+
+        def is_dir(the_path):
+            if the_path == ".":
+                return True
+            for the_file in urls:
+                if the_path == the_file:
+                    return False
+                elif the_file.startswith(the_path):
+                    return True
+            raise NotFoundException("The specified path doesn't exist")
+
+        if is_dir(path):
+            ret = []
+            for the_file in urls:
+                if path == "." or the_file.startswith(path):
+                    tmp = the_file[len(path)-1:].split("/", 1)[0]
+                    if tmp not in ret:
+                        ret.append(tmp)
+            return sorted(ret)
+        else:
+            downloader = Downloader(self.requester, None, self.verify_ssl)
+            auth, _ = self._file_server_capabilities(urls[path])
+            content = downloader.download(urls[path], auth=auth)
+
+            return decode_text(content)
diff --git a/conans/client/rest/rest_client_v2.py b/conans/client/rest/rest_client_v2.py
new file mode 100644
index 000000000..e3c066ba3
--- /dev/null
+++ b/conans/client/rest/rest_client_v2.py
@@ -0,0 +1,7 @@
+from conans.client.rest.rest_client_common import RestCommonMethods
+
+
+class RestV2Methods(RestCommonMethods):
+
+    def __init__(self, *args, **kwargs):
+        raise NotImplementedError()
diff --git a/conans/client/rest/uploader_downloader.py b/conans/client/rest/uploader_downloader.py
index 1f1d89d27..b52a9aac6 100644
--- a/conans/client/rest/uploader_downloader.py
+++ b/conans/client/rest/uploader_downloader.py
@@ -3,7 +3,8 @@ import time
 import traceback
 
 import conans.tools
-from conans.errors import ConanException, ConanConnectionError, NotFoundException
+from conans.errors import ConanException, ConanConnectionError, NotFoundException, \
+    AuthenticationException
 from conans.util.files import save_append, sha1sum, exception_message_safe, to_file_bytes, mkdir
 from conans.util.log import logger
 from conans.util.tracer import log_download
@@ -135,6 +136,8 @@ class Downloader(object):
         if not response.ok:  # Do not retry if not found or whatever controlled error
             if response.status_code == 404:
                 raise NotFoundException("Not found: %s" % url)
+            elif response.status_code == 401:
+                raise AuthenticationException()
             raise ConanException("Error %d downloading file %s" % (response.status_code, url))
 
         try:
@@ -211,6 +214,8 @@ class Downloader(object):
 
 
 def progress_units(progress, total):
+    if total == 0:
+        return 0
     return min(50, int(50 * progress / total))
 
 
diff --git a/conans/client/source.py b/conans/client/source.py
index 8668ab15b..8a15786c5 100644
--- a/conans/client/source.py
+++ b/conans/client/source.py
@@ -24,7 +24,7 @@ def complete_recipe_sources(remote_manager, client_cache, registry, conanfile, c
 
     # If not path to sources exists, we have a problem, at least an empty folder
     # should be there
-    current_remote = registry.get_ref(conan_reference)
+    current_remote = registry.get_recipe_remote(conan_reference)
     if not current_remote:
         raise ConanException("Error while trying to get recipe sources for %s. "
                              "No remote defined" % str(conan_reference))
diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py
index 4a46af356..afbdbf322 100644
--- a/conans/model/conan_file.py
+++ b/conans/model/conan_file.py
@@ -12,7 +12,6 @@ from conans.model.user_info import DepsUserInfo
 from conans.paths import RUN_LOG_NAME
 from conans.tools import environment_append, no_op
 from conans.client.output import Color
-from conans.client.run_environment import RunEnvironment
 from conans.client.tools.oss import os_info
 
 
@@ -264,7 +263,7 @@ class ConanFile(object):
             return tools.run_in_windows_bash(self, bashcmd=command, cwd=cwd, subsystem=subsystem,
                                              msys_mingw=msys_mingw)
         if run_environment:
-            with tools.environment_append(RunEnvironment(self).vars):
+            with tools.run_environment(self):
                 if os_info.is_macos:
                     command = 'DYLD_LIBRARY_PATH="%s" %s' % (os.environ.get('DYLD_LIBRARY_PATH', ''), command)
                 retcode = _run()
diff --git a/conans/requirements.txt b/conans/requirements.txt
index 505a5ab5a..ccaf199d7 100644
--- a/conans/requirements.txt
+++ b/conans/requirements.txt
@@ -1,7 +1,7 @@
 PyJWT>=1.4.0, <2.0.0
 requests>=2.7.0, <3.0.0
 colorama>=0.3.3, <0.4.0
-PyYAML>=3.11, <3.13.0
+PyYAML>=3.11, <3.14.0
 patch==1.16
 fasteners>=0.14.1
 six>=1.10.0


Test patch:
diff --git a/conans/test/functional/registry_test.py b/conans/test/functional/registry_test.py
index 1caf37ed9..201739190 100644
--- a/conans/test/functional/registry_test.py
+++ b/conans/test/functional/registry_test.py
@@ -59,11 +59,11 @@ class RegistryTest(unittest.TestCase):
 
         remotes = registry.remotes
         registry.set_ref(ref, remotes[0])
-        remote = registry.get_ref(ref)
+        remote = registry.get_recipe_remote(ref)
         self.assertEqual(remote, remotes[0])
 
         registry.set_ref(ref, remotes[0])
-        remote = registry.get_ref(ref)
+        remote = registry.get_recipe_remote(ref)
         self.assertEqual(remote, remotes[0])
 
     def insert_test(self):
diff --git a/conans/test/model/transitive_reqs_test.py b/conans/test/model/transitive_reqs_test.py
index f8863e41f..74ee6a0d4 100644
--- a/conans/test/model/transitive_reqs_test.py
+++ b/conans/test/model/transitive_reqs_test.py
@@ -39,7 +39,7 @@ class Retriever(object):
 
     def get_recipe(self, conan_ref, check_updates, update, remote_name):  # @UnusedVariable
         conan_path = os.path.join(self.folder, "/".join(conan_ref), CONANFILE)
-        return conan_path, None, None
+        return conan_path, None, None, conan_ref
 
 
 say_content = """
diff --git a/conans/test/model/version_ranges_test.py b/conans/test/model/version_ranges_test.py
index eb4062fbe..0aa04998d 100644
--- a/conans/test/model/version_ranges_test.py
+++ b/conans/test/model/version_ranges_test.py
@@ -108,7 +108,7 @@ class Retriever(object):
 
     def get_recipe(self, conan_ref, check_updates, update, remote_name):  # @UnusedVariables
         conan_path = os.path.join(self.folder, "/".join(conan_ref), CONANFILE)
-        return conan_path, None, None
+        return conan_path, None, None, conan_ref
 
 
 hello_content = """
diff --git a/conans/test/remote/auth_bearer_test.py b/conans/test/remote/auth_bearer_test.py
index b83fb63c6..355a60686 100644
--- a/conans/test/remote/auth_bearer_test.py
+++ b/conans/test/remote/auth_bearer_test.py
@@ -1,3 +1,4 @@
+import os
 import unittest
 from conans.test.utils.tools import TestServer, TestClient
 from bottle import request
@@ -58,7 +59,8 @@ class AuthorizeBearerTest(unittest.TestCase):
         errors = client.run("upload Hello/0.1@lasote/stable")
         self.assertFalse(errors)
 
-        expected_calls = [('get_conan_manifest_url', None),
+        expected_calls = [('ping', None),
+                          ('get_conan_manifest_url', None),
                           ('check_credentials', None),
                           ('authenticate', 'Basic'),
                           ('get_conanfile_snapshot', 'Bearer'),
@@ -84,7 +86,8 @@ class AuthorizeBearerTest(unittest.TestCase):
         errors = client.run("upload Hello/0.1@lasote/stable", ignore_error=True)
         self.assertTrue(errors)
 
-        expected_calls = [('get_conan_manifest_url', None),
+        expected_calls = [('ping', None),
+                          ('get_conan_manifest_url', None),
                           ('check_credentials', None),
                           ('authenticate', 'Basic'),
                           ('get_conanfile_snapshot', 'Bearer'),
diff --git a/conans/test/remote/rest_api_test.py b/conans/test/remote/rest_api_test.py
index 83a236b82..ac8d3579c 100644
--- a/conans/test/remote/rest_api_test.py
+++ b/conans/test/remote/rest_api_test.py
@@ -1,20 +1,51 @@
+import os
+import platform
 import unittest
+
+import requests
+from nose.plugins.attrib import attr
+
 from conans.client.rest.rest_client import RestApiClient
+from conans.client.rest.rest_client_v1 import complete_url
+from conans.model.info import ConanInfo
+from conans.model.manifest import FileTreeManifest
 from conans.model.ref import ConanFileReference, PackageReference
-from conans.test.utils.test_files import hello_source_files
+from conans.model.version import Version
 from conans.paths import CONANFILE, CONAN_MANIFEST, CONANINFO
-from conans.model.info import ConanInfo
+from conans.server.rest.bottle_plugins.version_checker import VersionCheckerPlugin
 from conans.test.server.utils.server_launcher import TestServerLauncher
-import requests
+from conans.test.utils.test_files import hello_source_files
 from conans.test.utils.test_files import temp_folder
-from conans.model.version import Version
-from conans.server.rest.bottle_plugins.version_checker import VersionCheckerPlugin
-import platform
-import os
-from conans.util.files import md5, save
-from conans.model.manifest import FileTreeManifest
-from nose.plugins.attrib import attr
 from conans.test.utils.tools import TestBufferConanOutput
+from conans.util.files import md5, save
+
+
+class RestApiUnitTest(unittest.TestCase):
+
+    def relative_url_completion_test(self):
+
+        # test absolute urls
+        self.assertEquals(complete_url("http://host2", "http://host"), "http://host")
+        self.assertEquals(complete_url("http://host2", "http://host:1234"), "http://host:1234")
+        self.assertEquals(complete_url("http://host2", "https://host"), "https://host")
+        self.assertEquals(complete_url("http://host2", "https://host:1234"), "https://host:1234")
+
+        # test relative urls
+        self.assertEquals(complete_url("http://host", "v1/path_to_file.txt"),
+                          "http://host/v1/path_to_file.txt")
+
+        self.assertEquals(complete_url("http://host:1234", "v1/path_to_file.txt"),
+                          "http://host:1234/v1/path_to_file.txt")
+
+        self.assertEquals(complete_url("https://host", "v1/path_to_file.txt"),
+                          "https://host/v1/path_to_file.txt")
+
+        self.assertEquals(complete_url("https://host:1234", "v1/path_to_file.txt"),
+                          "https://host:1234/v1/path_to_file.txt")
+
+        # test relative urls with subdirectory
+        self.assertEquals(complete_url("https://host:1234/subdir/", "v1/path_to_file.txt"),
+                          "https://host:1234/subdir/v1/path_to_file.txt")
 
 
 @attr('slow')
@@ -48,37 +79,6 @@ class RestApiTest(unittest.TestCase):
     def tearDown(self):
         RestApiTest.server.clean()
 
-    def relative_url_completion_test(self):
-        api = RestApiClient(TestBufferConanOutput(), requester=requests)
-
-        # test absolute urls
-        self.assertEquals(api._complete_url("http://host"), "http://host")
-        self.assertEquals(api._complete_url("http://host:1234"), "http://host:1234")
-        self.assertEquals(api._complete_url("https://host"), "https://host")
-        self.assertEquals(api._complete_url("https://host:1234"), "https://host:1234")
-
-        # test relative urls
-        api.remote_url = "http://host"
-        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
-                          "http://host/v1/path_to_file.txt")
-
-        api.remote_url = "http://host:1234"
-        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
-                          "http://host:1234/v1/path_to_file.txt")
-
-        api.remote_url = "https://host"
-        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
-                          "https://host/v1/path_to_file.txt")
-
-        api.remote_url = "https://host:1234"
-        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
-                          "https://host:1234/v1/path_to_file.txt")
-
-        # test relative urls with subdirectory
-        api.remote_url = "https://host:1234/subdir/"
-        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
-                          "https://host:1234/subdir/v1/path_to_file.txt")
-
     def server_info_test(self):
         check, version, capabilities = self.api.server_info()
         self.assertEquals(version, "0.16.0")
@@ -91,14 +91,8 @@ class RestApiTest(unittest.TestCase):
         self._upload_recipe(conan_reference)
 
         # Get the conans
-        urls = self.api.get_recipe_urls(conan_reference)
-        self.assertIsNotNone(urls)
-        self.assertIn(CONANFILE, urls)
-        self.assertIn(CONAN_MANIFEST, urls)
-
-        # Download them
         tmp_dir = temp_folder()
-        self.api.download_files_to_folder(urls, tmp_dir)
+        self.api.get_recipe(conan_reference, tmp_dir)
         self.assertIn(CONANFILE, os.listdir(tmp_dir))
         self.assertIn(CONAN_MANIFEST, os.listdir(tmp_dir))
 
@@ -122,13 +116,8 @@ class RestApiTest(unittest.TestCase):
         self._upload_package(package_reference)
 
         # Get the package
-        urls = self.api.get_package_urls(package_reference)
-        self.assertIsNotNone(urls)
-        self.assertIn("hello.cpp", urls)
-
-        # Download them
         tmp_dir = temp_folder()
-        self.api.download_files_to_folder(urls, tmp_dir)
+        self.api.get_package(package_reference, tmp_dir)
         self.assertIn("hello.cpp", os.listdir(tmp_dir))
 
     def get_package_info_test(self):
@@ -165,9 +154,10 @@ class RestApiTest(unittest.TestCase):
             self._upload_recipe(conan_reference, files)
 
             # Get the conans
-            urls = self.api.get_recipe_urls(conan_reference)
-            self.assertIsNotNone(urls)
-            self.assertIn("file999.cpp", urls)
+            tmp = temp_folder()
+            files = self.api.get_recipe(conan_reference, tmp)
+            self.assertIsNotNone(files)
+            self.assertTrue(os.path.exists(os.path.join(tmp, "file999.cpp")))
 
     def search_test(self):
         # Upload a conan1