Repository: CS-SI/eodag

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

Discussion:
github-actions[bot]: ## Unit Test Results
    2 files  ±0      2 suites  ±0   9m 47s [:stopwatch:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "duration of all tests") + 1m 42s
300 tests +1  297 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") +1  3 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") ±0  0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests") ±0 
600 runs  +2  594 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") +2  6 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") ±0  0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests") ±0 

Results for commit d1db0582. ± Comparison against base commit 837c127b.

[test-results]:data:application/gzip;base64,H4sIADnwY2MC/02MSw7DIAwFrxKx7sKQQEgvU5mfhJpPRWAV9e6FtpAsZ/w8B3F+tju5d+zWkT352MCkgNFva0YuxyzyKZZjD1DpsSety34aT/X0r7JqwqGfszifbAhb+JuQ1tIUUKEm+TQ08yuKypfgl689vS2LjxmIoUYBl4yPqKigxglLAWXvAJVCpqxiPUg7kPcH9xw55gUBAAA=

github-actions[bot]: <strong> </strong>

| File | Coverage |   |
| - | :-: | :-: |
| **All files** | `84%` | :white_check_mark: |
| eodag/api/core.py | `88%` | :white_check_mark: |
| tests/units/test\_core.py | `98%` | :white_check_mark: |
| tests/units/test\_utils.py | `94%` | :white_check_mark: |

<strong> </strong>

| File | Coverage |   |
| - | :-: | :-: |
| **All files** | `83%` | :white_check_mark: |
| eodag/api/core.py | `88%` | :white_check_mark: |
| tests/units/test\_core.py | `98%` | :white_check_mark: |
| tests/units/test\_utils.py | `94%` | :white_check_mark: |
| eodag/api/core.py | `87%` | :white_check_mark: |
| tests/units/test\_core.py | `98%` | :white_check_mark: |
| tests/units/test\_utils.py | `94%` | :white_check_mark: |

_Minimum allowed coverage is `70%`_

<p align="right">Generated by :monkey: cobertura-action against d1db058257ab161df6e10a83f0abba2beb2308e4 </p>

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")