From 06e1588d0692a786805f06bb7d283617d0868afe Mon Sep 17 00:00:00 2001 From: huangjx Date: Sat, 19 Feb 2022 00:44:45 +0800 Subject: [PATCH] added delete option for youtube-dl download;added options for copying links; --- appinfo/info.xml | 2 +- img/clippy.svg | 1 + lib/Controller/YoutubeController.php | 43 +++++++++++++----- lib/Search/torrentSearch.php | 2 +- lib/Tools/Helper.php | 27 +++++++++++- lib/Tools/Youtube.php | 12 ++--- lib/Tools/YoutubeHelper.php | 11 +++-- src/actions/buttonActions.js | 13 ++++++ src/css/clipboard.scss | 5 +++ src/css/style.scss | 4 +- src/lib/tooltip.js | 50 +++++++++++++++++++++ src/utils/clipboard.js | 66 ++++++++++++++++++++++++++++ src/utils/helper.js | 6 +++ 13 files changed, 219 insertions(+), 23 deletions(-) create mode 100644 img/clippy.svg create mode 100644 src/css/clipboard.scss create mode 100644 src/lib/tooltip.js create mode 100644 src/utils/clipboard.js diff --git a/appinfo/info.xml b/appinfo/info.xml index bd818e3..c41572a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -8,7 +8,7 @@ Search for torrents within the app from mutiple BT sites; Control Aria2 and manage download tasks from the web; download videos from 700+ video sites(youtube,youku,vimo,dailymotion,twitter,facebook and the likes - 0.6.1 + 0.6.5 agpl jiaxinhuang NCDownloader diff --git a/img/clippy.svg b/img/clippy.svg new file mode 100644 index 0000000..6e30e1a --- /dev/null +++ b/img/clippy.svg @@ -0,0 +1 @@ + diff --git a/lib/Controller/YoutubeController.php b/lib/Controller/YoutubeController.php index 4a57375..c691ef6 100644 --- a/lib/Controller/YoutubeController.php +++ b/lib/Controller/YoutubeController.php @@ -50,18 +50,18 @@ class YoutubeController extends Controller $folderLink = $this->urlGenerator->linkToRoute('files.view.index', $params); foreach ($data as $value) { $tmp = []; + $extra = unserialize($value['data']); $filename = sprintf('%s', $folderLink, $value['filename']); - $fileInfo = sprintf("%s | %s", $value['filesize'], date("Y-m-d H:i:s", $value['timestamp'])); + $fileInfo = sprintf('
%s | % s
', $extra['link'], $value['filesize'], date("Y-m-d H:i:s", $value['timestamp'])); $tmp['filename'] = array($filename, $fileInfo); $tmp['speed'] = explode("|", $value['speed']); $tmp['progress'] = $value['progress']; - if ((int) $value['status'] == Helper::STATUS['COMPLETE']) { - $path = $this->urlGenerator->linkToRoute('ncdownloader.Youtube.Delete'); - $tmp['actions'][] = ['name' => 'delete', 'path' => $path]; - } else { - $path = $this->urlGenerator->linkToRoute('ncdownloader.Youtube.Redownload'); - $tmp['actions'][] = ['name' => 'refresh', 'path' => $path]; - } + + $path = $this->urlGenerator->linkToRoute('ncdownloader.Youtube.Delete'); + $tmp['actions'][] = ['name' => 'delete', 'path' => $path]; + $path = $this->urlGenerator->linkToRoute('ncdownloader.Youtube.Redownload'); + $tmp['actions'][] = ['name' => 'refresh', 'path' => $path]; + $tmp['data_gid'] = $value['gid'] ?? 0; array_push($resp['row'], $tmp); } @@ -111,10 +111,30 @@ class YoutubeController extends Controller return new JSONResponse(['error' => "no gid value is received!"]); } - if ($this->dbconn->deleteByGid($gid)) { - return new JSONResponse(['message' => $gid . " Deleted!"]); - + $row = $this->dbconn->getByGid($gid); + $data = unserialize($row['data']); + if (!isset($data['pid'])) { + if ($this->dbconn->deleteByGid($gid)) { + $msg = sprintf("%s is deleted from database!", $gid); + } + return new JSONResponse(['message' => $msg]); } + $pid = $data['pid']; + if (!Helper::isRunning($pid)) { + if ($this->dbconn->deleteByGid($gid)) { + $msg = sprintf("%s is deleted from database!", $gid); + } else { + $msg = sprintf("process %d is not running!", $pid); + } + } else { + if (Helper::stop($pid)) { + $msg = sprintf("process %d has been terminated!", $pid); + } else { + $msg = sprintf("failed to terminate process %d!", $pid); + } + $this->dbconn->deleteByGid($gid); + } + return new JSONResponse(['message' => $msg]); } /** * @NoAdminRequired @@ -129,6 +149,7 @@ class YoutubeController extends Controller $row = $this->dbconn->getByGid($gid); $data = unserialize($row['data']); if (!empty($data['link'])) { + //$this->dbconn->deleteByGid($gid); $resp = $this->youtube->forceIPV4()->download($data['link']); folderScan::sync(); return new JSONResponse($resp); diff --git a/lib/Search/torrentSearch.php b/lib/Search/torrentSearch.php index 6a7499b..426f74e 100644 --- a/lib/Search/torrentSearch.php +++ b/lib/Search/torrentSearch.php @@ -46,7 +46,7 @@ class torrentSearch if (!$value) { continue; } - $value['actions'][] = array("name" => 'download', 'path' => '/index.php/apps/ncdownloader/new'); + $value['actions'] = [["name" => 'download', 'path' => '/index.php/apps/ncdownloader/new'], ['name' => 'clipboard']]; } } diff --git a/lib/Tools/Helper.php b/lib/Tools/Helper.php index 8405cbf..0c48dfc 100644 --- a/lib/Tools/Helper.php +++ b/lib/Tools/Helper.php @@ -43,6 +43,7 @@ class Helper { $host = parse_url($url, PHP_URL_HOST); //$sites = ['twitter.com', 'www.twitter.com']; + $sites = []; return (bool) (in_array($host, $sites)); } public static function parseUrl($url) @@ -134,7 +135,7 @@ class Helper public static function debug($msg) { $logger = \OC::$server->getLogger(); - $logger->debug($msg, ['app' => 'ncdownloader']); + $logger->error($msg, ['app' => 'ncdownloader']); } public static function log($msg, $file = "/tmp/nc.log") @@ -298,4 +299,28 @@ class Helper return filter_var($string, FILTER_SANITIZE_STRING); } + public static function doSignal($pid, $signal): bool + { + if (\function_exists('posix_kill')) { + $ok = @posix_kill($pid, $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { + $ok = false === fgets($pipes[2]); + } + + if (!$ok) { + return false; + } + return true; + } + + public static function isRunning($pid) + { + return self::doSignal($pid, 0); + } + + public static function stop($pid) + { + return self::doSignal($pid, 9); + } + } diff --git a/lib/Tools/Youtube.php b/lib/Tools/Youtube.php index b75a32f..40698a5 100644 --- a/lib/Tools/Youtube.php +++ b/lib/Tools/Youtube.php @@ -154,11 +154,13 @@ class Youtube //\OC::$server->getLogger()->error($process->getCommandLine(), ['app' => 'PHP']); $process = new Process($this->options, null, $this->env); $process->setTimeout($this->timeout); - $process->run(function ($type, $buffer) use ($url) { + $data = ['link' => $url]; + $process->run(function ($type, $buffer) use ($data, $process) { if (Process::ERR === $type) { - $this->onError($buffer); + // $this->onError($buffer); } else { - $this->onOutput($buffer, $url); + $data['pid'] = $process->getPid(); + $this->onOutput($buffer, $data); } }); if ($process->isSuccessful()) { @@ -173,9 +175,9 @@ class Youtube $this->helper->log($buffer); } - public function onOutput($buffer, $url) + public function onOutput($buffer, $extra) { - $this->helper->run($buffer, $url); + $this->helper->run($buffer, $extra); } public function getDownloadUrl($url) { diff --git a/lib/Tools/YoutubeHelper.php b/lib/Tools/YoutubeHelper.php index 4e963e7..5d03d7e 100644 --- a/lib/Tools/YoutubeHelper.php +++ b/lib/Tools/YoutubeHelper.php @@ -16,6 +16,7 @@ class YoutubeHelper '(\s+in\s+(?[\d:]{2,8}))?#i'; public $file = null; public $filesize = null; + protected $pid = 0; public function __construct() { $this->dbconn = new DbHelper(); @@ -47,9 +48,13 @@ class YoutubeHelper //$sql = sprintf("UPDATE %s set status = ? WHERE gid = ?", $this->tablename); $this->dbconn->updateStatus($this->gid, $this->status); } - public function run($buffer, $url) + public function setPid($pid) { - $this->gid = Helper::generateGID($url); + $this->pid = $pid; + } + public function run($buffer, $extra) + { + $this->gid = Helper::generateGID($extra['link']); $file = $this->getFilePath($buffer); if ($file) { $data = [ @@ -59,7 +64,7 @@ class YoutubeHelper 'filename' => basename($file), 'status' => Helper::STATUS['ACTIVE'], 'timestamp' => time(), - 'data' => serialize(['link' => $url]), + 'data' => serialize($extra), ]; //save the filename as this runs only once $this->file = $file; diff --git a/src/actions/buttonActions.js b/src/actions/buttonActions.js index 28425d3..fe97bf4 100644 --- a/src/actions/buttonActions.js +++ b/src/actions/buttonActions.js @@ -1,6 +1,9 @@ import Http from '../lib/http' import helper from '../utils/helper' import eventHandler from '../lib/eventHandler' +import Clipboard from '../utils/clipboard' +import '../css/clipboard.scss'; + const buttonHandler = (event, type) => { let element = event.target; event.stopPropagation(); @@ -9,6 +12,11 @@ const buttonHandler = (event, type) => { let row, data = {}; let removeRow = true; if (row = element.closest('.table-row-search')) { + if (element.className == 'icon-clipboard') { + const clippy = new Clipboard(element, row.dataset.link); + clippy.Copy(); + return; + } data['text-input-value'] = row.dataset.link; } else { row = element.closest('.table-row') @@ -38,5 +46,10 @@ const buttonHandler = (event, type) => { export default { run: function () { eventHandler.add("click", "#ncdownloader-table-wrapper", ".table-cell-action-item .button-container button", e => buttonHandler(e, '')); + eventHandler.add("click", "#ncdownloader-table-wrapper", ".table-row button.icon-clipboard", function (e) { + let element = e.target; + const clippy = new Clipboard(element); + clippy.Copy(); + }); } } \ No newline at end of file diff --git a/src/css/clipboard.scss b/src/css/clipboard.scss new file mode 100644 index 0000000..e819b7a --- /dev/null +++ b/src/css/clipboard.scss @@ -0,0 +1,5 @@ +#ncdownloader-tooltip { + color : #5d6395; + font-size : medium; + font-weight: bold; +} \ No newline at end of file diff --git a/src/css/style.scss b/src/css/style.scss index f64282e..186d16f 100644 --- a/src/css/style.scss +++ b/src/css/style.scss @@ -41,7 +41,9 @@ .icon-add { background-image: url('../../img/add.svg'); } - + .icon-clipboard { + background-image: url('../../img/clippy.svg'); + } #ncdownloader-form-wrapper { margin-left: 2px; diff --git a/src/lib/tooltip.js b/src/lib/tooltip.js new file mode 100644 index 0000000..b73a7f5 --- /dev/null +++ b/src/lib/tooltip.js @@ -0,0 +1,50 @@ +import $ from 'jquery' + +class Tooltip { + id = "ncdownloader-tooltip"; + messageNode; + style = {}; + text; + constructor(element, text) { + if (typeof element !== 'string' && !(element instanceof HTMLElement)) + throw ("invalid element!"); + this.element = typeof element == 'object' ? element : document.querySelector(element); + this.style = { + position: 'fixed', + display: 'block', + } + this.text = text || element.getAttribute("data-text"); + } + create(id) { + this.messageNode = document.createElement("div"); + this.messageNode.classList.add(this.id); + this.messageNode.setAttribute("id", this.id); + this.messageNode.style.display = this.style.display; + this.messageNode.style.position = this.style.position; + this.messageNode.style.zIndex = 10000; + let div = document.createElement('div'); + div.setAttribute("id", id); + let text = document.createTextNode(this.text); + div.appendChild(text); + this.messageNode.appendChild(div); + this.setPosition(); + return this; + } + render() { + document.body.appendChild(this.messageNode); + } + html() { + return this.messageNode; + } + setPosition(bottomMargin, leftMargin) { + bottomMargin = bottomMargin || 20; + leftMargin = leftMargin || 0; + let rect = this.element.getBoundingClientRect(); + let top = (rect['top'] + bottomMargin) + "px"; + let left = (rect['left'] - leftMargin) + "px"; + this.messageNode.style.top = top; + this.messageNode.style.left = left + } +} + +export default Tooltip; \ No newline at end of file diff --git a/src/utils/clipboard.js b/src/utils/clipboard.js new file mode 100644 index 0000000..4d1c646 --- /dev/null +++ b/src/utils/clipboard.js @@ -0,0 +1,66 @@ +import Tooltip from "../lib/tooltip"; + +class Clipboard { + element; + text; + + constructor(element, text) { + if (typeof element !== 'string' && !(element instanceof HTMLElement)) + throw ("invalid element!"); + this.element = typeof element == 'object' ? element : document.querySelector(element); + this.text = text || element.getAttribute("data-text"); + } + + _copy(text) { + let textArea = document.createElement("textarea"); + textArea.value = text; + + textArea.style.top = "0"; + textArea.style.left = "0"; + textArea.style.position = "fixed"; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + let result; + try { + result = document.execCommand('copy'); + //console.log('copied using exceCommand'); + + } catch (err) { + console.error('failed to copy', err); + result = false; + } finally { + document.body.removeChild(textArea); + } + if (result) { + this.ShowMsg("Copied!"); + } + } + + ShowMsg(msg) { + let tip = new Tooltip(this.element, msg); + let html = tip.create('copy-alert').html(); + document.body.appendChild(html); + const callback = (element) => { + element.remove() + } + setTimeout(() => { + callback(html) + }, 1000); + } + + Copy() { + if (!navigator.clipboard) { + return this._copy(this.text); + } + return navigator.clipboard.writeText(this.text).then(() => { + this.ShowMsg("Copied!"); + }, function (err) { + console.error('failed to copy text: ', err); + }); + } + +} + +export default Clipboard; \ No newline at end of file diff --git a/src/utils/helper.js b/src/utils/helper.js index 7f41624..cc67889 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -172,6 +172,12 @@ const helper = { >Loading...`; return html; + }, + getCssVar(prop) { + return window.getComputedStyle(document.documentElement).getPropertyValue(prop); + }, + getScrollTop() { + return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; } }