From e1eb17ad7a5cb7a1f41f84cb1ac81eb07d0ef375 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Mon, 10 Nov 2025 19:02:36 +0300 Subject: [PATCH 1/6] [CHORE] update dependancies and remove lagecy code --- app/library/Events.py | 1 - app/tests/test_events.py | 1 - pyproject.toml | 2 +- ui/app/stores/SocketStore.ts | 15 ------ uv.lock | 99 +++++++++++++++++++++++++++++++++++- 5 files changed, 98 insertions(+), 20 deletions(-) diff --git a/app/library/Events.py b/app/library/Events.py index e9973182..d9665b7e 100644 --- a/app/library/Events.py +++ b/app/library/Events.py @@ -97,7 +97,6 @@ class Events: Events.LOG_SUCCESS, Events.ITEM_ADDED, Events.ITEM_UPDATED, - Events.ITEM_COMPLETED, Events.ITEM_CANCELLED, Events.ITEM_DELETED, Events.ITEM_MOVED, diff --git a/app/tests/test_events.py b/app/tests/test_events.py index a23ee1e1..2b3d2912 100644 --- a/app/tests/test_events.py +++ b/app/tests/test_events.py @@ -91,7 +91,6 @@ class TestEvents: Events.LOG_SUCCESS, Events.ITEM_ADDED, Events.ITEM_UPDATED, - Events.ITEM_COMPLETED, ] for expected in expected_frontend: diff --git a/pyproject.toml b/pyproject.toml index bf58a242..4f9812f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "aiocron>=1.8", "python-dotenv>=1.0.1", "debugpy>=1.8.1", - "httpx", + "httpx[brotli,http2,socks,zstd]", "async-timeout", "pyjson5", "curl_cffi>=0.13", diff --git a/ui/app/stores/SocketStore.ts b/ui/app/stores/SocketStore.ts index b69d90c5..0e24cad9 100644 --- a/ui/app/stores/SocketStore.ts +++ b/ui/app/stores/SocketStore.ts @@ -122,21 +122,6 @@ export const useSocketStore = defineStore('socket', () => { } }, true); - on('item_completed', (stream: string) => { - const json = JSON.parse(stream); - - if (true === stateStore.has('queue', json.data._id)) { - stateStore.remove('queue', json.data._id); - } - - if (true === stateStore.has('history', json.data._id)) { - stateStore.update('history', json.data._id, json.data); - return; - } - - stateStore.add('history', json.data._id, json.data); - }); - on('item_cancelled', (stream: string) => { const item = JSON.parse(stream); const id = item.data._id diff --git a/uv.lock b/uv.lock index 38fa218a..342c2628 100644 --- a/uv.lock +++ b/uv.lock @@ -524,6 +524,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, ] +[[package]] +name = "h2" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779 }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, +] + [[package]] name = "httpcore" version = "1.0.9" @@ -552,6 +574,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] +[package.optional-dependencies] +brotli = [ + { name = "brotli", marker = "platform_python_implementation == 'CPython'" }, + { name = "brotlicffi", marker = "platform_python_implementation != 'CPython'" }, +] +http2 = [ + { name = "h2" }, +] +socks = [ + { name = "socksio" }, +] +zstd = [ + { name = "zstandard" }, +] + [[package]] name = "httpx-curl-cffi" version = "0.1.4" @@ -578,6 +615,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 }, ] +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, +] + [[package]] name = "idna" version = "3.11" @@ -1490,6 +1536,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763 }, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -1710,7 +1765,7 @@ dependencies = [ { name = "dateparser" }, { name = "debugpy" }, { name = "defusedxml" }, - { name = "httpx" }, + { name = "httpx", extra = ["brotli", "http2", "socks", "zstd"] }, { name = "httpx-curl-cffi" }, { name = "jmespath" }, { name = "jsonschema" }, @@ -1761,7 +1816,7 @@ requires-dist = [ { name = "dateparser", specifier = ">=1.2.1" }, { name = "debugpy", specifier = ">=1.8.1" }, { name = "defusedxml", specifier = ">=0.7.1" }, - { name = "httpx" }, + { name = "httpx", extras = ["brotli", "http2", "socks", "zstd"] }, { name = "httpx-curl-cffi", specifier = ">=0.1.4" }, { name = "jmespath", specifier = ">=1.0.1" }, { name = "jsonschema", specifier = ">=4.23.0" }, @@ -1801,3 +1856,43 @@ sdist = { url = "https://files.pythonhosted.org/packages/11/f2/690a35762cf8366ce wheels = [ { url = "https://files.pythonhosted.org/packages/de/62/c2da1c495291a52e561257d017585e08906d288035d025ccf636f6b9a266/zipstream_ng-1.9.0-py3-none-any.whl", hash = "sha256:31dc2cf617abdbf28d44f2e08c0d14c8eee2ea0ec26507a7e4d5d5f97c564b7a", size = 24852 }, ] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735 }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440 }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070 }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001 }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120 }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230 }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173 }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736 }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368 }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022 }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889 }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952 }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054 }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113 }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936 }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232 }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671 }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887 }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658 }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849 }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095 }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751 }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818 }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402 }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108 }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248 }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123 }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591 }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513 }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118 }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940 }, +] From 3754722126acbab6a7716c47614f7e52c940e150 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Mon, 10 Nov 2025 21:47:24 +0300 Subject: [PATCH 2/6] Doc: update docs update tmpfs --- FAQ.md | 37 +++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 38 insertions(+) diff --git a/FAQ.md b/FAQ.md index 8db37eaa..c26c8c4e 100644 --- a/FAQ.md +++ b/FAQ.md @@ -507,3 +507,40 @@ Thats it, the `main.yml` will now disable the docker/github container registries naming, your container name will be named `REGISTRY/ytptube` and the tags will be the same as the ones used in the github registry. Unfortunately, the `native-builder.yml` workflow doesn't support self-hosted repositories at the moment. + +# Getting No space left on device error + +If you encounter this error: `OSError: [Errno 28] No space left on device` This indicates that either +the `/tmp` or `/downloads` directory has run out of available space. + +This issue commonly occurs when: + +- `/tmp` is mounted as `tmpfs` (memory-based storage) +- Your system has limited RAM +- You're downloading large video files + +Since videos are temporarily stored in `/tmp` before being moved to the final download location, memory-based storage +may be insufficient for large downloads. + +To fix the issue, modify your `compose.yaml` to use a disk-based directory for temporary files: + +```yaml +services: + ytptube: + user: "${UID:-1000}:${UID:-1000}" + image: ghcr.io/arabcoders/ytptube:latest + container_name: ytptube + restart: unless-stopped + ports: + - "8081:8081" + volumes: + - ./config:/config:rw + - ./downloads:/downloads/local:rw + - ./temp:/tmp:rw +``` + +> [!NOTE] +> Replace the `tmpfs` mount with a local directory volume (`./temp:/tmp:rw`). This allows temporary files to use disk space instead of RAM. + +After making the changes, restart your container. This should resolve the "No space left on device" +error during download. diff --git a/README.md b/README.md index e3c19ecc..30619b10 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ services: > [!IMPORTANT] > Make sure to change the `user` line to match your user id and group id +> If you have low RAM, remove the `tmpfs` and mount a disk-based directory to `/tmp` instead. See [FAQ](FAQ.md#getting-no-space-left-on-device-error) for more information. ```bash $ mkdir -p ./{config,downloads} && docker compose -f compose.yaml up -d From e6296de3fe43c988582bfb3b1753f4977a4a6719 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Mon, 10 Nov 2025 22:45:15 +0300 Subject: [PATCH 3/6] Fix: handle errors when fetching remote thumbnail and return appropriate status code --- app/routes/api/images.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/routes/api/images.py b/app/routes/api/images.py index db7c92b3..c7e4e389 100644 --- a/app/routes/api/images.py +++ b/app/routes/api/images.py @@ -71,6 +71,12 @@ async def get_thumbnail(request: Request, config: Config) -> Response: LOG.debug(f"Fetching thumbnail from '{url}'.") response = await client.request(method="GET", url=url, follow_redirects=True) + if response.status_code != web.HTTPOk.status_code: + LOG.error(f"Failed to fetch thumbnail from '{url}'. Status code: {response.status_code}.") + return web.json_response( + data={"error": "failed to retrieve the thumbnail."}, status=response.status_code + ) + return web.Response( body=response.content, headers={ From fa9f1fcefc407cbbff5357e25a5c214af3548f9e Mon Sep 17 00:00:00 2001 From: arabcoders Date: Tue, 11 Nov 2025 17:24:07 +0300 Subject: [PATCH 4/6] Fix: reduce the initial connection data --- app/library/Events.py | 3 +++ app/library/HttpSocket.py | 2 +- app/routes/socket/connection.py | 36 +++++++++++++++++++-------------- ui/app/stores/SocketStore.ts | 26 +++++++++++++++++------- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/app/library/Events.py b/app/library/Events.py index d9665b7e..f7528927 100644 --- a/app/library/Events.py +++ b/app/library/Events.py @@ -24,6 +24,8 @@ class Events: CONNECTED: str = "connected" + CONFIGURATION: str = "configuration" + LOG_INFO: str = "log_info" LOG_WARNING: str = "log_warning" LOG_ERROR: str = "log_error" @@ -90,6 +92,7 @@ class Events: """ return [ + Events.CONFIGURATION, Events.CONNECTED, Events.LOG_INFO, Events.LOG_WARNING, diff --git a/app/library/HttpSocket.py b/app/library/HttpSocket.py index 543ccf2a..16f3e4b2 100644 --- a/app/library/HttpSocket.py +++ b/app/library/HttpSocket.py @@ -44,7 +44,7 @@ class HttpSocket: self.sio = sio or socketio.AsyncServer( async_handlers=True, async_mode="aiohttp", - cors_allowed_origins=[], + cors_allowed_origins="*", transports=["websocket", "polling"], logger=self.config.debug, engineio_logger=self.config.debug, diff --git a/app/routes/socket/connection.py b/app/routes/socket/connection.py index 34c685d9..3364386d 100644 --- a/app/routes/socket/connection.py +++ b/app/routes/socket/connection.py @@ -23,25 +23,31 @@ class _Data: @route(RouteType.SOCKET, "connect", "socket_connect") async def connect(config: Config, queue: DownloadQueue, notify: EventBus, sid: str): - data = { - **queue.get(), - "config": config.frontend(), - "presets": Presets.get_instance().get_all(), - "dl_fields": DLFields.get_instance().get_all(), - "paused": queue.is_paused(), - } - - data["folders"] = list_folders( - path=Path(config.download_path), - base=Path(config.download_path), - depth_limit=config.download_path_depth-1, + notify.emit( + Events.CONFIGURATION, + data={ + "config": config.frontend(), + "presets": Presets.get_instance().get_all(), + "dl_fields": DLFields.get_instance().get_all(), + "paused": queue.is_paused(), + }, + title="Client connected", + message=f"Client '{sid}' connected.", + to=sid, ) notify.emit( Events.CONNECTED, - data=data, - title="Client connected", - message=f"Client '{sid}' connected.", + data={ + "folders": list_folders( + path=Path(config.download_path), + base=Path(config.download_path), + depth_limit=config.download_path_depth - 1, + ), + **queue.get(), + }, + title="Sending initial download data", + message=f"Sending initial download data to client '{sid}'.", to=sid, ) diff --git a/ui/app/stores/SocketStore.ts b/ui/app/stores/SocketStore.ts index 0e24cad9..8c226a93 100644 --- a/ui/app/stores/SocketStore.ts +++ b/ui/app/stores/SocketStore.ts @@ -54,8 +54,10 @@ export const useSocketStore = defineStore('socket', () => { transports: ['websocket', 'polling'], withCredentials: true, reconnection: true, - reconnectionAttempts: 30, - reconnectionDelay: 5000 + reconnectionAttempts: 50, + reconnectionDelay: 5000, + tryAllTransports: true, + timeout: 10000 * 5, } as Partial let url = runtimeConfig.public.wss @@ -70,28 +72,38 @@ export const useSocketStore = defineStore('socket', () => { connectionStatus.value = 'connecting'; socket.value = io(url, opts) - socket.value.on('connect', () => { + on("connect_error", (e: any) => { + if (!socket.value) { + return; + } + console.error("Socket connection error:", e); + socket.value.io.opts.transports = ["polling", "websocket"]; + }); + + on('connect', () => { isConnected.value = true connectionStatus.value = 'connected'; }); - socket.value.on('disconnect', () => { + on('disconnect', () => { isConnected.value = false connectionStatus.value = 'disconnected'; }); - socket.value.on('connected', stream => { + on('configuration', stream => { const json = JSON.parse(stream) - config.setAll({ app: json.data.config, tasks: json.data.tasks, - folders: json.data.folders, presets: json.data.presets, dl_fields: json.data.dl_fields, paused: Boolean(json.data.paused) } as Partial) + }) + on('connected', stream => { + const json = JSON.parse(stream); + config.add('folders', json.data.folders) stateStore.addAll('queue', json.data.queue || {}) stateStore.addAll('history', json.data.done || {}) }) From 28207bcd4115a7f743763e0da4721b267159768f Mon Sep 17 00:00:00 2001 From: arabcoders Date: Tue, 11 Nov 2025 17:25:51 +0300 Subject: [PATCH 5/6] Refactor: swap the order to poll and than upgrade to WS --- ui/app/stores/SocketStore.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ui/app/stores/SocketStore.ts b/ui/app/stores/SocketStore.ts index 8c226a93..b8060a1b 100644 --- a/ui/app/stores/SocketStore.ts +++ b/ui/app/stores/SocketStore.ts @@ -51,7 +51,7 @@ export const useSocketStore = defineStore('socket', () => { const connect = () => { const opts = { - transports: ['websocket', 'polling'], + transports: ['polling', 'websocket'], withCredentials: true, reconnection: true, reconnectionAttempts: 50, @@ -72,13 +72,7 @@ export const useSocketStore = defineStore('socket', () => { connectionStatus.value = 'connecting'; socket.value = io(url, opts) - on("connect_error", (e: any) => { - if (!socket.value) { - return; - } - console.error("Socket connection error:", e); - socket.value.io.opts.transports = ["polling", "websocket"]; - }); + on("connect_error", (e: any) => console.error("Socket connection error:", e)); on('connect', () => { isConnected.value = true From 4e86729206c8427ff63db0d9fe57398936a46bb1 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Tue, 11 Nov 2025 22:08:32 +0300 Subject: [PATCH 6/6] FEAT: add auto clear env variable --- FAQ.md | 7 ++++- app/library/DownloadQueue.py | 51 ++++++++++++++++++++++++++++++++++++ app/library/Utils.py | 3 +++ app/library/config.py | 4 +++ ui/app/layouts/default.vue | 4 +-- 5 files changed, 66 insertions(+), 3 deletions(-) diff --git a/FAQ.md b/FAQ.md index c26c8c4e..5ba877b9 100644 --- a/FAQ.md +++ b/FAQ.md @@ -47,6 +47,7 @@ or the `environment:` section in `compose.yaml` file. | YTP_ALLOW_INTERNAL_URLS | Allow requests to internal URLs | `false` | | YTP_SIMPLE_MODE | Switch default interface to Simple mode. | `false` | | YTP_STATIC_UI_PATH | Path to custom static UI files. | `(not_set)` | +| YTP_AUTO_CLEAR_HISTORY_DAYS | Number of days after which completed download history is cleared. | `0` | > [!NOTE] > To raise the maximum workers for specific extractor, you need to add a ENV variable that follows the pattern `YTP_MAX_WORKERS_FOR_`. @@ -54,8 +55,12 @@ or the `environment:` section in `compose.yaml` file. > The limit should not exceed the `YTP_MAX_WORKERS` value as it will be ignored. > [!IMPORTANT] -> The env variable `YTP_SIMPLE_MODE` only control what being displayed for first time visitor, the users can still switch between the two modes via the WebUI settings page. +> The env variable `YTP_SIMPLE_MODE` only control what being displayed for first time visitor, the users can still switch between the two modes via the WebUI settings page. +## Notes about YTP_AUTO_CLEAR_HISTORY_DAYS + +- `0` days means no automatic clearing of the download history. lowest value that will trigger the clearing is `1` day. +- This setting will **NOT** delete the downloaded files, it will only clear the history from the database. # Browser extensions & bookmarklets diff --git a/app/library/DownloadQueue.py b/app/library/DownloadQueue.py index b5c38786..68b02946 100644 --- a/app/library/DownloadQueue.py +++ b/app/library/DownloadQueue.py @@ -140,6 +140,15 @@ class DownloadQueue(metaclass=Singleton): func=self._check_live, id=f"{__class__.__name__}.{__class__._check_live.__name__}", ) + + if self.config.auto_clear_history_days > 0: + Scheduler.get_instance().add( + # timer="8 */1 * * *", + timer="* * * * *", + func=self._delete_old_history, + id=f"{__class__.__name__}.{__class__._delete_old_history.__name__}", + ) + # app.on_shutdown.append(self.on_shutdown) async def test(self) -> bool: @@ -1262,6 +1271,48 @@ class DownloadQueue(metaclass=Singleton): LOG.exception(e) LOG.error(f"Failed to retry item '{item_ref}'. {e!s}") + async def _delete_old_history(self) -> None: + """ + Automatically delete old download history based on user specified days. + 0 or negative value disables this feature. + """ + if self.config.auto_clear_history_days < 0 or self.is_paused() or self.done.empty(): + return + + cutoff_date: datetime = datetime.now(UTC) - timedelta(days=self.config.auto_clear_history_days) + items_to_delete: list[tuple[str, ItemDTO]] = [] + + for key, download in self.done.items(): + info: ItemDTO = download.info + if not info or not info.timestamp: + continue + + if "finished" != info.status or not info.filename: + continue + + try: + timestamp_seconds: float = info.timestamp / 1e9 + item_datetime: datetime = datetime.fromtimestamp(timestamp_seconds, tz=UTC) + if item_datetime < cutoff_date: + items_to_delete.append((key, info)) + except (OSError, ValueError, OverflowError) as e: + LOG.error(f"Failed to parse timestamp '{info.timestamp}' for item '{info.title}': {e}") + + titles: list[str] = [] + for key, info in items_to_delete: + item_name: str = info.title or info.id or info._id + self._notify.emit( + Events.ITEM_DELETED, + data=info, + title="Download Cleared", + message=f"'{item_name}' record removed from history.", + ) + titles.append(item_name) + self.done.delete(key) + + if titles: + LOG.info(f"Automatically cleared '{', '.join(titles)}' from download history due to age.") + def _handle_task_exception(self, task: asyncio.Task) -> None: if task.cancelled(): return diff --git a/app/library/Utils.py b/app/library/Utils.py index efd77d19..358c26a2 100644 --- a/app/library/Utils.py +++ b/app/library/Utils.py @@ -482,6 +482,9 @@ def check_id(file: Path) -> bool | str: id: str | None = match.groupdict().get("id") try: + if not file.parent.exists(): + return False + for f in file.parent.iterdir(): if id not in f.stem: continue diff --git a/app/library/config.py b/app/library/config.py index 8825ecaf..05abc8f2 100644 --- a/app/library/config.py +++ b/app/library/config.py @@ -192,6 +192,9 @@ class Config(metaclass=Singleton): playlist_items_concurrency: int = 4 """The number of concurrent playlist items to be processed at same time.""" + auto_clear_history_days: int = 0 + """Number of days after which completed download history is automatically cleared. 0 to disable.""" + pictures_backends: list[str] = [ "https://unsplash.it/1920/1080?random", "https://picsum.photos/1920/1080", @@ -230,6 +233,7 @@ class Config(metaclass=Singleton): "playlist_items_concurrency", "download_path_depth", "download_info_expires", + "auto_clear_history_days", ) "The variables that are integers." diff --git a/ui/app/layouts/default.vue b/ui/app/layouts/default.vue index 8413251f..7242880e 100644 --- a/ui/app/layouts/default.vue +++ b/ui/app/layouts/default.vue @@ -103,7 +103,7 @@