# -*- coding: utf-8 -*- # This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) # Copyright (C) 2019 OzzieIsaacs, pwr # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import division, print_function, unicode_literals import os import json from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean from sqlalchemy.ext.declarative import declarative_base from . import constants, cli, logger log = logger.create() _Base = declarative_base() # Baseclass for representing settings in app.db with email server settings and Calibre database settings # (application settings) class _Settings(_Base): __tablename__ = 'settings' id = Column(Integer, primary_key=True) mail_server = Column(String, default='mail.example.org') mail_port = Column(Integer, default=25) mail_use_ssl = Column(SmallInteger, default=0) mail_login = Column(String, default='mail@example.com') mail_password = Column(String, default='mypassword') mail_from = Column(String, default='automailer ') config_calibre_dir = Column(String) config_port = Column(Integer, default=constants.DEFAULT_PORT) config_certfile = Column(String) config_keyfile = Column(String) config_calibre_web_title = Column(String, default=u'Calibre-Web') config_books_per_page = Column(Integer, default=60) config_random_books = Column(Integer, default=4) config_authors_max = Column(Integer, default=0) config_read_column = Column(Integer, default=0) config_title_regex = Column(String, default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+') config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL) config_access_log = Column(SmallInteger, default=0) config_uploading = Column(SmallInteger, default=0) config_anonbrowse = Column(SmallInteger, default=0) config_public_reg = Column(SmallInteger, default=0) config_default_role = Column(SmallInteger, default=0) config_default_show = Column(SmallInteger, default=6143) config_columns_to_ignore = Column(String) config_use_google_drive = Column(Boolean, default=False) config_google_drive_folder = Column(String) config_google_drive_watch_changes_response = Column(String) config_remote_login = Column(Boolean, default=False) config_use_goodreads = Column(Boolean, default=False) config_goodreads_api_key = Column(String) config_goodreads_api_secret = Column(String) config_login_type = Column(Integer, default=0) # config_use_ldap = Column(Boolean) config_ldap_provider_url = Column(String) config_ldap_dn = Column(String) # config_use_github_oauth = Column(Boolean) config_github_oauth_client_id = Column(String) config_github_oauth_client_secret = Column(String) # config_use_google_oauth = Column(Boolean) config_google_oauth_client_id = Column(String) config_google_oauth_client_secret = Column(String) config_ldap_provider_url = Column(String, default='localhost') config_ldap_port = Column(SmallInteger, default=389) config_ldap_schema = Column(String, default='ldap') config_ldap_serv_username = Column(String) config_ldap_serv_password = Column(String) config_ldap_use_ssl = Column(Boolean, default=False) config_ldap_use_tls = Column(Boolean, default=False) config_ldap_require_cert = Column(Boolean, default=False) config_ldap_cert_path = Column(String) config_ldap_dn = Column(String) config_ldap_user_object = Column(String) config_ldap_openldap = Column(Boolean, default=False) config_mature_content_tags = Column(String, default='') config_logfile = Column(String) config_access_logfile = Column(String) config_ebookconverter = Column(Integer, default=0) config_converterpath = Column(String) config_calibre = Column(String) config_rarfile_location = Column(String) config_theme = Column(Integer, default=0) config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE) def __repr__(self): return self.__class__.__name__ # Class holds all application specific settings in calibre-web class _ConfigSQL(object): # pylint: disable=no-member def __init__(self, session): self._session = session self._settings = None self.db_configured = None self.config_calibre_dir = None self.load() def _read_from_storage(self): if self._settings is None: log.debug("_ConfigSQL._read_from_storage") self._settings = self._session.query(_Settings).first() return self._settings def get_config_certfile(self): if cli.certfilepath: return cli.certfilepath if cli.certfilepath == "": return None return self.config_certfile def get_config_keyfile(self): if cli.keyfilepath: return cli.keyfilepath if cli.certfilepath == "": return None return self.config_keyfile def get_config_ipaddress(self): return cli.ipadress or "" def get_ipaddress_type(self): return cli.ipv6 def _has_role(self, role_flag): return constants.has_flag(self.config_default_role, role_flag) def role_admin(self): return self._has_role(constants.ROLE_ADMIN) def role_download(self): return self._has_role(constants.ROLE_DOWNLOAD) def role_viewer(self): return self._has_role(constants.ROLE_VIEWER) def role_upload(self): return self._has_role(constants.ROLE_UPLOAD) def role_edit(self): return self._has_role(constants.ROLE_EDIT) def role_passwd(self): return self._has_role(constants.ROLE_PASSWD) def role_edit_shelfs(self): return self._has_role(constants.ROLE_EDIT_SHELFS) def role_delete_books(self): return self._has_role(constants.ROLE_DELETE_BOOKS) def show_element_new_user(self, value): return constants.has_flag(self.config_default_show, value) def show_detail_random(self): return self.show_element_new_user(constants.DETAIL_RANDOM) def show_mature_content(self): return self.show_element_new_user(constants.MATURE_CONTENT) def mature_content_tags(self): mct = self.config_mature_content_tags.split(",") return [t.strip() for t in mct] def get_log_level(self): return logger.get_level_name(self.config_log_level) def get_mail_settings(self): return {k:v for k, v in self.__dict__.items() if k.startswith('mail_')} def set_from_dictionary(self, dictionary, field, convertor=None, default=None): '''Possibly updates a field of this object. The new value, if present, is grabbed from the given dictionary, and optionally passed through a convertor. :returns: `True` if the field has changed value ''' new_value = dictionary.get(field, default) if new_value is None: # log.debug("_ConfigSQL set_from_dictionary field '%s' not found", field) return False if field not in self.__dict__: log.warning("_ConfigSQL trying to set unknown field '%s' = %r", field, new_value) return False if convertor is not None: new_value = convertor(new_value) current_value = self.__dict__.get(field) if current_value == new_value: return False # log.debug("_ConfigSQL set_from_dictionary '%s' = %r (was %r)", field, new_value, current_value) setattr(self, field, new_value) return True def load(self): '''Load all configuration values from the underlying storage.''' s = self._read_from_storage() # type: _Settings for k, v in s.__dict__.items(): if k[0] != '_': if v is None: # if the storage column has no value, apply the (possible) default column = s.__class__.__dict__.get(k) if column.default is not None: v = column.default.arg setattr(self, k, v) if self.config_google_drive_watch_changes_response: self.config_google_drive_watch_changes_response = json.loads(self.config_google_drive_watch_changes_response) self.db_configured = (self.config_calibre_dir and (not self.config_use_google_drive or os.path.exists(self.config_calibre_dir + '/metadata.db'))) logger.setup(self.config_logfile, self.config_log_level) def save(self): '''Apply all configuration values to the underlying storage.''' s = self._read_from_storage() # type: _Settings for k, v in self.__dict__.items(): if k[0] == '_': continue if hasattr(s, k): # and getattr(s, k, None) != v: # log.debug("_Settings save '%s' = %r", k, v) setattr(s, k, v) log.debug("_ConfigSQL updating storage") self._session.merge(s) self._session.commit() self.load() def invalidate(self): log.warning("invalidating configuration") self.db_configured = False self.config_calibre_dir = None self.save() def _migrate_table(session, orm_class): changed = False for column_name, column in orm_class.__dict__.items(): if column_name[0] != '_': try: session.query(column).first() except exc.OperationalError as err: log.debug("%s: %s", column_name, err) column_default = "" if column.default is None else ("DEFAULT %r" % column.default.arg) alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__, column_name, column.type, column_default) session.execute(alter_table) changed = True if changed: session.commit() def _migrate_database(session): # make sure the table is created, if it does not exist _Base.metadata.create_all(session.bind) _migrate_table(session, _Settings) def load_configuration(session): _migrate_database(session) if not session.query(_Settings).count(): session.add(_Settings()) session.commit() return _ConfigSQL(session)