home · contact · privacy
Enable marking queued downloads for playlist insertion once ready. master
authorPlom Heller <plom@plomlompom.com>
Wed, 18 Mar 2026 03:12:33 +0000 (04:12 +0100)
committerPlom Heller <plom@plomlompom.com>
Wed, 18 Mar 2026 03:12:33 +0000 (04:12 +0100)
src/templates/_base.js
src/templates/downloads.js
src/templates/file.js
src/ytplom/http.py
src/ytplom/misc.py

index d83b0da72485265f714bbc945942fa4bde1927e5..4692e19430d1b50875d5568f5b76a46322014dcd 100644 (file)
@@ -23,7 +23,7 @@ eslint
 "function-paren-newline": "off",
 "max-lines": [
     "error",
-    {"max": 450, "skipBlankLines": true, "skipComments": true}
+    {"max": 463, "skipBlankLines": true, "skipComments": true}
 ],
 "max-lines-per-function": [
     "error",
@@ -89,10 +89,10 @@ export const
     ],
     CMD_ADD_NEXT = "inject",
     CMD_ADD_PLAY = "injectplay",
+    CMD_NEXT = "next",
     CMD_PAUSE = "pause",
     CMD_PLAY = "play",
     CMD_PREV = "prev",
-    CMD_NEXT = "next",
     CMD_RM = "rm",
     IDX_PATH_ID = 2,
     IDX_START = 0,
@@ -179,6 +179,20 @@ export const addChildTo = (
     return el;
 };
 
