Rename ycdl.py -> ycdldb.py; add exceptions.py.

This commit is contained in:
voussoir 2020-03-11 14:27:27 -07:00
parent 3d3da805b6
commit b48c2fc37c
4 changed files with 94 additions and 44 deletions

View file

@ -29,7 +29,7 @@ STATIC_DIR = root_dir.with_child('static')
FAVICON_PATH = STATIC_DIR.with_child('favicon.png') FAVICON_PATH = STATIC_DIR.with_child('favicon.png')
youtube_core = ycdl.ytapi.Youtube(bot.get_youtube_key()) youtube_core = ycdl.ytapi.Youtube(bot.get_youtube_key())
youtube = ycdl.YCDL(youtube_core) ycdldb = ycdl.ycdldb.YCDLDB(youtube_core)
site = flask.Flask( site = flask.Flask(
__name__, __name__,
@ -136,9 +136,9 @@ def favicon():
@site.route('/channels') @site.route('/channels')
def get_channels(): def get_channels():
channels = youtube.get_channels() channels = ycdldb.get_channels()
for channel in channels: for channel in channels:
channel['has_pending'] = youtube.channel_has_pending(channel['id']) channel['has_pending'] = ycdldb.channel_has_pending(channel['id'])
return flask.render_template('channels.html', channels=channels) return flask.render_template('channels.html', channels=channels)
@site.route('/videos') @site.route('/videos')
@ -149,14 +149,14 @@ def get_channels():
def get_channel(channel_id=None, download_filter=None): def get_channel(channel_id=None, download_filter=None):
if channel_id is not None: if channel_id is not None:
try: try:
youtube.add_channel(channel_id) ycdldb.add_channel(channel_id)
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
channel = youtube.get_channel(channel_id) channel = ycdldb.get_channel(channel_id)
else: else:
channel = None channel = None
videos = youtube.get_videos(channel_id=channel_id, download_filter=download_filter) videos = ycdldb.get_videos(channel_id=channel_id, download_filter=download_filter)
search_terms = request.args.get('q', '').lower().strip().replace('+', ' ').split() search_terms = request.args.get('q', '').lower().strip().replace('+', ' ').split()
if search_terms: if search_terms:
@ -164,8 +164,8 @@ def get_channel(channel_id=None, download_filter=None):
video_id = request.args.get('v', '') video_id = request.args.get('v', '')
if video_id: if video_id:
youtube.insert_video(video_id) ycdldb.insert_video(video_id)
videos = [youtube.get_video(video_id)] videos = [ycdldb.get_video(video_id)]
limit = request.args.get('limit', None) limit = request.args.get('limit', None)
if limit is not None: if limit is not None:
@ -184,8 +184,8 @@ def get_channel(channel_id=None, download_filter=None):
'channel.html', 'channel.html',
channel=channel, channel=channel,
download_filter=download_filter, download_filter=download_filter,
videos=videos,
query_string='?' + request.query_string.decode('utf-8'), query_string='?' + request.query_string.decode('utf-8'),
videos=videos,
) )
@site.route('/mark_video_state', methods=['POST']) @site.route('/mark_video_state', methods=['POST'])
@ -197,16 +197,16 @@ def post_mark_video_state():
try: try:
video_ids = video_ids.split(',') video_ids = video_ids.split(',')
for video_id in video_ids: for video_id in video_ids:
youtube.mark_video_state(video_id, state, commit=False) ycdldb.mark_video_state(video_id, state, commit=False)
youtube.sql.commit() ycdldb.sql.commit()
except ycdl.NoSuchVideo: except ycdl.exceptions.NoSuchVideo:
youtube.rollback() ycdldb.rollback()
traceback.print_exc() traceback.print_exc()
flask.abort(404) flask.abort(404)
except ycdl.InvalidVideoState: except ycdl.exceptions.InvalidVideoState:
youtube.rollback() ycdldb.rollback()
flask.abort(400) flask.abort(400)
return make_json_response({'video_ids': video_ids, 'state': state}) return make_json_response({'video_ids': video_ids, 'state': state})
@ -215,7 +215,7 @@ def post_mark_video_state():
def post_refresh_all_channels(): def post_refresh_all_channels():
force = request.form.get('force', False) force = request.form.get('force', False)
force = ycdl.helpers.truthystring(force) force = ycdl.helpers.truthystring(force)
youtube.refresh_all_channels(force=force) ycdldb.refresh_all_channels(force=force)
return make_json_response({}) return make_json_response({})
@site.route('/refresh_channel', methods=['POST']) @site.route('/refresh_channel', methods=['POST'])
@ -229,14 +229,14 @@ def post_refresh_channel():
if not (len(channel_id) == 24 and channel_id.startswith('UC')): if not (len(channel_id) == 24 and channel_id.startswith('UC')):
# It seems they have given us a username instead. # It seems they have given us a username instead.
try: try:
channel_id = youtube.youtube.get_user_id(username=channel_id) channel_id = ycdldb.youtube.get_user_id(username=channel_id)
except IndexError: except IndexError:
flask.abort(404) flask.abort(404)
force = request.form.get('force', False) force = request.form.get('force', False)
force = ycdl.helpers.truthystring(force) force = ycdl.helpers.truthystring(force)
youtube.add_channel(channel_id, commit=False) ycdldb.add_channel(channel_id, commit=False)
youtube.refresh_channel(channel_id, force=force) ycdldb.refresh_channel(channel_id, force=force)
return make_json_response({}) return make_json_response({})
@site.route('/start_download', methods=['POST']) @site.route('/start_download', methods=['POST'])
@ -247,11 +247,11 @@ def post_start_download():
try: try:
video_ids = video_ids.split(',') video_ids = video_ids.split(',')
for video_id in video_ids: for video_id in video_ids:
youtube.download_video(video_id, commit=False) ycdldb.download_video(video_id, commit=False)
youtube.sql.commit() ycdldb.sql.commit()
except ycdl.ytapi.VideoNotFound: except ycdl.ytapi.VideoNotFound:
youtube.rollback() ycdldb.rollback()
flask.abort(404) flask.abort(404)
return make_json_response({'video_ids': video_ids, 'state': 'downloaded'}) return make_json_response({'video_ids': video_ids, 'state': 'downloaded'})
@ -266,7 +266,7 @@ def refresher_thread():
time.sleep(60 * 60 * 6) time.sleep(60 * 60 * 6)
print('Starting refresh job.') print('Starting refresh job.')
thread_kwargs = {'force': False, 'skip_failures': True} thread_kwargs = {'force': False, 'skip_failures': True}
refresh_job = threading.Thread(target=youtube.refresh_all_channels, kwargs=thread_kwargs, daemon=True) refresh_job = threading.Thread(target=ycdldb.refresh_all_channels, kwargs=thread_kwargs, daemon=True)
refresh_job.start() refresh_job.start()
refresher = threading.Thread(target=refresher_thread, daemon=True) refresher = threading.Thread(target=refresher_thread, daemon=True)

