Repository: sissbruecker/linkding

Issue:
Promote single-file timeout to env variable
https://github.com/sissbruecker/linkding/blob/5d8fdebb7c4918c3face9478543ccf86b2db47ef/bookmarks/services/singlefile.py#L27

Can you please promote single-file timeout value to an environment variable? I run linkding on a raspberry pi and sometimes taking a snapshot is sloooow. 

For example:
```
time single-file https://www.hightidestoredtla.shop/products/bullet-ballpoint-pen-light?variant=38
087018184895

real    1m31.228s
user    0m13.667s
sys     0m11.835s
```

In my case I'd like to set a maybe 2 or 3 minute timeout. 


Discussion:
Sounds like a good idea 👍. Since you already know how it's done, care to make a contribution?
Weird you want me to help and not just complain? :) Sent a PR. Had to figure out how you're using settings and a quick hot-patch way to test.



Patch:
diff --git a/bookmarks/services/singlefile.py b/bookmarks/services/singlefile.py
index 7ccc3a89..755cffcb 100644
--- a/bookmarks/services/singlefile.py
+++ b/bookmarks/services/singlefile.py
@@ -24,7 +24,7 @@ def create_snapshot(url: str, filepath: str):
     try:
         # Use start_new_session=True to create a new process group
         process = subprocess.Popen(args, start_new_session=True)
-        process.wait(timeout=60)
+        process.wait(timeout=settings.LD_SINGLEFILE_TIMEOUT_SEC)
 
         # check if the file was created
         if not os.path.exists(temp_filepath):
diff --git a/docs/Options.md b/docs/Options.md
index 9d2bb63f..e5c9faef 100644
--- a/docs/Options.md
+++ b/docs/Options.md
@@ -246,3 +246,10 @@ See the default URL for how to insert the placeholder to the favicon provider UR
 
 Alternative favicon providers:
 - DuckDuckGo: `https://icons.duckduckgo.com/ip3/{domain}.ico`
+
+
+### `LD_SINGLEFILE_TIMEOUT_SEC`
+
+Values: `Float` | Default =  60.0
+
+When creating archive snapshots, control the timeout for how long to wait for `single-file` to complete, in `seconds`. Defaults to 60 seconds; on lower-powered hardware you may need to increase this value. 
\ No newline at end of file
diff --git a/siteroot/settings/base.py b/siteroot/settings/base.py
index b0116a62..619425bd 100644
--- a/siteroot/settings/base.py
+++ b/siteroot/settings/base.py
@@ -295,6 +295,7 @@
 )
 LD_SINGLEFILE_PATH = os.getenv("LD_SINGLEFILE_PATH", "single-file")
 LD_SINGLEFILE_OPTIONS = os.getenv("LD_SINGLEFILE_OPTIONS", "")
+LD_SINGLEFILE_TIMEOUT_SEC = float(os.getenv("LD_SINGLEFILE_TIMEOUT_SEC", 60))
 
 # Monolith isn't used at the moment, as the local snapshot implementation
 # switched to single-file after the prototype. Keeping this around in case


Test patch:
diff --git a/bookmarks/tests/test_singlefile_service.py b/bookmarks/tests/test_singlefile_service.py
index c592f060..f4337764 100644
--- a/bookmarks/tests/test_singlefile_service.py
+++ b/bookmarks/tests/test_singlefile_service.py
@@ -3,7 +3,7 @@
 import subprocess
 from unittest import mock
 
-from django.test import TestCase
+from django.test import TestCase, override_settings
 
 from bookmarks.services import singlefile
 
@@ -50,3 +50,24 @@ def test_create_snapshot_failure(self):
         with mock.patch("subprocess.Popen"):
             with self.assertRaises(singlefile.SingeFileError):
                 singlefile.create_snapshot("http://example.com", self.html_filepath)
+
+    def test_create_snapshot_default_timeout_setting(self):
+        mock_process = mock.Mock()
+        mock_process.wait.return_value = 0
+        self.create_test_file()
+
+        with mock.patch("subprocess.Popen", return_value=mock_process):
+            singlefile.create_snapshot("http://example.com", self.html_filepath)
+
+            mock_process.wait.assert_called_with(timeout=60)
+
+    @override_settings(LD_SINGLEFILE_TIMEOUT_SEC=120)
+    def test_create_snapshot_custom_timeout_setting(self):
+        mock_process = mock.Mock()
+        mock_process.wait.return_value = 0
+        self.create_test_file()
+
+        with mock.patch("subprocess.Popen", return_value=mock_process):
+            singlefile.create_snapshot("http://example.com", self.html_filepath)
+
+            mock_process.wait.assert_called_with(timeout=120)