From 0bf0fee835dcefbd8a09a757f3ef95a257e96c3f Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Fri, 27 Oct 2023 14:16:40 -0400 Subject: [PATCH 01/30] Initial pass at Base URL --- arm/ripper/utils.py | 6 +++++- arm/ui/comments.json | 1 + setup/arm.yaml | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/arm/ripper/utils.py b/arm/ripper/utils.py index abe4e22e8..469f5ea03 100755 --- a/arm/ripper/utils.py +++ b/arm/ripper/utils.py @@ -92,10 +92,14 @@ def notify_entry(job): f"{job.disctype} at {datetime.datetime.now()}") database_adder(notification) if job.disctype in ["dvd", "bluray"]: + if cfg.arm_config["UI_BASE_URL"] == "": + display_address = (f"http://{check_ip()}:{job.config.WEBSERVER_PORT}") + else: + display_address = str(cfg.arm_config["UI_BASE_URL"]); # Send the notifications notify(job, NOTIFY_TITLE, f"Found disc: {job.title}. Disc type is {job.disctype}. Main Feature is {job.config.MAINFEATURE}." - f"Edit entry here: http://{check_ip()}:{job.config.WEBSERVER_PORT}/jobdetail?job_id={job.job_id}") + f"Edit entry here: {display_address}/jobdetail?job_id={job.job_id}") elif job.disctype == "music": notify(job, NOTIFY_TITLE, f"Found music CD: {job.label}. Ripping all tracks") elif job.disctype == "data": diff --git a/arm/ui/comments.json b/arm/ui/comments.json index 938e3f265..048a643c1 100644 --- a/arm/ui/comments.json +++ b/arm/ui/comments.json @@ -44,6 +44,7 @@ "DBFILE": "# Path to ARM database file", "WEBSERVER_IP": "# IP address of web server (this machine)\n# Use x.x.x.x to autodetect the IP address to use", "WEBSERVER_PORT": "# Port for web server", + "UI_BASE_URL": "The display URL to use in notifications. Must include protocol (e.g. http:// or https://)", "SET_MEDIA_PERMISSIONS": "# Enabling this setting will allow you to adjust the default file permissions of the outputted files\n# The default value is set to 777 for read/write/execute for all users, but can be changed below using the \"CHMOD_VALUE\" setting\n# This setting is helpful when storing the data locally on the system", "CHMOD_VALUE": "", "SET_MEDIA_OWNER": "# Don't bother. This does not do anything yet", diff --git a/setup/arm.yaml b/setup/arm.yaml index 41665fdc4..5058e0124 100755 --- a/setup/arm.yaml +++ b/setup/arm.yaml @@ -165,6 +165,8 @@ WEBSERVER_IP: x.x.x.x # Port for web server WEBSERVER_PORT: 8080 +# Base URL to use for notifications and display purposes +UI_BASE_URL: ######################## ## File Permissions ## From d54069be6c32b1524b5ece51ea4b31c93a27938b Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Mon, 30 Oct 2023 00:16:43 -0400 Subject: [PATCH 02/30] Second attempt at changes --- arm/models/models.py | 1 + arm/ripper/utils.py | 2 +- arm/ui/comments.json | 2 +- setup/arm.yaml | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/arm/models/models.py b/arm/models/models.py index fe62f162e..240c56965 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -290,6 +290,7 @@ class Config(db.Model): DBFILE = db.Column(db.String(255)) WEBSERVER_IP = db.Column(db.String(25)) WEBSERVER_PORT = db.Column(db.Integer) + UI_BASE_URL = db.Column(db.String(128)) SET_MEDIA_PERMISSIONS = db.Column(db.Boolean) CHMOD_VALUE = db.Column(db.Integer) SET_MEDIA_OWNER = db.Column(db.Boolean) diff --git a/arm/ripper/utils.py b/arm/ripper/utils.py index 469f5ea03..1a17b2105 100755 --- a/arm/ripper/utils.py +++ b/arm/ripper/utils.py @@ -95,7 +95,7 @@ def notify_entry(job): if cfg.arm_config["UI_BASE_URL"] == "": display_address = (f"http://{check_ip()}:{job.config.WEBSERVER_PORT}") else: - display_address = str(cfg.arm_config["UI_BASE_URL"]); + display_address = str(cfg.arm_config["UI_BASE_URL"]) # Send the notifications notify(job, NOTIFY_TITLE, f"Found disc: {job.title}. Disc type is {job.disctype}. Main Feature is {job.config.MAINFEATURE}." diff --git a/arm/ui/comments.json b/arm/ui/comments.json index 048a643c1..0708f9772 100644 --- a/arm/ui/comments.json +++ b/arm/ui/comments.json @@ -44,7 +44,7 @@ "DBFILE": "# Path to ARM database file", "WEBSERVER_IP": "# IP address of web server (this machine)\n# Use x.x.x.x to autodetect the IP address to use", "WEBSERVER_PORT": "# Port for web server", - "UI_BASE_URL": "The display URL to use in notifications. Must include protocol (e.g. http:// or https://)", + "UI_BASE_URL": "# Base URL to use for notifications and display purposes\n#Be sure to include protocol and port if needed (e.g. http://example.com:8091 or https://example.com)", "SET_MEDIA_PERMISSIONS": "# Enabling this setting will allow you to adjust the default file permissions of the outputted files\n# The default value is set to 777 for read/write/execute for all users, but can be changed below using the \"CHMOD_VALUE\" setting\n# This setting is helpful when storing the data locally on the system", "CHMOD_VALUE": "", "SET_MEDIA_OWNER": "# Don't bother. This does not do anything yet", diff --git a/setup/arm.yaml b/setup/arm.yaml index 5058e0124..012bf4dd8 100755 --- a/setup/arm.yaml +++ b/setup/arm.yaml @@ -166,7 +166,8 @@ WEBSERVER_IP: x.x.x.x WEBSERVER_PORT: 8080 # Base URL to use for notifications and display purposes -UI_BASE_URL: +# Be sure to include protocol and port if needed (e.g. http://example.com:8091 or https://example.com) +UI_BASE_URL: "" ######################## ## File Permissions ## From daa2d9b4d37d80512e8723789a9c770c8f3d5500 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Wed, 1 Nov 2023 23:34:21 -0400 Subject: [PATCH 03/30] Add database migration --- .../469d88477c13_add_ui_base_url_to_config.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 arm/migrations/versions/469d88477c13_add_ui_base_url_to_config.py diff --git a/arm/migrations/versions/469d88477c13_add_ui_base_url_to_config.py b/arm/migrations/versions/469d88477c13_add_ui_base_url_to_config.py new file mode 100644 index 000000000..34b51ee03 --- /dev/null +++ b/arm/migrations/versions/469d88477c13_add_ui_base_url_to_config.py @@ -0,0 +1,26 @@ +"""Add UI_BASE_URL to config + +Revision ID: 469d88477c13 +Revises: 2e0dc31fcb2e +Create Date: 2023-11-01 22:22:10.112867 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '469d88477c13' +down_revision = '2e0dc31fcb2e' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('config', + sa.Column('UI_BASE_URL', sa.String(length=128), nullable=True) + ) + + +def downgrade(): + op.drop_column('config', 'UI_BASE_URL') From a42a76b0bd48dd4bfbe06b6cb21879e79fee65fd Mon Sep 17 00:00:00 2001 From: wolfy Date: Sat, 11 Nov 2023 20:39:53 -0700 Subject: [PATCH 04/30] copied job class into new file --- arm/models/job.py | 216 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 arm/models/job.py diff --git a/arm/models/job.py b/arm/models/job.py new file mode 100644 index 000000000..362d1ff01 --- /dev/null +++ b/arm/models/job.py @@ -0,0 +1,216 @@ +import logging +import os +import psutil +import pyudev +import subprocess +import time + +from prettytable import PrettyTable +from arm.ripper import music_brainz +from arm.ui import db + +import arm.config.config as cfg + + +class Job(db.Model): + """ + Job Class hold most of the details for each job + connects to track, config + """ + job_id = db.Column(db.Integer, primary_key=True) + arm_version = db.Column(db.String(20)) + crc_id = db.Column(db.String(63)) + logfile = db.Column(db.String(256)) + start_time = db.Column(db.DateTime) + stop_time = db.Column(db.DateTime) + job_length = db.Column(db.String(12)) + status = db.Column(db.String(32)) + stage = db.Column(db.String(63)) + no_of_titles = db.Column(db.Integer) + title = db.Column(db.String(256)) + title_auto = db.Column(db.String(256)) + title_manual = db.Column(db.String(256)) + year = db.Column(db.String(4)) + year_auto = db.Column(db.String(4)) + year_manual = db.Column(db.String(4)) + video_type = db.Column(db.String(20)) + video_type_auto = db.Column(db.String(20)) + video_type_manual = db.Column(db.String(20)) + imdb_id = db.Column(db.String(15)) + imdb_id_auto = db.Column(db.String(15)) + imdb_id_manual = db.Column(db.String(15)) + poster_url = db.Column(db.String(256)) + poster_url_auto = db.Column(db.String(256)) + poster_url_manual = db.Column(db.String(256)) + devpath = db.Column(db.String(15)) + mountpoint = db.Column(db.String(20)) + hasnicetitle = db.Column(db.Boolean) + errors = db.Column(db.Text) + disctype = db.Column(db.String(20)) # dvd/bluray/data/music/unknown + label = db.Column(db.String(256)) + path = db.Column(db.String(256)) + ejected = db.Column(db.Boolean) + updated = db.Column(db.Boolean) + pid = db.Column(db.Integer) + pid_hash = db.Column(db.Integer) + tracks = db.relationship('Track', backref='job', lazy='dynamic') + config = db.relationship('Config', uselist=False, backref="job") + + def __init__(self, devpath): + """Return a disc object""" + self.devpath = devpath + self.mountpoint = "/mnt" + devpath + self.hasnicetitle = False + self.video_type = "unknown" + self.ejected = False + self.updated = False + if cfg.arm_config['VIDEOTYPE'] != "auto": + self.video_type = cfg.arm_config['VIDEOTYPE'] + self.parse_udev() + self.get_pid() + self.stage = str(round(time.time() * 100)) + + if self.disctype == "dvd" and not self.label: + logging.info("No disk label Available. Trying lsdvd") + command = f"lsdvd {devpath} | grep 'Disc Title' | cut -d ' ' -f 3-" + lsdvdlbl = str(subprocess.check_output(command, shell=True).strip(), 'utf-8') + self.label = lsdvdlbl + + def __str__(self): + """Returns a string of the object""" + + return_string = self.__class__.__name__ + ": " + for attr, value in self.__dict__.items(): + return_string = return_string + "(" + str(attr) + "=" + str(value) + ") " + + return return_string + + def __repr__(self): + return f'' + + def parse_udev(self): + """Parse udev for properties of current disc""" + context = pyudev.Context() + device = pyudev.Devices.from_device_file(context, self.devpath) + self.disctype = "unknown" + + for key, value in device.items(): + logging.debug(f"pyudev: {key}: {value}") + if key == "ID_FS_LABEL": + self.label = value + if value == "iso9660": + self.disctype = "data" + elif key == "ID_CDROM_MEDIA_BD": + self.disctype = "bluray" + elif key == "ID_CDROM_MEDIA_DVD": + self.disctype = "dvd" + elif key == "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO": + self.disctype = "music" + else: + continue + + def get_pid(self): + """ + Get the jobs process id + :return: None + """ + pid = os.getpid() + process_id = psutil.Process(pid) + self.pid = pid + self.pid_hash = hash(process_id) + + def get_disc_type(self, found_hvdvd_ts): + """ + Checks/corrects the current disc-type + :param found_hvdvd_ts: gets pushed in from utils - saves importing utils + :return: None + """ + if self.disctype == "music": + logging.debug("Disc is music.") + self.label = music_brainz.main(self) + elif os.path.isdir(self.mountpoint + "/VIDEO_TS"): + logging.debug(f"Found: {self.mountpoint}/VIDEO_TS") + self.disctype = "dvd" + elif os.path.isdir(self.mountpoint + "/video_ts"): + logging.debug(f"Found: {self.mountpoint}/video_ts") + self.disctype = "dvd" + elif os.path.isdir(self.mountpoint + "/BDMV"): + logging.debug(f"Found: {self.mountpoint}/BDMV") + self.disctype = "bluray" + elif os.path.isdir(self.mountpoint + "/HVDVD_TS"): + logging.debug(f"Found: {self.mountpoint}/HVDVD_TS") + # do something here + elif found_hvdvd_ts: + logging.debug("Found file: HVDVD_TS") + # do something here too + else: + logging.debug("Did not find valid dvd/bd files. Changing disc-type to 'data'") + self.disctype = "data" + + def identify_audio_cd(self): + """ + Get the title for audio cds to use for the logfile name. + + Needs the job class passed into it so it can be forwarded to mb + + return - only the logfile - setup_logging() adds the full path + """ + # Use the music label if we can find it - defaults to music_cd.log + disc_id = music_brainz.get_disc_id(self) + logging.debug(f"music_id: {disc_id}") + mb_title = music_brainz.get_title(disc_id, self) + logging.debug(f"mm_title: {mb_title}") + + if mb_title == "not identified": + self.label = self.title = "not identified" + logfile = "music_cd.log" + new_log_file = f"music_cd_{round(time.time() * 100)}.log" + else: + logfile = f"{mb_title}.log" + new_log_file = f"{mb_title}_{round(time.time() * 100)}.log" + + temp_log_full = os.path.join(cfg.arm_config['LOGPATH'], logfile) + logfile = new_log_file if os.path.isfile(temp_log_full) else logfile + return logfile + + def pretty_table(self): + """Returns a string of the prettytable""" + pretty_table = PrettyTable() + pretty_table.field_names = ["Config", "Value"] + pretty_table._max_width = {"Config": 50, "Value": 60} + for attr, value in self.__dict__.items(): + if attr == "config": + pretty_table.add_row([str(attr), str(value.pretty_table())]) + else: + pretty_table.add_row([str(attr), str(value)]) + return str(pretty_table.get_string()) + + def get_d(self): + """ + Return a dict of class - exclude the _sa_instance_state + :return: dict containing all attribs from class + """ + return_dict = {} + for key, value in self.__dict__.items(): + if '_sa_instance_state' not in key: + return_dict[str(key)] = str(value) + return return_dict + + def eject(self): + """Eject disc if it hasn't previously been ejected""" + if not self.ejected: + self.ejected = True + try: + # This might always return true + if bool(os.system("umount " + self.devpath)): + logging.debug(f"Unmounted disc {self.devpath}") + else: + logging.debug(f"Failed to unmount {self.devpath}") + if bool(os.system("eject -sv " + self.devpath)): + logging.debug(f"Ejected disc {self.devpath}") + else: + logging.debug(f"Failed to eject {self.devpath}") + except Exception as error: + logging.debug(f"{self.devpath} couldn't be ejected {error}") + + From 6fb7edbb3e9ed27ca81fc2adb7cef77007df7b22 Mon Sep 17 00:00:00 2001 From: wolfy Date: Sat, 11 Nov 2023 22:56:14 -0700 Subject: [PATCH 05/30] updated all references and confirmed it's working --- arm/models/job.py | 1 - arm/models/models.py | 202 -------------------------------- arm/ripper/main.py | 4 +- arm/ripper/utils.py | 9 +- arm/ui/database/database.py | 10 +- arm/ui/history/history.py | 10 +- arm/ui/jobs/jobs.py | 15 +-- arm/ui/json_api.py | 3 +- arm/ui/routes.py | 3 +- arm/ui/sendmovies/sendmovies.py | 4 +- arm/ui/settings/DriveUtils.py | 3 +- arm/ui/settings/settings.py | 11 +- arm/ui/utils.py | 9 +- 13 files changed, 45 insertions(+), 239 deletions(-) diff --git a/arm/models/job.py b/arm/models/job.py index 362d1ff01..0740100e4 100644 --- a/arm/models/job.py +++ b/arm/models/job.py @@ -8,7 +8,6 @@ from prettytable import PrettyTable from arm.ripper import music_brainz from arm.ui import db - import arm.config.config as cfg diff --git a/arm/models/models.py b/arm/models/models.py index 240c56965..bca8fa379 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -24,208 +24,6 @@ HIDDEN_VALUE = "" -class Job(db.Model): - """ - Job Class hold most of the details for each job - connects to track, config - """ - job_id = db.Column(db.Integer, primary_key=True) - arm_version = db.Column(db.String(20)) - crc_id = db.Column(db.String(63)) - logfile = db.Column(db.String(256)) - start_time = db.Column(db.DateTime) - stop_time = db.Column(db.DateTime) - job_length = db.Column(db.String(12)) - status = db.Column(db.String(32)) - stage = db.Column(db.String(63)) - no_of_titles = db.Column(db.Integer) - title = db.Column(db.String(256)) - title_auto = db.Column(db.String(256)) - title_manual = db.Column(db.String(256)) - year = db.Column(db.String(4)) - year_auto = db.Column(db.String(4)) - year_manual = db.Column(db.String(4)) - video_type = db.Column(db.String(20)) - video_type_auto = db.Column(db.String(20)) - video_type_manual = db.Column(db.String(20)) - imdb_id = db.Column(db.String(15)) - imdb_id_auto = db.Column(db.String(15)) - imdb_id_manual = db.Column(db.String(15)) - poster_url = db.Column(db.String(256)) - poster_url_auto = db.Column(db.String(256)) - poster_url_manual = db.Column(db.String(256)) - devpath = db.Column(db.String(15)) - mountpoint = db.Column(db.String(20)) - hasnicetitle = db.Column(db.Boolean) - errors = db.Column(db.Text) - disctype = db.Column(db.String(20)) # dvd/bluray/data/music/unknown - label = db.Column(db.String(256)) - path = db.Column(db.String(256)) - ejected = db.Column(db.Boolean) - updated = db.Column(db.Boolean) - pid = db.Column(db.Integer) - pid_hash = db.Column(db.Integer) - tracks = db.relationship('Track', backref='job', lazy='dynamic') - config = db.relationship('Config', uselist=False, backref="job") - - def __init__(self, devpath): - """Return a disc object""" - self.devpath = devpath - self.mountpoint = "/mnt" + devpath - self.hasnicetitle = False - self.video_type = "unknown" - self.ejected = False - self.updated = False - if cfg.arm_config['VIDEOTYPE'] != "auto": - self.video_type = cfg.arm_config['VIDEOTYPE'] - self.parse_udev() - self.get_pid() - self.stage = str(round(time.time() * 100)) - - if self.disctype == "dvd" and not self.label: - logging.info("No disk label Available. Trying lsdvd") - command = f"lsdvd {devpath} | grep 'Disc Title' | cut -d ' ' -f 3-" - lsdvdlbl = str(subprocess.check_output(command, shell=True).strip(), 'utf-8') - self.label = lsdvdlbl - - def __str__(self): - """Returns a string of the object""" - - return_string = self.__class__.__name__ + ": " - for attr, value in self.__dict__.items(): - return_string = return_string + "(" + str(attr) + "=" + str(value) + ") " - - return return_string - - def __repr__(self): - return f'' - - def parse_udev(self): - """Parse udev for properties of current disc""" - context = pyudev.Context() - device = pyudev.Devices.from_device_file(context, self.devpath) - self.disctype = "unknown" - - for key, value in device.items(): - logging.debug(f"pyudev: {key}: {value}") - if key == "ID_FS_LABEL": - self.label = value - if value == "iso9660": - self.disctype = "data" - elif key == "ID_CDROM_MEDIA_BD": - self.disctype = "bluray" - elif key == "ID_CDROM_MEDIA_DVD": - self.disctype = "dvd" - elif key == "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO": - self.disctype = "music" - else: - continue - - def get_pid(self): - """ - Get the jobs process id - :return: None - """ - pid = os.getpid() - process_id = psutil.Process(pid) - self.pid = pid - self.pid_hash = hash(process_id) - - def get_disc_type(self, found_hvdvd_ts): - """ - Checks/corrects the current disc-type - :param found_hvdvd_ts: gets pushed in from utils - saves importing utils - :return: None - """ - if self.disctype == "music": - logging.debug("Disc is music.") - self.label = music_brainz.main(self) - elif os.path.isdir(self.mountpoint + "/VIDEO_TS"): - logging.debug(f"Found: {self.mountpoint}/VIDEO_TS") - self.disctype = "dvd" - elif os.path.isdir(self.mountpoint + "/video_ts"): - logging.debug(f"Found: {self.mountpoint}/video_ts") - self.disctype = "dvd" - elif os.path.isdir(self.mountpoint + "/BDMV"): - logging.debug(f"Found: {self.mountpoint}/BDMV") - self.disctype = "bluray" - elif os.path.isdir(self.mountpoint + "/HVDVD_TS"): - logging.debug(f"Found: {self.mountpoint}/HVDVD_TS") - # do something here - elif found_hvdvd_ts: - logging.debug("Found file: HVDVD_TS") - # do something here too - else: - logging.debug("Did not find valid dvd/bd files. Changing disc-type to 'data'") - self.disctype = "data" - - def identify_audio_cd(self): - """ - Get the title for audio cds to use for the logfile name. - - Needs the job class passed into it so it can be forwarded to mb - - return - only the logfile - setup_logging() adds the full path - """ - # Use the music label if we can find it - defaults to music_cd.log - disc_id = music_brainz.get_disc_id(self) - logging.debug(f"music_id: {disc_id}") - mb_title = music_brainz.get_title(disc_id, self) - logging.debug(f"mm_title: {mb_title}") - - if mb_title == "not identified": - self.label = self.title = "not identified" - logfile = "music_cd.log" - new_log_file = f"music_cd_{round(time.time() * 100)}.log" - else: - logfile = f"{mb_title}.log" - new_log_file = f"{mb_title}_{round(time.time() * 100)}.log" - - temp_log_full = os.path.join(cfg.arm_config['LOGPATH'], logfile) - logfile = new_log_file if os.path.isfile(temp_log_full) else logfile - return logfile - - def pretty_table(self): - """Returns a string of the prettytable""" - pretty_table = PrettyTable() - pretty_table.field_names = ["Config", "Value"] - pretty_table._max_width = {"Config": 50, "Value": 60} - for attr, value in self.__dict__.items(): - if attr == "config": - pretty_table.add_row([str(attr), str(value.pretty_table())]) - else: - pretty_table.add_row([str(attr), str(value)]) - return str(pretty_table.get_string()) - - def get_d(self): - """ - Return a dict of class - exclude the _sa_instance_state - :return: dict containing all attribs from class - """ - return_dict = {} - for key, value in self.__dict__.items(): - if '_sa_instance_state' not in key: - return_dict[str(key)] = str(value) - return return_dict - - def eject(self): - """Eject disc if it hasn't previously been ejected""" - if not self.ejected: - self.ejected = True - try: - # This might always return true - if bool(os.system("umount " + self.devpath)): - logging.debug(f"Unmounted disc {self.devpath}") - else: - logging.debug(f"Failed to unmount {self.devpath}") - if bool(os.system("eject -sv " + self.devpath)): - logging.debug(f"Ejected disc {self.devpath}") - else: - logging.debug(f"Failed to eject {self.devpath}") - except Exception as error: - logging.debug(f"{self.devpath} couldn't be ejected {error}") - - class Track(db.Model): """ Holds all the individual track details for each job """ track_id = db.Column(db.Integer, primary_key=True) diff --git a/arm/ripper/main.py b/arm/ripper/main.py index 6973fa5ae..48386981b 100644 --- a/arm/ripper/main.py +++ b/arm/ripper/main.py @@ -21,7 +21,9 @@ from arm.ripper import logger, utils, identify, arm_ripper, music_brainz # noqa: E402 import arm.config.config as cfg # noqa E402 -from arm.models.models import Job, Config # noqa: E402 +#from arm.models.models import Job, Config # noqa: E402 +from arm.models.models import Config # noqa: E402 +from arm.models.job import Job # noqa: E402 from arm.ui import app, db, constants # noqa E402 from arm.ui.settings import DriveUtils as drive_utils # noqa E402 import arm.config.config as cfg # noqa E402 diff --git a/arm/ripper/utils.py b/arm/ripper/utils.py index 1a17b2105..fd9181f64 100755 --- a/arm/ripper/utils.py +++ b/arm/ripper/utils.py @@ -23,6 +23,7 @@ from arm.ripper import apprise_bulk from arm.ui import db from arm.models import models +from arm.models.job import Job NOTIFY_TITLE = "ARM notification" @@ -621,7 +622,7 @@ def clean_old_jobs(): Check for running jobs - Update failed jobs that are no longer running\n :return: None """ - active_jobs = db.session.query(models.Job).filter(models.Job.status.notin_(['fail', 'success'])).all() + active_jobs = db.session.query(Job).filter(Job.status.notin_(['fail', 'success'])).all() # Clean up abandoned jobs for job in active_jobs: if psutil.pid_exists(job.pid): @@ -679,8 +680,8 @@ def duplicate_run_check(dev_path): this stops that issue :return: None """ - running_jobs = db.session.query(models.Job).filter( - models.Job.status.notin_(['fail', 'success']), models.Job.devpath == dev_path).all() + running_jobs = db.session.query(Job).filter( + Job.status.notin_(['fail', 'success']), Job.devpath == dev_path).all() if len(running_jobs) >= 1: for j in running_jobs: print(j.start_time - datetime.datetime.now()) @@ -758,7 +759,7 @@ def job_dupe_check(job): :return: True/False, dict/None """ logging.debug(f"Trying to find jobs with matching Label={job.label}") - previous_rips = models.Job.query.filter_by(label=job.label, status="success") + previous_rips = Job.query.filter_by(label=job.label, status="success") results = {} i = 0 for j in previous_rips: diff --git a/arm/ui/database/database.py b/arm/ui/database/database.py index ddd60074c..a52737c29 100644 --- a/arm/ui/database/database.py +++ b/arm/ui/database/database.py @@ -14,7 +14,7 @@ import arm.ui.utils as ui_utils from arm.ui import app, db, constants -from arm.models import models as models +from arm.models.job import Job import arm.config.config as cfg from arm.ui.metadata import get_omdb_poster from arm.ui.forms import DBUpdate @@ -45,10 +45,10 @@ def view_database(): # Check for database file if os.path.isfile(cfg.arm_config['DBFILE']): - jobs = models.Job.query.order_by(db.desc(models.Job.job_id)).paginate(page=page, - max_per_page=int( - armui_cfg.database_limit), - error_out=False) + jobs = Job.query.order_by(db.desc(Job.job_id)).paginate(page=page, + max_per_page=int( + armui_cfg.database_limit), + error_out=False) else: app.logger.error('ERROR: /database no database, file doesnt exist') jobs = {} diff --git a/arm/ui/history/history.py b/arm/ui/history/history.py index d02317b4e..f6ac11ac0 100644 --- a/arm/ui/history/history.py +++ b/arm/ui/history/history.py @@ -10,7 +10,7 @@ import arm.ui.utils as ui_utils from arm.ui import app, db -from arm.models import models as models +from arm.models.job import Job import arm.config.config as cfg route_history = Blueprint('route_history', __name__, @@ -34,10 +34,10 @@ def history(): if os.path.isfile(cfg.arm_config['DBFILE']): # after roughly 175 entries firefox readermode will break # jobs = Job.query.filter_by().limit(175).all() - jobs = models.Job.query.order_by(db.desc(models.Job.job_id)).paginate(page=page, - max_per_page=int( - armui_cfg.database_limit), - error_out=False) + jobs = Job.query.order_by(db.desc(Job.job_id)).paginate(page=page, + max_per_page=int( + armui_cfg.database_limit), + error_out=False) else: app.logger.error('ERROR: /history database file doesnt exist') jobs = {} diff --git a/arm/ui/jobs/jobs.py b/arm/ui/jobs/jobs.py index 5866d5ef6..f7aa45ecb 100644 --- a/arm/ui/jobs/jobs.py +++ b/arm/ui/jobs/jobs.py @@ -20,6 +20,7 @@ import arm.ui.utils as ui_utils from arm.ui import app, db, constants, json_api from arm.models import models as models +from arm.models.job import Job import arm.config.config as cfg from arm.ui.forms import TitleSearchForm, ChangeParamsForm @@ -38,7 +39,7 @@ def jobdetail(): displays them in a clear and easy to ready format """ job_id = request.args.get('job_id') - job = models.Job.query.get(job_id) + job = Job.query.get(job_id) tracks = job.tracks.all() search_results = ui_utils.metadata_selector("get_details", job.title, job.year, job.imdb_id) if search_results and 'Error' not in search_results: @@ -54,7 +55,7 @@ def title_search(): The initial search page """ job_id = request.args.get('job_id') - job = models.Job.query.get(job_id) + job = Job.query.get(job_id) form = TitleSearchForm(request.args) if form.validate(): flash(f'Search for {request.args.get("title")}, year={request.args.get("year")}', 'success') @@ -71,7 +72,7 @@ def customtitle(): """ job_id = request.args.get('job_id') ui_utils.job_id_validator(job_id) - job = models.Job.query.get(job_id) + job = Job.query.get(job_id) form = TitleSearchForm(obj=job) if request.args.get("title"): args = { @@ -122,7 +123,7 @@ def updatetitle(): # updatetitle?title=Home&year=2015&imdbID=tt2224026&type=movie& # poster=http://image.tmdb.org/t/p/original/usFenYnk6mr8C62dB1MoAfSWMGR.jpg&job_id=109 job_id = request.args.get('job_id') - job = models.Job.query.get(job_id) + job = Job.query.get(job_id) old_title = job.title old_year = job.year job.title = job.title_manual = ui_utils.clean_for_filename(request.args.get('title')) @@ -147,7 +148,7 @@ def rips(): """ This no longer works properly because of the 'transcoding' status """ - return render_template('activerips.html', jobs=models.Job.query.filter_by(status="active")) + return render_template('activerips.html', jobs=Job.query.filter_by(status="active")) @route_jobs.route('/changeparams') @@ -157,7 +158,7 @@ def changeparams(): For updating Config params or changing/correcting job.disctype manually """ config_id = request.args.get('config_id') - job = models.Job.query.get(config_id) + job = Job.query.get(config_id) config = job.config form = ChangeParamsForm(obj=config) return render_template('changeparams.html', title='Change Parameters', form=form, config=config) @@ -178,7 +179,7 @@ def list_titles(): app.logger.debug("list_titles - no job supplied") flash(constants.NO_JOB, "danger") raise ValidationError - job = models.Job.query.get(job_id) + job = Job.query.get(job_id) form = TitleSearchForm(obj=job) search_results = ui_utils.metadata_selector("search", title, year) if search_results is None or 'Error' in search_results or ( diff --git a/arm/ui/json_api.py b/arm/ui/json_api.py index 25b857e67..5e4bd4ec4 100755 --- a/arm/ui/json_api.py +++ b/arm/ui/json_api.py @@ -12,8 +12,9 @@ from flask import request import arm.config.config as cfg +from arm.models.job import Job +from arm.models.models import Config, Track, Notifications, UISettings from arm.ui import app, db -from arm.models.models import Job, Config, Track, Notifications, UISettings from arm.ui.forms import ChangeParamsForm from arm.ui.utils import job_id_validator, database_updater from arm.ui.settings import DriveUtils as drive_utils # noqa E402 diff --git a/arm/ui/routes.py b/arm/ui/routes.py index 638fb9df4..5e40f4423 100644 --- a/arm/ui/routes.py +++ b/arm/ui/routes.py @@ -23,6 +23,7 @@ import arm.ui.utils as ui_utils from arm.ui import app, db, constants from arm.models import models as models +from arm.models.job import Job import arm.config.config as cfg from arm.ui.forms import DBUpdate from arm.ui.settings.ServerUtil import ServerUtil @@ -71,7 +72,7 @@ def home(): if os.path.isfile(cfg.arm_config['DBFILE']): try: - jobs = db.session.query(models.Job).filter(models.Job.status.notin_(['fail', 'success'])).all() + jobs = db.session.query(Job).filter(Job.status.notin_(['fail', 'success'])).all() except Exception: # db isn't setup return redirect(url_for('setup')) diff --git a/arm/ui/sendmovies/sendmovies.py b/arm/ui/sendmovies/sendmovies.py index 95f63e652..1c2f95f93 100644 --- a/arm/ui/sendmovies/sendmovies.py +++ b/arm/ui/sendmovies/sendmovies.py @@ -8,7 +8,7 @@ from flask import render_template, request, Blueprint from arm.ui import db -from arm.models import models as models +from arm.models.job import Job route_sendmovies = Blueprint('route_sendmovies', __name__, template_folder='templates', @@ -25,6 +25,6 @@ def send_movies(): if request.args.get('s') is None: return render_template('send_movies_form.html') - job_list = db.session.query(models.Job).filter_by(hasnicetitle=True, disctype="dvd").all() + job_list = db.session.query(Job).filter_by(hasnicetitle=True, disctype="dvd").all() return_job_list = [job.job_id for job in job_list] return render_template('send_movies.html', job_list=return_job_list) diff --git a/arm/ui/settings/DriveUtils.py b/arm/ui/settings/DriveUtils.py index 3ea86e410..5c5395108 100644 --- a/arm/ui/settings/DriveUtils.py +++ b/arm/ui/settings/DriveUtils.py @@ -16,6 +16,7 @@ from arm.ui import app, db from arm.models import models +from arm.models.job import Job def drives_search(): @@ -137,7 +138,7 @@ def job_cleanup(job_id): """ Function called when removing a job from the database, removing the data in the previous job field """ - job = models.Job.query.filter_by(job_id=job_id).first() + job = Job.query.filter_by(job_id=job_id).first() drive = models.SystemDrives.query.filter_by(mount=job.devpath).first() drive.job_id_previous = None app.logger.debug(f"Job {job.job_id} cleared from drive {drive.mount} previous") diff --git a/arm/ui/settings/settings.py b/arm/ui/settings/settings.py index 3e23553a3..11a270eac 100644 --- a/arm/ui/settings/settings.py +++ b/arm/ui/settings/settings.py @@ -27,6 +27,7 @@ import arm.ui.utils as ui_utils from arm.ui import app, db from arm.models import models as models +from arm.models.job import Job import arm.config.config as cfg from arm.ui.settings import DriveUtils as drive_utils from arm.ui.forms import SettingsForm, UiSettingsForm, AbcdeForm, SystemInfoDrives @@ -56,11 +57,11 @@ def settings(): # stats for info page with open(os.path.join(cfg.arm_config["INSTALLPATH"], 'VERSION')) as version_file: version = version_file.read().strip() - failed_rips = models.Job.query.filter_by(status="fail").count() - total_rips = models.Job.query.filter_by().count() - movies = models.Job.query.filter_by(video_type="movie").count() - series = models.Job.query.filter_by(video_type="series").count() - cds = models.Job.query.filter_by(disctype="music").count() + failed_rips = Job.query.filter_by(status="fail").count() + total_rips = Job.query.filter_by().count() + movies = Job.query.filter_by(video_type="movie").count() + series = Job.query.filter_by(video_type="series").count() + cds = Job.query.filter_by(disctype="music").count() stats = {'python_version': platform.python_version(), 'arm_version': version, diff --git a/arm/ui/utils.py b/arm/ui/utils.py index 409f5d659..b4c6e2d52 100644 --- a/arm/ui/utils.py +++ b/arm/ui/utils.py @@ -24,6 +24,7 @@ from arm.config import config_utils from arm.ui import app, db from arm.models import models +from arm.models.job import Job from arm.ui.metadata import tmdb_search, get_tmdb_poster, tmdb_find, call_omdb_api # Path definitions @@ -448,7 +449,7 @@ def job_dupe_check(crc_id): """ if crc_id is None: return False, None - jobs = models.Job.query.filter_by(crc_id=crc_id, status="success", hasnicetitle=True) + jobs = Job.query.filter_by(crc_id=crc_id, status="success", hasnicetitle=True) # app.logger.debug("search - posts=" + str(jobs)) return_results = {} i = 0 @@ -521,7 +522,7 @@ def set_media_owner(dirpath, cur_dir, uid, gid): # Validate job is valid job_id_validator(j_id) - job = models.Job.query.get(j_id) + job = Job.query.get(j_id) if not job: raise TypeError("Job Has Been Deleted From The Database") # If there is no path saved in the job @@ -575,7 +576,7 @@ def send_to_remote_db(job_id): :param job_id: Job id :return: dict/json to return to user """ - job = models.Job.query.get(job_id) + job = Job.query.get(job_id) return_dict = {} api_key = cfg.arm_config['ARM_API_KEY'] @@ -812,7 +813,7 @@ def import_movie_add(poster_image, imdb_id, movie_group, my_path): } app.logger.debug(movie_dict) # Create the new job and use the found values - new_movie = models.Job("/dev/sr0") + new_movie = Job("/dev/sr0") new_movie.title = movie_dict['title'] new_movie.year = movie_dict['year'] new_movie.crc_id = hash_object.hexdigest() From 11b516578415c4aa3a244a9b8878a4d8569c6c77 Mon Sep 17 00:00:00 2001 From: wolfy Date: Sat, 11 Nov 2023 23:15:15 -0700 Subject: [PATCH 06/30] moved track class into track.py --- arm/models/track.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 arm/models/track.py diff --git a/arm/models/track.py b/arm/models/track.py new file mode 100644 index 000000000..6a51e0a4e --- /dev/null +++ b/arm/models/track.py @@ -0,0 +1,41 @@ +from arm.ui import db + + +class Track(db.Model): + """ Holds all the individual track details for each job """ + track_id = db.Column(db.Integer, primary_key=True) + job_id = db.Column(db.Integer, db.ForeignKey('job.job_id')) + track_number = db.Column(db.String(4)) + length = db.Column(db.Integer) + aspect_ratio = db.Column(db.String(20)) + fps = db.Column(db.Float) + main_feature = db.Column(db.Boolean) + basename = db.Column(db.String(256)) + filename = db.Column(db.String(256)) + orig_filename = db.Column(db.String(256)) + new_filename = db.Column(db.String(256)) + ripped = db.Column(db.Boolean) + status = db.Column(db.String(32)) + error = db.Column(db.Text) + source = db.Column(db.String(32)) + + def __init__(self, job_id, track_number, length, aspect_ratio, + fps, main_feature, source, basename, filename): + """Return a track object""" + self.job_id = job_id + self.track_number = track_number + self.length = length + self.aspect_ratio = aspect_ratio + self.fps = fps + self.main_feature = main_feature + self.source = source + self.basename = basename + self.filename = filename + self.ripped = False + + def __repr__(self): + return f'' + + def __str__(self): + """Returns a string of the object""" + return self.__class__.__name__ + ": " + self.track_number From 39a2a206ba794ef7ad4d88e57feb33fc9d591fd5 Mon Sep 17 00:00:00 2001 From: wolfy Date: Sun, 12 Nov 2023 01:27:33 -0600 Subject: [PATCH 07/30] updated all references and confirmed it's working --- arm/models/job.py | 3 +-- arm/models/models.py | 40 ---------------------------------------- arm/ripper/makemkv.py | 4 ++-- arm/ripper/utils.py | 3 ++- arm/ui/__init__.py | 2 +- arm/ui/json_api.py | 3 ++- 6 files changed, 8 insertions(+), 47 deletions(-) diff --git a/arm/models/job.py b/arm/models/job.py index 0740100e4..fb1d57450 100644 --- a/arm/models/job.py +++ b/arm/models/job.py @@ -9,6 +9,7 @@ from arm.ripper import music_brainz from arm.ui import db import arm.config.config as cfg +from arm.models.track import Track class Job(db.Model): @@ -211,5 +212,3 @@ def eject(self): logging.debug(f"Failed to eject {self.devpath}") except Exception as error: logging.debug(f"{self.devpath} couldn't be ejected {error}") - - diff --git a/arm/models/models.py b/arm/models/models.py index bca8fa379..f2677c8ed 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -24,46 +24,6 @@ HIDDEN_VALUE = "" -class Track(db.Model): - """ Holds all the individual track details for each job """ - track_id = db.Column(db.Integer, primary_key=True) - job_id = db.Column(db.Integer, db.ForeignKey('job.job_id')) - track_number = db.Column(db.String(4)) - length = db.Column(db.Integer) - aspect_ratio = db.Column(db.String(20)) - fps = db.Column(db.Float) - main_feature = db.Column(db.Boolean) - basename = db.Column(db.String(256)) - filename = db.Column(db.String(256)) - orig_filename = db.Column(db.String(256)) - new_filename = db.Column(db.String(256)) - ripped = db.Column(db.Boolean) - status = db.Column(db.String(32)) - error = db.Column(db.Text) - source = db.Column(db.String(32)) - - def __init__(self, job_id, track_number, length, aspect_ratio, - fps, main_feature, source, basename, filename): - """Return a track object""" - self.job_id = job_id - self.track_number = track_number - self.length = length - self.aspect_ratio = aspect_ratio - self.fps = fps - self.main_feature = main_feature - self.source = source - self.basename = basename - self.filename = filename - self.ripped = False - - def __repr__(self): - return f'' - - def __str__(self): - """Returns a string of the object""" - return self.__class__.__name__ + ": " + self.track_number - - class Config(db.Model): """ Holds all the config settings for each job as these may change between each job """ diff --git a/arm/ripper/makemkv.py b/arm/ripper/makemkv.py index cd0ffe58a..92a1f35f7 100644 --- a/arm/ripper/makemkv.py +++ b/arm/ripper/makemkv.py @@ -7,7 +7,7 @@ import subprocess import shlex -from arm.models import models +from arm.models.track import Track from arm.ripper import utils # noqa: E402 from arm.ui import db # noqa: F401, E402 import arm.config.config as cfg # noqa E402 @@ -69,7 +69,7 @@ def makemkv(logfile, job): get_track_info(mdisc, job) if job.config.MAINFEATURE: logging.info("Trying to find mainfeature") - track = models.Track.query.filter_by(job_id=job.job_id).order_by(models.Track.length.desc()).first() + track = Track.query.filter_by(job_id=job.job_id).order_by(Track.length.desc()).first() rip_mainfeature(job, track, logfile, rawpath) # if no maximum length, process the whole disc in one command elif int(job.config.MAXLENGTH) > 99998: diff --git a/arm/ripper/utils.py b/arm/ripper/utils.py index fd9181f64..f2434cec1 100755 --- a/arm/ripper/utils.py +++ b/arm/ripper/utils.py @@ -24,6 +24,7 @@ from arm.ui import db from arm.models import models from arm.models.job import Job +from arm.models.track import Track NOTIFY_TITLE = "ARM notification" @@ -513,7 +514,7 @@ def put_track(job, t_no, seconds, aspect, fps, mainfeature, source, filename="") f"Track #{int(t_no):02} Length: {seconds: >4} fps: {float(fps):2.3f} " f"aspect: {aspect: >4} Mainfeature: {mainfeature} Source: {source}") - job_track = models.Track( + job_track = Track( job_id=job.job_id, track_number=t_no, length=seconds, diff --git a/arm/ui/__init__.py b/arm/ui/__init__.py index fcb5eca54..abcbbfd6f 100644 --- a/arm/ui/__init__.py +++ b/arm/ui/__init__.py @@ -48,7 +48,7 @@ app.config['SQLALCHEMY_DATABASE_URI'] = sqlitefile app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # We should really generate a key for each system -app.config['SECRET_KEY'] = "Big secret key" # make this random! +app.config['SECRET_KEY'] = "Big secret key" # TODO: make this random! # Set the global Flask Login state, set to True will ignore any @login_required app.config['LOGIN_DISABLED'] = cfg.arm_config['DISABLE_LOGIN'] # Set debug pin as it is hidden normally diff --git a/arm/ui/json_api.py b/arm/ui/json_api.py index 5e4bd4ec4..c06605d6c 100755 --- a/arm/ui/json_api.py +++ b/arm/ui/json_api.py @@ -13,7 +13,8 @@ import arm.config.config as cfg from arm.models.job import Job -from arm.models.models import Config, Track, Notifications, UISettings +from arm.models.models import Config, Notifications, UISettings +from arm.models.track import Track from arm.ui import app, db from arm.ui.forms import ChangeParamsForm from arm.ui.utils import job_id_validator, database_updater From 11288f681fc0f0ea2fb0a6249406f6708decf1fa Mon Sep 17 00:00:00 2001 From: wolfy Date: Sun, 12 Nov 2023 01:33:42 -0600 Subject: [PATCH 08/30] moved Config class into models/config.py --- arm/models/config.py | 118 +++++++++++++++++++++++++++++++++++++++++++ arm/models/models.py | 116 ------------------------------------------ 2 files changed, 118 insertions(+), 116 deletions(-) create mode 100644 arm/models/config.py diff --git a/arm/models/config.py b/arm/models/config.py new file mode 100644 index 000000000..bde4800b4 --- /dev/null +++ b/arm/models/config.py @@ -0,0 +1,118 @@ +from prettytable import PrettyTable + +from arm.ui import db + + +hidden_attribs = ("OMDB_API_KEY", "EMBY_USERID", "EMBY_PASSWORD", + "EMBY_API_KEY", "PB_KEY", "IFTTT_KEY", "PO_KEY", + "PO_USER_KEY", "PO_APP_KEY", "ARM_API_KEY", + "TMDB_API_KEY", "_sa_instance_state") +HIDDEN_VALUE = "" + + +class Config(db.Model): + """ Holds all the config settings for each job + as these may change between each job """ + CONFIG_ID = db.Column(db.Integer, primary_key=True) + job_id = db.Column(db.Integer, db.ForeignKey('job.job_id')) + ARM_CHECK_UDF = db.Column(db.Boolean) + GET_VIDEO_TITLE = db.Column(db.Boolean) + SKIP_TRANSCODE = db.Column(db.Boolean) + VIDEOTYPE = db.Column(db.String(25)) + MINLENGTH = db.Column(db.String(6)) + MAXLENGTH = db.Column(db.String(6)) + MANUAL_WAIT = db.Column(db.Boolean) + MANUAL_WAIT_TIME = db.Column(db.Integer) + RAW_PATH = db.Column(db.String(255)) + TRANSCODE_PATH = db.Column(db.String(255)) + COMPLETED_PATH = db.Column(db.String(255)) + EXTRAS_SUB = db.Column(db.String(255)) + INSTALLPATH = db.Column(db.String(255)) + LOGPATH = db.Column(db.String(255)) + LOGLEVEL = db.Column(db.String(255)) + LOGLIFE = db.Column(db.Integer) + DBFILE = db.Column(db.String(255)) + WEBSERVER_IP = db.Column(db.String(25)) + WEBSERVER_PORT = db.Column(db.Integer) + SET_MEDIA_PERMISSIONS = db.Column(db.Boolean) + CHMOD_VALUE = db.Column(db.Integer) + SET_MEDIA_OWNER = db.Column(db.Boolean) + CHOWN_USER = db.Column(db.String(50)) + CHOWN_GROUP = db.Column(db.String(50)) + RIPMETHOD = db.Column(db.String(25)) + MKV_ARGS = db.Column(db.String(25)) + DELRAWFILES = db.Column(db.Boolean) + HASHEDKEYS = db.Column(db.Boolean) + HB_PRESET_DVD = db.Column(db.String(256)) + HB_PRESET_BD = db.Column(db.String(256)) + DEST_EXT = db.Column(db.String(10)) + HANDBRAKE_CLI = db.Column(db.String(25)) + MAINFEATURE = db.Column(db.Boolean) + HB_ARGS_DVD = db.Column(db.String(256)) + HB_ARGS_BD = db.Column(db.String(256)) + EMBY_REFRESH = db.Column(db.Boolean) + EMBY_SERVER = db.Column(db.String(25)) + EMBY_PORT = db.Column(db.String(6)) + EMBY_CLIENT = db.Column(db.String(25)) + EMBY_DEVICE = db.Column(db.String(50)) + EMBY_DEVICEID = db.Column(db.String(128)) + EMBY_USERNAME = db.Column(db.String(50)) + EMBY_USERID = db.Column(db.String(128)) + EMBY_PASSWORD = db.Column(db.String(128)) + EMBY_API_KEY = db.Column(db.String(64)) + NOTIFY_RIP = db.Column(db.Boolean) + NOTIFY_TRANSCODE = db.Column(db.Boolean) + PB_KEY = db.Column(db.String(64)) + IFTTT_KEY = db.Column(db.String(64)) + IFTTT_EVENT = db.Column(db.String(25)) + PO_USER_KEY = db.Column(db.String(64)) + PO_APP_KEY = db.Column(db.String(64)) + OMDB_API_KEY = db.Column(db.String(64)) + + def __init__(self, c, job_id): + self.__dict__.update(c) + self.job_id = job_id + + def __str__(self): + """Returns a string of the object""" + return_string = self.__class__.__name__ + ": " + for attr, value in self.__dict__.items(): + if str(attr) in hidden_attribs and value: + value = HIDDEN_VALUE + return_string = return_string + "(" + str(attr) + "=" + str(value) + ") " + + return return_string + + def list_params(self): + """Returns a string of the object""" + return_string = self.__class__.__name__ + ": " + for attr, value in self.__dict__.items(): + if return_string: + return_string = return_string + "\n" + if str(attr) in hidden_attribs and value: + value = HIDDEN_VALUE + return_string = return_string + str(attr) + ":" + str(value) + + return return_string + + def pretty_table(self): + """Returns a string of the PrettyTable""" + pretty_table = PrettyTable() + pretty_table.field_names = ["Config", "Value"] + pretty_table._max_width = {"Config": 20, "Value": 30} + for attr, value in self.__dict__.items(): + if str(attr) in hidden_attribs and value: + value = HIDDEN_VALUE + pretty_table.add_row([str(attr), str(value)]) + return str(pretty_table.get_string()) + + def get_d(self): + """ + Return a dict of class - exclude the any sensitive info + :return: dict containing all attribs from class + """ + return_dict = {} + for key, value in self.__dict__.items(): + if str(key) not in hidden_attribs: + return_dict[str(key)] = str(value) + return return_dict diff --git a/arm/models/models.py b/arm/models/models.py index f2677c8ed..f70fc9d28 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -17,122 +17,6 @@ from arm.ui import db import arm.config.config as cfg -hidden_attribs = ("OMDB_API_KEY", "EMBY_USERID", "EMBY_PASSWORD", - "EMBY_API_KEY", "PB_KEY", "IFTTT_KEY", "PO_KEY", - "PO_USER_KEY", "PO_APP_KEY", "ARM_API_KEY", - "TMDB_API_KEY", "_sa_instance_state") -HIDDEN_VALUE = "" - - -class Config(db.Model): - """ Holds all the config settings for each job - as these may change between each job """ - CONFIG_ID = db.Column(db.Integer, primary_key=True) - job_id = db.Column(db.Integer, db.ForeignKey('job.job_id')) - ARM_CHECK_UDF = db.Column(db.Boolean) - GET_VIDEO_TITLE = db.Column(db.Boolean) - SKIP_TRANSCODE = db.Column(db.Boolean) - VIDEOTYPE = db.Column(db.String(25)) - MINLENGTH = db.Column(db.String(6)) - MAXLENGTH = db.Column(db.String(6)) - MANUAL_WAIT = db.Column(db.Boolean) - MANUAL_WAIT_TIME = db.Column(db.Integer) - RAW_PATH = db.Column(db.String(255)) - TRANSCODE_PATH = db.Column(db.String(255)) - COMPLETED_PATH = db.Column(db.String(255)) - EXTRAS_SUB = db.Column(db.String(255)) - INSTALLPATH = db.Column(db.String(255)) - LOGPATH = db.Column(db.String(255)) - LOGLEVEL = db.Column(db.String(255)) - LOGLIFE = db.Column(db.Integer) - DBFILE = db.Column(db.String(255)) - WEBSERVER_IP = db.Column(db.String(25)) - WEBSERVER_PORT = db.Column(db.Integer) - UI_BASE_URL = db.Column(db.String(128)) - SET_MEDIA_PERMISSIONS = db.Column(db.Boolean) - CHMOD_VALUE = db.Column(db.Integer) - SET_MEDIA_OWNER = db.Column(db.Boolean) - CHOWN_USER = db.Column(db.String(50)) - CHOWN_GROUP = db.Column(db.String(50)) - RIPMETHOD = db.Column(db.String(25)) - MKV_ARGS = db.Column(db.String(25)) - DELRAWFILES = db.Column(db.Boolean) - HASHEDKEYS = db.Column(db.Boolean) - HB_PRESET_DVD = db.Column(db.String(256)) - HB_PRESET_BD = db.Column(db.String(256)) - DEST_EXT = db.Column(db.String(10)) - HANDBRAKE_CLI = db.Column(db.String(25)) - MAINFEATURE = db.Column(db.Boolean) - HB_ARGS_DVD = db.Column(db.String(256)) - HB_ARGS_BD = db.Column(db.String(256)) - EMBY_REFRESH = db.Column(db.Boolean) - EMBY_SERVER = db.Column(db.String(25)) - EMBY_PORT = db.Column(db.String(6)) - EMBY_CLIENT = db.Column(db.String(25)) - EMBY_DEVICE = db.Column(db.String(50)) - EMBY_DEVICEID = db.Column(db.String(128)) - EMBY_USERNAME = db.Column(db.String(50)) - EMBY_USERID = db.Column(db.String(128)) - EMBY_PASSWORD = db.Column(db.String(128)) - EMBY_API_KEY = db.Column(db.String(64)) - NOTIFY_RIP = db.Column(db.Boolean) - NOTIFY_TRANSCODE = db.Column(db.Boolean) - PB_KEY = db.Column(db.String(64)) - IFTTT_KEY = db.Column(db.String(64)) - IFTTT_EVENT = db.Column(db.String(25)) - PO_USER_KEY = db.Column(db.String(64)) - PO_APP_KEY = db.Column(db.String(64)) - OMDB_API_KEY = db.Column(db.String(64)) - - def __init__(self, c, job_id): - self.__dict__.update(c) - self.job_id = job_id - - def __str__(self): - """Returns a string of the object""" - return_string = self.__class__.__name__ + ": " - for attr, value in self.__dict__.items(): - if str(attr) in hidden_attribs and value: - value = HIDDEN_VALUE - return_string = return_string + "(" + str(attr) + "=" + str(value) + ") " - - return return_string - - def list_params(self): - """Returns a string of the object""" - return_string = self.__class__.__name__ + ": " - for attr, value in self.__dict__.items(): - if return_string: - return_string = return_string + "\n" - if str(attr) in hidden_attribs and value: - value = HIDDEN_VALUE - return_string = return_string + str(attr) + ":" + str(value) - - return return_string - - def pretty_table(self): - """Returns a string of the PrettyTable""" - pretty_table = PrettyTable() - pretty_table.field_names = ["Config", "Value"] - pretty_table._max_width = {"Config": 20, "Value": 30} - for attr, value in self.__dict__.items(): - if str(attr) in hidden_attribs and value: - value = HIDDEN_VALUE - pretty_table.add_row([str(attr), str(value)]) - return str(pretty_table.get_string()) - - def get_d(self): - """ - Return a dict of class - exclude the any sensitive info - :return: dict containing all attribs from class - """ - return_dict = {} - for key, value in self.__dict__.items(): - if str(key) not in hidden_attribs: - return_dict[str(key)] = str(value) - return return_dict - - class User(db.Model, UserMixin): """ Class to hold admin users From 4f81d81442dc636b0a2ff812efba7a7fca6bd01f Mon Sep 17 00:00:00 2001 From: wolfy Date: Sun, 12 Nov 2023 02:10:15 -0600 Subject: [PATCH 09/30] updated all references and confirmed it's working --- arm/models/job.py | 3 +++ arm/ripper/main.py | 3 +-- arm/ui/json_api.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/arm/models/job.py b/arm/models/job.py index fb1d57450..fa608be2d 100644 --- a/arm/models/job.py +++ b/arm/models/job.py @@ -9,7 +9,10 @@ from arm.ripper import music_brainz from arm.ui import db import arm.config.config as cfg + +# THESE IMPORTS ARE REQUIRED FOR THE db.Relationships to work from arm.models.track import Track +from arm.models.config import Config class Job(db.Model): diff --git a/arm/ripper/main.py b/arm/ripper/main.py index 48386981b..725836fea 100644 --- a/arm/ripper/main.py +++ b/arm/ripper/main.py @@ -21,8 +21,7 @@ from arm.ripper import logger, utils, identify, arm_ripper, music_brainz # noqa: E402 import arm.config.config as cfg # noqa E402 -#from arm.models.models import Job, Config # noqa: E402 -from arm.models.models import Config # noqa: E402 +from arm.models.config import Config # noqa: E402 from arm.models.job import Job # noqa: E402 from arm.ui import app, db, constants # noqa E402 from arm.ui.settings import DriveUtils as drive_utils # noqa E402 diff --git a/arm/ui/json_api.py b/arm/ui/json_api.py index c06605d6c..6c684ed87 100755 --- a/arm/ui/json_api.py +++ b/arm/ui/json_api.py @@ -13,7 +13,8 @@ import arm.config.config as cfg from arm.models.job import Job -from arm.models.models import Config, Notifications, UISettings +from arm.models.models import Notifications, UISettings +from arm.models.config import Config from arm.models.track import Track from arm.ui import app, db from arm.ui.forms import ChangeParamsForm From 9647144c985236d3b111397b789531c568e943dc Mon Sep 17 00:00:00 2001 From: wolfy Date: Sun, 12 Nov 2023 02:12:18 -0600 Subject: [PATCH 10/30] moved User class into user.py --- arm/models/models.py | 27 --------------------------- arm/models/user.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 27 deletions(-) create mode 100644 arm/models/user.py diff --git a/arm/models/models.py b/arm/models/models.py index f70fc9d28..71c7efa69 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -17,33 +17,6 @@ from arm.ui import db import arm.config.config as cfg -class User(db.Model, UserMixin): - """ - Class to hold admin users - """ - user_id = db.Column(db.Integer, index=True, primary_key=True) - email = db.Column(db.String(64)) - password = db.Column(db.String(128)) - hash = db.Column(db.String(256)) - - def __init__(self, email=None, password=None, hashed=None): - self.email = email - self.password = password - self.hash = hashed - - def __repr__(self): - """ Return users name """ - return f'' - - def __str__(self): - """Returns a string of the object""" - return self.__class__.__name__ + ": " + self.email - - def get_id(self): - """ Return users id """ - return self.user_id - - class AlembicVersion(db.Model): """ Class to hold the A.R.M db version diff --git a/arm/models/user.py b/arm/models/user.py new file mode 100644 index 000000000..8ab1ce6dc --- /dev/null +++ b/arm/models/user.py @@ -0,0 +1,30 @@ +from flask_login import UserMixin + +from arm.ui import db + + +class User(db.Model, UserMixin): + """ + Class to hold admin users + """ + user_id = db.Column(db.Integer, index=True, primary_key=True) + email = db.Column(db.String(64)) + password = db.Column(db.String(128)) + hash = db.Column(db.String(256)) + + def __init__(self, email=None, password=None, hashed=None): + self.email = email + self.password = password + self.hash = hashed + + def __repr__(self): + """ Return users name """ + return f'' + + def __str__(self): + """Returns a string of the object""" + return self.__class__.__name__ + ": " + self.email + + def get_id(self): + """ Return users id """ + return self.user_id From 4710b24f44464f8cad5e908fee3a66a5801fed63 Mon Sep 17 00:00:00 2001 From: wolfy Date: Sun, 12 Nov 2023 03:30:06 -0600 Subject: [PATCH 11/30] updated all references and confirmed it's working --- arm/models/models.py | 1 + arm/ripper/utils.py | 3 ++- arm/ui/auth/auth.py | 12 ++++++------ arm/ui/routes.py | 3 ++- arm/ui/utils.py | 5 +++-- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/arm/models/models.py b/arm/models/models.py index 71c7efa69..457ec353c 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -16,6 +16,7 @@ from arm.ripper import music_brainz from arm.ui import db import arm.config.config as cfg +from arm.models.job import Job class AlembicVersion(db.Model): """ diff --git a/arm/ripper/utils.py b/arm/ripper/utils.py index f2434cec1..a2df9f674 100755 --- a/arm/ripper/utils.py +++ b/arm/ripper/utils.py @@ -25,6 +25,7 @@ from arm.models import models from arm.models.job import Job from arm.models.track import Track +from arm.models.user import User NOTIFY_TITLE = "ARM notification" @@ -485,7 +486,7 @@ def try_add_default_user(): username = "admin" pass1 = "password".encode('utf-8') hashed = bcrypt.gensalt(12) - database_adder(models.User(email=username, password=bcrypt.hashpw(pass1, hashed), hashed=hashed)) + database_adder(User(email=username, password=bcrypt.hashpw(pass1, hashed), hashed=hashed)) perm_file = Path(PurePath(cfg.arm_config['INSTALLPATH'], "installed")) write_permission_file = open(perm_file, "w") write_permission_file.write("boop!") diff --git a/arm/ui/auth/auth.py b/arm/ui/auth/auth.py index d8ea7dcef..bf99c89e8 100644 --- a/arm/ui/auth/auth.py +++ b/arm/ui/auth/auth.py @@ -14,7 +14,7 @@ current_user, login_user, logout_user # noqa: F401 from arm.ui import app, db, constants # noqa: F811 -from arm.models import models as models +from arm.models.user import User from arm.ui.forms import SetupForm, DBUpdate import arm.ui.utils as ui_utils @@ -48,7 +48,7 @@ def login(): return_redirect = None # if there is no user in the database try: - user_list = models.User.query.all() + user_list = User.query.all() # If we don't raise an exception but the usr table is empty if not user_list: app.logger.debug("No admin found") @@ -67,7 +67,7 @@ def login(): if request.method == 'POST' and form.validate_on_submit(): login_username = request.form['username'] # we know there is only ever 1 admin account, so we can pull it and check against it locally - admin = models.User.query.filter_by().first() + admin = User.query.filter_by().first() app.logger.debug("user= " + str(admin)) # our pass password = admin.password @@ -106,14 +106,14 @@ def update_password(): updating password for the admin account """ # get current user - user = models.User.query.first() + user = User.query.first() # After a login for is submitted form = SetupForm() if request.method == 'POST' and form.validate_on_submit(): username = str(request.form['username']).strip() new_password = str(request.form['newpassword']).strip().encode('utf-8') - user = models.User.query.filter_by(email=username).first() + user = User.query.filter_by(email=username).first() password = user.password hashed = user.hash # our new one @@ -145,7 +145,7 @@ def load_user(user_id): :return: """ try: - return models.User.query.get(int(user_id)) + return User.query.get(int(user_id)) except Exception: app.logger.debug("Error getting user") return None diff --git a/arm/ui/routes.py b/arm/ui/routes.py index 5e40f4423..b00eed0ed 100644 --- a/arm/ui/routes.py +++ b/arm/ui/routes.py @@ -24,6 +24,7 @@ from arm.ui import app, db, constants from arm.models import models as models from arm.models.job import Job +from arm.models.user import User import arm.config.config as cfg from arm.ui.forms import DBUpdate from arm.ui.settings.ServerUtil import ServerUtil @@ -179,7 +180,7 @@ def load_user(user_id): :return: """ try: - return models.User.query.get(int(user_id)) + return User.query.get(int(user_id)) except Exception: app.logger.debug("Error getting user") return None diff --git a/arm/ui/utils.py b/arm/ui/utils.py index b4c6e2d52..d5d800907 100644 --- a/arm/ui/utils.py +++ b/arm/ui/utils.py @@ -25,6 +25,7 @@ from arm.ui import app, db from arm.models import models from arm.models.job import Job +from arm.models.user import User from arm.ui.metadata import tmdb_search, get_tmdb_poster, tmdb_find, call_omdb_api # Path definitions @@ -400,7 +401,7 @@ def setup_database(): # This checks for a user table try: - admins = models.User.query.all() + admins = User.query.all() app.logger.debug(f"Number of admins: {len(admins)}") if len(admins) > 0: return True @@ -418,7 +419,7 @@ def setup_database(): # UI config is already set within the alembic migration file - 9cae4aa05dd7_create_settingsui_table.py # Create default user to save problems with ui and ripper having diff setups hashed = bcrypt.gensalt(12) - default_user = models.User(email="admin", password=bcrypt.hashpw("password".encode('utf-8'), hashed), + default_user = User(email="admin", password=bcrypt.hashpw("password".encode('utf-8'), hashed), hashed=hashed) app.logger.debug("DB Init - Admin user loaded") db.session.add(default_user) From 149d0fdeaf7fd2bfb372fb04809818f2b7cf4618 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Nov 2023 02:10:50 +0000 Subject: [PATCH 12/30] [Automated] Increment Version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a60dace1b..17808c859 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.60 \ No newline at end of file +2.6.61 From 0ce22f45f18c7b3cf835474c24261c99051fd440 Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 13:45:25 -0600 Subject: [PATCH 13/30] moved AlembicVersion class into alembic_version.py --- arm/models/alembic_version.py | 17 +++++++++++++++++ arm/models/models.py | 16 ---------------- 2 files changed, 17 insertions(+), 16 deletions(-) create mode 100644 arm/models/alembic_version.py diff --git a/arm/models/alembic_version.py b/arm/models/alembic_version.py new file mode 100644 index 000000000..5c5672fba --- /dev/null +++ b/arm/models/alembic_version.py @@ -0,0 +1,17 @@ +from arm.ui import db + +class AlembicVersion(db.Model): + """ + Class to hold the A.R.M db version + """ + version_num = db.Column(db.String(36), autoincrement=False, primary_key=True) + + def __init__(self, version=None): + self.version_num = version + + def __repr__(self): + return f'' + + def __str__(self): + """Returns a string of the object""" + return self.__class__.__name__ + ": " + self.version_num diff --git a/arm/models/models.py b/arm/models/models.py index 457ec353c..76f10d191 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -18,22 +18,6 @@ import arm.config.config as cfg from arm.models.job import Job -class AlembicVersion(db.Model): - """ - Class to hold the A.R.M db version - """ - version_num = db.Column(db.String(36), autoincrement=False, primary_key=True) - - def __init__(self, version=None): - self.version_num = version - - def __repr__(self): - return f'' - - def __str__(self): - """Returns a string of the object""" - return self.__class__.__name__ + ": " + self.version_num - class UISettings(db.Model): """ From 13670e56cb2af6c62a591004b1ddac127818122d Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 14:49:14 -0600 Subject: [PATCH 14/30] updated all references and confirmed it's working --- arm/models/alembic_version.py | 1 + arm/models/config.py | 2 +- arm/ui/utils.py | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/arm/models/alembic_version.py b/arm/models/alembic_version.py index 5c5672fba..4fdaa9761 100644 --- a/arm/models/alembic_version.py +++ b/arm/models/alembic_version.py @@ -1,5 +1,6 @@ from arm.ui import db + class AlembicVersion(db.Model): """ Class to hold the A.R.M db version diff --git a/arm/models/config.py b/arm/models/config.py index bde4800b4..46a944e86 100644 --- a/arm/models/config.py +++ b/arm/models/config.py @@ -108,7 +108,7 @@ def pretty_table(self): def get_d(self): """ - Return a dict of class - exclude the any sensitive info + Return a dict of class - exclude any sensitive info :return: dict containing all attribs from class """ return_dict = {} diff --git a/arm/ui/utils.py b/arm/ui/utils.py index d5d800907..a743c20c9 100644 --- a/arm/ui/utils.py +++ b/arm/ui/utils.py @@ -24,6 +24,7 @@ from arm.config import config_utils from arm.ui import app, db from arm.models import models +from arm.models.alembic_version import AlembicVersion from arm.models.job import Job from arm.models.user import User from arm.ui.metadata import tmdb_search, get_tmdb_poster, tmdb_find, call_omdb_api @@ -64,7 +65,7 @@ def database_updater(args, job, wait_time=90): def check_db_version(install_path, db_file): """ - Check if db exists and is up to date. + Check if db exists and is up-to-date. If it doesn't exist create it. If it's out of date update it. """ from alembic.script import ScriptDirectory @@ -144,7 +145,7 @@ def arm_db_get(): """ Get the Alembic Head revision """ - alembic_db = models.AlembicVersion() + alembic_db = AlembicVersion() db_revision = alembic_db.query.first() app.logger.debug(f"Database Head is: {db_revision.version_num}") return db_revision @@ -152,7 +153,7 @@ def arm_db_get(): def arm_db_check(): """ - Check if db exists and is up to date. + Check if db exists and is up-to-date. """ db_file = cfg.arm_config['DBFILE'] db_exists = False From 9dff6619a3bedd8fdc887c64bc137aece81d13d2 Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 15:21:11 -0600 Subject: [PATCH 15/30] moved UISettings class into ui_settings.py.py --- arm/models/models.py | 45 ------------------------------------ arm/models/ui_settings.py | 46 +++++++++++++++++++++++++++++++++++++ arm/ui/json_api.py | 3 ++- arm/ui/settings/settings.py | 3 ++- arm/ui/utils.py | 3 ++- 5 files changed, 52 insertions(+), 48 deletions(-) create mode 100644 arm/models/ui_settings.py diff --git a/arm/models/models.py b/arm/models/models.py index 76f10d191..71f9e8488 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -19,51 +19,6 @@ from arm.models.job import Job -class UISettings(db.Model): - """ - Class to hold the A.R.M ui settings - """ - id = db.Column(db.Integer, autoincrement=True, primary_key=True) - use_icons = db.Column(db.Boolean) - save_remote_images = db.Column(db.Boolean) - bootstrap_skin = db.Column(db.String(64)) - language = db.Column(db.String(4)) - index_refresh = db.Column(db.Integer) - database_limit = db.Column(db.Integer) - notify_refresh = db.Column(db.Integer) - - def __init__(self, use_icons=None, save_remote_images=None, - bootstrap_skin=None, language=None, index_refresh=None, - database_limit=None, notify_refresh=None): - self.use_icons = use_icons - self.save_remote_images = save_remote_images - self.bootstrap_skin = bootstrap_skin - self.language = language - self.index_refresh = index_refresh - self.database_limit = database_limit - self.notify_refresh = notify_refresh - - def __repr__(self): - return f'' - - def __str__(self): - """Returns a string of the object""" - - return_string = self.__class__.__name__ + ": " - for attr, value in self.__dict__.items(): - return_string = return_string + "(" + str(attr) + "=" + str(value) + ") " - - return return_string - - def get_d(self): - """ Returns a dict of the object""" - return_dict = {} - for key, value in self.__dict__.items(): - if '_sa_instance_state' not in key: - return_dict[str(key)] = str(value) - return return_dict - - class Notifications(db.Model): """ Class to hold the A.R.M notifications diff --git a/arm/models/ui_settings.py b/arm/models/ui_settings.py new file mode 100644 index 000000000..5cc050c51 --- /dev/null +++ b/arm/models/ui_settings.py @@ -0,0 +1,46 @@ +from arm.ui import db + + +class UISettings(db.Model): + """ + Class to hold the A.R.M ui settings + """ + id = db.Column(db.Integer, autoincrement=True, primary_key=True) + use_icons = db.Column(db.Boolean) + save_remote_images = db.Column(db.Boolean) + bootstrap_skin = db.Column(db.String(64)) + language = db.Column(db.String(4)) + index_refresh = db.Column(db.Integer) + database_limit = db.Column(db.Integer) + notify_refresh = db.Column(db.Integer) + + def __init__(self, use_icons=None, save_remote_images=None, + bootstrap_skin=None, language=None, index_refresh=None, + database_limit=None, notify_refresh=None): + self.use_icons = use_icons + self.save_remote_images = save_remote_images + self.bootstrap_skin = bootstrap_skin + self.language = language + self.index_refresh = index_refresh + self.database_limit = database_limit + self.notify_refresh = notify_refresh + + def __repr__(self): + return f'' + + def __str__(self): + """Returns a string of the object""" + + return_string = self.__class__.__name__ + ": " + for attr, value in self.__dict__.items(): + return_string = return_string + "(" + str(attr) + "=" + str(value) + ") " + + return return_string + + def get_d(self): + """ Returns a dict of the object""" + return_dict = {} + for key, value in self.__dict__.items(): + if '_sa_instance_state' not in key: + return_dict[str(key)] = str(value) + return return_dict diff --git a/arm/ui/json_api.py b/arm/ui/json_api.py index 6c684ed87..4fa68189a 100755 --- a/arm/ui/json_api.py +++ b/arm/ui/json_api.py @@ -13,9 +13,10 @@ import arm.config.config as cfg from arm.models.job import Job -from arm.models.models import Notifications, UISettings +from arm.models.models import Notifications from arm.models.config import Config from arm.models.track import Track +from arm.models.ui_settings import UISettings from arm.ui import app, db from arm.ui.forms import ChangeParamsForm from arm.ui.utils import job_id_validator, database_updater diff --git a/arm/ui/settings/settings.py b/arm/ui/settings/settings.py index 11a270eac..044e55237 100644 --- a/arm/ui/settings/settings.py +++ b/arm/ui/settings/settings.py @@ -28,6 +28,7 @@ from arm.ui import app, db from arm.models import models as models from arm.models.job import Job +from arm.models.ui_settings import UISettings import arm.config.config as cfg from arm.ui.settings import DriveUtils as drive_utils from arm.ui.forms import SettingsForm, UiSettingsForm, AbcdeForm, SystemInfoDrives @@ -172,7 +173,7 @@ def save_ui_settings(): """ form = UiSettingsForm() success = False - arm_ui_cfg = models.UISettings.query.get(1) + arm_ui_cfg = UISettings.query.get(1) if form.validate_on_submit(): use_icons = (str(form.use_icons.data).strip().lower() == "true") save_remote_images = (str(form.save_remote_images.data).strip().lower() == "true") diff --git a/arm/ui/utils.py b/arm/ui/utils.py index a743c20c9..20eb9fcdb 100644 --- a/arm/ui/utils.py +++ b/arm/ui/utils.py @@ -26,6 +26,7 @@ from arm.models import models from arm.models.alembic_version import AlembicVersion from arm.models.job import Job +from arm.models.ui_settings import UISettings from arm.models.user import User from arm.ui.metadata import tmdb_search, get_tmdb_poster, tmdb_find, call_omdb_api @@ -208,7 +209,7 @@ def arm_db_cfg(): # if the database has been updated # UISettings could be incorrect, return None try: - armui_cfg = models.UISettings.query.get(1) + armui_cfg = UISettings.query.get(1) app.jinja_env.globals.update(armui_cfg=armui_cfg) except Exception as e: app.logger.debug(f"arm_cfg request error {e}") From 8d59a392f933dd7fcee7d2bacbff46948c48aac0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Nov 2023 20:49:35 +0000 Subject: [PATCH 16/30] [Automated] Increment Version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 17808c859..bf763b012 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.61 +2.6.62 From 979d6bd503b2f2613e88f543bc59a460a5c993e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Nov 2023 21:21:34 +0000 Subject: [PATCH 17/30] [Automated] Increment Version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bf763b012..d2435b39b 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.62 +2.6.63 From 352abdb4d75fe91d078535432d93b1651179508d Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 15:30:03 -0600 Subject: [PATCH 18/30] moved Notifications class into notifications.py --- arm/models/models.py | 41 ------------------------------------ arm/models/notifications.py | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 41 deletions(-) create mode 100644 arm/models/notifications.py diff --git a/arm/models/models.py b/arm/models/models.py index 71f9e8488..ca219532e 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -19,47 +19,6 @@ from arm.models.job import Job -class Notifications(db.Model): - """ - Class to hold the A.R.M notifications - """ - id = db.Column(db.Integer, autoincrement=True, primary_key=True) - seen = db.Column(db.Boolean) - trigger_time = db.Column(db.DateTime) - dismiss_time = db.Column(db.DateTime) - title = db.Column(db.String(256)) - message = db.Column(db.String(256)) - diff_time = None - cleared = db.Column(db.Boolean, default=False, nullable=False) - cleared_time = db.Column(db.DateTime) - - def __init__(self, title=None, message=None): - self.seen = False - self.trigger_time = datetime.datetime.now() - self.title = title - self.message = message - - def __repr__(self): - return f'' - - def __str__(self): - """Returns a string of the object""" - - return_string = self.__class__.__name__ + ": " - for attr, value in self.__dict__.items(): - return_string = return_string + "(" + str(attr) + "=" + str(value) + ") " - - return return_string - - def get_d(self): - """ Returns a dict of the object""" - return_dict = {} - for key, value in self.__dict__.items(): - if '_sa_instance_state' not in key: - return_dict[str(key)] = str(value) - return return_dict - - class SystemInfo(db.Model): """ Class to hold the system (server) information diff --git a/arm/models/notifications.py b/arm/models/notifications.py new file mode 100644 index 000000000..af6e57af5 --- /dev/null +++ b/arm/models/notifications.py @@ -0,0 +1,42 @@ +from arm.ui import db + + +class Notifications(db.Model): + """ + Class to hold the A.R.M notifications + """ + id = db.Column(db.Integer, autoincrement=True, primary_key=True) + seen = db.Column(db.Boolean) + trigger_time = db.Column(db.DateTime) + dismiss_time = db.Column(db.DateTime) + title = db.Column(db.String(256)) + message = db.Column(db.String(256)) + diff_time = None + cleared = db.Column(db.Boolean, default=False, nullable=False) + cleared_time = db.Column(db.DateTime) + + def __init__(self, title=None, message=None): + self.seen = False + self.trigger_time = datetime.datetime.now() + self.title = title + self.message = message + + def __repr__(self): + return f'' + + def __str__(self): + """Returns a string of the object""" + + return_string = self.__class__.__name__ + ": " + for attr, value in self.__dict__.items(): + return_string = return_string + "(" + str(attr) + "=" + str(value) + ") " + + return return_string + + def get_d(self): + """ Returns a dict of the object""" + return_dict = {} + for key, value in self.__dict__.items(): + if '_sa_instance_state' not in key: + return_dict[str(key)] = str(value) + return return_dict From c2e7b2964899b5271d6722da3082dfe0f5460211 Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 15:57:52 -0600 Subject: [PATCH 19/30] updated all references and confirmed it's working --- arm/models/notifications.py | 2 ++ arm/ripper/utils.py | 9 +++++---- arm/ui/jobs/jobs.py | 13 +++++++------ arm/ui/json_api.py | 4 ++-- arm/ui/notifications/notifications.py | 8 ++++---- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/arm/models/notifications.py b/arm/models/notifications.py index af6e57af5..1f7004550 100644 --- a/arm/models/notifications.py +++ b/arm/models/notifications.py @@ -1,3 +1,5 @@ +import datetime + from arm.ui import db diff --git a/arm/ripper/utils.py b/arm/ripper/utils.py index a2df9f674..170087b5c 100755 --- a/arm/ripper/utils.py +++ b/arm/ripper/utils.py @@ -24,6 +24,7 @@ from arm.ui import db from arm.models import models from arm.models.job import Job +from arm.models.notifications import Notifications from arm.models.track import Track from arm.models.user import User @@ -44,7 +45,7 @@ def notify(job, title, body): if cfg.arm_config["NOTIFY_JOBID"]: title = f"{title} - {job.job_id}" # Send to local db - notification = models.Notifications(title, body) + notification = Notifications(title, body) database_adder(notification) bash_notify(cfg.arm_config, title, body) @@ -90,9 +91,9 @@ def notify_entry(job): :return: None """ # TODO make this better or merge with notify/class - notification = models.Notifications(f"New Job: {job.job_id} has started. Disctype: {job.disctype}", - f"New job has started to rip - {job.label}," - f"{job.disctype} at {datetime.datetime.now()}") + notification = Notifications(f"New Job: {job.job_id} has started. Disctype: {job.disctype}", + f"New job has started to rip - {job.label}," + f"{job.disctype} at {datetime.datetime.now()}") database_adder(notification) if job.disctype in ["dvd", "bluray"]: if cfg.arm_config["UI_BASE_URL"] == "": diff --git a/arm/ui/jobs/jobs.py b/arm/ui/jobs/jobs.py index f7aa45ecb..7ac8e8027 100644 --- a/arm/ui/jobs/jobs.py +++ b/arm/ui/jobs/jobs.py @@ -21,6 +21,7 @@ from arm.ui import app, db, constants, json_api from arm.models import models as models from arm.models.job import Job +from arm.models.notifications import Notifications import arm.config.config as cfg from arm.ui.forms import TitleSearchForm, ChangeParamsForm @@ -80,9 +81,9 @@ def customtitle(): 'title_manual': request.args.get("title"), 'year': request.args.get("year") } - notification = models.Notifications(f"Job: {job.job_id} was updated", - f'Title: {job.title} ({job.year}) was updated to ' - f'{request.args.get("title")} ({request.args.get("year")})') + notification = Notifications(f"Job: {job.job_id} was updated", + f'Title: {job.title} ({job.year}) was updated to ' + f'{request.args.get("title")} ({request.args.get("year")})') db.session.add(notification) ui_utils.database_updater(args, job) flash(f'Custom title changed. Title={job.title}, Year={job.year}.', "success") @@ -132,9 +133,9 @@ def updatetitle(): job.imdb_id = job.imdb_id_manual = request.args.get('imdbID') job.poster_url = job.poster_url_manual = request.args.get('poster') job.hasnicetitle = True - notification = models.Notifications(f"Job: {job.job_id} was updated", - f'Title: {old_title} ({old_year}) was updated to ' - f'{request.args.get("title")} ({request.args.get("year")})') + notification = Notifications(f"Job: {job.job_id} was updated", + f'Title: {old_title} ({old_year}) was updated to ' + f'{request.args.get("title")} ({request.args.get("year")})') db.session.add(notification) db.session.commit() flash(f'Title: {old_title} ({old_year}) was updated to ' diff --git a/arm/ui/json_api.py b/arm/ui/json_api.py index 4fa68189a..f76585cb0 100755 --- a/arm/ui/json_api.py +++ b/arm/ui/json_api.py @@ -12,9 +12,9 @@ from flask import request import arm.config.config as cfg -from arm.models.job import Job -from arm.models.models import Notifications from arm.models.config import Config +from arm.models.job import Job +from arm.models.notifications import Notifications from arm.models.track import Track from arm.models.ui_settings import UISettings from arm.ui import app, db diff --git a/arm/ui/notifications/notifications.py b/arm/ui/notifications/notifications.py index 452f99b97..84c1ff31b 100644 --- a/arm/ui/notifications/notifications.py +++ b/arm/ui/notifications/notifications.py @@ -12,7 +12,7 @@ import arm.ui.utils as ui_utils from arm.ui import app -from arm.models import models as models +from arm.models.notifications import Notifications route_notifications = Blueprint('route_notifications', __name__, template_folder='templates', @@ -25,7 +25,7 @@ def arm_nav_notify(): inject the unread notification count to all pages for the navbar count """ try: - notify_count = models.Notifications.query.filter_by(cleared='0').count() + notify_count = Notifications.query.filter_by(cleared='0').count() app.logger.debug(notify_count) except Exception: @@ -40,7 +40,7 @@ def arm_notification(): """ function to display all current notifications """ - notifications_new = models.Notifications.query.filter_by(cleared='0').order_by(models.Notifications.id.desc()).all() + notifications_new = Notifications.query.filter_by(cleared='0').order_by(Notifications.id.desc()).all() if len(notifications_new) != 0: if len(notifications_new) > 100: @@ -62,7 +62,7 @@ def arm_notification_close(): """ function to close all open notifications """ - notifications = models.Notifications.query.filter_by(cleared='0').all() + notifications = Notifications.query.filter_by(cleared='0').all() if len(notifications) != 0: # get the current time for each notification and then save back into notification From b42a10e31d91e97355ecee813d68ea8a3f3f7a16 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Nov 2023 21:58:16 +0000 Subject: [PATCH 20/30] [Automated] Increment Version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index d2435b39b..f2ad14202 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.63 +2.6.64 From ee8ca6dbd6e968d497a81cf6e22fef313f899c3e Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 16:02:13 -0600 Subject: [PATCH 21/30] moved SystemInfo class into system_info.py --- arm/models/models.py | 62 ----------------------------------- arm/models/system_info.py | 68 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 62 deletions(-) create mode 100644 arm/models/system_info.py diff --git a/arm/models/models.py b/arm/models/models.py index ca219532e..9d16839fa 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -19,68 +19,6 @@ from arm.models.job import Job -class SystemInfo(db.Model): - """ - Class to hold the system (server) information - """ - id = db.Column(db.Integer, index=True, primary_key=True) - name = db.Column(db.String(100)) - cpu = db.Column(db.String(20)) - description = db.Column(db.Unicode(200)) - mem_total = db.Column(db.Float()) - - def __init__(self, name="ARM Server", description="Automatic Ripping Machine main server"): - self.get_cpu_info() - self.get_memory() - self.name = name - self.description = description - - def get_cpu_info(self): - """ - function to collect and return some cpu info - ideally want to return {name} @ {speed} Ghz - """ - self.cpu = "Unknown" - if platform.system() == "Windows": - self.cpu = platform.processor() - elif platform.system() == "Darwin": - self.cpu = subprocess.check_output(['/usr/sbin/sysctl', "-n", "machdep.cpu.brand_string"]).strip() - elif platform.system() == "Linux": - command = "cat /proc/cpuinfo" - fulldump = str(subprocess.check_output(command, shell=True).strip()) - # Take any float trailing "MHz", some whitespace, and a colon. - speeds = re.search(r"\\nmodel name\\t:.*?GHz\\n", fulldump) - if speeds: - # We have intel CPU - speeds = str(speeds.group()) - speeds = speeds.replace('\\n', ' ') - speeds = speeds.replace('\\t', ' ') - speeds = speeds.replace('model name :', '') - self.cpu = speeds - # AMD CPU - amd_name_full = re.search(r"model name\\t: (.*?)\\n", fulldump) - if amd_name_full: - amd_name = amd_name_full.group(1) - amd_mhz = re.search(r"cpu MHz(?:\\t)*: ([.0-9]*)\\n", fulldump) # noqa: W605 - if amd_mhz: - amd_ghz = round(float(amd_mhz.group(1)) / 1000, 2) # this is a good idea - self.cpu = str(amd_name) + " @ " + str(amd_ghz) + " GHz" - # ARM64 CPU - arm_cpu = re.search(r"\\nmodel name\\t:(.*?)\\n", fulldump) - if arm_cpu: - self.cpu = str(arm_cpu.group(1))[:19] - else: - self.cpu = "N/A" - - def get_memory(self): - """ get the system total memory """ - try: - memory = psutil.virtual_memory() - self.mem_total = round(memory.total / 1073741824, 1) - except EnvironmentError: - self.mem_total = 0 - - class SystemDrives(db.Model): """ Class to hold the system cd/dvd/blueray drive information diff --git a/arm/models/system_info.py b/arm/models/system_info.py new file mode 100644 index 000000000..0c91f0931 --- /dev/null +++ b/arm/models/system_info.py @@ -0,0 +1,68 @@ +import platform +import psutil +import re +import subprocess + +from arm.ui import db + + +class SystemInfo(db.Model): + """ + Class to hold the system (server) information + """ + id = db.Column(db.Integer, index=True, primary_key=True) + name = db.Column(db.String(100)) + cpu = db.Column(db.String(20)) + description = db.Column(db.Unicode(200)) + mem_total = db.Column(db.Float()) + + def __init__(self, name="ARM Server", description="Automatic Ripping Machine main server"): + self.get_cpu_info() + self.get_memory() + self.name = name + self.description = description + + def get_cpu_info(self): + """ + function to collect and return some cpu info + ideally want to return {name} @ {speed} Ghz + """ + self.cpu = "Unknown" + if platform.system() == "Windows": + self.cpu = platform.processor() + elif platform.system() == "Darwin": + self.cpu = subprocess.check_output(['/usr/sbin/sysctl', "-n", "machdep.cpu.brand_string"]).strip() + elif platform.system() == "Linux": + command = "cat /proc/cpuinfo" + fulldump = str(subprocess.check_output(command, shell=True).strip()) + # Take any float trailing "MHz", some whitespace, and a colon. + speeds = re.search(r"\\nmodel name\\t:.*?GHz\\n", fulldump) + if speeds: + # We have intel CPU + speeds = str(speeds.group()) + speeds = speeds.replace('\\n', ' ') + speeds = speeds.replace('\\t', ' ') + speeds = speeds.replace('model name :', '') + self.cpu = speeds + # AMD CPU + amd_name_full = re.search(r"model name\\t: (.*?)\\n", fulldump) + if amd_name_full: + amd_name = amd_name_full.group(1) + amd_mhz = re.search(r"cpu MHz(?:\\t)*: ([.0-9]*)\\n", fulldump) # noqa: W605 + if amd_mhz: + amd_ghz = round(float(amd_mhz.group(1)) / 1000, 2) # this is a good idea + self.cpu = str(amd_name) + " @ " + str(amd_ghz) + " GHz" + # ARM64 CPU + arm_cpu = re.search(r"\\nmodel name\\t:(.*?)\\n", fulldump) + if arm_cpu: + self.cpu = str(arm_cpu.group(1))[:19] + else: + self.cpu = "N/A" + + def get_memory(self): + """ get the system total memory """ + try: + memory = psutil.virtual_memory() + self.mem_total = round(memory.total / 1073741824, 1) + except EnvironmentError: + self.mem_total = 0 From 4bc1c36a5a85b565719d319b5d9a54caf9eef100 Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 16:26:11 -0600 Subject: [PATCH 22/30] updated all references and confirmed it's working --- arm/ui/routes.py | 4 ++-- arm/ui/settings/settings.py | 3 ++- arm/ui/utils.py | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/arm/ui/routes.py b/arm/ui/routes.py index b00eed0ed..0a34c7ae4 100644 --- a/arm/ui/routes.py +++ b/arm/ui/routes.py @@ -22,8 +22,8 @@ import arm.ui.utils as ui_utils from arm.ui import app, db, constants -from arm.models import models as models from arm.models.job import Job +from arm.models.system_info import SystemInfo from arm.models.user import User import arm.config.config as cfg from arm.ui.forms import DBUpdate @@ -61,7 +61,7 @@ def home(): return render_template(page_support_databaseupdate, db_update=db_update, dbform=dbform) # System details in class server - server = models.SystemInfo.query.filter_by(id="1").first() + server = SystemInfo.query.filter_by(id="1").first() serverutil = ServerUtil() # System details in class server diff --git a/arm/ui/settings/settings.py b/arm/ui/settings/settings.py index 044e55237..aab24ae2d 100644 --- a/arm/ui/settings/settings.py +++ b/arm/ui/settings/settings.py @@ -28,6 +28,7 @@ from arm.ui import app, db from arm.models import models as models from arm.models.job import Job +from arm.models.system_info import SystemInfo from arm.models.ui_settings import UISettings import arm.config.config as cfg from arm.ui.settings import DriveUtils as drive_utils @@ -80,7 +81,7 @@ def settings(): armui_cfg = ui_utils.arm_db_cfg() # System details in class server - server = models.SystemInfo.query.filter_by(id="1").first() + server = SystemInfo.query.filter_by(id="1").first() serverutil = ServerUtil() # System details in class server diff --git a/arm/ui/utils.py b/arm/ui/utils.py index 20eb9fcdb..050542b0c 100644 --- a/arm/ui/utils.py +++ b/arm/ui/utils.py @@ -23,9 +23,9 @@ from arm.ui.settings import DriveUtils as drive_utils from arm.config import config_utils from arm.ui import app, db -from arm.models import models from arm.models.alembic_version import AlembicVersion from arm.models.job import Job +from arm.models.system_info import SystemInfo from arm.models.ui_settings import UISettings from arm.models.user import User from arm.ui.metadata import tmdb_search, get_tmdb_poster, tmdb_find, call_omdb_api @@ -265,9 +265,9 @@ def arm_db_initialise(): Initialise the ARM DB, ensure system values and disk drives are loaded """ # Check system/server information is loaded - if not models.SystemInfo.query.filter_by(id="1").first(): + if not SystemInfo.query.filter_by(id="1").first(): # Define system info and load to db - server = models.SystemInfo() + server = SystemInfo() app.logger.debug("****** System Information ******") app.logger.debug(f"Name: {server.name}") app.logger.debug(f"CPU: {server.cpu}") @@ -303,7 +303,7 @@ def get_info(directory): Used to read stats from files -Used for view logs page :param directory: - :return: list containing a list with each files stats + :return: list containing a list with each file's stats """ file_list = [] for i in os.listdir(directory): @@ -426,7 +426,7 @@ def setup_database(): app.logger.debug("DB Init - Admin user loaded") db.session.add(default_user) # Server config - server = models.SystemInfo() + server = SystemInfo() db.session.add(server) app.logger.debug("DB Init - Server info loaded") db.session.commit() From 86a84712f253003e67a9164719bf47c8c4436c86 Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 16:33:55 -0600 Subject: [PATCH 23/30] moved SystemDrives class into system_drives.py --- arm/models/models.py | 76 --------------------------------- arm/models/system_drives.py | 84 +++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 76 deletions(-) create mode 100644 arm/models/system_drives.py diff --git a/arm/models/models.py b/arm/models/models.py index 9d16839fa..2c452e3ee 100755 --- a/arm/models/models.py +++ b/arm/models/models.py @@ -19,79 +19,3 @@ from arm.models.job import Job -class SystemDrives(db.Model): - """ - Class to hold the system cd/dvd/blueray drive information - """ - drive_id = db.Column(db.Integer, index=True, primary_key=True) - name = db.Column(db.String(100)) - type = db.Column(db.String(20)) - mount = db.Column(db.String(100)) - open = db.Column(db.Boolean) - job_id_current = db.Column(db.Integer, db.ForeignKey("job.job_id")) - job_id_previous = db.Column(db.Integer, db.ForeignKey("job.job_id")) - description = db.Column(db.Unicode(200)) - - # relationship - join current and previous jobs to the jobs table - job_current = db.relationship("Job", backref="Current", foreign_keys=[job_id_current]) - job_previous = db.relationship("Job", backref="Previous", foreign_keys=[job_id_previous]) - - def __init__(self, name, mount, job, job_previous, description): - self.name = name - self.mount = mount - self.open = False - self.job_id_current = job - self.job_id_previous = job_previous - self.description = description - self.drive_type() - - def drive_type(self): - """find the Drive type (CD, DVD, Blueray) from the udev values""" - context = pyudev.Context() - device = pyudev.Devices.from_device_file(context, self.mount) - temp = "" - - for key, value in device.items(): - if key == "ID_CDROM" and value: - temp += "CD" - elif key == "ID_CDROM_DVD" and value: - temp += "/DVD" - elif key == "ID_CDROM_BD" and value: - temp += "/BluRay" - self.type = temp - - def new_job(self, job_id): - """new job assigned to the drive, add the job id""" - self.job_id_current = job_id - - def job_finished(self): - """update Job IDs between current and previous jobs""" - self.job_id_previous = self.job_id_current - self.job_id_current = None - # eject drive (not implemented, as job.eject() decleared in a lot of places) - # self.open_close() - - def open_close(self): - """Open or Close the drive""" - if self.open: - # If open, then close the drive - try: - os.system("eject -tv " + self.mount) - self.open = False - except Exception as error: - logging.debug(f"{self.mount} unable to be closed {error}") - else: - # if closed, open/eject the drive - if self.job_id_current: - logging.debug(f"{self.mount} unable to eject - current job [{self.job_id_current}] is in progress.") - else: - try: - # eject the drive - # eject returns 0 for successful, 1 for failure - if not bool(os.system("eject -v " + self.mount)): - logging.debug(f"Ejected disc {self.mount}") - else: - logging.debug(f"Failed to eject {self.mount}") - self.open = True - except Exception as error: - logging.debug(f"{self.mount} couldn't be ejected {error}") diff --git a/arm/models/system_drives.py b/arm/models/system_drives.py new file mode 100644 index 000000000..f59fcb8a3 --- /dev/null +++ b/arm/models/system_drives.py @@ -0,0 +1,84 @@ +import logging +import os + +import pyudev + +from arm.ui import db + + +class SystemDrives(db.Model): + """ + Class to hold the system cd/dvd/Blu-ray drive information + """ + drive_id = db.Column(db.Integer, index=True, primary_key=True) + name = db.Column(db.String(100)) + type = db.Column(db.String(20)) + mount = db.Column(db.String(100)) + open = db.Column(db.Boolean) + job_id_current = db.Column(db.Integer, db.ForeignKey("job.job_id")) + job_id_previous = db.Column(db.Integer, db.ForeignKey("job.job_id")) + description = db.Column(db.Unicode(200)) + + # relationship - join current and previous jobs to the jobs table + job_current = db.relationship("Job", backref="Current", foreign_keys=[job_id_current]) + job_previous = db.relationship("Job", backref="Previous", foreign_keys=[job_id_previous]) + + def __init__(self, name, mount, job, job_previous, description): + self.name = name + self.mount = mount + self.open = False + self.job_id_current = job + self.job_id_previous = job_previous + self.description = description + self.drive_type() + + def drive_type(self): + """find the Drive type (CD, DVD, Blu-ray) from the udev values""" + context = pyudev.Context() + device = pyudev.Devices.from_device_file(context, self.mount) + temp = "" + + for key, value in device.items(): + if key == "ID_CDROM" and value: + temp += "CD" + elif key == "ID_CDROM_DVD" and value: + temp += "/DVD" + elif key == "ID_CDROM_BD" and value: + temp += "/BluRay" + self.type = temp + + def new_job(self, job_id): + """new job assigned to the drive, add the job id""" + self.job_id_current = job_id + + def job_finished(self): + """update Job IDs between current and previous jobs""" + self.job_id_previous = self.job_id_current + self.job_id_current = None + # eject drive (not implemented, as job.eject() declared in a lot of places) + # self.open_close() + + def open_close(self): + """Open or Close the drive""" + if self.open: + # If open, then close the drive + try: + os.system("eject -tv " + self.mount) + self.open = False + except Exception as error: + logging.debug(f"{self.mount} unable to be closed {error}") + else: + # if closed, open/eject the drive + if self.job_id_current: + logging.debug(f"{self.mount} unable to eject - current job [{self.job_id_current}] is in progress.") + else: + try: + # eject the drive + # eject returns 0 for successful, 1 for failure + if not bool(os.system("eject -v " + self.mount)): + logging.debug(f"Ejected disc {self.mount}") + else: + logging.debug(f"Failed to eject {self.mount}") + self.open = True + except Exception as error: + logging.debug(f"{self.mount} couldn't be ejected {error}") From fabb27aa975ec8f84f61b52f1ec4f1a34fff425f Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 17:38:36 -0600 Subject: [PATCH 24/30] updated all references and confirmed it's working --- arm/ripper/__init__.py | 2 +- arm/ripper/utils.py | 8 ++++---- arm/ui/jobs/jobs.py | 1 - arm/ui/settings/DriveUtils.py | 22 +++++++++++----------- arm/ui/settings/settings.py | 15 +++++++-------- arm/ui/utils.py | 8 ++++---- devtools/armnotify.py | 2 +- 7 files changed, 28 insertions(+), 30 deletions(-) diff --git a/arm/ripper/__init__.py b/arm/ripper/__init__.py index 9e17722bc..51d1bc10d 100755 --- a/arm/ripper/__init__.py +++ b/arm/ripper/__init__.py @@ -1,3 +1,3 @@ #!/usr/bin/env python3 # """ Allows us to import from arm.ripper folder""" -from arm.ripper import logger, utils, makemkv, handbrake, identify, ARMInfo # noqa F401 \ No newline at end of file +from arm.ripper import logger, utils, makemkv, handbrake, identify, ARMInfo # noqa F401 diff --git a/arm/ripper/utils.py b/arm/ripper/utils.py index 170087b5c..cbd65add3 100755 --- a/arm/ripper/utils.py +++ b/arm/ripper/utils.py @@ -17,16 +17,16 @@ import requests import apprise import psutil -import arm.config.config as cfg from netifaces import interfaces, ifaddresses, AF_INET -from arm.ripper import apprise_bulk -from arm.ui import db -from arm.models import models + +import arm.config.config as cfg +from arm.ui import db # needs to be imported before models from arm.models.job import Job from arm.models.notifications import Notifications from arm.models.track import Track from arm.models.user import User +from arm.ripper import apprise_bulk NOTIFY_TITLE = "ARM notification" diff --git a/arm/ui/jobs/jobs.py b/arm/ui/jobs/jobs.py index 7ac8e8027..8f7e5697a 100644 --- a/arm/ui/jobs/jobs.py +++ b/arm/ui/jobs/jobs.py @@ -19,7 +19,6 @@ import arm.ui.utils as ui_utils from arm.ui import app, db, constants, json_api -from arm.models import models as models from arm.models.job import Job from arm.models.notifications import Notifications import arm.config.config as cfg diff --git a/arm/ui/settings/DriveUtils.py b/arm/ui/settings/DriveUtils.py index 5c5395108..e369d0b85 100644 --- a/arm/ui/settings/DriveUtils.py +++ b/arm/ui/settings/DriveUtils.py @@ -15,8 +15,8 @@ import logging from arm.ui import app, db -from arm.models import models from arm.models.job import Job +from arm.models.system_drives import SystemDrives def drives_search(): @@ -45,25 +45,25 @@ def drives_search(): def drives_update(): """ - scan the system for new cd/dvd/blueray drives + scan the system for new cd/dvd/Blu-ray drives """ udev_drives = drives_search() i = 1 new_count = 0 # Get the number of current drives in the database - drive_count = models.SystemDrives.query.count() + drive_count = SystemDrives.query.count() for drive_mount in udev_drives: - # Check drive doesnt already exist - if not models.SystemDrives.query.filter_by(mount=drive_mount).first(): + # Check drive doesn't already exist + if not SystemDrives.query.filter_by(mount=drive_mount).first(): # New drive, set previous job to none last_job = None new_count += 1 # Create new disk (name, type, mount, open, job id, previos job id, description ) - db_drive = models.SystemDrives(f"Drive {drive_count + new_count}", - drive_mount, None, last_job, "Classic burner") + db_drive = SystemDrives(f"Drive {drive_count + new_count}", + drive_mount, None, last_job, "Classic burner") app.logger.debug("****** Drive Information ******") app.logger.debug(f"Name: {db_drive.name}") app.logger.debug(f"Type: {db_drive.type}") @@ -88,7 +88,7 @@ def drives_check_status(): """ Check the drive job status """ - drives = models.SystemDrives.query.all() + drives = SystemDrives.query.all() for drive in drives: # Check if the current job is active, if not remove current job_current id if drive.job_id_current is not None and drive.job_id_current > 0 and drive.job_current is not None: @@ -105,7 +105,7 @@ def drives_check_status(): drive_status_debug(drive) # Requery data to ensure current pending job status change - drives = models.SystemDrives.query.all() + drives = SystemDrives.query.all() return drives @@ -139,7 +139,7 @@ def job_cleanup(job_id): Function called when removing a job from the database, removing the data in the previous job field """ job = Job.query.filter_by(job_id=job_id).first() - drive = models.SystemDrives.query.filter_by(mount=job.devpath).first() + drive = SystemDrives.query.filter_by(mount=job.devpath).first() drive.job_id_previous = None app.logger.debug(f"Job {job.job_id} cleared from drive {drive.mount} previous") @@ -148,7 +148,7 @@ def update_drive_job(job): """ Function to take current job task and update the associated drive ID into the database """ - drive = models.SystemDrives.query.filter_by(mount=job.devpath).first() + drive = SystemDrives.query.filter_by(mount=job.devpath).first() drive.new_job(job.job_id) logging.debug(f"Updating drive [{job.devpath}] current job, with id [{job.job_id}]") try: diff --git a/arm/ui/settings/settings.py b/arm/ui/settings/settings.py index aab24ae2d..334bb8950 100644 --- a/arm/ui/settings/settings.py +++ b/arm/ui/settings/settings.py @@ -26,12 +26,12 @@ import arm.ui.utils as ui_utils from arm.ui import app, db -from arm.models import models as models from arm.models.job import Job +from arm.models.system_drives import SystemDrives from arm.models.system_info import SystemInfo from arm.models.ui_settings import UISettings import arm.config.config as cfg -from arm.ui.settings import DriveUtils as drive_utils +from arm.ui.settings import DriveUtils from arm.ui.forms import SettingsForm, UiSettingsForm, AbcdeForm, SystemInfoDrives from arm.ui.settings.ServerUtil import ServerUtil import arm.ripper.utils as ripper_utils @@ -90,7 +90,7 @@ def settings(): # form_drive = SystemInfoDrives(request.form) # System Drives (CD/DVD/Blueray drives) - drives = drive_utils.drives_check_status() + drives = DriveUtils.drives_check_status() # Load up the comments.json, so we can comment the arm.yaml comments = ui_utils.generate_comments() @@ -260,8 +260,7 @@ def server_info(): app.logger.debug( "Drive id: " + str(form_drive.id.data) + " Updated db description: " + form_drive.description.data) - drive = models.SystemDrives.query.filter_by( - drive_id=form_drive.id.data).first() + drive = SystemDrives.query.filter_by(drive_id=form_drive.id.data).first() drive.description = str(form_drive.description.data).strip() db.session.commit() # Return to systeminfo page (refresh page) @@ -276,11 +275,11 @@ def system_drive_scan(): """ Page - systemdrivescan Method - GET - Overview - Scan for a to the system drives and update the databse. + Overview - Scan for the system drives and update the database. """ global redirect_settings # Update to scan for changes from system - new_count = drive_utils.drives_update() + new_count = DriveUtils.drives_update() flash(f"ARM found {new_count} new drives", "success") return redirect(redirect_settings) @@ -299,7 +298,7 @@ def drive_eject(id): Server System - change state of CD/DVD/BluRay drive - toggle eject """ global redirect_settings - drive = models.SystemDrives.query.filter_by(drive_id=id).first() + drive = SystemDrives.query.filter_by(drive_id=id).first() drive.open_close() db.session.commit() return redirect(redirect_settings) diff --git a/arm/ui/utils.py b/arm/ui/utils.py index 050542b0c..4488241ab 100644 --- a/arm/ui/utils.py +++ b/arm/ui/utils.py @@ -20,15 +20,15 @@ import arm.config.config as cfg from arm.config.config_utils import arm_yaml_test_bool -from arm.ui.settings import DriveUtils as drive_utils from arm.config import config_utils -from arm.ui import app, db from arm.models.alembic_version import AlembicVersion from arm.models.job import Job from arm.models.system_info import SystemInfo from arm.models.ui_settings import UISettings from arm.models.user import User +from arm.ui import app, db from arm.ui.metadata import tmdb_search, get_tmdb_poster, tmdb_find, call_omdb_api +from arm.ui.settings import DriveUtils # Path definitions path_migrations = "arm/migrations" @@ -277,7 +277,7 @@ def arm_db_initialise(): db.session.add(server) db.session.commit() # Scan and load drives to database - drive_utils.drives_update() + DriveUtils.drives_update() def make_dir(path): @@ -431,7 +431,7 @@ def setup_database(): app.logger.debug("DB Init - Server info loaded") db.session.commit() # Scan and load drives to database - drive_utils.drives_update() + DriveUtils.drives_update() app.logger.debug("DB Init - Drive info loaded") return True except Exception: diff --git a/devtools/armnotify.py b/devtools/armnotify.py index 7fd40efd4..6057808f8 100644 --- a/devtools/armnotify.py +++ b/devtools/armnotify.py @@ -10,7 +10,7 @@ import sys sys.path.insert(0, '/opt/arm') from arm.ripper import utils # noqa E402 -from arm.models.models import Job # noqa E402 +from arm.models.job import Job # noqa E402 def test(): From 9834d15798513069d6953db88e44e0b09b62806a Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 17:54:16 -0600 Subject: [PATCH 25/30] removed deprecated file --- arm/models/models.py | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100755 arm/models/models.py diff --git a/arm/models/models.py b/arm/models/models.py deleted file mode 100755 index 2c452e3ee..000000000 --- a/arm/models/models.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Hold all models for ARM -""" -import datetime -import os -import subprocess -import logging -import time -import pyudev -import psutil -import platform -import re - -from prettytable import PrettyTable -from flask_login import LoginManager, current_user, login_user, UserMixin # noqa: F401 -from arm.ripper import music_brainz -from arm.ui import db -import arm.config.config as cfg -from arm.models.job import Job - - From f159ebfb442f1e8f0e1a98410b718888204d1cb3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Nov 2023 22:26:35 +0000 Subject: [PATCH 26/30] [Automated] Increment Version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index f2ad14202..b069e63fd 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.64 +2.6.65 From b57e2a58f5554f7f79fb9be85767fe003be13027 Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 13 Nov 2023 18:34:19 -0600 Subject: [PATCH 27/30] fixed flake8 failures --- arm/models/job.py | 4 ++-- arm/ripper/utils.py | 2 +- arm/ui/utils.py | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/arm/models/job.py b/arm/models/job.py index fa608be2d..6a6ccef70 100644 --- a/arm/models/job.py +++ b/arm/models/job.py @@ -11,8 +11,8 @@ import arm.config.config as cfg # THESE IMPORTS ARE REQUIRED FOR THE db.Relationships to work -from arm.models.track import Track -from arm.models.config import Config +from arm.models.track import Track # noqa: F401 +from arm.models.config import Config # noqa: F401 class Job(db.Model): diff --git a/arm/ripper/utils.py b/arm/ripper/utils.py index cbd65add3..694108598 100755 --- a/arm/ripper/utils.py +++ b/arm/ripper/utils.py @@ -21,7 +21,7 @@ from netifaces import interfaces, ifaddresses, AF_INET import arm.config.config as cfg -from arm.ui import db # needs to be imported before models +from arm.ui import db # needs to be imported before models from arm.models.job import Job from arm.models.notifications import Notifications from arm.models.track import Track diff --git a/arm/ui/utils.py b/arm/ui/utils.py index 4488241ab..9ac90267a 100644 --- a/arm/ui/utils.py +++ b/arm/ui/utils.py @@ -421,8 +421,7 @@ def setup_database(): # UI config is already set within the alembic migration file - 9cae4aa05dd7_create_settingsui_table.py # Create default user to save problems with ui and ripper having diff setups hashed = bcrypt.gensalt(12) - default_user = User(email="admin", password=bcrypt.hashpw("password".encode('utf-8'), hashed), - hashed=hashed) + default_user = User(email="admin", password=bcrypt.hashpw("password".encode('utf-8'), hashed), hashed=hashed) app.logger.debug("DB Init - Admin user loaded") db.session.add(default_user) # Server config From 761f0dc88239660f2325a9202eda7c99597f39ea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Nov 2023 23:54:42 +0000 Subject: [PATCH 28/30] [Automated] Increment Version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b069e63fd..78dcc483a 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.65 +2.6.66 From b627232f5563e09e6a4f8ea743032e60837f7178 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Nov 2023 00:34:59 +0000 Subject: [PATCH 29/30] [Automated] Increment Version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 78dcc483a..39103acc0 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.66 +2.6.67 From 50cced53d45d6a1227ae9459fbc5b5d7c71fd1b1 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Mon, 30 Oct 2023 00:16:43 -0400 Subject: [PATCH 30/30] Second attempt at changes --- arm/models/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/arm/models/config.py b/arm/models/config.py index 46a944e86..03ba40921 100644 --- a/arm/models/config.py +++ b/arm/models/config.py @@ -34,6 +34,7 @@ class Config(db.Model): DBFILE = db.Column(db.String(255)) WEBSERVER_IP = db.Column(db.String(25)) WEBSERVER_PORT = db.Column(db.Integer) + UI_BASE_URL = db.Column(db.String(128)) SET_MEDIA_PERMISSIONS = db.Column(db.Boolean) CHMOD_VALUE = db.Column(db.Integer) SET_MEDIA_OWNER = db.Column(db.Boolean)