# included libs
from typing import Any, Callable
from json import loads as json_loads
-from os import environ, remove as os_remove
+from os import remove as os_remove
from os.path import join as path_join
from urllib.request import urlopen
# non-included libs
from scp import SCPClient # type: ignore
from ytplom.misc import (
PAGE_NAMES, PATH_DB, PATH_DOWNLOADS, PATH_TEMP,
- DatabaseConnection, PathStr, QuotaLog, VideoFile,
+ Config, DatabaseConnection, PathStr, QuotaLog, VideoFile,
YoutubeQuery, YoutubeVideo)
-# what we might want to manually define per environs
-YTPLOM_REMOTE = environ.get('YTPLOM_REMOTE')
-YTPLOM_PORT = environ.get('YTPLOM_PORT')
-
PATH_DB_REMOTE = PathStr(path_join(PATH_TEMP, 'remote_db.sql'))
ATTR_NAME_LAST_UPDATE = 'last_update'
def main():
"""Connect to remote, sync local+remote DBs, + downloads where missing."""
+ config = Config()
ssh = SSHClient()
ssh.load_system_host_keys()
- ssh.connect(YTPLOM_REMOTE)
+ ssh.connect(config.remote)
scp = SCPClient(ssh.get_transport())
scp.get(PATH_DB, PATH_DB_REMOTE)
local_db = DatabaseConnection(PATH_DB)
remote_db.commit_close()
scp.put(PATH_DB_REMOTE, PATH_DB)
os_remove(PATH_DB_REMOTE)
- for host, direction, mover in ((YTPLOM_REMOTE, 'local->remote', scp.put),
- ('localhost', 'remote->local', scp.get)):
- url_missing = f'http://{host}:{YTPLOM_PORT}/{PAGE_NAMES["missing"]}'
+ for host, port, direction, mover in (
+ (config.remote, config.port_remote, 'local->remote', scp.put),
+ (config.host, config.port, 'remote->local', scp.get)):
+ url_missing = f'http://{host}:{port}/{PAGE_NAMES["missing"]}'
with urlopen(url_missing) as response:
missing = json_loads(response.read())
for path in (path_join(PATH_DOWNLOADS, path) for path in missing):
from random import shuffle
from time import time, sleep
from datetime import datetime, timedelta
-from json import dumps as json_dumps
+from json import dumps as json_dumps, load as json_load
from uuid import uuid4
from sqlite3 import connect as sql_connect, Cursor, Row
from http.server import HTTPServer, BaseHTTPRequestHandler
from yt_dlp import YoutubeDL # type: ignore
import googleapiclient.discovery # type: ignore
-# what we might want to manually define per environs
-API_KEY = environ.get('GOOGLE_API_KEY')
-HTTP_PORT = int(environ.get('YTPLOM_PORT', 8084))
+# default configuration
+DEFAULTS = {
+ 'host': '127.0.0.1', # NB: to be found remotely, use '0.0.0.0'!
+ 'port': 8090,
+ 'port_remote': 8090
+}
# type definitions for mypy
DatetimeStr = NewType('DatetimeStr', str)
PATH_DB = PathStr(path_join(PATH_APP_DATA, 'db.sql'))
PATH_TEMP = PathStr(path_join(PATH_CACHE, 'temp'))
PATH_THUMBNAILS = PathStr(path_join(PATH_CACHE, 'thumbnails'))
+PATH_CONFFILE = PathStr(path_join(PATH_HOME, '.config/ytplom/config.json'))
# template paths
PATH_TEMPLATES = PathStr(path_join(PATH_APP_DATA, 'templates'))
QUOTA_COST_YOUTUBE_SEARCH = QuotaCost(100)
QUOTA_COST_YOUTUBE_DETAILS = QuotaCost(1)
-# local expectations
-TIMESTAMP_FMT = '%Y-%m-%d %H:%M:%S.%f'
-LEGAL_EXTENSIONS = {'webm', 'mp4', 'mkv'}
-
# database stuff
EXPECTED_DB_VERSION = 1
SQL_DB_VERSION = SqlText('PRAGMA user_version')
f'init_{EXPECTED_DB_VERSION}.sql'))
# other
+ENVIRON_PREFIX = 'YTPLOM_'
+TIMESTAMP_FMT = '%Y-%m-%d %H:%M:%S.%f'
+LEGAL_EXTENSIONS = {'webm', 'mp4', 'mkv'}
NAME_INSTALLER = PathStr('install.sh')
VIDEO_FLAGS: dict[FlagName, FlagsInt] = {
FlagName('delete'): FlagsInt(1 << 62)
return list(conn.execute(SQL_DB_VERSION))[0][0]
+class Config:
+ """Collects user-configurable settings."""
+ host: str
+ remote: str
+ port: int
+ port_remote: int
+ api_key: str
+
+ def __init__(self):
+ def set_attrs_from_dict(d):
+ for attr_name, type_ in self.__class__.__annotations__.items():
+ if attr_name in d:
+ setattr(self, attr_name, type_(d[attr_name]))
+ set_attrs_from_dict(DEFAULTS)
+ if isfile(PATH_CONFFILE):
+ with open(PATH_CONFFILE, 'r', encoding='utf8') as f:
+ conffile = json_load(f)
+ set_attrs_from_dict(conffile)
+ set_attrs_from_dict({k[len(ENVIRON_PREFIX):].lower(): v
+ for k, v in environ.items()
+ if k.isupper() and k.startswith(ENVIRON_PREFIX)})
+
+
class DatabaseConnection:
"""Wrapped sqlite3.Connection."""
class Server(HTTPServer):
"""Extension of HTTPServer providing for Player and DownloadsManager."""
- def __init__(self, *args, **kwargs) -> None:
+ def __init__(self, config: Config, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
+ self.config = config
self.jinja = JinjaEnv(loader=JinjaFSLoader(PATH_TEMPLATES))
self.player = Player()
self.downloads = DownloadsManager()
def collect_results(query_txt: QueryText) -> list[YoutubeVideo]:
_ensure_expected_dirs([PATH_THUMBNAILS])
- youtube = googleapiclient.discovery.build('youtube', 'v3',
- developerKey=API_KEY)
+ youtube = googleapiclient.discovery.build(
+ 'youtube', 'v3', developerKey=self.server.config.api_key)
QuotaLog.update(conn, QUOTA_COST_YOUTUBE_SEARCH)
search_request = youtube.search().list(
q=query_txt,