# This file is part of stov, written by Helmut Pozimski 2012-2021. # # stov is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 2 of the License. # # stov is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with stov. If not, see . # -*- coding: utf8 -*- """This file takes care of reading, storing and updating the configuration of stov, the configuration file is expected to be in json format and reside in ~/.stov/stov.json. """ import json import logging import os from lib_stov import stov_exceptions LOGGER = logging.getLogger("stov") class Conf: """This class is used to create the object which is responsible for all configuration operations. """ _instance = None def __init__(self): """Constructor Constructs the conf object with some reasonable default values which should work on most systems, existence of a local mail server is assumed. """ self.values = { "database": "stov.sqlite", "downloaddir": str(os.environ['HOME']) + "/stov", "maxvideos": "25", "mailhost": "localhost", "mailto": "root", "mailfrom": "stov@localhost", "smtpport": "25", "auth_needed": "no", "user_name": "", "password": "", "youtube-dl": "", "notify": "yes", "config_version": "10", "db_version": "6", "video_codec": "mp4", "video_height": "1080", "audio_quality": "bestaudio", "maxfails": 50, "check_title": "no" } self._explanations = { "database": _("the name of your database file"), "downloaddir": _("the directory where downloaded videos are " "saved"), "maxvideos": _("the maximum number of videos to retrieve for each " "subscription"), "mailhost": _("the host name of your mail server"), "mailto": _("the address used for notifications"), "mailfrom": _("the sender address of notification e-mails"), "smtpport": _("the port to use on your mail server"), "auth_needed": _("if your mail server requires authentication"), "user_name": _("the user name used to authenticate to your mail " "server"), "password": _("the password used to authenticate to your mail " "server"), "youtube-dl": _("the path to your youtube-dl installation"), "notify": _("if you want to be notified via e-mail about new " "videos"), "video_codec": _("which video codec you prefer (h264, webm or " "flv)"), "video_height": _("sets the height of the video to download (e.g." " 1080 for 1920x180)"), "audio_quality": _("sets the requested audio quality " "(e.g. bestaudio)"), "check_title": _("if you want to compare the title of a video " "with the subscription search string") } self.dbpath = str(os.environ['HOME']) + "/.stov/" + \ self.values["database"] self.outputlevel = "default" def __new__(cls, *args, **kwargs): if not Conf._instance: Conf._instance = super(Conf, cls).__new__(cls) return Conf._instance @staticmethod def get_instance(): """ Returns the singleton instance of the object :return: Conf object :rtype: lib_stov.configuration.Conf """ return Conf._instance def write_config(self): """Writes the configuration from the dictionary into the configuration file for stov. The configuration is written into the home directory of the user by default. """ LOGGER.debug(_("Writing configuration file.")) try: configfile = open(str(os.environ['HOME']) + "/.stov/stov.json", "w") except IOError as exc: raise stov_exceptions.ConfigFileWriteErrorException() from exc else: json.dump(self.values, configfile, indent=0) configfile.close() def initialize(self): """Creates the necessary directory for the stov configuration and calls the internal methods to create the database and the configuration file. """ LOGGER.debug(_("Creating stov configuration directory")) try: os.mkdir(str(os.environ['HOME']) + "/.stov", 0o750) except os.error as exc: raise stov_exceptions.DirectoryCreationFailedException() from exc else: self.write_config() def read_old_config(self): """Reads the existing plain text configuration file and places the values in the dictionary. Existing values (such as default values) are overwritten. """ LOGGER.debug(_("Opening old configuration file to convert it to " "json")) try: configfile = open(str(os.environ['HOME']) + "/.stov/stov.config", "r") except IOError as exc: raise stov_exceptions.ConfigFileReadErrorException() from exc for lines in configfile: tmpline = lines.strip() tmplist = tmpline.split("=") self.values[tmplist[0].lower()] = tmplist[1] configfile.close() self.dbpath = str(os.environ['HOME']) + "/.stov/" \ + self.values["database"] def read_config(self): """Reads the existing json configuration files and loads the values in the dictionary. """ LOGGER.debug(_("Opening configuration file")) try: configfile = open(str(os.environ['HOME']) + "/.stov/stov.json", "r") except IOError as exc: raise stov_exceptions.ConfigFileReadErrorException() from exc else: self.values.update(json.load(configfile)) configfile.close() def check_config(self): """Checks if the configuration is up-to-date with the running stov version. """ try: current_version = int(self.values["config_version"]) except ValueError as exc: raise stov_exceptions.InvalidConfigurationVersionException() \ from exc self.values["config_version"] = "0" self.read_config() if self.values["config_version"] == "0" \ or int(self.values["config_version"]) < current_version: self.values["config_version"] = str(current_version) return False self.values["config_version"] = current_version return True def update_config(self): """Update the configuration to the latest version""" version_buffer = self.values["config_version"] self.read_config() self.values["config_version"] = version_buffer self.write_config() def check_db(self): """Checks the database if it is up-to-date""" current_db_version = int(self.values["db_version"]) self.values["db_version"] = "0" self.read_config() if self.values["db_version"] == "0" or \ int(self.values["db_version"]) < \ int(current_db_version): self.values["db_version"] = str(current_db_version) return False self.values["db_version"] = str(current_db_version) return True def assist(self): """ Ask the user to set all required configuration parameters """ print(_("This assistant will help you to perform the initial " "configuration of stov. \nThe default value will be " "displayed in brackets.\n" "Please specify now :\n")) for value in self._explanations: print(self._explanations[value] + " [" + self.values[value] + "]:" + " ") user_input = input() if user_input != "": self.values[value] = user_input self.dbpath = str(os.environ['HOME']) + "/.stov/" + \ self.values["database"] def migrate_config(self): """Migrates the configuration from the old plain text config to the new and shiny json configuration file. """ try: self.read_old_config() self.write_config() except stov_exceptions.ConfigFileReadErrorException as exc: raise stov_exceptions.ConfigurationMigrationFailed() from exc except stov_exceptions.ConfigFileWriteErrorException as exc: raise stov_exceptions.ConfigurationMigrationFailed from exc else: os.remove(str(os.environ['HOME']) + "/.stov/stov.config") def configure_logging(self, verbose=False, quiet=False): """ Sets up logging for stov and returns a logger object :param verbose: whether to use verbose mode :type verbose: bool :param quiet: whether to use quiet mode :type quiet: bool :return: logger object :rtype: logging.Logger """ logger = logging.getLogger("stov") # verbose takes precedence if verbose: logger.setLevel(logging.DEBUG) self.outputlevel = "verbose" elif quiet: logger.setLevel(logging.ERROR) self.outputlevel = "quiet" else: logger.setLevel(logging.INFO) console_handler = logging.StreamHandler() logger.addHandler(console_handler) return logger def get_value(self, identifier): """ Returns the value for the specified identifier :param identifier: the identifier in the value dictionary :return: """ return self.values[identifier]