View file

@ -1,5 +1,4 @@
from . import exceptions
from . import helpers from . import helpers
from . import ycdl from . import ycdldb
from . import ytapi from . import ytapi
YCDL = ycdl.YCDL

53
ycdl/exceptions.py Normal file
View file

@ -0,0 +1,53 @@
import re
def pascal_to_loudsnakes(text):
'''
NoSuchPhoto -> NO_SUCH_PHOTO
'''
match = re.findall(r'[A-Z][a-z]*', text)
text = '_'.join(match)
text = text.upper()
return text
class ErrorTypeAdder(type):
'''
During definition, the Exception class will automatically receive a class
attribute called `error_type` which is just the class's name as a string
in the loudsnake casing style. NoSuchPhoto -> NO_SUCH_PHOTO.
This is used for serialization of the exception object and should
basically act as a status code when displaying the error to the user.
Thanks Unutbu
http://stackoverflow.com/a/18126678
'''
def __init__(cls, name, bases, clsdict):
type.__init__(cls, name, bases, clsdict)
cls.error_type = pascal_to_loudsnakes(name)
class YCDLException(Exception, metaclass=ErrorTypeAdder):
'''
Subtypes should have a class attribute `error_message`. The error message
may contain {format} strings which will be formatted using the
Exception's constructor arguments.
'''
error_message = ''
def __init__(self, *args, **kwargs):
super().__init__()
self.given_args = args
self.given_kwargs = kwargs
self.error_message = self.error_message.format(*args, **kwargs)
self.args = (self.error_message, args, kwargs)
def __str__(self):
return self.error_type + '\n' + self.error_message
class InvalidVideoState(YCDLException):
error_message = '{} is not a valid state.'
class NoSuchVideo(YCDLException):
error_message = 'Video {} does not exist.'
class DatabaseOutOfDate(YCDLException):
error_message = 'Database is out-of-date. {current} should be {new}.'

