+++ /dev/null
-#!/usr/bin/env python3
-'''Ensure expected directories, files for app to work exist.'''
-from shutil import copyfile
-from os import makedirs, scandir
-from os.path import basename, join as path_join
-from ytplom.misc import PATH_APP_DATA, PATH_MIGRATIONS, PATH_TEMPLATES
-
-for path in (PATH_APP_DATA, PATH_TEMPLATES, PATH_MIGRATIONS):
- print(f'ensuring {path}')
- makedirs(path)
-for path_dir in (PATH_MIGRATIONS, PATH_TEMPLATES):
- for entry in scandir(basename(path_dir)):
- target_path = path_join(path_dir, basename(entry.path))
- print(f'copying {entry.path} to {target_path}')
- copyfile(entry.path, target_path)
-print('installation finished')
--- /dev/null
+#!/usr/bin/sh
+set -e
+set -x
+
+PATH_APP_SHARE=~/.local/share/ytplom
+mkdir -p "${PATH_APP_SHARE}"
+cp -r ./install_to_share/* "${PATH_APP_SHARE}/"
--- /dev/null
+CREATE TABLE yt_queries (
+ id TEXT PRIMARY KEY,
+ text TEXT NOT NULL,
+ retrieved_at TEXT NOT NULL
+);
+CREATE TABLE yt_videos (
+ id TEXT PRIMARY KEY,
+ title TEXT NOT NULL,
+ description TEXT NOT NULL,
+ published_at TEXT NOT NULL,
+ duration TEXT NOT NULL,
+ definition TEXT NOT NULL
+);
+CREATE TABLE yt_query_results (
+ query_id TEXT NOT NULL,
+ video_id TEXT NOT NULL,
+ PRIMARY KEY (query_id, video_id),
+ FOREIGN KEY (query_id) REFERENCES yt_queries(id),
+ FOREIGN KEY (video_id) REFERENCES yt_videos(id)
+);
+CREATE TABLE quota_costs (
+ id TEXT PRIMARY KEY,
+ timestamp TEXT NOT NULL,
+ cost INT NOT NULL
+);
+CREATE TABLE files (
+ rel_path TEXT PRIMARY KEY,
+ yt_id TEXT NOT NULL DEFAULT "",
+ flags INTEGER NOT NULL DEFAULT 0,
+ FOREIGN KEY (yt_id) REFERENCES yt_videos(id)
+);
+
--- /dev/null
+{% import '_macros.tmpl' as macros %}
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+{% block script %}
+{% endblock %}
+</script>
+<style>
+body { background-color: #aaaa00; }
+{% block css %}
+{% endblock %}
+</style>
+</head>
+<body>
+{% block body %}
+{% endblock %}
+</body>
+</html>
--- /dev/null
+{% macro _link_if(cond, target) %}{% if cond %}<a href="/{{target}}">{% endif %}{{target}}{% if cond %}</a>{% endif %}{% endmacro %}
+
+
+{% macro nav_head(selected="") %}
+<p>
+{{ _link_if("playlist" != selected, "playlist") }}
+·
+{{ _link_if("videos" != selected, "videos") }}
+·
+{{ _link_if("queries" != selected, "queries") }}
+</p>
+<hr />
+{% endmacro %}
--- /dev/null
+{% extends '_base.tmpl' %}
+
+
+{% block script %}
+const RELOAD_INTERVAL_S = 10;
+const PATH_LAST_UPDATE = '/_last_playlist_update.json';
+const MSG_SERVER_DOWN = 'Server seems to be unavailable.';
+const MSG_ERR_UNKNOWN = 'Unknown error checking ' + PATH_LAST_UPDATE;
+const last_update = '{{last_update}}';
+async function keep_updated() {
+ try {
+ const response = await fetch(PATH_LAST_UPDATE);
+ const data = await response.json();
+ if (data.last_update != last_update) {
+ location.reload();
+ }
+ } catch(error) {
+ const status = document.getElementById('status');
+ if (error instanceof TypeError && !error.response) {
+ status.innerText = MSG_SERVER_DOWN;
+ } else {
+ status.innerText = MSG_ERR_UNKNOWN;
+ }
+ }
+ setTimeout(keep_updated, RELOAD_INTERVAL_S * 1000);
+}
+window.onload = keep_updated;
+{% endblock %}
+
+
+{% block css %}
+table { width: 100%; }
+#status { text-align: center; font-weight: bold; }
+td.history { width: 50%; }
+{% endblock %}
+
+
+{% block body %}
+{{ macros.nav_head("playlist") }}
+<table>
+<tr><td id="status" colspan=2>
+{% if running %}{% if pause %}PAUSED{% else %}PLAYING{% endif %}{% else %}STOPPED{% endif %}:<br />
+{{ current_title }}<br />
+<form action="/playlist" method="POST">
+<input type="submit" name="pause" autofocus value="{% if paused %}resume{% else %}pause{% endif %}">
+<input type="submit" name="prev" value="prev">
+<input type="submit" name="next" value="next">
+<input type="submit" name="stop" value="{% if running %}stop{% else %}start{% endif %}">
+<input type="submit" name="reload" value="reload">
+</form>
+</td></tr>
+{% for prev_title, next_title in tuples %}
+<tr><td class="history">
+{{ prev_title }}
+</td><td class="history">
+{{ next_title }}
+</td></tr>
+{% endfor %}
+</table>
+{% endblock %}
+
--- /dev/null
+{% extends '_base.tmpl' %}
+
+
+{% block body %}
+{{ macros.nav_head("queries") }}
+<p>quota: {{quota_count}}/100000</p>
+<form action="/queries" method="POST" />
+<input name="query" />
+</form>
+<table>
+<tr>
+<th>retrieved at</th>
+<th>query</th>
+</tr>
+{% for query in queries %}
+<tr>
+<td>{{query.retrieved_at[:19]}}</td>
+<td><a href="/query/{{query.id_}}">{{query.text}}</a></td>
+</tr>
+{% endfor %}
+</table>
+{% endblock %}
--- /dev/null
+{% extends '_base.tmpl' %}
+
+
+{% block body %}
+{{ macros.nav_head() }}
+<p>query: {{query}}</p>
+<table>
+{% for video in videos %}
+<tr>
+<td>
+<a href="/yt_video/{{video.id_}}"><img src="/thumbnails/{{video.id_}}.jpg" /></a>
+</td>
+<td>
+{{video.definition}}<br />
+{{video.duration}}
+</td>
+<td>
+<b><a href="/yt_video/{{video.id_}}">{{video.title}}</a></b> · {{video.description}}
+</td>
+</tr>
+{% endfor %}
+</table>
+{% endblock %}
--- /dev/null
+{% extends '_base.tmpl' %}
+
+
+{% block body %}
+{{ macros.nav_head() }}
+<table>
+<tr><th>path:</th><td>{{file.rel_path}}</td></tr>
+<tr><th>YouTube ID:</th><td><a href="/yt_video/{{file.yt_id}}">{{file.yt_id}}</a></tr>
+<tr><th>present:</th><td>{% if file.present %}<a href="/dl/{{file.yt_id}}">yes</a>{% else %}no{% endif %}</td></tr>
+</table>
+<form action="/video/{{file.yt_id}}" method="POST" />
+{% for flag_name in flag_names %}
+{{ flag_name }}: <input type="checkbox" name="{{flag_name}}" {% if file.flag_set(flag_name) %}checked {% endif %} /><br />
+{% endfor %}
+<input type="submit" />
+</form>
+{% endblock %}
--- /dev/null
+{% extends '_base.tmpl' %}
+
+
+{% block body %}
+{{ macros.nav_head("videos") }}
+<p>downloaded videos:</p>
+<ul>
+{% for video_id, path in videos %}
+<li><a href="/video/{{video_id}}">{{ path }}</a>
+{% endfor %}
+</ul>
+{% endblock %}
--- /dev/null
+{% extends '_base.tmpl' %}
+
+
+{% block body %}
+{{ macros.nav_head() }}
+<table>
+<tr><th>title:</th><td>{{video_data.title}}</td></tr>
+<tr><th>thumbnail:</th><td><img src="/thumbnails/{{video_data.id_}}.jpg" /></td></tr>
+<tr><th>description:</th><td>{{video_data.description}}</td></tr>
+<tr><th>duration:</th><td>{{video_data.duration}}</td></tr>
+<tr><th>definition:</th><td>{{video_data.definition}}</td></tr>
+<tr><th>YouTube ID:</th><td>{{video_data.id_}} (<a href="{{youtube_prefix}}{{video_data.id_}}">watch</a>)</td></tr>
+<tr><th>download:</th><td>{% if is_temp %}working on it{% else %}<a href="/dl/{{video_data.id_}}">{{ file_path if file_path else "please do" }}</a>{% endif %}</td></tr>
+<tr>
+<th>linked queries:</th>
+<td>
+<ul>
+{% for query in queries %}<li><a href="/query/{{query.id_}}">{{query.text}}</a>{% endfor %}
+</ul>
+</td>
+</tr>
+</table>
+{% endblock %}
+++ /dev/null
-CREATE TABLE yt_queries (
- id TEXT PRIMARY KEY,
- text TEXT NOT NULL,
- retrieved_at TEXT NOT NULL
-);
-CREATE TABLE yt_videos (
- id TEXT PRIMARY KEY,
- title TEXT NOT NULL,
- description TEXT NOT NULL,
- published_at TEXT NOT NULL,
- duration TEXT NOT NULL,
- definition TEXT NOT NULL
-);
-CREATE TABLE yt_query_results (
- query_id TEXT NOT NULL,
- video_id TEXT NOT NULL,
- PRIMARY KEY (query_id, video_id),
- FOREIGN KEY (query_id) REFERENCES yt_queries(id),
- FOREIGN KEY (video_id) REFERENCES yt_videos(id)
-);
-CREATE TABLE quota_costs (
- id TEXT PRIMARY KEY,
- timestamp TEXT NOT NULL,
- cost INT NOT NULL
-);
-CREATE TABLE files (
- rel_path TEXT PRIMARY KEY,
- yt_id TEXT NOT NULL DEFAULT "",
- flags INTEGER NOT NULL DEFAULT 0,
- FOREIGN KEY (yt_id) REFERENCES yt_videos(id)
-);
-
+++ /dev/null
-{% import '_macros.tmpl' as macros %}
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="UTF-8">
-<script>
-{% block script %}
-{% endblock %}
-</script>
-<style>
-body { background-color: #aaaa00; }
-{% block css %}
-{% endblock %}
-</style>
-</head>
-<body>
-{% block body %}
-{% endblock %}
-</body>
-</html>
+++ /dev/null
-{% macro _link_if(cond, target) %}{% if cond %}<a href="/{{target}}">{% endif %}{{target}}{% if cond %}</a>{% endif %}{% endmacro %}
-
-
-{% macro nav_head(selected="") %}
-<p>
-{{ _link_if("playlist" != selected, "playlist") }}
-·
-{{ _link_if("videos" != selected, "videos") }}
-·
-{{ _link_if("queries" != selected, "queries") }}
-</p>
-<hr />
-{% endmacro %}
+++ /dev/null
-{% extends '_base.tmpl' %}
-
-
-{% block script %}
-const RELOAD_INTERVAL_S = 10;
-const PATH_LAST_UPDATE = '/_last_playlist_update.json';
-const MSG_SERVER_DOWN = 'Server seems to be unavailable.';
-const MSG_ERR_UNKNOWN = 'Unknown error checking ' + PATH_LAST_UPDATE;
-const last_update = '{{last_update}}';
-async function keep_updated() {
- try {
- const response = await fetch(PATH_LAST_UPDATE);
- const data = await response.json();
- if (data.last_update != last_update) {
- location.reload();
- }
- } catch(error) {
- const status = document.getElementById('status');
- if (error instanceof TypeError && !error.response) {
- status.innerText = MSG_SERVER_DOWN;
- } else {
- status.innerText = MSG_ERR_UNKNOWN;
- }
- }
- setTimeout(keep_updated, RELOAD_INTERVAL_S * 1000);
-}
-window.onload = keep_updated;
-{% endblock %}
-
-
-{% block css %}
-table { width: 100%; }
-#status { text-align: center; font-weight: bold; }
-td.history { width: 50%; }
-{% endblock %}
-
-
-{% block body %}
-{{ macros.nav_head("playlist") }}
-<table>
-<tr><td id="status" colspan=2>
-{% if running %}{% if pause %}PAUSED{% else %}PLAYING{% endif %}{% else %}STOPPED{% endif %}:<br />
-{{ current_title }}<br />
-<form action="/playlist" method="POST">
-<input type="submit" name="pause" autofocus value="{% if paused %}resume{% else %}pause{% endif %}">
-<input type="submit" name="prev" value="prev">
-<input type="submit" name="next" value="next">
-<input type="submit" name="stop" value="{% if running %}stop{% else %}start{% endif %}">
-<input type="submit" name="reload" value="reload">
-</form>
-</td></tr>
-{% for prev_title, next_title in tuples %}
-<tr><td class="history">
-{{ prev_title }}
-</td><td class="history">
-{{ next_title }}
-</td></tr>
-{% endfor %}
-</table>
-{% endblock %}
-
+++ /dev/null
-{% extends '_base.tmpl' %}
-
-
-{% block body %}
-{{ macros.nav_head("queries") }}
-<p>quota: {{quota_count}}/100000</p>
-<form action="/queries" method="POST" />
-<input name="query" />
-</form>
-<table>
-<tr>
-<th>retrieved at</th>
-<th>query</th>
-</tr>
-{% for query in queries %}
-<tr>
-<td>{{query.retrieved_at[:19]}}</td>
-<td><a href="/query/{{query.id_}}">{{query.text}}</a></td>
-</tr>
-{% endfor %}
-</table>
-{% endblock %}
+++ /dev/null
-{% extends '_base.tmpl' %}
-
-
-{% block body %}
-{{ macros.nav_head() }}
-<p>query: {{query}}</p>
-<table>
-{% for video in videos %}
-<tr>
-<td>
-<a href="/yt_video/{{video.id_}}"><img src="/thumbnails/{{video.id_}}.jpg" /></a>
-</td>
-<td>
-{{video.definition}}<br />
-{{video.duration}}
-</td>
-<td>
-<b><a href="/yt_video/{{video.id_}}">{{video.title}}</a></b> · {{video.description}}
-</td>
-</tr>
-{% endfor %}
-</table>
-{% endblock %}
+++ /dev/null
-{% extends '_base.tmpl' %}
-
-
-{% block body %}
-{{ macros.nav_head() }}
-<table>
-<tr><th>path:</th><td>{{file.rel_path}}</td></tr>
-<tr><th>YouTube ID:</th><td><a href="/yt_video/{{file.yt_id}}">{{file.yt_id}}</a></tr>
-<tr><th>present:</th><td>{% if file.present %}<a href="/dl/{{file.yt_id}}">yes</a>{% else %}no{% endif %}</td></tr>
-</table>
-<form action="/video/{{file.yt_id}}" method="POST" />
-{% for flag_name in flag_names %}
-{{ flag_name }}: <input type="checkbox" name="{{flag_name}}" {% if file.flag_set(flag_name) %}checked {% endif %} /><br />
-{% endfor %}
-<input type="submit" />
-</form>
-{% endblock %}
+++ /dev/null
-{% extends '_base.tmpl' %}
-
-
-{% block body %}
-{{ macros.nav_head("videos") }}
-<p>downloaded videos:</p>
-<ul>
-{% for video_id, path in videos %}
-<li><a href="/video/{{video_id}}">{{ path }}</a>
-{% endfor %}
-</ul>
-{% endblock %}
+++ /dev/null
-{% extends '_base.tmpl' %}
-
-
-{% block body %}
-{{ macros.nav_head() }}
-<table>
-<tr><th>title:</th><td>{{video_data.title}}</td></tr>
-<tr><th>thumbnail:</th><td><img src="/thumbnails/{{video_data.id_}}.jpg" /></td></tr>
-<tr><th>description:</th><td>{{video_data.description}}</td></tr>
-<tr><th>duration:</th><td>{{video_data.duration}}</td></tr>
-<tr><th>definition:</th><td>{{video_data.definition}}</td></tr>
-<tr><th>YouTube ID:</th><td>{{video_data.id_}} (<a href="{{youtube_prefix}}{{video_data.id_}}">watch</a>)</td></tr>
-<tr><th>download:</th><td>{% if is_temp %}working on it{% else %}<a href="/dl/{{video_data.id_}}">{{ file_path if file_path else "please do" }}</a>{% endif %}</td></tr>
-<tr>
-<th>linked queries:</th>
-<td>
-<ul>
-{% for query in queries %}<li><a href="/query/{{query.id_}}">{{query.text}}</a>{% endfor %}
-</ul>
-</td>
-</tr>
-</table>
-{% endblock %}
# local expectations
TIMESTAMP_FMT = '%Y-%m-%d %H:%M:%S.%f'
LEGAL_EXTENSIONS = {'webm', 'mp4', 'mkv'}
-VIDEO_FLAGS: dict[FlagName, FlagsInt] = {
- FlagName('delete'): FlagsInt(1 << 62)
-}
# tables to create database with
EXPECTED_DB_VERSION = 0
PATH_DB_SCHEMA = PathStr(path_join(PATH_MIGRATIONS,
f'init_{EXPECTED_DB_VERSION}.sql'))
+# other
+NAME_INSTALLER = 'install.sh'
+VIDEO_FLAGS: dict[FlagName, FlagsInt] = {
+ FlagName('delete'): FlagsInt(1 << 62)
+}
+
class NotFoundException(Exception):
"""Raise on expected data missing, e.g. DB fetches finding nothing."""
if not isdir(path_db_dir):
raise NotFoundException(
f'cannot find {path_db_dir} as directory to put DB '
- 'into, did you run install.py?')
+ f'into, did you run {NAME_INSTALLER}?')
with sql_connect(self._path) as conn:
with open(PATH_DB_SCHEMA, 'r', encoding='utf8') as f:
conn.executescript(f.read())