Repository: conan-io/conan

Issue:
Bug handling errors while downloading a file
If an error happens while downloading a file (signed URL), the client is failing to print an output message because of a None output object.




Discussion:

Resolving patch:
diff --git a/conans/__init__.py b/conans/__init__.py
--- a/conans/__init__.py
+++ b/conans/__init__.py
@@ -19,3 +19,4 @@
 SERVER_CAPABILITIES = [COMPLEX_SEARCH_CAPABILITY, API_V2]
 
 __version__ = '1.7.3'
+
diff --git a/conans/client/rest/rest_client_v1.py b/conans/client/rest/rest_client_v1.py
--- a/conans/client/rest/rest_client_v1.py
+++ b/conans/client/rest/rest_client_v1.py
@@ -31,22 +31,20 @@ class RestV1Methods(RestCommonMethods):
     def remote_api_url(self):
         return "%s/v1" % self.remote_url.rstrip("/")
 
-    def _download_files(self, file_urls, output=None):
+    def _download_files(self, file_urls):
         """
         :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)
+        downloader = Downloader(self.requester, self._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)
+            self._output.writeln("Downloading %s" % filename)
             auth, _ = self._file_server_capabilities(resource_url)
             contents = downloader.download(resource_url, auth=auth)
-            if output:
-                output.writeln("")
+            self._output.writeln("")
             yield os.path.normpath(filename), contents
 
     def _file_server_capabilities(self, resource_url):


Test patch:
diff --git a/conans/test/remote/broken_download_test.py b/conans/test/remote/broken_download_test.py
--- a/conans/test/remote/broken_download_test.py
+++ b/conans/test/remote/broken_download_test.py
@@ -1,5 +1,5 @@
 import unittest
-from conans.test.utils.tools import TestServer, TestClient
+from conans.test.utils.tools import TestServer, TestClient, TestRequester
 from conans.test.utils.cpp_test_files import cpp_hello_conan_files
 from conans.model.ref import ConanFileReference
 import os
@@ -26,3 +26,35 @@ def basic_test(self):
         client.run("install Hello/0.1@lasote/stable --build", ignore_error=True)
         self.assertIn("ERROR: Error while downloading/extracting files to", client.user_io.out)
         self.assertFalse(os.path.exists(client.paths.export(ref)))
+
+    def client_retries_test(self):
+        server = TestServer()
+        servers = {"default": server}
+        conanfile = """from conans import ConanFile
+
+class ConanFileToolsTest(ConanFile):
+    pass
+"""
+        client = TestClient(servers=servers, users={"default": [("lasote", "mypass")]})
+        client.save({"conanfile.py": conanfile})
+        client.run("create . lib/1.0@lasote/stable")
+        client.run("upload lib/1.0@lasote/stable -c --all")
+
+        class DownloadFilesBrokenRequester(TestRequester):
+
+            def __init__(self, *args, **kwargs):
+                self.first_fail = False
+                super(DownloadFilesBrokenRequester, self).__init__(*args, **kwargs)
+
+            def get(self, url, **kwargs):
+                if "conaninfo.txt?signature" in url and not self.first_fail:
+                    self.first_fail = True
+                    raise ConnectionError("Fake connection error exception")
+                else:
+                    return super(DownloadFilesBrokenRequester, self).get(url, **kwargs)
+
+        client2 = TestClient(servers=servers,
+                             users={"default": [("lasote", "mypass")]},
+                             requester_class=DownloadFilesBrokenRequester)
+        client2.run("install lib/1.0@lasote/stable")
+        self.assertEqual(1, str(client2.out).count("Waiting 0 seconds to retry..."))