mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-19 23:39:33 +05:30
refactor: switched to yt-dlp way of path resolution, optimized database migrations and improved queuing
This commit is contained in:
28
package-lock.json
generated
28
package-lock.json
generated
@@ -64,6 +64,7 @@
|
|||||||
"recharts": "^3.1.2",
|
"recharts": "^3.1.2",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"ulid": "^3.0.1",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^4.1.0",
|
"zod": "^4.1.0",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
@@ -141,6 +142,7 @@
|
|||||||
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@@ -2948,6 +2950,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
|
||||||
"integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
|
"integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "5.85.5"
|
"@tanstack/query-core": "5.85.5"
|
||||||
},
|
},
|
||||||
@@ -3396,6 +3399,7 @@
|
|||||||
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
|
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.10.0"
|
"undici-types": "~7.10.0"
|
||||||
}
|
}
|
||||||
@@ -3406,6 +3410,7 @@
|
|||||||
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
|
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -3416,6 +3421,7 @@
|
|||||||
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
}
|
}
|
||||||
@@ -3479,6 +3485,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001726",
|
"caniuse-lite": "^1.0.30001726",
|
||||||
"electron-to-chromium": "^1.5.173",
|
"electron-to-chromium": "^1.5.173",
|
||||||
@@ -3771,7 +3778,8 @@
|
|||||||
"version": "8.6.0",
|
"version": "8.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||||
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/embla-carousel-react": {
|
"node_modules/embla-carousel-react": {
|
||||||
"version": "8.6.0",
|
"version": "8.6.0",
|
||||||
@@ -4371,6 +4379,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -4412,6 +4421,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -4442,6 +4452,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
@@ -4454,6 +4465,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
||||||
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
@@ -4477,6 +4489,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/use-sync-external-store": "^0.0.6",
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
"use-sync-external-store": "^1.4.0"
|
"use-sync-external-store": "^1.4.0"
|
||||||
@@ -4653,7 +4666,8 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/redux-thunk": {
|
"node_modules/redux-thunk": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -4864,6 +4878,15 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ulid": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ulid/-/ulid-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-dPJyqPzx8preQhqq24bBG1YNkvigm87K8kVEHCD+ruZg24t6IFEFv00xMWfxcC4djmFtiTLdFuADn4+DOz6R7Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"ulid": "dist/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.10.0",
|
"version": "7.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||||
@@ -4995,6 +5018,7 @@
|
|||||||
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
|
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"recharts": "^3.1.2",
|
"recharts": "^3.1.2",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"ulid": "^3.0.1",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^4.1.0",
|
"zod": "^4.1.0",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
|
|||||||
@@ -73,119 +73,7 @@ pub fn get_migrations() -> Vec<Migration> {
|
|||||||
version: 2,
|
version: 2,
|
||||||
description: "add_columns_to_downloads",
|
description: "add_columns_to_downloads",
|
||||||
sql: "
|
sql: "
|
||||||
ALTER TABLE downloads ADD COLUMN output_format TEXT;
|
-- Create temporary table with all new columns
|
||||||
ALTER TABLE downloads ADD COLUMN embed_metadata INTEGER NOT NULL DEFAULT 0;
|
|
||||||
ALTER TABLE downloads ADD COLUMN embed_thumbnail INTEGER NOT NULL DEFAULT 0;
|
|
||||||
ALTER TABLE downloads ADD COLUMN sponsorblock_remove TEXT;
|
|
||||||
ALTER TABLE downloads ADD COLUMN sponsorblock_mark TEXT;
|
|
||||||
ALTER TABLE downloads ADD COLUMN created_at TEXT;
|
|
||||||
ALTER TABLE downloads ADD COLUMN updated_at TEXT;
|
|
||||||
|
|
||||||
-- Update existing rows with current timestamp
|
|
||||||
UPDATE downloads SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULL;
|
|
||||||
UPDATE downloads SET updated_at = CURRENT_TIMESTAMP WHERE updated_at IS NULL;
|
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_downloads_updated_at
|
|
||||||
AFTER UPDATE ON downloads
|
|
||||||
FOR EACH ROW
|
|
||||||
BEGIN
|
|
||||||
UPDATE downloads SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
-- Create trigger for new inserts to set created_at and updated_at
|
|
||||||
CREATE TRIGGER IF NOT EXISTS set_downloads_timestamps
|
|
||||||
AFTER INSERT ON downloads
|
|
||||||
FOR EACH ROW
|
|
||||||
WHEN NEW.created_at IS NULL OR NEW.updated_at IS NULL
|
|
||||||
BEGIN
|
|
||||||
UPDATE downloads
|
|
||||||
SET created_at = COALESCE(NEW.created_at, CURRENT_TIMESTAMP),
|
|
||||||
updated_at = COALESCE(NEW.updated_at, CURRENT_TIMESTAMP)
|
|
||||||
WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
",
|
|
||||||
kind: MigrationKind::Up,
|
|
||||||
},
|
|
||||||
Migration {
|
|
||||||
version: 3,
|
|
||||||
description: "add_use_aria2_column_to_downloads_with_proper_position",
|
|
||||||
sql: "
|
|
||||||
-- Create temporary table with the new column in the correct position
|
|
||||||
CREATE TABLE downloads_temp (
|
|
||||||
id INTEGER PRIMARY KEY NOT NULL,
|
|
||||||
download_id TEXT UNIQUE NOT NULL,
|
|
||||||
download_status TEXT NOT NULL,
|
|
||||||
video_id TEXT NOT NULL,
|
|
||||||
format_id TEXT NOT NULL,
|
|
||||||
subtitle_id TEXT,
|
|
||||||
queue_index INTEGER,
|
|
||||||
playlist_id TEXT,
|
|
||||||
playlist_index INTEGER,
|
|
||||||
resolution TEXT,
|
|
||||||
ext TEXT,
|
|
||||||
abr REAL,
|
|
||||||
vbr REAL,
|
|
||||||
acodec TEXT,
|
|
||||||
vcodec TEXT,
|
|
||||||
dynamic_range TEXT,
|
|
||||||
process_id INTEGER,
|
|
||||||
status TEXT,
|
|
||||||
progress REAL,
|
|
||||||
total INTEGER,
|
|
||||||
downloaded INTEGER,
|
|
||||||
speed REAL,
|
|
||||||
eta INTEGER,
|
|
||||||
filepath TEXT,
|
|
||||||
filetype TEXT,
|
|
||||||
filesize INTEGER,
|
|
||||||
output_format TEXT,
|
|
||||||
embed_metadata INTEGER NOT NULL DEFAULT 0,
|
|
||||||
embed_thumbnail INTEGER NOT NULL DEFAULT 0,
|
|
||||||
sponsorblock_remove TEXT,
|
|
||||||
sponsorblock_mark TEXT,
|
|
||||||
use_aria2 INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (video_id) REFERENCES video_info (video_id),
|
|
||||||
FOREIGN KEY (playlist_id) REFERENCES playlist_info (playlist_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Copy all data from original table to temporary table
|
|
||||||
INSERT INTO downloads_temp SELECT
|
|
||||||
id, download_id, download_status, video_id, format_id, subtitle_id,
|
|
||||||
queue_index, playlist_id, playlist_index, resolution, ext, abr, vbr,
|
|
||||||
acodec, vcodec, dynamic_range, process_id, status, progress, total,
|
|
||||||
downloaded, speed, eta, filepath, filetype, filesize, output_format,
|
|
||||||
embed_metadata, embed_thumbnail, sponsorblock_remove, sponsorblock_mark,
|
|
||||||
0, -- use_aria2 default value
|
|
||||||
created_at, updated_at
|
|
||||||
FROM downloads;
|
|
||||||
|
|
||||||
-- Drop existing triggers for the original table
|
|
||||||
DROP TRIGGER IF EXISTS update_downloads_updated_at;
|
|
||||||
DROP TRIGGER IF EXISTS set_downloads_timestamps;
|
|
||||||
|
|
||||||
-- Drop the original table
|
|
||||||
DROP TABLE downloads;
|
|
||||||
|
|
||||||
-- Rename temporary table to original name
|
|
||||||
ALTER TABLE downloads_temp RENAME TO downloads;
|
|
||||||
|
|
||||||
-- Create only the update trigger (as insert trigger not needed anymore)
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_downloads_updated_at
|
|
||||||
AFTER UPDATE ON downloads
|
|
||||||
FOR EACH ROW
|
|
||||||
BEGIN
|
|
||||||
UPDATE downloads SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
",
|
|
||||||
kind: MigrationKind::Up,
|
|
||||||
},
|
|
||||||
Migration {
|
|
||||||
version: 4,
|
|
||||||
description: "add_custom_command_column_to_downloads",
|
|
||||||
sql: "
|
|
||||||
-- Create temporary table with the new column in the correct position
|
|
||||||
CREATE TABLE downloads_temp (
|
CREATE TABLE downloads_temp (
|
||||||
id INTEGER PRIMARY KEY NOT NULL,
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
download_id TEXT UNIQUE NOT NULL,
|
download_id TEXT UNIQUE NOT NULL,
|
||||||
@@ -220,33 +108,38 @@ pub fn get_migrations() -> Vec<Migration> {
|
|||||||
sponsorblock_mark TEXT,
|
sponsorblock_mark TEXT,
|
||||||
use_aria2 INTEGER NOT NULL DEFAULT 0,
|
use_aria2 INTEGER NOT NULL DEFAULT 0,
|
||||||
custom_command TEXT,
|
custom_command TEXT,
|
||||||
|
queue_config TEXT,
|
||||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (video_id) REFERENCES video_info (video_id),
|
FOREIGN KEY (video_id) REFERENCES video_info (video_id),
|
||||||
FOREIGN KEY (playlist_id) REFERENCES playlist_info (playlist_id)
|
FOREIGN KEY (playlist_id) REFERENCES playlist_info (playlist_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Copy all data from original table to temporary table
|
-- Copy all data from original table to temporary table with default values for new columns
|
||||||
INSERT INTO downloads_temp SELECT
|
INSERT INTO downloads_temp SELECT
|
||||||
id, download_id, download_status, video_id, format_id, subtitle_id,
|
id, download_id, download_status, video_id, format_id, subtitle_id,
|
||||||
queue_index, playlist_id, playlist_index, resolution, ext, abr, vbr,
|
queue_index, playlist_id, playlist_index, resolution, ext, abr, vbr,
|
||||||
acodec, vcodec, dynamic_range, process_id, status, progress, total,
|
acodec, vcodec, dynamic_range, process_id, status, progress, total,
|
||||||
downloaded, speed, eta, filepath, filetype, filesize, output_format,
|
downloaded, speed, eta, filepath, filetype, filesize,
|
||||||
embed_metadata, embed_thumbnail, sponsorblock_remove, sponsorblock_mark,
|
NULL, -- output_format
|
||||||
use_aria2, NULL, -- custom_command default value
|
0, -- embed_metadata
|
||||||
created_at, updated_at
|
0, -- embed_thumbnail
|
||||||
|
NULL, -- sponsorblock_remove
|
||||||
|
NULL, -- sponsorblock_mark
|
||||||
|
0, -- use_aria2
|
||||||
|
NULL, -- custom_command
|
||||||
|
NULL, -- queue_config
|
||||||
|
CURRENT_TIMESTAMP, -- created_at
|
||||||
|
CURRENT_TIMESTAMP -- updated_at
|
||||||
FROM downloads;
|
FROM downloads;
|
||||||
|
|
||||||
-- Drop existing triggers for the original table
|
|
||||||
DROP TRIGGER IF EXISTS update_downloads_updated_at;
|
|
||||||
|
|
||||||
-- Drop the original table
|
-- Drop the original table
|
||||||
DROP TABLE downloads;
|
DROP TABLE downloads;
|
||||||
|
|
||||||
-- Rename temporary table to original name
|
-- Rename temporary table to original name
|
||||||
ALTER TABLE downloads_temp RENAME TO downloads;
|
ALTER TABLE downloads_temp RENAME TO downloads;
|
||||||
|
|
||||||
-- Re-Create the update trigger
|
-- Create trigger for updating updated_at timestamp
|
||||||
CREATE TRIGGER IF NOT EXISTS update_downloads_updated_at
|
CREATE TRIGGER IF NOT EXISTS update_downloads_updated_at
|
||||||
AFTER UPDATE ON downloads
|
AFTER UPDATE ON downloads
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
|
|||||||
140
src/App.tsx
140
src/App.tsx
@@ -7,7 +7,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
import { arch, exeExtension } from "@tauri-apps/plugin-os";
|
import { arch, exeExtension } from "@tauri-apps/plugin-os";
|
||||||
import { downloadDir, join, resourceDir, tempDir } from "@tauri-apps/api/path";
|
import { downloadDir, join, resourceDir, tempDir } from "@tauri-apps/api/path";
|
||||||
import { useBasePathsStore, useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useDownloadStatesStore, useKvPairsStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
import { useBasePathsStore, useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useDownloadStatesStore, useKvPairsStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
||||||
import { determineFileType, generateDownloadId, generateSafeFilePath, generateVideoId, isObjEmpty, parseProgressLine, sanitizeFilename } from "@/utils";
|
import { determineFileType, generateVideoId, isObjEmpty, parseProgressLine } from "@/utils";
|
||||||
import { Command } from "@tauri-apps/plugin-shell";
|
import { Command } from "@tauri-apps/plugin-shell";
|
||||||
import { RawVideoInfo } from "@/types/video";
|
import { RawVideoInfo } from "@/types/video";
|
||||||
import { useDeleteDownloadState, useSaveDownloadState, useSavePlaylistInfo, useSaveVideoInfo, useUpdateDownloadFilePath, useUpdateDownloadStatus } from "@/services/mutations";
|
import { useDeleteDownloadState, useSaveDownloadState, useSavePlaylistInfo, useSaveVideoInfo, useUpdateDownloadFilePath, useUpdateDownloadStatus } from "@/services/mutations";
|
||||||
@@ -27,7 +27,8 @@ import useAppUpdater from "@/helpers/use-app-updater";
|
|||||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useLogger } from "@/helpers/use-logger";
|
import { useLogger } from "@/helpers/use-logger";
|
||||||
import { DownloadConfiguration } from "./types/settings";
|
import { DownloadConfiguration } from "@/types/settings";
|
||||||
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
export default function App({ children }: { children: React.ReactNode }) {
|
export default function App({ children }: { children: React.ReactNode }) {
|
||||||
const { data: downloadStates, isSuccess: isSuccessFetchingDownloadStates } = useFetchAllDownloadStates();
|
const { data: downloadStates, isSuccess: isSuccessFetchingDownloadStates } = useFetchAllDownloadStates();
|
||||||
@@ -45,7 +46,6 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid);
|
const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid);
|
||||||
|
|
||||||
// const isUsingDefaultSettings = useSettingsPageStatesStore((state) => state.isUsingDefaultSettings);
|
|
||||||
const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings);
|
const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings);
|
||||||
const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey);
|
const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey);
|
||||||
const appVersion = useSettingsPageStatesStore(state => state.appVersion);
|
const appVersion = useSettingsPageStatesStore(state => state.appVersion);
|
||||||
@@ -91,7 +91,6 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
const isErrored = useDownloaderPageStatesStore((state) => state.isErrored);
|
const isErrored = useDownloaderPageStatesStore((state) => state.isErrored);
|
||||||
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
||||||
const erroredDownloadId = useDownloaderPageStatesStore((state) => state.erroredDownloadId);
|
const erroredDownloadId = useDownloaderPageStatesStore((state) => state.erroredDownloadId);
|
||||||
const downloadConfiguration = useDownloaderPageStatesStore((state) => state.downloadConfiguration);
|
|
||||||
const setIsErrored = useDownloaderPageStatesStore((state) => state.setIsErrored);
|
const setIsErrored = useDownloaderPageStatesStore((state) => state.setIsErrored);
|
||||||
const setIsErrorExpected = useDownloaderPageStatesStore((state) => state.setIsErrorExpected);
|
const setIsErrorExpected = useDownloaderPageStatesStore((state) => state.setIsErrorExpected);
|
||||||
const setErroredDownloadId = useDownloaderPageStatesStore((state) => state.setErroredDownloadId);
|
const setErroredDownloadId = useDownloaderPageStatesStore((state) => state.setErroredDownloadId);
|
||||||
@@ -122,7 +121,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
const hasRunYtDlpAutoUpdateRef = useRef(false);
|
const hasRunYtDlpAutoUpdateRef = useRef(false);
|
||||||
const isRegisteredToMacOsRef = useRef(false);
|
const isRegisteredToMacOsRef = useRef(false);
|
||||||
|
|
||||||
const fetchVideoMetadata = async (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState): Promise<RawVideoInfo | null> => {
|
const fetchVideoMetadata = async (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState, downloadConfig?: DownloadConfiguration): Promise<RawVideoInfo | null> => {
|
||||||
try {
|
try {
|
||||||
const args = [url, '--dump-single-json', '--no-warnings'];
|
const args = [url, '--dump-single-json', '--no-warnings'];
|
||||||
if (formatId) args.push('-f', formatId);
|
if (formatId) args.push('-f', formatId);
|
||||||
@@ -132,6 +131,17 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
if (STRICT_DOWNLOADABILITY_CHECK && !formatId) args.push('--check-all-formats');
|
if (STRICT_DOWNLOADABILITY_CHECK && !formatId) args.push('--check-all-formats');
|
||||||
if (STRICT_DOWNLOADABILITY_CHECK && formatId) args.push('--check-formats');
|
if (STRICT_DOWNLOADABILITY_CHECK && formatId) args.push('--check-formats');
|
||||||
|
|
||||||
|
if ((USE_CUSTOM_COMMANDS && CUSTOM_COMMANDS && downloadConfig?.custom_command) || resumeState?.custom_command) {
|
||||||
|
let customCommandArgs = null;
|
||||||
|
if (resumeState?.custom_command) {
|
||||||
|
customCommandArgs = resumeState.custom_command;
|
||||||
|
} else if (CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfig?.custom_command)) {
|
||||||
|
let customCommand = CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfig?.custom_command);
|
||||||
|
customCommandArgs = customCommand ? customCommand.args : '';
|
||||||
|
}
|
||||||
|
if (customCommandArgs && customCommandArgs.trim() !== '') args.push(...customCommandArgs.split(' '));
|
||||||
|
}
|
||||||
|
|
||||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && USE_PROXY && PROXY_URL) args.push('--proxy', PROXY_URL);
|
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && USE_PROXY && PROXY_URL) args.push('--proxy', PROXY_URL);
|
||||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && USE_FORCE_INTERNET_PROTOCOL && FORCE_INTERNET_PROTOCOL) {
|
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && USE_FORCE_INTERNET_PROTOCOL && FORCE_INTERNET_PROTOCOL) {
|
||||||
if (FORCE_INTERNET_PROTOCOL === 'ipv4') {
|
if (FORCE_INTERNET_PROTOCOL === 'ipv4') {
|
||||||
@@ -146,6 +156,19 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
} else if (IMPORT_COOKIES_FROM === 'file' && COOKIES_FILE) {
|
} else if (IMPORT_COOKIES_FROM === 'file' && COOKIES_FILE) {
|
||||||
args.push('--cookies', COOKIES_FILE);
|
args.push('--cookies', COOKIES_FILE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (USE_SPONSORBLOCK || (resumeState?.sponsorblock_remove || resumeState?.sponsorblock_mark))) {
|
||||||
|
if (SPONSORBLOCK_MODE === 'remove' || resumeState?.sponsorblock_remove) {
|
||||||
|
let sponsorblockRemove = resumeState?.sponsorblock_remove || (SPONSORBLOCK_REMOVE === 'custom' ? (
|
||||||
|
SPONSORBLOCK_REMOVE_CATEGORIES.length > 0 ? SPONSORBLOCK_REMOVE_CATEGORIES.join(',') : 'default'
|
||||||
|
) : (SPONSORBLOCK_REMOVE));
|
||||||
|
args.push('--sponsorblock-remove', sponsorblockRemove);
|
||||||
|
} else if (SPONSORBLOCK_MODE === 'mark' || resumeState?.sponsorblock_mark) {
|
||||||
|
let sponsorblockMark = resumeState?.sponsorblock_mark || (SPONSORBLOCK_MARK === 'custom' ? (
|
||||||
|
SPONSORBLOCK_MARK_CATEGORIES.length > 0 ? SPONSORBLOCK_MARK_CATEGORIES.join(',') : 'default'
|
||||||
|
) : (SPONSORBLOCK_MARK));
|
||||||
|
args.push('--sponsorblock-mark', sponsorblockMark);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const command = Command.sidecar('binaries/yt-dlp', args);
|
const command = Command.sidecar('binaries/yt-dlp', args);
|
||||||
|
|
||||||
@@ -246,20 +269,27 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const videoId = resumeState?.video_id || generateVideoId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
const videoId = resumeState?.video_id || generateVideoId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
||||||
const playlistId = isPlaylist ? (resumeState?.playlist_id || generateVideoId(videoMetadata.playlist_id, videoMetadata.webpage_url_domain)) : null;
|
const playlistId = isPlaylist ? (resumeState?.playlist_id || generateVideoId(videoMetadata.playlist_id, videoMetadata.webpage_url_domain)) : null;
|
||||||
const downloadId = resumeState?.download_id || generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
const downloadId = resumeState?.download_id || ulid() /*generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain)*/;
|
||||||
const tempDownloadPathForYtdlp = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.%(ext)s`);
|
// const tempDownloadPathForYtdlp = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.%(ext)s`);
|
||||||
const tempDownloadPath = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.${videoMetadata.ext}`);
|
// const tempDownloadPath = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.${videoMetadata.ext}`);
|
||||||
let downloadFilePath = resumeState?.filepath || await join(downloadDirPath, sanitizeFilename(`${videoMetadata.title}_${videoMetadata.resolution || 'unknown'}[${videoMetadata.id}].${videoMetadata.ext}`));
|
// let downloadFilePath = resumeState?.filepath || await join(downloadDirPath, sanitizeFilename(`${videoMetadata.title}_${videoMetadata.resolution || 'unknown'}[${videoMetadata.id}].${videoMetadata.ext}`));
|
||||||
|
let downloadFilePath: string | null = null;
|
||||||
let processPid: number | null = null;
|
let processPid: number | null = null;
|
||||||
const args = [
|
const args = [
|
||||||
url,
|
url,
|
||||||
'--newline',
|
'--newline',
|
||||||
'--progress-template',
|
'--progress-template',
|
||||||
'status:%(progress.status)s,progress:%(progress._percent_str)s,speed:%(progress.speed)f,downloaded:%(progress.downloaded_bytes)d,total:%(progress.total_bytes)d,eta:%(progress.eta)d',
|
'status:%(progress.status)s,progress:%(progress._percent_str)s,speed:%(progress.speed)f,downloaded:%(progress.downloaded_bytes)d,total:%(progress.total_bytes)d,eta:%(progress.eta)d',
|
||||||
|
'--paths',
|
||||||
|
`temp:${tempDownloadDirPath}`,
|
||||||
|
'--paths',
|
||||||
|
`home:${downloadDirPath}`,
|
||||||
'--output',
|
'--output',
|
||||||
tempDownloadPathForYtdlp,
|
`%(title)s_%(resolution|unknown)s[${downloadId}].%(ext)s`,
|
||||||
// '--ffmpeg-location',
|
'--windows-filenames',
|
||||||
// ffmpegPath,
|
'--restrict-filenames',
|
||||||
|
'--exec',
|
||||||
|
'after_move:echo Finalpath: {}',
|
||||||
'-f',
|
'-f',
|
||||||
selectedFormat,
|
selectedFormat,
|
||||||
'--no-mtime',
|
'--no-mtime',
|
||||||
@@ -277,11 +307,11 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let customCommandArgs = null;
|
let customCommandArgs = null;
|
||||||
if ((USE_CUSTOM_COMMANDS && CUSTOM_COMMANDS && downloadConfiguration.custom_command) || resumeState?.custom_command) {
|
if ((USE_CUSTOM_COMMANDS && CUSTOM_COMMANDS && downloadConfig.custom_command) || resumeState?.custom_command) {
|
||||||
if (resumeState?.custom_command) {
|
if (resumeState?.custom_command) {
|
||||||
customCommandArgs = resumeState.custom_command;
|
customCommandArgs = resumeState.custom_command;
|
||||||
} else if (CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfiguration.custom_command)) {
|
} else if (CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfig.custom_command)) {
|
||||||
let customCommand = CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfiguration.custom_command);
|
let customCommand = CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfig.custom_command);
|
||||||
customCommandArgs = customCommand ? customCommand.args : '';
|
customCommandArgs = customCommand ? customCommand.args : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,10 +356,10 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let embedMetadata = 0;
|
let embedMetadata = 0;
|
||||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfiguration.embed_metadata || resumeState?.embed_metadata || EMBED_VIDEO_METADATA || EMBED_AUDIO_METADATA)) {
|
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfig.embed_metadata || resumeState?.embed_metadata || EMBED_VIDEO_METADATA || EMBED_AUDIO_METADATA)) {
|
||||||
const shouldEmbedForVideo = (fileType === 'video+audio' || fileType === 'video') && (downloadConfiguration.embed_metadata || resumeState?.embed_metadata || (EMBED_VIDEO_METADATA && downloadConfiguration.embed_metadata === null));
|
const shouldEmbedForVideo = (fileType === 'video+audio' || fileType === 'video') && (downloadConfig.embed_metadata || resumeState?.embed_metadata || (EMBED_VIDEO_METADATA && downloadConfig.embed_metadata === null));
|
||||||
const shouldEmbedForAudio = fileType === 'audio' && (downloadConfiguration.embed_metadata || resumeState?.embed_metadata || (EMBED_AUDIO_METADATA && downloadConfiguration.embed_metadata === null));
|
const shouldEmbedForAudio = fileType === 'audio' && (downloadConfig.embed_metadata || resumeState?.embed_metadata || (EMBED_AUDIO_METADATA && downloadConfig.embed_metadata === null));
|
||||||
const shouldEmbedForUnknown = fileType === 'unknown' && (downloadConfiguration.embed_metadata || resumeState?.embed_metadata);
|
const shouldEmbedForUnknown = fileType === 'unknown' && (downloadConfig.embed_metadata || resumeState?.embed_metadata);
|
||||||
|
|
||||||
if (shouldEmbedForUnknown || shouldEmbedForVideo || shouldEmbedForAudio) {
|
if (shouldEmbedForUnknown || shouldEmbedForVideo || shouldEmbedForAudio) {
|
||||||
embedMetadata = 1;
|
embedMetadata = 1;
|
||||||
@@ -338,7 +368,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let embedThumbnail = 0;
|
let embedThumbnail = 0;
|
||||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfiguration.embed_thumbnail || resumeState?.embed_thumbnail || (fileType === 'audio' && EMBED_AUDIO_THUMBNAIL && downloadConfiguration.embed_thumbnail === null))) {
|
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfig.embed_thumbnail || resumeState?.embed_thumbnail || (fileType === 'audio' && EMBED_AUDIO_THUMBNAIL && downloadConfig.embed_thumbnail === null))) {
|
||||||
embedThumbnail = 1;
|
embedThumbnail = 1;
|
||||||
args.push('--embed-thumbnail');
|
args.push('--embed-thumbnail');
|
||||||
}
|
}
|
||||||
@@ -412,32 +442,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
setErroredDownloadId(downloadId);
|
setErroredDownloadId(downloadId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (await fs.exists(tempDownloadPath)) {
|
LOG.info(`YT-DLP Download ${downloadId}`, `yt-dlp exited with code ${data.code}`);
|
||||||
downloadFilePath = await generateSafeFilePath(downloadFilePath);
|
|
||||||
LOG.info('NEODLP', `yt-dlp download completed with id: ${downloadId}, moving downloaded file from: "${tempDownloadPath}" to final destination: "${downloadFilePath}"`);
|
|
||||||
await fs.copyFile(tempDownloadPath, downloadFilePath);
|
|
||||||
await fs.remove(tempDownloadPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadFilePathUpdater.mutate({ download_id: downloadId, filepath: downloadFilePath }, {
|
|
||||||
onSuccess: (data) => {
|
|
||||||
console.log("Download filepath updated successfully:", data);
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error("Failed to update download filepath:", error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
downloadStatusUpdater.mutate({ download_id: downloadId, download_status: 'completed' }, {
|
|
||||||
onSuccess: (data) => {
|
|
||||||
console.log("Download status updated successfully:", data);
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error("Failed to update download status:", error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -499,6 +504,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
sponsorblock_mark: sponsorblockMark,
|
sponsorblock_mark: sponsorblockMark,
|
||||||
use_aria2: useAria2,
|
use_aria2: useAria2,
|
||||||
custom_command: customCommandArgs,
|
custom_command: customCommandArgs,
|
||||||
|
queue_config: null
|
||||||
};
|
};
|
||||||
downloadStateSaver.mutate(state, {
|
downloadStateSaver.mutate(state, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@@ -512,6 +518,36 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
} else {
|
} else {
|
||||||
console.log(line);
|
console.log(line);
|
||||||
if (line.trim() !== '') LOG.info(`YT-DLP Download ${downloadId}`, line);
|
if (line.trim() !== '') LOG.info(`YT-DLP Download ${downloadId}`, line);
|
||||||
|
|
||||||
|
if (line.startsWith('Finalpath: ')) {
|
||||||
|
downloadFilePath = line.replace('Finalpath: ', '').trim().replace(/^"|"$/g, '');
|
||||||
|
const downloadedFileExt = downloadFilePath.split('.').pop();
|
||||||
|
|
||||||
|
// Update completion status after a short delay to ensure database states are propagated correctly
|
||||||
|
console.log(`Download completed with ID: ${downloadId}, updating filepath and status after 1s delay...`);
|
||||||
|
setTimeout(() => {
|
||||||
|
LOG.info('NEODLP', `yt-dlp download completed with id: ${downloadId}`);
|
||||||
|
downloadFilePathUpdater.mutate({ download_id: downloadId, filepath: downloadFilePath as string, ext: downloadedFileExt as string }, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
console.log("Download filepath updated successfully:", data);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Failed to update download filepath:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
downloadStatusUpdater.mutate({ download_id: downloadId, download_status: 'completed' }, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
console.log("Download status updated successfully:", data);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Failed to update download status:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -591,7 +627,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
sponsorblock_remove: resumeState?.sponsorblock_remove || null,
|
sponsorblock_remove: resumeState?.sponsorblock_remove || null,
|
||||||
sponsorblock_mark: resumeState?.sponsorblock_mark || null,
|
sponsorblock_mark: resumeState?.sponsorblock_mark || null,
|
||||||
use_aria2: resumeState?.use_aria2 || 0,
|
use_aria2: resumeState?.use_aria2 || 0,
|
||||||
custom_command: resumeState?.custom_command || null
|
custom_command: resumeState?.custom_command || null,
|
||||||
|
queue_config: resumeState?.queue_config || ((!ongoingDownloads || ongoingDownloads && ongoingDownloads?.length < MAX_PARALLEL_DOWNLOADS) ? null : JSON.stringify(downloadConfig))
|
||||||
}
|
}
|
||||||
downloadStateSaver.mutate(state, {
|
downloadStateSaver.mutate(state, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@@ -683,7 +720,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
await startDownload(
|
await startDownload(
|
||||||
downloadState.playlist_id && downloadState.playlist_index ? downloadState.playlist_url : downloadState.url,
|
downloadState.playlist_id && downloadState.playlist_index ? downloadState.playlist_url : downloadState.url,
|
||||||
downloadState.format_id,
|
downloadState.format_id,
|
||||||
{
|
downloadState.queue_config ? JSON.parse(downloadState.queue_config) : {
|
||||||
output_format: null,
|
output_format: null,
|
||||||
embed_metadata: null,
|
embed_metadata: null,
|
||||||
embed_thumbnail: null,
|
embed_thumbnail: null,
|
||||||
@@ -785,7 +822,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
await startDownload(
|
await startDownload(
|
||||||
downloadToStart.url,
|
downloadToStart.url,
|
||||||
downloadToStart.format_id,
|
downloadToStart.format_id,
|
||||||
downloadConfiguration,
|
downloadToStart.queue_config ? JSON.parse(downloadToStart.queue_config) : {
|
||||||
|
output_format: null,
|
||||||
|
embed_metadata: null,
|
||||||
|
embed_thumbnail: null,
|
||||||
|
custom_command: null
|
||||||
|
},
|
||||||
downloadToStart.subtitle_id,
|
downloadToStart.subtitle_id,
|
||||||
downloadToStart
|
downloadToStart
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { RawVideoInfo } from '@/types/video';
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
interface AppContextType {
|
interface AppContextType {
|
||||||
fetchVideoMetadata: (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState) => Promise<RawVideoInfo | null>;
|
fetchVideoMetadata: (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState, downloadConfig?: DownloadConfiguration) => Promise<RawVideoInfo | null>;
|
||||||
startDownload: (url: string, selectedFormat: string, downloadConfig: DownloadConfiguration, selectedSubtitles?: string | null, resumeState?: DownloadState, playlistItems?: string) => Promise<void>;
|
startDownload: (url: string, selectedFormat: string, downloadConfig: DownloadConfiguration, selectedSubtitles?: string | null, resumeState?: DownloadState, playlistItems?: string) => Promise<void>;
|
||||||
pauseDownload: (state: DownloadState) => Promise<void>;
|
pauseDownload: (state: DownloadState) => Promise<void>;
|
||||||
resumeDownload: (state: DownloadState) => Promise<void>;
|
resumeDownload: (state: DownloadState) => Promise<void>;
|
||||||
|
|||||||
@@ -203,7 +203,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
|||||||
sponsorblock_remove = $29,
|
sponsorblock_remove = $29,
|
||||||
sponsorblock_mark = $30,
|
sponsorblock_mark = $30,
|
||||||
use_aria2 = $31,
|
use_aria2 = $31,
|
||||||
custom_command = $32
|
custom_command = $32,
|
||||||
|
queue_config = $33
|
||||||
WHERE download_id = $1`,
|
WHERE download_id = $1`,
|
||||||
[
|
[
|
||||||
downloadState.download_id,
|
downloadState.download_id,
|
||||||
@@ -237,7 +238,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
|||||||
downloadState.sponsorblock_remove,
|
downloadState.sponsorblock_remove,
|
||||||
downloadState.sponsorblock_mark,
|
downloadState.sponsorblock_mark,
|
||||||
downloadState.use_aria2,
|
downloadState.use_aria2,
|
||||||
downloadState.custom_command
|
downloadState.custom_command,
|
||||||
|
downloadState.queue_config
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -273,8 +275,9 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
|||||||
sponsorblock_remove,
|
sponsorblock_remove,
|
||||||
sponsorblock_mark,
|
sponsorblock_mark,
|
||||||
use_aria2,
|
use_aria2,
|
||||||
custom_command
|
custom_command,
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32)`,
|
queue_config
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33)`,
|
||||||
[
|
[
|
||||||
downloadState.download_id,
|
downloadState.download_id,
|
||||||
downloadState.download_status,
|
downloadState.download_status,
|
||||||
@@ -307,7 +310,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
|||||||
downloadState.sponsorblock_remove,
|
downloadState.sponsorblock_remove,
|
||||||
downloadState.sponsorblock_mark,
|
downloadState.sponsorblock_mark,
|
||||||
downloadState.use_aria2,
|
downloadState.use_aria2,
|
||||||
downloadState.custom_command
|
downloadState.custom_command,
|
||||||
|
downloadState.queue_config
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -320,11 +324,11 @@ export const updateDownloadStatus = async (download_id: string, download_status:
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateDownloadFilePath = async (download_id: string, filepath: string) => {
|
export const updateDownloadFilePath = async (download_id: string, filepath: string, ext: string) => {
|
||||||
const db = await Database.load('sqlite:database.db')
|
const db = await Database.load('sqlite:database.db')
|
||||||
return await db.execute(
|
return await db.execute(
|
||||||
'UPDATE downloads SET filepath = $2 WHERE download_id = $1',
|
'UPDATE downloads SET filepath = $2, ext = $3 WHERE download_id = $1',
|
||||||
[download_id, filepath]
|
[download_id, filepath, ext]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,4 +455,4 @@ export const deleteKvPair = async (key: string) => {
|
|||||||
'DELETE FROM kv_store WHERE key = $1',
|
'DELETE FROM kv_store WHERE key = $1',
|
||||||
[key]
|
[key]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ export function useUpdateDownloadStatus() {
|
|||||||
|
|
||||||
export function useUpdateDownloadFilePath() {
|
export function useUpdateDownloadFilePath() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (data: { download_id: string; filepath: string }) =>
|
mutationFn: (data: { download_id: string; filepath: string, ext: string }) =>
|
||||||
updateDownloadFilePath(data.download_id, data.filepath)
|
updateDownloadFilePath(data.download_id, data.filepath, data.ext)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,4 +64,4 @@ export function useDeleteKvPair() {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (key: string) => deleteKvPair(key)
|
mutationFn: (key: string) => deleteKvPair(key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export interface DownloadState {
|
|||||||
sponsorblock_mark: string | null;
|
sponsorblock_mark: string | null;
|
||||||
use_aria2: number;
|
use_aria2: number;
|
||||||
custom_command: string | null;
|
custom_command: string | null;
|
||||||
|
queue_config: string | null;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
}
|
}
|
||||||
@@ -81,6 +82,7 @@ export interface Download {
|
|||||||
sponsorblock_mark: string | null;
|
sponsorblock_mark: string | null;
|
||||||
use_aria2: number;
|
use_aria2: number;
|
||||||
custom_command: string | null;
|
custom_command: string | null;
|
||||||
|
queue_config: string | null;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
@@ -92,4 +94,4 @@ export interface DownloadProgress {
|
|||||||
downloaded: number | null;
|
downloaded: number | null;
|
||||||
total: number | null;
|
total: number | null;
|
||||||
eta: number | null;
|
eta: number | null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user