From 02f8584762a7d8032771152e444de43c3e0af8f7 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 30 Dec 2021 20:45:11 -0700 Subject: [PATCH] Squashed commit of the following: commit 1663e1be9e1a9b82521be9ae257e0b91bfdd3199 Author: Roland Date: Thu Dec 30 19:37:25 2021 -0700 Setting downloader to video when a channel schedules it's videos for download. commit a6811f9e87b0754cc7c1dd197ae300f197620bbb Author: Roland Date: Thu Dec 30 19:33:27 2021 -0700 Setting channel downloader when spreading channel downloads. commit b63c8f132aa00705c1a46dc04c51a4889fcd7ec7 Author: Roland Date: Thu Dec 30 19:28:53 2021 -0700 Fixing DownloadManager.kill commit 120ce7cd2cff981d2af7e8d33d139be9e2b50e13 Author: Roland Date: Thu Dec 30 19:10:12 2021 -0700 Fixing OTP api urls. commit d3b985241b0851c926feafb1b875ab6203057bf2 Author: Roland Date: Thu Dec 30 19:07:48 2021 -0700 Channel Cancel button does not submit form. commit 9d1a0ecd55f08a4bf255030045c1307c3d716157 Author: Roland Date: Thu Dec 30 19:00:53 2021 -0700 A Channel without an existing Download cannot be downloaded. commit e7c920f3825a0f12f5475f01ce11632665cdb3f6 Author: Roland Date: Thu Dec 30 18:41:02 2021 -0700 Fixing typing. commit d56c8fde249e94f858214c16197da1b8a0919422 Author: Roland Date: Thu Dec 30 18:36:02 2021 -0700 Testing bad downloader param. commit e3c57ef674ecdf3bd39df4fe341e516ba0edab92 Author: Roland Date: Thu Dec 30 18:31:35 2021 -0700 Adding __json__ to Downloader for sending them to the FE. Hiding ChannelDownloader because it will be handled specifically. commit 047b50711919b6aa2d4a7fe8e25a963e60d4ee3a Author: Roland Date: Thu Dec 30 18:20:37 2021 -0700 Downloader can be specified by the user. commit 4a169ba43cfd75edf0db0d89f78ec1bf4c4dfe3a Author: Roland Date: Tue Dec 28 18:37:30 2021 -0700 Download info a set in create_downloads. commit 8778d53fa1063faffba8e2ba505cd9f0de7be20e Author: Roland Date: Tue Dec 28 18:21:43 2021 -0700 Fixing Download.info_json name. commit b28bacfd5be6b0df82944b246db7248cc4e3c405 Author: Roland Date: Tue Dec 28 18:17:53 2021 -0700 Downloader names are in the class, not in the init. commit f5ce952da01540a856eb5a0b084496da5c9279cf Author: Roland Date: Tue Dec 28 18:07:42 2021 -0700 Download info can be saved during the download process and can be used by the Downloader. --- .../versions/db0a1560d923_downloader_info.py | 33 +++++ app/src/api.js | 38 ++++-- app/src/components/Admin.js | 3 +- app/src/components/Channels.js | 7 +- app/src/components/Upload.js | 50 +++++-- modules/archive/__init__.py | 11 +- modules/otp/api.py | 4 +- modules/videos/channel/lib.py | 26 ++-- modules/videos/conftest.py | 20 ++- modules/videos/downloader.py | 52 ++++--- modules/videos/models.py | 3 - modules/videos/test/test_downloader.py | 128 +++++++++++++++--- wrolpi/conftest.py | 7 + wrolpi/downloader.py | 120 +++++++++------- wrolpi/root_api.py | 13 +- wrolpi/schema.py | 3 +- wrolpi/test/test_downloader.py | 48 ++++--- 17 files changed, 416 insertions(+), 150 deletions(-) create mode 100644 alembic/versions/db0a1560d923_downloader_info.py diff --git a/alembic/versions/db0a1560d923_downloader_info.py b/alembic/versions/db0a1560d923_downloader_info.py new file mode 100644 index 00000000..184029b3 --- /dev/null +++ b/alembic/versions/db0a1560d923_downloader_info.py @@ -0,0 +1,33 @@ +"""Downloader info + +Revision ID: db0a1560d923 +Revises: 84b6993a6091 +Create Date: 2021-12-27 16:46:02.929119 + +""" +import os + +from alembic import op +from sqlalchemy.orm import Session + +# revision identifiers, used by Alembic. +revision = 'db0a1560d923' +down_revision = '84b6993a6091' +branch_labels = None +depends_on = None + +DOCKERIZED = True if os.environ.get('DOCKER', '').lower().startswith('t') else False + + +def upgrade(): + bind = op.get_bind() + session = Session(bind=bind) + session.execute('ALTER TABLE download ADD COLUMN IF NOT EXISTS info_json JSONB') + session.execute('ALTER TABLE download ADD COLUMN IF NOT EXISTS downloader TEXT') + + +def downgrade(): + bind = op.get_bind() + session = Session(bind=bind) + session.execute('ALTER TABLE download DROP COLUMN IF EXISTS info_json') + session.execute('ALTER TABLE download DROP COLUMN IF EXISTS downloader') diff --git a/app/src/api.js b/app/src/api.js index f261a88d..02e214cf 100644 --- a/app/src/api.js +++ b/app/src/api.js @@ -36,7 +36,7 @@ async function apiCall(url, method, body, ms = 10000) { if (response.status === 403 && (await response.json())['code'] === 17) { toast({ type: 'error', - header: 'WROL Mode is enabled!', + title: 'WROL Mode is enabled!', description: 'This functionality is not enabled while WROL Mode is enabled!', time: 5000, }); @@ -47,14 +47,14 @@ async function apiCall(url, method, body, ms = 10000) { if (e.message === 'promise timeout') { toast({ type: 'error', - header: 'Server failed to respond!', + title: 'Server failed to respond!', description: 'Timeout while waiting for server response. See server logs.', time: 5000, }); } else { toast({ type: 'error', - header: 'Error!', + title: 'Error!', description: 'See client logs', time: 5000, }); @@ -138,7 +138,7 @@ export async function deleteVideo(video_id) { if (data.code === 17) { toast({ type: 'warning', - header: 'WROL Mode Enabled', + title: 'WROL Mode Enabled', description: 'This cannot be done while WROL Mode is enabled.', time: 5000, }); @@ -147,7 +147,7 @@ export async function deleteVideo(video_id) { if (response.status !== 204) { toast({ type: 'error', - header: 'Unexpected server response', + title: 'Unexpected server response', description: 'Failed to delete video. See server logs.', time: 5000, }); @@ -209,7 +209,16 @@ export async function download() { export async function downloadChannel(link) { let url = `${VIDEOS_API}/download/${link}`; - await fetch(url, {method: 'POST'}); + let response = await apiPost(url); + let json = await response.json(); + if (response.status === 400 && json['code'] === 30) { + toast({ + type: 'error', + title: 'Cannot Download!', + description: 'This channel does not have a download record. Create one!', + time: 5000, + }); + } } export async function refreshChannel(link) { @@ -219,11 +228,11 @@ export async function refreshChannel(link) { export async function encryptOTP(otp, plaintext) { let body = {otp, plaintext}; - let response = await apiPost(`${OTP_API}:encrypt_otp`, body); + let response = await apiPost(`${OTP_API}/encrypt_otp`, body); if (response.status !== 200) { toast({ type: 'error', - header: 'Error!', + title: 'Error!', description: 'Failed to encrypt OTP', time: 5000, }); @@ -234,11 +243,11 @@ export async function encryptOTP(otp, plaintext) { export async function decryptOTP(otp, ciphertext) { let body = {otp, ciphertext}; - let response = await apiPost(`${OTP_API}:decrypt_otp`, body); + let response = await apiPost(`${OTP_API}/decrypt_otp`, body); if (response.status !== 200) { toast({ type: 'error', - header: 'Error!', + title: 'Error!', description: 'Failed to decrypt OTP', time: 5000, }); @@ -339,8 +348,8 @@ export async function fetchDomains() { } } -export async function postDownload(urls) { - let body = {'urls': urls}; +export async function postDownload(urls, downloader) { + let body = {urls: urls, downloader: downloader}; let response = await apiPost(`${API_URI}/download`, body); return response; } @@ -350,3 +359,8 @@ export async function killDownload(download_id) { let response = await apiPost(`${API_URI}/download/${download_id}/kill`); return response; } + +export async function getDownloaders() { + let response = await apiGet(`${API_URI}/downloaders`); + return await response.json(); +} diff --git a/app/src/components/Admin.js b/app/src/components/Admin.js index b7035cc7..d8a6f782 100644 --- a/app/src/components/Admin.js +++ b/app/src/components/Admin.js @@ -219,7 +219,8 @@ class StoppableRow extends React.Component { handleStart = async (e) => { e.preventDefault(); - await postDownload(`${this.props.url}`); + let downloader = this.props.downloader || null; + await postDownload(`${this.props.url}`, downloader); this.closeStart(); await this.props.fetchDownloads(); }; diff --git a/app/src/components/Channels.js b/app/src/components/Channels.js index 4c600da1..7d110f16 100644 --- a/app/src/components/Channels.js +++ b/app/src/components/Channels.js @@ -269,6 +269,11 @@ class ChannelPage extends APIForm { this.setState({deleteOpen: true}); } + handleCancel = (e) => { + e.preventDefault(); + this.props.history.goBack(); + } + render() { return ( @@ -410,7 +415,7 @@ class ChannelPage extends APIForm { diff --git a/app/src/components/Upload.js b/app/src/components/Upload.js index 1338ba61..451bd189 100644 --- a/app/src/components/Upload.js +++ b/app/src/components/Upload.js @@ -1,6 +1,6 @@ import React from "react"; -import {Button, Form, Header, Segment, TextArea} from "semantic-ui-react"; -import {postDownload} from "../api"; +import {Button, Dropdown, Form, Header, Segment, TextArea} from "semantic-ui-react"; +import {getDownloaders, postDownload} from "../api"; const validUrl = /^(http|https):\/\/[^ "]+$/; @@ -11,14 +11,29 @@ class Downloader extends React.Component { urls: '', valid: true, pending: false, + downloaders: [], + downloader: null, }; } + async componentDidMount() { + await this.fetchDownloaders(); + } + + fetchDownloaders = async () => { + let {downloaders} = await getDownloaders(); + downloaders = downloaders.map((i) => { + return {key: i.name, text: i.pretty_name || i.name, value: i.name} + }) + this.setState({downloaders}); + } + submitDownload = async () => { - if (this.state.urls) { + let {urls, downloader} = this.state; + if (urls) { this.setState({pending: true}); try { - let response = await postDownload(this.state.urls); + let response = await postDownload(urls, downloader); if (response.status === 204) { this.setState({urls: '', pending: false}); } @@ -52,14 +67,25 @@ class Downloader extends React.Component { return (
-
Enter the URLs you wish to bring offline.
-