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.
-