+export const addCheckboxTo = (
+    parent,
+    checked,
+    onclick
+) => addChildTo(
+    "input",
+    parent,
+    {
+        checked,
+        onclick,
+        "type": "checkbox"
+    }
+);
+
 export const addATo = (
     parent,
     textContent,
@@ -364,11 +378,11 @@ eventHandlers.player = (
     playerDiv.innerHTML = "";
     addPlayerBtnToDiv(
         CMD_PREV,
-        CMD_PREV,
+        CMD_PREV
     );
     addPlayerBtnToDiv(
         CMD_NEXT,
-        CMD_NEXT,
+        CMD_NEXT
     );
     addPlayerBtnToDiv(
         data.is_playing ? CMD_PAUSE : CMD_PLAY,
index 7a1d7ab39c238aef4bb57bb0d68628c1d2e279d9..d59dd7c6087ec9caa9166e8214bdf2b9db9fd658 100644 (file)
@@ -10,7 +10,7 @@ eslint
 ],
 "max-lines-per-function": [
     "error",
-    112
+    146
 ],
 "max-params": [
     "error",
@@ -50,6 +50,8 @@ import {
     SYMBOL_UP,
     addATdTo,
     addButtonTo,
+    addChildTo,
+    addCheckboxTo,
     addPlayerBtnTo,
     addTdTo,
     drawTable,
@@ -80,6 +82,17 @@ eventHandlers.downloads = (data) => {
                 command
             )
         ),
+        addInjectionCheckbox = (
+            td,
+            item
+        ) => addCheckboxTo(
+            td,
+            item.to_inject,
+            () => wrappedCommand(
+                PATH_DOWNLOADS_JSON,
+                `${item.to_inject ? "un" : "in"}ject_${item.yt_id}`
+            )
+        ),
         drawDownloadTable = (
             items,
             populateRow,
@@ -127,6 +140,10 @@ eventHandlers.downloads = (data) => {
             downloading
         ) => {
             const tdEntryControl = addTdTo(tr);  // col 1
+            addInjectionCheckbox(
+                tdEntryControl,
+                data.downloading
+            );
             addCommandButtonTo(
                 tdEntryControl,
                 SYMBOL_RM,
@@ -160,6 +177,10 @@ eventHandlers.downloads = (data) => {
                 `${CMD_RM}_${idx}`
             );
             const tdEntryControl = addTdTo(tr);  // col 2
+            addInjectionCheckbox(
+                tdEntryControl,
+                toDownload
+            );
             for (
                 const [
                     symbol,
index 75e1773122014921dac775facf23e46404f69364..37654c23b173b824e695cf966c7fd86cf194fd5a 100644 (file)
@@ -32,6 +32,7 @@ import {
     PATH_PREFIX_FILE_JSON,
     PREFIX_CLICKABLE,
     addATo,
+    addCheckboxTo,
     addChildTo,
     addPlayerBtnTo,
     addTdTo,
@@ -90,15 +91,11 @@ const updateFileData = async () => { // eslint-disable-line one-var
             if (allowEdit) {
                 const tdCheckbox = addTdTo(tr);
                 tdCheckbox.classList.add(CLS_TAG_CHECKBOX);
-                addChildTo(
-                    "input",
+                addCheckboxTo(
                     tdCheckbox,
-                    {
-                        "checked": true,
-                        /* eslint-disable-next-line no-use-before-define */
-                        "onclick": sendUpdate,
-                        "type": "checkbox"
-                    }
+                    true,
+                    /* eslint-disable-next-line no-use-before-define */
+                    sendUpdate
                 );
             }
             addATo(
index 28041766d623ce4174d8cc9bab36047b8c732293..eefcfc72ec88bef5316f292c57ac099fcb1a4c98 100644 (file)
@@ -41,7 +41,7 @@ class Server(ThreadingMixIn, PlomHttpServer):
                          _TaskHandler, *args, **kwargs)
         self.config = config
         self.player = Player()
-        self.downloads = DownloadsManager()
+        self.downloads = DownloadsManager(playlist_inject=self.player.inject)
         self.downloads.clean_unfinished()
         self.downloads.start_thread()
         VideoFile.call_forget\
index efd389a9dfb7a1e0e400f1153918e74225fdacb8..c068b5075b6a2fc9b38121d8da558bf0c1bec578 100644 (file)
@@ -844,7 +844,9 @@ class Player:
 class DownloadsManager:
     """Manages downloading and downloads access."""
 
-    def __init__(self) -> None:
+    def __init__(self, playlist_inject: Callable[[VideoFile], None]) -> None:
+        self._playlist_inject = playlist_inject
+        self._to_inject: set[str] = set()
         self._inherited: list[YoutubeId] = []
         self._downloaded: list[YoutubeId] = []
         self._downloading: Optional[YoutubeId] = None
@@ -900,9 +902,11 @@ class DownloadsManager:
 
     def overview(
             self, conn: DbConn
-            ) -> dict[str, list[dict[str, str]] | Optional[dict[str, str]]]:
+            ) -> dict[str,
+                      list[dict[str, str | bool]]
+                      | Optional[dict[str, str | bool]]]:
         'What has been, what will be, and what currently is being downloaded.'
-        downloaded: list[dict[str, str]] = []
+        downloaded: list[dict[str, str | bool]] = []
         yt_id: Optional[YoutubeId]
         for yt_id in self._downloaded:
             try:
@@ -912,7 +916,7 @@ class DownloadsManager:
             downloaded += [{'yt_id': yt_id,
                             'path': str(file.rel_path),
                             'digest': file.digest.b64}]  # + title?
-        to_download: list[dict[str, str]] = []
+        to_download: list[dict[str, str | bool]] = []
         for idx, yt_id in enumerate(self._to_download):
             try:
                 yt_video = YoutubeVideo.get_one(conn, yt_id)
@@ -920,8 +924,9 @@ class DownloadsManager:
                 continue
             to_download += [{'yt_id': yt_id,
                              'title': yt_video.title,
-                             'idx': str(idx)}]
-        downloading: Optional[dict[str, str]] = None
+                             'idx': str(idx),
+                             'to_inject': yt_id in self._to_inject}]
+        downloading: Optional[dict[str, str | bool]] = None
         if (yt_id := self._downloading):
             try:
                 yt_video = YoutubeVideo.get_one(conn, yt_id)
@@ -930,7 +935,8 @@ class DownloadsManager:
             else:
                 downloading = {'yt_id': yt_id,
                                'title': yt_video.title,
-                               'status': self._status}
+                               'status': self._status,
+                               'to_inject': yt_id in self._to_inject}
         return {'downloaded': downloaded,
                 'downloading': downloading,
                 'to_download': to_download}
@@ -948,6 +954,7 @@ class DownloadsManager:
     def _queue_download(self, yt_id: YoutubeId) -> None:
         if yt_id == self._downloading or yt_id in self._downloaded:
             return
+        self._to_inject.add(yt_id)
         self._to_download += [yt_id]
         if not self._downloading:
             self.q.put('download_next')
@@ -983,6 +990,9 @@ class DownloadsManager:
         with DbConn() as conn:
             file.save(conn)
             conn.commit()
+        if yt_id in self._to_inject:
+            self._playlist_inject(file)
+            self._to_inject.remove(yt_id)
         self._downloaded += [yt_id]
         self._downloading = None
         return yt_id
@@ -1081,6 +1091,10 @@ class DownloadsManager:
                             self._queue_download(yt_id)
                         elif command == 'forget':
                             self._forget_file(yt_id)
+                        elif command == 'inject':
+                            self._to_inject.add(yt_id)
+                        elif command == 'unject' and yt_id in self._to_inject:
+                            self._to_inject.remove(yt_id)
                 self._update_timestamp(yt_id)
 
         Thread(target=loop, daemon=False).start()