Repository: CS-SI/eodag (python)

Issue:
accepts all file URI formats
EODAG should support `file:/path/to/file` formatting the same way it already supports `file:///path/to/file`.

See https://datatracker.ietf.org/doc/html/rfc8089#appendix-B

Pull request description:
fix: accept all local files uri formats
fixes #533 

Patch:
diff --git a/eodag/api/core.py b/eodag/api/core.py
index cd5d696b..a88a3212 100644
--- a/eodag/api/core.py
+++ b/eodag/api/core.py
@@ -1708,7 +1708,7 @@ class EODataAccessGateway(object):
         If the metadata mapping for ``downloadLink`` is set to something that can be
         interpreted as a link on a
         local filesystem, the download is skipped (by now, only a link starting
-        with ``file://`` is supported). Therefore, any user that knows how to extract
+        with ``file:/`` is supported). Therefore, any user that knows how to extract
         product location from product metadata on a provider can override the
         ``downloadLink`` metadata mapping in the right way. For example, using the
         environment variable:
@@ -1742,7 +1742,7 @@ class EODataAccessGateway(object):
         :raises: :class:`~eodag.utils.exceptions.PluginImplementationError`
         :raises: :class:`RuntimeError`
         """
-        if product.location.startswith("file://"):
+        if product.location.startswith("file:/"):
             logger.info("Local product detected. Download skipped")
             return uri_to_path(product.location)
         if product.downloader is None:


Test patch:
diff --git a/tests/units/test_core.py b/tests/units/test_core.py
index f3514a29..c109a695 100644
--- a/tests/units/test_core.py
+++ b/tests/units/test_core.py
@@ -1498,3 +1498,34 @@ class TestCoreSearch(TestCoreBase):
         dag.set_preferred_provider("dummy_provider")
         dag.search_all(productType="S2_MSI_L1C", items_per_page=7)
         self.assertEqual(mocked_search_iter_page.call_args[1]["items_per_page"], 7)
+
+
+class TestCoreDownload(TestCoreBase):
+    @classmethod
+    def setUpClass(cls):
+        super(TestCoreDownload, cls).setUpClass()
+        cls.dag = EODataAccessGateway()
+
+    def test_download_local_product(self):
+        """download must skip local products"""
+        product = EOProduct("dummy", dict(geometry="POINT (0 0)", id="dummy_product"))
+
+        product.location = "file:///some/path"
+        with self.assertLogs(level="INFO") as cm:
+            self.dag.download(product)
+            self.assertIn("Local product detected. Download skipped", str(cm.output))
+
+        product.location = "file:/some/path"
+        with self.assertLogs(level="INFO") as cm:
+            self.dag.download(product)
+            self.assertIn("Local product detected. Download skipped", str(cm.output))
+
+        product.location = "file:///c:/some/path"
+        with self.assertLogs(level="INFO") as cm:
+            self.dag.download(product)
+            self.assertIn("Local product detected. Download skipped", str(cm.output))
+
+        product.location = "file:/c:/some/path"
+        with self.assertLogs(level="INFO") as cm:
+            self.dag.download(product)
+            self.assertIn("Local product detected. Download skipped", str(cm.output))
diff --git a/tests/units/test_utils.py b/tests/units/test_utils.py
index d5596270..8616d6c2 100644
--- a/tests/units/test_utils.py
+++ b/tests/units/test_utils.py
@@ -62,11 +62,15 @@ class TestUtils(unittest.TestCase):
         if sys.platform == "win32":
             expected_path = r"C:\tmp\file.txt"
             tested_uri = r"file:///C:/tmp/file.txt"
+            other_tested_uri = r"file:/C:/tmp/file.txt"
         else:
             expected_path = "/tmp/file.txt"
             tested_uri = "file:///tmp/file.txt"
+            other_tested_uri = "file:/tmp/file.txt"
         actual_path = uri_to_path(tested_uri)
         self.assertEqual(actual_path, expected_path)
+        actual_path = uri_to_path(other_tested_uri)
+        self.assertEqual(actual_path, expected_path)
         with self.assertRaises(ValueError):
             uri_to_path("not_a_uri")