From b3bfb4fc5320cbc53d46ec027aebbc0e8ab00473 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 13 May 2020 14:11:44 +0200 Subject: [PATCH] Updated database class and FFmpeg download path In this update, the database class is almost completed (create database from file is still pending) and FFmpeg was configured for converting videos directly to RAM using the /run/user/{uid} path --- .idea/.gitignore | 7 +- .idea/dataSources.xml | 11 + .idea/sqldialects.xml | 7 + Design/Database/psql_model.sql | 24 +- YouTubeMDBot/.idea/YouTubeMDBot.iml | 6 +- YouTubeMDBot/.idea/misc.xml | 2 +- YouTubeMDBot/.idea/sqldialects.xml | 7 + YouTubeMDBot/__init__.py | 34 -- YouTubeMDBot/audio/ffmpeg.py | 12 +- YouTubeMDBot/database/psql.py | 406 ++++++++++++++++-- YouTubeMDBot/downloader/youtube_downloader.py | 15 +- YouTubeMDBot/requirements.txt | 1 + YouTubeMDBot/utils/__init__.py | 1 + YouTubeMDBot/utils/temporary_dir.py | 54 +++ 14 files changed, 486 insertions(+), 101 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/sqldialects.xml create mode 100644 YouTubeMDBot/.idea/sqldialects.xml create mode 100644 YouTubeMDBot/utils/temporary_dir.py diff --git a/.idea/.gitignore b/.idea/.gitignore index 0e40fe8..07b83ce 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,8 @@ # Default ignored files -/workspace.xml \ No newline at end of file +/workspace.xml +# Datasource local storage ignored files +/dataSources/ + +# Datasource local storage ignored files +/dataSources.local.xml \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..3328c70 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,11 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..3eda9fa --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Design/Database/psql_model.sql b/Design/Database/psql_model.sql index 83c3361..73846aa 100644 --- a/Design/Database/psql_model.sql +++ b/Design/Database/psql_model.sql @@ -1,13 +1,13 @@ -- PostgreSQL model for YouTubeMDBot application -- Created by Javinator9889 - thu, 24 October, 2019 --- Last modification: mon, 4 November, 2019 --- Version 1.1 +-- Last modification: Sat, 29 February, 2020 +-- Version 1.2 -- DROP schema - only for testing --- DROP SCHEMA IF EXISTS youtubemd CASCADE; --- DROP TYPE IF EXISTS AFORMAT; --- DROP TYPE IF EXISTS aquality; --- DROP TYPE IF EXISTS behaviour; +DROP SCHEMA IF EXISTS youtubemd CASCADE; +DROP TYPE IF EXISTS AFORMAT; +DROP TYPE IF EXISTS aquality; +DROP TYPE IF EXISTS behaviour; -- Custom "enum" types CREATE TYPE AFORMAT AS ENUM ('mp3', 'm4a', 'ogg'); @@ -154,9 +154,9 @@ CREATE TABLE IF NOT EXISTS youtubemd.YouTubeStats ); -- Additional indexes -CREATE INDEX youtubemd.user_preferences_ix ON youtubemd.Preferences ("user_id"); -CREATE INDEX youtubemd.video_metadata_ix ON youtubemd.Video_Has_Metadata ("id", "metadata_id"); -CREATE INDEX youtubemd.history_ix ON youtubemd.History ("id", "file_id", "user_id", "metadata_id"); +CREATE INDEX user_preferences_idx ON youtubemd.Preferences ("user_id"); +CREATE INDEX video_metadata_idx ON youtubemd.Video_Has_Metadata ("id", "metadata_id"); +CREATE INDEX history_idx ON youtubemd.History ("id", "file_id", "user_id", "metadata_id"); -- Trigger that updates different stats CREATE FUNCTION youtubemd.process_stats() RETURNS trigger AS @@ -237,21 +237,21 @@ BEGIN END; $$ LANGUAGE plpgsql; -CREATE FUNCTION youtubemd.clear_daily_stats() AS +CREATE FUNCTION youtubemd.clear_daily_stats() RETURNS VOID AS $$ BEGIN UPDATE youtubemd.YouTubeStats SET daily_requests = 0; END; $$ LANGUAGE plpgsql; -CREATE FUNCTION youtubemd.clear_weekly_stats() AS +CREATE FUNCTION youtubemd.clear_weekly_stats() RETURNS VOID AS $$ BEGIN UPDATE youtubemd.YouTubeStats SET weekly_requests = 0; END; $$ LANGUAGE plpgsql; -CREATE FUNCTION youtubemd.clear_monthly_stats() AS +CREATE FUNCTION youtubemd.clear_monthly_stats() RETURNS VOID AS $$ BEGIN UPDATE youtubemd.YouTubeStats SET monthly_requests = 0; diff --git a/YouTubeMDBot/.idea/YouTubeMDBot.iml b/YouTubeMDBot/.idea/YouTubeMDBot.iml index 6711606..3d4ed49 100755 --- a/YouTubeMDBot/.idea/YouTubeMDBot.iml +++ b/YouTubeMDBot/.idea/YouTubeMDBot.iml @@ -2,10 +2,10 @@ - + - - \ No newline at end of file diff --git a/YouTubeMDBot/.idea/misc.xml b/YouTubeMDBot/.idea/misc.xml index 8656114..6649a8c 100755 --- a/YouTubeMDBot/.idea/misc.xml +++ b/YouTubeMDBot/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/YouTubeMDBot/.idea/sqldialects.xml b/YouTubeMDBot/.idea/sqldialects.xml new file mode 100644 index 0000000..1c7bb63 --- /dev/null +++ b/YouTubeMDBot/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/YouTubeMDBot/__init__.py b/YouTubeMDBot/__init__.py index 25f0ea6..e9bdb16 100755 --- a/YouTubeMDBot/__init__.py +++ b/YouTubeMDBot/__init__.py @@ -13,38 +13,4 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from .logging_utils import LoggingHandler -from .logging_utils import setup_logging -from .constants import * - -from .errors import EmptyBodyError -from .errors import FinishedException - -from .api import YouTubeAPI -from .api import YouTubeVideoData - -from .audio import FFmpegMP3 -from .audio import FFmpegOGG -from .audio import FFmpegOpener -from .audio import FFmpegM4A -from .audio import FPCalc -from .audio import ffmpeg_available - -from .multiprocess import ThreadPoolBase - -from .decorators import restricted -from .decorators import send_action - -from .commands import StartHandler - -from .utils import CQueue -from .utils import get_yt_video_id - -from .downloader import YouTubeDownloader -from .downloader import MultipleYouTubeDownloader -from .downloader import M4AYouTubeDownloader - -from .metadata import AudioMetadata -from .metadata import MetadataIdentifier -from .metadata import YouTubeMetadataIdentifier diff --git a/YouTubeMDBot/audio/ffmpeg.py b/YouTubeMDBot/audio/ffmpeg.py index 03ef697..43a0b51 100755 --- a/YouTubeMDBot/audio/ffmpeg.py +++ b/YouTubeMDBot/audio/ffmpeg.py @@ -15,12 +15,12 @@ # along with this program. If not, see . from abc import ABC from abc import abstractmethod -from typing import List from subprocess import PIPE from subprocess import Popen +from typing import List -from ..constants import FFMPEG_OPENER from ..constants import FFMPEG_CONVERTER +from ..constants import FFMPEG_OPENER from ..constants import FFMPEG_VOLUME @@ -145,7 +145,7 @@ def convert(self) -> int: :raises NotImplementedError when trying to access this method directly on super class. """ - raise NotImplementedError + return self.process() class FFmpegMP3(FFmpegExporter): @@ -162,7 +162,7 @@ def convert(self) -> int: command.append("-f") command.append("mp3") command.append("-") - return self.process() + return super().convert() class FFmpegOGG(FFmpegExporter): @@ -179,7 +179,7 @@ def convert(self) -> int: command.append("-f") command.append("ogg") command.append("-") - return self.process() + return super().convert() class FFmpegM4A(FFmpegExporter): @@ -202,4 +202,4 @@ def convert(self) -> int: command.append("-f") command.append("ipod") command.append(self.filename) - return self.process() + return super().convert() diff --git a/YouTubeMDBot/database/psql.py b/YouTubeMDBot/database/psql.py index 9e0d4d2..ae0c352 100644 --- a/YouTubeMDBot/database/psql.py +++ b/YouTubeMDBot/database/psql.py @@ -13,17 +13,16 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import psycopg2 - from abc import ABC -from abc import abstractmethod - -from typing import Any -from typing import Optional - +from concurrent.futures import Future +from datetime import datetime +from threading import Condition from threading import Lock from threading import Thread -from threading import Condition +from typing import List +from typing import Optional + +import psycopg2 from .. import CQueue from .. import DB_HOST @@ -32,11 +31,18 @@ from .. import DB_PORT from .. import DB_USER +instance_lock = Lock() + class Query: - def __init__(self, statement: str, values: tuple = None): + def __init__(self, + statement: str, + values: tuple = None, + returning_id: bool = False): self.statement = statement self.values = values + self.returning_id = returning_id + self.return_value: Future = Future() class PostgreSQLBase(ABC): @@ -45,28 +51,29 @@ class PostgreSQLBase(ABC): def __new__(cls, min_ops: int = 100, **kwargs): - if PostgreSQLBase.__instance is None: - cls.__instance = object.__new__(cls) - cls.__instance.connection = psycopg2.connect(user=DB_USER, - password=DB_PASSWORD, - host=DB_HOST, - port=DB_PORT, - dbname=DB_NAME) - cls.__instance.min_ops = min_ops - cls.__instance._iuthread = Thread(target=cls.__iuhandler, - name="iuthread") - cls.__instance._qthread = Thread(name="qthread") - cls.__instance.lock = Lock() - cls.__instance.__close = False - cls.__instance.pending_ops = CQueue() - cls.__instance.waiting_ops = CQueue() - cls.__instance.updating_database = False - cls.__instance.iucond = Condition() - cls.__instance.qcond = Condition() - cls.__instance._iuthread.start() - for key, value in kwargs.items(): - setattr(cls.__instance, key, value) - return cls.__instance + with instance_lock: + if PostgreSQLBase.__instance is None: + cls.__instance = object.__new__(cls) + cls.__instance.connection = psycopg2.connect(user=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=DB_PORT, + dbname=DB_NAME) + cls.__instance.min_ops = min_ops + cls.__instance._iuthread = Thread(target=cls.__iuhandler, + name="iuthread") + cls.__instance._qthread = Thread(name="qthread") + cls.__instance.lock = Lock() + cls.__instance.__close = False + cls.__instance.pending_ops = CQueue() + cls.__instance.waiting_ops = CQueue() + cls.__instance.updating_database = False + cls.__instance.iucond = Condition() + cls.__instance.qcond = Condition() + cls.__instance._iuthread.start() + for key, value in kwargs.items(): + setattr(cls.__instance, key, value) + return cls.__instance @property def close(self) -> bool: @@ -99,6 +106,8 @@ def __iuhandler(self): with self.connection.cursor() as cursor: for query in self.pending_ops: cursor.execute(query.statement, query.values) + if query.returning_id: + query.return_value.set_result(cursor.fetchone()[0]) self.connection.commit() self.updating_database = False self.qcond.notify_all() @@ -114,11 +123,12 @@ def __qhandler(self): self.pending_ops.put(query) self.iucond.notify_all() - def insert(self, query: str, args=()): + def insert(self, query: str, args=(), returning_id: bool = False) -> Query: if not self.close: - insert_query = Query(query, args) + insert_query = Query(query, args, returning_id) self.waiting_ops.put(insert_query) self.qcond.notify_all() + return insert_query def update(self, query: str, args=()): if not self.close: @@ -126,24 +136,36 @@ def update(self, query: str, args=()): self.waiting_ops.put(update_query) self.qcond.notify_all() - def fetchone(self, query: str, args=()): + def fetchone(self, query: str, args=()) -> list: if not self.close: with self.connection.cursor() as cursor: cursor.execute(query, args) return cursor.fetchone() - def fetchmany(self, query: str, rows: int, args=()): + def fetchmany(self, query: str, rows: int, args=()) -> list: if not self.close: with self.connection.cursor() as cursor: cursor.execute(query, args) return cursor.fetchmany(rows) - def fetchall(self, query: str, args=()): + def fetchall(self, query: str, args=()) -> list: if not self.close: with self.connection.cursor() as cursor: cursor.execute(query, args) return cursor.fetchall() + def delete(self, query: str, args=()): + if not self.close: + with self.connection.cursor() as cursor: + cursor.execute(query, args) + cursor.commit() + + def callproc(self, proc: str, args=()) -> list: + if not self.close: + with self.connection.cursor() as cursor: + cursor.callproc(proc, args) + return cursor.fetchall() + def __del__(self): self.close = True if not self.waiting_ops.empty(): @@ -158,5 +180,315 @@ def __del__(self): del self.pending_ops +class UserDB(PostgreSQLBase): + def get_user_information(self, user_id: int) -> dict: + data = self.fetchone( + """SELECT id, name, tag, lang, first_access + FROM youtubemd.User WHERE id = %s""", (user_id,) + ) + return { + "id": data[0], + "name": data[1], + "tag": data[2], + "lang": data[3], + "first_access": data[4] + } + + def get_user_history(self, user_id: int) -> list: + data = self.fetchall( + """SELECT id, file_id, metadata_id, date FROM youtubemd.History + WHERE user_id = %s""", (user_id,) + ) + result = [] + for row in data: + result.append({ + "id": row[0], + "file_id": row[1], + "metadata_id": row[2], + "date": row[3] + }) + return result + + def register_new_user(self, user_id: int, name: str, tag: str, lang: str): + now = datetime.now() + query = """ + INSERT INTO youtubemd.User (id, name, tag, lang, first_access) + VALUES (%s, %s, %s, %s, %s) + """ + self.insert(query, (user_id, name, tag, lang, now)) + self.insert( + """INSERT INTO youtubemd.Preferences (user_id) VALUES (%s)""", + (user_id,) + ) + + def update_username(self, user_id: int, name: str): + self.update( + """UPDATE youtubemd.User SET name = %s WHERE user_id = %s""", + (name, user_id) + ) + + def update_user_lang(self, user_id: int, lang: str): + self.update( + """UPDATE youtubemd.User SET lang = %s WHERE user_id = %s""", + (lang, user_id) + ) + + def update_user_tag(self, user_id: int, tag: str): + self.update( + """UPDATE youtubemd.User SET tag = %s WHERE user_id = %s""", + (tag, user_id) + ) + + def register_new_download(self, + user_id: int, + file_id: str, + metadata_id: str): + now = datetime.now() + self.insert( + """INSERT INTO youtubemd.History + (file_id, user_id, metadata_id, date) + VALUES (%s, %s, %s, %s)""", + (file_id, metadata_id, user_id, now.date()) + ) + + +class PreferencesDB(PostgreSQLBase): + def get_user_preferences(self, user_id: int) -> dict: + data = self.fetchone( + """SELECT audio_format, audio_quality, send_song_link, + ask_for_metadata FROM youtubemd.Preferences WHERE User_id = %s""", + (user_id,) + ) + return { + "audio_format": data[0], + "audio_quality": data[1], + "send_song_link": data[2], + "ask_for_metadata": data[3] + } + + def update_user_audio_format(self, user_id: int, audio_format: str): + self.update( + """UPDATE youtubemd.Preferences SET audio_format = %s WHERE + user_id = %s""", (audio_format, user_id) + ) + + def update_user_audio_quality(self, user_id: int, audio_quality: str): + self.update( + """UPDATE youtubemd.Preferences SET audio_quality = %s WHERE + user_id = %s""", (audio_quality, user_id) + ) + + def update_user_behaviour(self, user_id: int, behaviour: str): + self.update( + """UPDATE youtubemd.Preferences SET song_behaviour = %s WHERE + user_id = %s""", (behaviour, user_id) + ) + + def update_user_send_song_link(self, user_id: int, send_song_link: bool): + self.update( + """UPDATE youtubemd.Preferences SET send_song_link = %s WHERE + user_id = %s""", (send_song_link, user_id) + ) + + class YouTubeDB(PostgreSQLBase): - pass + def set_youtube_id(self, youtube_id: str): + self.insert( + """INSERT INTO youtubemd.YouTube(id) VALUES (%s) + ON CONFLICT DO NOTHING""", + (youtube_id,) + ) + + def increment_times_requested(self, youtube_id: str): + self.update( + """UPDATE youtubemd.YouTube SET + times_requested = times_requested + 1 WHERE id = %s""", + (youtube_id,) + ) + + def is_id_registered(self, youtube_id: str) -> bool: + return self.fetchone( + """SELECT EXISTS(SELECT 1 FROM youtubemd.YouTube WHERE id = %s)""", + (youtube_id,) + )[0] + + +class MetadataDB(PostgreSQLBase): + def register_new_metadata(self, + artist: str, + album: str, + cover: bytes, + release_id: str, + recording_id: str, + duration: int, + title: str, + youtube_id: str, + is_custom_metadata: bool = False) -> int: + metadata_id = self.insert( + """INSERT INTO youtubemd.Metadata(artist, album, cover, + release_id, recording_id, duration, title, custom_metadata) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id""", + (artist, album, cover, release_id, recording_id, duration, title, + is_custom_metadata), returning_id=True + ).return_value.result() + self.insert( + """INSERT INTO youtubemd.Video_Has_Metadata(id, metadata_id) + VALUES (%s, %s)""", (youtube_id, metadata_id) + ) + return metadata_id + + def get_metadata_for_id(self, metadata_id: int) -> dict: + data = self.fetchone( + """SELECT artist, album, cover, release_id, recording_id, + duration, title, custom_metadata FROM youtubemd.Metadata + WHERE id = %s""", (metadata_id,) + ) + return { + "artist": data[0], + "album": data[1], + "cover": data[2], + "release_id": data[3], + "recording_id": data[4], + "duration": data[5], + "title": data[6], + "custom_metadata": data[7] + } + + def get_metadata_for_youtube_id(self, youtube_id: str) -> dict: + data = self.fetchone( + """SELECT metadata_id FROM youtubemd.Video_Has_Metadata + WHERE id = %s""", (youtube_id,) + ) + return self.get_metadata_for_id(data[0]) + + def update_metadata(self, + metadata_id: int, + artist: str, + album: str, + cover: bytes, + release_id: str, + recording_id: str, + duration: int, + title: str, + is_custom_metadata: bool = False): + self.update( + """UPDATE youtubemd.Metadata SET + artist = %s, + album = %s, + cover = %s, + release_id = %s, + recording_id = %s, + duration = %s, + title = %s, + custom_metadata = %s WHERE id = %s""", + (artist, album, cover, release_id, recording_id, duration, title, + is_custom_metadata, metadata_id) + ) + + +class FileDB(PostgreSQLBase): + def new_file(self, + file_id: str, + metadata_id: int, + audio_quality: str, + size: int, + user_id: int): + self.insert( + """INSERT INTO youtubemd.File(id, metadata_id, audio_quality, size) + VALUES (%s, %s, %s, %s) ON CONFLICT DO NOTHING""", + (file_id, metadata_id, audio_quality, size) + ) + date = datetime.now().date() + self.insert( + """INSERT INTO youtubemd.History(file_id, user_id, metadata_id, + date) VALUES (%s, %s, %s, %s) ON CONFLICT DO NOTHING""", + (file_id, user_id, metadata_id, date) + ) + + def get_file_info_by_id(self, file_id: str) -> dict: + data = self.fetchone( + """SELECT id, metadata_id, audio_quality, size + FROM youtubemd.File WHERE file_id = %s""", (file_id,) + ) + return { + "id": data[0], + "metadata_id": data[1], + "audio_quality": data[2], + "size": data[3] + } + + def get_file_for_youtube_id(self, youtube_id: str) -> Optional[str]: + youtube_database = YouTubeDB() + if youtube_database.is_id_registered(youtube_id): + metadata = MetadataDB() + metadata_id = metadata.get_metadata_for_youtube_id(youtube_id) + return self.get_file_for_metadata_id(metadata_id) + else: + return None + + def get_file_for_metadata_id(self, metadata_id: int) -> str: + return self.fetchone( + """SELECT file_id FROM youtubemd.File WHERE metadata_id = %s""", + (metadata_id,) + )[0] + + +class HistoryDB(PostgreSQLBase): + def get_history_for_user_id(self, user_id: int) -> List[dict]: + data = self.fetchall( + """SELECT id, file_id, user_id, metadata_id, date FROM + youtubemd.History WHERE user_id = %s""", (user_id,) + ) + history = list() + for value in data: + history.append( + { + "id": value[0], + "file_id": value[1], + "user_id": value[2], + "metadata_id": value[3] + } + ) + return history + + def remove_history_entry(self, history_id: int): + self.delete( + """DELETE FROM youtubemd.History WHERE id = %s""", (history_id,) + ) + + def remove_all_history_for_user(self, user_id: int): + self.delete( + """DELETE FROM youtubemd.History WHERE user_id = %s""", (user_id,) + ) + + +class YouTubeStatsDB(PostgreSQLBase): + def _get_top_ten(self, procedure: str, name: str) -> List[dict]: + data = self.callproc(procedure) + top_ten = list() + for values in data: + top_ten.append( + { + "id": values[0], + name: values[1] + } + ) + return top_ten + + def get_top_ten_daily(self) -> List[dict]: + return self._get_top_ten("youtubemd.top_10_daily", "daily_requests") + + def get_top_ten_weekly(self) -> List[dict]: + return self._get_top_ten("youtubemd.top_10_weekly", "weekly_requests") + + def get_top_ten_monthly(self) -> List[dict]: + return self._get_top_ten("youtubemd.top_10_monthly", "monthly_requests") + + def clear_top_ten_daily(self): + self.callproc("youtubemd.clear_daily_stats") + + def clear_top_ten_weekly(self): + self.callproc("youtubemd.clear_weekly_stats") + + def clear_top_ten_monthly(self): + self.callproc("youtubemd.clear_monthly_stats") diff --git a/YouTubeMDBot/downloader/youtube_downloader.py b/YouTubeMDBot/downloader/youtube_downloader.py index b94df2d..8e0b050 100755 --- a/YouTubeMDBot/downloader/youtube_downloader.py +++ b/YouTubeMDBot/downloader/youtube_downloader.py @@ -17,11 +17,11 @@ from typing import Any from typing import Callable from typing import Tuple -from tempfile import NamedTemporaryFile -from .. import ThreadPoolBase from .. import FFmpegM4A -from ..constants.app_constants import YDL_CLI_OPTIONS +from .. import TemporaryDir +from .. import ThreadPoolBase +from .. import YDL_CLI_OPTIONS class YouTubeDownloader: @@ -72,17 +72,18 @@ def __init__(self, url: str, bitrate: str = None): def download(self) -> Tuple[BytesIO, bytes]: io, data = super().download() - m4a_file = NamedTemporaryFile(suffix=".m4a") + temp_dir = TemporaryDir() + m4a_file = temp_dir.create_new_file(suffix=".m4a") m4a_converter = FFmpegM4A(data=data, filename=m4a_file.name, bitrate=self.user_bitrate) ret = m4a_converter.convert() if ret != 0: + m4a_file.close() raise RuntimeError("ffmpeg is unable to convert file - output: " + m4a_converter.get_extra().decode("utf-8")) - with open(m4a_file.name, "rb") as out_m4a: - m4a_data = out_m4a.read() - m4a_file.close() + with m4a_file: + m4a_data = m4a_file.read() return BytesIO(m4a_data), m4a_data diff --git a/YouTubeMDBot/requirements.txt b/YouTubeMDBot/requirements.txt index 7e8f024..d9070ce 100755 --- a/YouTubeMDBot/requirements.txt +++ b/YouTubeMDBot/requirements.txt @@ -6,3 +6,4 @@ ujson youtube_dl python-telegram-bot psycopg2 +pyacoustid diff --git a/YouTubeMDBot/utils/__init__.py b/YouTubeMDBot/utils/__init__.py index 1a5b972..ceb3340 100755 --- a/YouTubeMDBot/utils/__init__.py +++ b/YouTubeMDBot/utils/__init__.py @@ -14,4 +14,5 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . from ..utils.queue import CQueue +from ..utils.temporary_dir import TemporaryDir from ..utils.youtube_utils import get_yt_video_id diff --git a/YouTubeMDBot/utils/temporary_dir.py b/YouTubeMDBot/utils/temporary_dir.py new file mode 100644 index 0000000..5fe0fb6 --- /dev/null +++ b/YouTubeMDBot/utils/temporary_dir.py @@ -0,0 +1,54 @@ +# YouTubeMDBot +# Copyright (C) 2020 - Javinator9889 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +from tempfile import NamedTemporaryFile +from tempfile import TemporaryDirectory + + +class TemporaryDir: + __instance = None + + def __new__(cls, *args, **kwargs): + if TemporaryDir.__instance is None: + cls.__instance = object.__new__(cls) + user_id = os.getuid() + temporary_location = f"/run/user/{user_id}/" + cls.__instance.dir = TemporaryDirectory(dir=temporary_location) + for key, value in kwargs.items(): + setattr(cls.__instance, key, value) + return cls.__instance + + def create_new_file(self, + mode: str = "w+b", + buffering=None, + encoding=None, + newline=None, + suffix=None, + prefix=None, + delete=True, + *, + errors=None) -> NamedTemporaryFile: + return NamedTemporaryFile(mode, buffering, encoding, newline, suffix, + prefix, + dir=self.dir.name, + delete=delete, + errors=errors) + + def close(self): + self.dir.cleanup() + + def __del__(self): + self.close()