monthly update
This commit is contained in:
74
lib/db.js
Normal file
74
lib/db.js
Normal file
@@ -0,0 +1,74 @@
|
||||
const uuid = require('uuid')
|
||||
const { logger } = require('./logger')
|
||||
const { existsInProc } = require('./procUtils')
|
||||
|
||||
const db = require('better-sqlite3')('downloads.db')
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
db.exec(`CREATE TABLE downloads (
|
||||
uid varchar(36) NOT NULL,
|
||||
url text NOT NULL,
|
||||
title text,
|
||||
thumbnail text,
|
||||
created date,
|
||||
size text,
|
||||
process_pid int NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
)`)
|
||||
} catch (e) {
|
||||
logger('db', 'Table already created, ignoring')
|
||||
}
|
||||
}
|
||||
|
||||
async function get_db() {
|
||||
return db
|
||||
}
|
||||
|
||||
async function insertDownload(url, title, thumbnail, size, PID) {
|
||||
const uid = uuid.v1()
|
||||
db
|
||||
.prepare(`
|
||||
INSERT INTO downloads
|
||||
(uid, url, title, thumbnail, size, process_pid)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run(uid, url, title, thumbnail, size, PID)
|
||||
|
||||
return uid
|
||||
}
|
||||
|
||||
async function retrieveAll() {
|
||||
return db
|
||||
.prepare('SELECT * FROM downloads')
|
||||
.all()
|
||||
}
|
||||
|
||||
async function deleteDownloadById(uid) {
|
||||
db.prepare(`DELETE FROM downloads WHERE uid=${uid}`).run()
|
||||
}
|
||||
|
||||
async function deleteDownloadByPID(PID) {
|
||||
db.prepare(`DELETE FROM downloads WHERE process_pid=${PID}`).run()
|
||||
}
|
||||
|
||||
async function pruneDownloads() {
|
||||
const all = await retrieveAll()
|
||||
return all.map(job => {
|
||||
if (existsInProc(job.process_pid)) {
|
||||
return job
|
||||
} else {
|
||||
deleteDownloadByPID(job.process_pid)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
getDB: get_db,
|
||||
insertDownload: insertDownload,
|
||||
retrieveAll: retrieveAll,
|
||||
deleteDownloadById: deleteDownloadById,
|
||||
deleteDownloadByPID: deleteDownloadByPID,
|
||||
pruneDownloads: pruneDownloads,
|
||||
}
|
||||
@@ -1,29 +1,41 @@
|
||||
const { spawn } = require('child_process');
|
||||
const logger = require('./logger');
|
||||
const { from, interval } = require('rxjs');
|
||||
const { throttle } = require('rxjs/operators');
|
||||
const { insertDownload, deleteDownloadByPID, pruneDownloads } = require('./db');
|
||||
const { logger } = require('./logger');
|
||||
const { retriveStdoutFromProcFd, killProcess } = require('./procUtils');
|
||||
let settings;
|
||||
|
||||
try {
|
||||
settings = require('../settings.json')
|
||||
settings = require('../settings.json');
|
||||
}
|
||||
catch (e) {
|
||||
console.warn("settings.json not found")
|
||||
console.warn("settings.json not found");
|
||||
}
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
const download = (socket, url) => {
|
||||
if (url === '' || url === null) {
|
||||
socket.emit('progress', { status: 'Done!' })
|
||||
return
|
||||
async function download(socket, payload) {
|
||||
|
||||
if (!payload || payload.url === '' || payload.url === null) {
|
||||
socket.emit('progress', { status: 'Done!' });
|
||||
return;
|
||||
}
|
||||
|
||||
getDownloadInfo(socket, url)
|
||||
const url = payload.url
|
||||
const params = payload.params?.xa ? '-x' : '';
|
||||
|
||||
await getDownloadInfo(socket, url);
|
||||
|
||||
const ytldp = spawn(`./lib/yt-dlp${isWindows ? '.exe' : ''}`,
|
||||
['-o', `${settings.download_path || 'downloads/'}%(title)s.%(ext)s`, url]
|
||||
)
|
||||
[
|
||||
'-o', `${settings.download_path || 'downloads/'}%(title)s.%(ext)s`,
|
||||
params,
|
||||
url
|
||||
]
|
||||
);
|
||||
|
||||
await insertDownload(url, null, null, null, ytldp.pid);
|
||||
|
||||
from(ytldp.stdout) // stdout as observable
|
||||
.pipe(throttle(() => interval(500))) // discard events closer than 500ms
|
||||
@@ -31,49 +43,69 @@ const download = (socket, url) => {
|
||||
next: (stdout) => {
|
||||
//let _stdout = String(stdout)
|
||||
socket.emit('progress', formatter(String(stdout))) // finally, emit
|
||||
//logger('download', `Fetching ${stdout}`)
|
||||
//logger('download', `Fetching ${_stdout}`)
|
||||
},
|
||||
complete: () => {
|
||||
socket.emit('progress', { status: 'Done!' })
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ytldp.on('exit', () => {
|
||||
socket.emit('progress', { status: 'Done!' })
|
||||
logger('download', 'Done!')
|
||||
|
||||
deleteDownloadByPID(ytldp.pid).then(() => {
|
||||
logger('db', `Deleted ${ytldp.pid} because SIGKILL`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getDownloadInfo = (socket, url) => {
|
||||
async function retriveDownload(socket) {
|
||||
const downloads = await pruneDownloads();
|
||||
|
||||
if (downloads.length > 0) {
|
||||
for (const _download of downloads) {
|
||||
await killProcess(_download.process_pid);
|
||||
await download(socket, _download.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getDownloadInfo(socket, url) {
|
||||
let stdoutChunks = [];
|
||||
const ytdlpInfo = spawn(`./lib/yt-dlp${isWindows ? '.exe' : ''}`, ['-s', '-j', url]);
|
||||
|
||||
ytdlpInfo.stdout.on('data', (data) => {
|
||||
stdoutChunks.push(data)
|
||||
})
|
||||
stdoutChunks.push(data);
|
||||
});
|
||||
|
||||
ytdlpInfo.on('exit', () => {
|
||||
const buffer = Buffer.concat(stdoutChunks)
|
||||
const json = JSON.parse(buffer.toString())
|
||||
socket.emit('info', json)
|
||||
try {
|
||||
const buffer = Buffer.concat(stdoutChunks);
|
||||
const json = JSON.parse(buffer.toString());
|
||||
socket.emit('info', json);
|
||||
} catch (e) {
|
||||
socket.emit('progress', { status: 'Aborted' });
|
||||
logger('download', 'Done!');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const abortDownload = (socket) => {
|
||||
function abortDownload(socket) {
|
||||
const res = process.platform === 'win32' ?
|
||||
spawn('taskkill', ['/IM', 'yt-dlp.exe', '/F', '/T']) :
|
||||
spawn('killall', ['yt-dlp'])
|
||||
spawn('killall', ['yt-dlp']);
|
||||
res.on('exit', () => {
|
||||
socket.emit('progress', 'Aborted!')
|
||||
logger('download', 'Aborting downloads')
|
||||
})
|
||||
socket.emit('progress', { status: 'Aborted' });
|
||||
logger('download', 'Aborting downloads');
|
||||
});
|
||||
}
|
||||
|
||||
const formatter = (stdout) => {
|
||||
const cleanStdout = stdout
|
||||
.replace(/\s\s+/g, ' ')
|
||||
.split(' ')
|
||||
const status = cleanStdout[0].replace(/\[|\]|\r/g, '')
|
||||
.split(' ');
|
||||
const status = cleanStdout[0].replace(/\[|\]|\r/g, '');
|
||||
switch (status) {
|
||||
case 'download':
|
||||
return {
|
||||
@@ -82,7 +114,7 @@ const formatter = (stdout) => {
|
||||
size: cleanStdout[3],
|
||||
dlSpeed: cleanStdout[5],
|
||||
}
|
||||
case 'merger':
|
||||
case 'merge':
|
||||
return {
|
||||
status: 'merging',
|
||||
progress: '100',
|
||||
@@ -95,4 +127,5 @@ const formatter = (stdout) => {
|
||||
module.exports = {
|
||||
download: download,
|
||||
abortDownload: abortDownload,
|
||||
retriveDownload: retriveDownload,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
const logger = (proto, args) => {
|
||||
console.log(`[${proto}] ${args}`)
|
||||
console.log(`[${proto}]\t${args}`)
|
||||
}
|
||||
|
||||
module.exports = logger
|
||||
const splash = () => {
|
||||
console.log("-------------------------------------------------")
|
||||
console.log("yt-dlp-webUI - A web-ui for yt-dlp, simply enough")
|
||||
console.log("-------------------------------------------------")
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logger: logger,
|
||||
splash: splash,
|
||||
}
|
||||
38
lib/procUtils.js
Normal file
38
lib/procUtils.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
const { logger } = require('./logger');
|
||||
|
||||
function existsInProc(pid) {
|
||||
try {
|
||||
return fs.statSync(`/proc/${pid}`)
|
||||
} catch (e) {
|
||||
logger('proc', `pid ${pid} not found in procfs`)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
function retriveStdoutFromProcFd(pid) {
|
||||
if (existsInProc(pid)) {
|
||||
const unixSocket = fs.readlinkSync(`/proc/${pid}/fd/1`).replace('socket:[', '127.0.0.1:').replace(']', '')
|
||||
if (unixSocket) {
|
||||
console.log(unixSocket)
|
||||
logger('proc', `found pending job on pid: ${pid} attached to UNIX socket: ${unixSocket}`)
|
||||
return net.createConnection(unixSocket)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
async function killProcess(pid) {
|
||||
const res = spawn('kill', [pid])
|
||||
res.on('exit', () => {
|
||||
logger('proc', `Successfully killed yt-dlp process, pid: ${pid}`)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
existsInProc: existsInProc,
|
||||
//retriveStdoutFromProcFd: retriveStdoutFromProcFd,
|
||||
killProcess: killProcess,
|
||||
}
|
||||
Reference in New Issue
Block a user