View file

@ -3,6 +3,7 @@ import os
import sqlite3 import sqlite3
import traceback import traceback
from . import exceptions
from . import helpers from . import helpers
from . import ytapi from . import ytapi
@ -10,7 +11,7 @@ from voussoirkit import sqlhelpers
def YOUTUBE_DL_COMMAND(video_id): def YOUTUBE_DL_COMMAND(video_id):
path = 'D:\\Incoming\\ytqueue\\{id}.ytqueue'.format(id=video_id) path = f'D:\\Incoming\\ytqueue\\{video_id}.ytqueue'
open(path, 'w') open(path, 'w')
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -74,9 +75,6 @@ SQL_VIDEO = {key:index for (index, key) in enumerate(SQL_VIDEO_COLUMNS)}
DEFAULT_DBNAME = 'ycdl.db' DEFAULT_DBNAME = 'ycdl.db'
ERROR_DATABASE_OUTOFDATE = 'Database is out-of-date. {current} should be {new}.'
def assert_is_abspath(path): def assert_is_abspath(path):
''' '''
TO DO: Determine whether this is actually correct. TO DO: Determine whether this is actually correct.
@ -85,13 +83,7 @@ def assert_is_abspath(path):
raise ValueError('Not an abspath') raise ValueError('Not an abspath')
class InvalidVideoState(Exception): class YCDLDB:
pass
class NoSuchVideo(Exception):
pass
class YCDL:
def __init__(self, youtube, database_filename=None, youtube_dl_function=None): def __init__(self, youtube, database_filename=None, youtube_dl_function=None):
self.youtube = youtube self.youtube = youtube
if database_filename is None: if database_filename is None:
@ -105,10 +97,7 @@ class YCDL:
self.cur.execute('PRAGMA user_version') self.cur.execute('PRAGMA user_version')
existing_version = self.cur.fetchone()[0] existing_version = self.cur.fetchone()[0]
if existing_version != DATABASE_VERSION: if existing_version != DATABASE_VERSION:
message = ERROR_DATABASE_OUTOFDATE raise exceptions.DatabaseOutOfDate(current=existing_version, new=DATABASE_VERSION)
message = message.format(current=existing_version, new=DATABASE_VERSION)
print(message)
raise SystemExit
if youtube_dl_function: if youtube_dl_function:
self.youtube_dl_function = youtube_dl_function self.youtube_dl_function = youtube_dl_function
@ -203,6 +192,15 @@ class YCDL:
if commit: if commit:
self.sql.commit() self.sql.commit()
def get_all_states(self):
query = 'SELECT DISTINCT download FROM videos'
self.cur.execute(query)
states = self.cur.fetchall()
if states is None:
return []
states = [row[0] for row in states]
return sorted(states)
def get_channel(self, channel_id): def get_channel(self, channel_id):
self.cur.execute('SELECT * FROM channels WHERE id == ?', [channel_id]) self.cur.execute('SELECT * FROM channels WHERE id == ?', [channel_id])
fetch = self.cur.fetchone() fetch = self.cur.fetchone()
@ -294,11 +292,11 @@ class YCDL:
''' '''
Mark the video as ignored, pending, or downloaded. Mark the video as ignored, pending, or downloaded.
''' '''
if state not in ['ignored', 'pending', 'downloaded']: if state not in ['ignored', 'pending', 'downloaded', 'coldstorage']:
raise InvalidVideoState(state) raise exceptions.InvalidVideoState(state)
self.cur.execute('SELECT * FROM videos WHERE id == ?', [video_id]) self.cur.execute('SELECT * FROM videos WHERE id == ?', [video_id])
if self.cur.fetchone() is None: if self.cur.fetchone() is None:
raise NoSuchVideo(video_id) raise exceptions.NoSuchVideo(video_id)
self.cur.execute('UPDATE videos SET download = ? WHERE id == ?', [state, video_id]) self.cur.execute('UPDATE videos SET download = ? WHERE id == ?', [state, video_id])
if commit: if commit:
self.sql.commit() self.sql.commit()