From e554d36bd0dd1faa5ea80d0ebd472f9e7db6a4a6 Mon Sep 17 00:00:00 2001
From: Dmitry Shelepnev <mitshel@mail.ru>
Date: Sat, 7 Jan 2017 20:42:39 +0300
Subject: [PATCH] Move all SOPDS settings to database

---
 .../management/commands/sopds_scanner.py      | 19 ++---
 .../management/commands/sopds_server.py       |  9 ++-
 opds_catalog/settings.py                      | 77 +++++++++++--------
 sopds/settings.py                             | 19 -----
 sopds_web_backend/views.py                    | 33 ++++----
 5 files changed, 78 insertions(+), 79 deletions(-)

diff --git a/opds_catalog/management/commands/sopds_scanner.py b/opds_catalog/management/commands/sopds_scanner.py
index e16c6fe..f75be29 100644
--- a/opds_catalog/management/commands/sopds_scanner.py
+++ b/opds_catalog/management/commands/sopds_scanner.py
@@ -8,11 +8,12 @@ from apscheduler.schedulers.blocking import BlockingScheduler
 
 from django.core.management.base import BaseCommand
 from django.db import transaction, connection, connections
-from django.conf import settings
+from django.conf import settings as main_settings
 
 from opds_catalog.models import Counter
 from opds_catalog.sopdscan import opdsScanner
-from opds_catalog.settings import SCANNER_LOG, SCAN_SHED_DAY, SCAN_SHED_DOW, SCAN_SHED_HOUR, SCAN_SHED_MIN, LOGLEVEL, SCANNER_PID
+#from opds_catalog.settings import SCANNER_LOG, SCAN_SHED_DAY, SCAN_SHED_DOW, SCAN_SHED_HOUR, SCAN_SHED_MIN, LOGLEVEL, SCANNER_PID
+from opds_catalog import settings 
 
 class Command(BaseCommand):
     help = 'Scan Books Collection.'
@@ -23,16 +24,16 @@ class Command(BaseCommand):
         parser.add_argument('--daemon',action='store_true', dest='daemonize', default=False, help='Daemonize server')
         
     def handle(self, *args, **options): 
-        self.pidfile = os.path.join(settings.BASE_DIR, SCANNER_PID)
+        self.pidfile = os.path.join(main_settings.BASE_DIR, settings.SCANNER_PID)
         action = options['command']            
         self.logger = logging.getLogger('')
         self.logger.setLevel(logging.DEBUG)
         formatter=logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
 
-        if LOGLEVEL!=logging.NOTSET:
+        if settings.LOGLEVEL!=logging.NOTSET:
             # Создаем обработчик для записи логов в файл
-            fh = logging.FileHandler(SCANNER_LOG)
-            fh.setLevel(LOGLEVEL)
+            fh = logging.FileHandler(settings.SCANNER_LOG)
+            fh.setLevel(settings.LOGLEVEL)
             fh.setFormatter(formatter)
             self.logger.addHandler(fh)
 
@@ -76,9 +77,9 @@ class Command(BaseCommand):
             
     def start(self):
         writepid(self.pidfile)
-        self.stdout.write('Startup scheduled book-scan (min=%s, hour=%s, day_of_week=%s, day=%s).'%(SCAN_SHED_MIN,SCAN_SHED_HOUR,SCAN_SHED_DOW,SCAN_SHED_DAY))
+        self.stdout.write('Startup scheduled book-scan (min=%s, hour=%s, day_of_week=%s, day=%s).'%(settings.SCAN_SHED_MIN,settings.SCAN_SHED_HOUR,settings.SCAN_SHED_DOW,settings.SCAN_SHED_DAY))
         sched = BlockingScheduler()
-        sched.add_job(self.scan, 'cron', day=SCAN_SHED_DAY, day_of_week=SCAN_SHED_DOW, hour=SCAN_SHED_HOUR, minute=SCAN_SHED_MIN)
+        sched.add_job(self.scan, 'cron', day=settings.SCAN_SHED_DAY, day_of_week=settings.SCAN_SHED_DOW, hour=settings.SCAN_SHED_HOUR, minute=settings.SCAN_SHED_MIN)
         quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'
         self.stdout.write("Quit the server with %s.\n"%quit_command)  
         try:
@@ -118,7 +119,7 @@ def daemonize():
     os.umask(0)
 
     std_in = open("/dev/null", 'r')
-    std_out = open(SCANNER_LOG, 'a+')
+    std_out = open(settings.SCANNER_LOG, 'a+')
     os.dup2(std_in.fileno(), sys.stdin.fileno())
     os.dup2(std_out.fileno(), sys.stdout.fileno())
     os.dup2(std_out.fileno(), sys.stderr.fileno())    
diff --git a/opds_catalog/management/commands/sopds_server.py b/opds_catalog/management/commands/sopds_server.py
index dceee88..6803071 100644
--- a/opds_catalog/management/commands/sopds_server.py
+++ b/opds_catalog/management/commands/sopds_server.py
@@ -2,11 +2,12 @@ import os
 import signal
 import sys
 
-from django.conf import settings
+from django.conf import settings as main_settings
 from django.core.management.base import BaseCommand
 from django.core.management import call_command
 
-from opds_catalog.settings import SERVER_LOG, SERVER_PID
+#from opds_catalog.settings import SERVER_LOG, SERVER_PID
+from opds_catalog import settings
 
 class Command(BaseCommand):
     help = 'HTTP/OPDS built-in server'
@@ -19,7 +20,7 @@ class Command(BaseCommand):
 
 
     def handle(self, *args, **options):
-        self.pidfile = os.path.join(settings.BASE_DIR, SERVER_PID)
+        self.pidfile = os.path.join(main_settings.BASE_DIR, settings.SERVER_PID)
         action = options['command']
         self.addr = options['host']
         self.port = int(options['port'])
@@ -75,7 +76,7 @@ def daemonize():
     os.umask(0)
 
     std_in = open("/dev/null", 'r')
-    std_out = open(SERVER_LOG, 'a+')
+    std_out = open(settings.SERVER_LOG, 'a+')
     os.dup2(std_in.fileno(), sys.stdin.fileno())
     os.dup2(std_out.fileno(), sys.stdout.fileno())
     os.dup2(std_out.fileno(), sys.stderr.fileno())    
diff --git a/opds_catalog/settings.py b/opds_catalog/settings.py
index c719a5b..81242f7 100644
--- a/opds_catalog/settings.py
+++ b/opds_catalog/settings.py
@@ -1,30 +1,31 @@
 import logging
 import os
 from django.conf import settings
+from constance import config
 
 loglevels={'debug':logging.DEBUG,'info':logging.INFO,'warning':logging.WARNING,'error':logging.ERROR,'critical':logging.CRITICAL,'none':logging.NOTSET}
 
-VERSION = "0.39"
+VERSION = "0.40"
 
 # ROOT_LIB содержит путь к каталогу в котором расположена ваша коллекция книг
-ROOT_LIB = getattr(settings, "SOPDS_ROOT_LIB", "books/")
+ROOT_LIB = config.SOPDS_ROOT_LIB
 
 # Списк форматов книг, которые будут включаться в каталог
-BOOK_EXTENSIONS = getattr(settings, "SOPDS_BOOK_EXTESIONS", ['.pdf', '.djvu', '.fb2', '.epub'])
+BOOK_EXTENSIONS = config.SOPDS_BOOK_EXTENSIONS.split()
 
 # Скрывает, найденные дубликаты в выдачах книг
-DOUBLES_HIDE = getattr(settings, "SOPDS_DOUBLES_HIDE", True)
+DOUBLES_HIDE = config.SOPDS_DOUBLES_HIDE
 
 # Извлекать метаинформацию из книг fb2
-FB2PARSE = getattr(settings, "SOPDS_FB2PARSE", True)
+FB2PARSE = config.SOPDS_FB2PARSE
 
 # cover_show - способ показа обложек:
 # False - не показывать, 
 # True - извлекать обложки на лету и показывать 
-COVER_SHOW = getattr(settings, "SOPDS_COVER_SHOW", True)
+COVER_SHOW = config.SOPDS_COVER_SHOW
 
 # ZIPSCAN = True  - Приводит к сканированию файлов архива
-ZIPSCAN = getattr(settings, "SOPDS_ZIPSCAN", True)
+ZIPSCAN = config.SOPDS_ZIPSCAN
 
 # Указываем какая кодировка для названий файлов используется в ZIP-архивах
 # доступные кодировки: cp437, cp866, cp1251, utf-8
@@ -33,73 +34,73 @@ ZIPSCAN = getattr(settings, "SOPDS_ZIPSCAN", True)
 # то автоматически определить правильную кодировку для имен файлов не представляется возможным
 # поэтому для того чтобы кириллические имена файлов не ваыглядели как крякозябры следует применять кодировку cp866
 # по умолчанию также используется значение zip_codepage = cp866
-ZIPCODEPAGE = getattr(settings, "SOPDS_ZIPCODEPAGE", "cp866")
+ZIPCODEPAGE = config.SOPDS_ZIPCODEPAGE
 
 # Если INPX_ENABLE = True, то при обнаружении INPX файла в каталоге, сканер не сканирует его содержимое вместе с подгаталогами, а загружает
 # данные из найденного INPX файла. Сканер считает что сами архивыс книгами расположены в этом же каталоге. 
 # Т.е. INPX-файл должен находится именно в папке с архивами книг
-INPX_ENABLE = getattr(settings, "SOPDS_INPX_ENABLE", True)
+INPX_ENABLE = config.SOPDS_INPX_ENABLE
 
 # Если INPX_SKIP_UNCHANGED = True, то сканер пропускает повторное сканирование, если размер INPX не изменялся
-INPX_SKIP_UNCHANGED = getattr(settings, "SOPDS_INPX_SKIP_UNCHANGED", True)
+INPX_SKIP_UNCHANGED = config.SOPDS_INPX_SKIP_UNCHANGED
 
 # Если INPX_TEST_ZIP = True, то сканер пытается найти описанный в INPX архив. Если какой-то архив не обнаруживается, 
 # то сканер не будет добавлять вязанные с ним данные из INPX в базу данных
 # соответсвенно, если INPX_TEST_ZIP = False, то никаких проверок сканер не производит, а просто добавляет данные из INPX в БД
 # это гораздо быстрее
-INPX_TEST_ZIP = getattr(settings, "SOPDS_INPX_TEST_ZIP", False)
+INPX_TEST_ZIP = config.SOPDS_INPX_TEST_ZIP
 
 # Если INPX_TEST_FILES = True, то сканер пытается найти описанный в INPX конкретный файл с книгой (уже внутри архивов). Если какой-то файл не обнаруживается, 
 # то сканер не будет добавлять эту книгу в базу данных
 # соответсвенно, если INPX_TEST_FILES = False, то никаких проверок сканер не производит, а просто добавляет книгу из INPX в БД
 # это гораздо быстрее
-INPX_TEST_FILES = getattr(settings, "SOPDS_TEST_FILES", False)
+INPX_TEST_FILES = config.SOPDS_INPX_TEST_FILES
 
 # Установка DELETE_LOGICAL=True приведет к тому, что при обнаружении сканером, что книга удалена, запись в БД об этой книге будет удалена логически (avail=0)
 # Если DELETE_LOGICAL=False, то произойдет физическое удаление таких записей из базы данных
 # пока работает только DELETE_LOGICAL = False
-DELETE_LOGICAL = getattr(settings, "SOPDS_DELETE_LOGICAL", False)
+DELETE_LOGICAL = config.SOPDS_DELETE_LOGICAL
 
-SPLITITEMS = getattr(settings, "SOPDS_SPLITITEMS", 300)
+SPLITITEMS = config.SOPDS_SPLITITEMS
 
 # Количество выдаваемых результатов на одну страницу
-MAXITEMS = getattr(settings, "SOPDS_MAXITEMS", 60)
+MAXITEMS = config.SOPDS_MAXITEMS
 
 # FB2TOEPUB и FB2TOMOBI задают пути к програмам - конвертерам из FB2 в EPUB и MOBI
-FB2TOEPUB = getattr(settings, "SOPDS_FB2TOEPUB", "")
-FB2TOMOBI = getattr(settings, "SOPDS_FB2TOMOBI", "")
+FB2TOEPUB = config.SOPDS_FB2TOEPUB
+FB2TOMOBI = config.SOPDS_FB2TOMOBI
 
 # TEMP_DIR задает путь к временному каталогу, который используется для копирования оригинала и результата конвертации
-TEMP_DIR = getattr(settings, "SOPDS_TEMP_DIR", os.path.join(settings.BASE_DIR,'tmp'))
+TEMP_DIR = config.SOPDS_TEMP_DIR
 
 # При скачивании вместо оригинального имени файла книги выдает транслитерацию названия книги
-TITLE_AS_FILENAME = getattr(settings, "SOPDS_TITLE_AS_FILENAME", True)
+TITLE_AS_FILENAME = config.SOPDS_TITLE_AS_FILENAME
 
 # Включение дополнительного меню выбора алфавита
-ALPHABET_MENU = getattr(settings, "SOPDS_ALPHABET_MENU", True)
+ALPHABET_MENU = config.SOPDS_ALPHABET_MENU
 
 # Обложка, которая будет демонстрироваться для книг без обложек
-NOCOVER_PATH = getattr(settings, "SOPDS_NOCOVER_PATH", os.path.join(settings.BASE_DIR,'static/images/nocover.jpg'))
+NOCOVER_PATH = config.SOPDS_NOCOVER_PATH
 
 # Включение BASIC - авторизации
-AUTH = getattr(settings, "SOPDS_AUTH", False)
+AUTH = config.SOPDS_AUTH
 
 # параметры SERVER_LOG и SCANNER_LOG задают размещение LOG файлов этих процессов
-SERVER_LOG = getattr(settings, "SOPDS_SERVER_LOG", os.path.join(settings.BASE_DIR,'opds_catalog/log/sopds_server.log'))
-SCANNER_LOG = getattr(settings, "SOPDS_SCANNER_LOG", os.path.join(settings.BASE_DIR,'opds_catalog/log/sopds_scanner.log'))
+SERVER_LOG = config.SOPDS_SERVER_LOG
+SCANNER_LOG = config.SOPDS_SCANNER_LOG
 
 # параметры SERVER_PID и SCANNER_PID задают размещение PID файлов этих процессов при демонизации
-SERVER_PID = getattr(settings, "SOPDS_SERVER_PID", os.path.join(settings.BASE_DIR,'opds_catalog/tmp/sopds_server.pid'))
-SCANNER_PID = getattr(settings, "SOPDS_SCANNER_PID", os.path.join(settings.BASE_DIR,'opds_catalog/tmp/sopds_scanner.pid'))
+SERVER_PID = config.SOPDS_SERVER_PID
+SCANNER_PID = config.SOPDS_SCANNER_PID
 
 # SCAN_SHED устанавливают значения шедулера, для периодического сканирования коллекции книг
 # при помощи manage.py sopds_scanner ...
 # Возможные значения можно найти на следующей странице; 
 # https://apscheduler.readthedocs.io/en/latest/modules/triggers/cron.html#module-apscheduler.triggers.cron
-SCAN_SHED_MIN = getattr(settings, "SOPDS_SCAN_SHED_MIN", '0')
-SCAN_SHED_HOUR = getattr(settings, "SOPDS_SCAN_SHED_HOUR", '0')
-SCAN_SHED_DAY = getattr(settings, "SOPDS_SCAN_SHED_DAY", '*')
-SCAN_SHED_DOW = getattr(settings, "SOPDS_SCAN_SHED_DOW", '*')
+SCAN_SHED_MIN = config.SOPDS_SCAN_SHED_MIN
+SCAN_SHED_HOUR = config.SOPDS_SCAN_SHED_HOUR
+SCAN_SHED_DAY = config.SOPDS_SCAN_SHED_DAY
+SCAN_SHED_DOW = config.SOPDS_SCAN_SHED_DOW
 
 TITLE = getattr(settings, "SOPDS_TITLE", "SimpleOPDS")
 SUBTITLE = getattr(settings, "SOPDS_SUBTITLE", "SimpleOPDS Catalog by www.sopds.ru. Version %s."%VERSION)
@@ -111,9 +112,23 @@ if loglevel.lower() in loglevels:
 else:
     LOGLEVEL=logging.NOTSET
     
+# Отработка изменения конфигурации    
+import sys
+from constance.signals import config_updated
+from django.dispatch import receiver
+
+@receiver(config_updated)
+def constance_updated(sender, updated_key, new_value, **kwargs):
+    if updated_key == 'SOPDS_BOOK_EXTENSIONS':
+        value = new_value.split()
+    else:
+        value = new_value
+    key=updated_key.replace('SOPDS_','')
+    setattr(sys.modules[__name__], key, value)
+    print(key, value, MAXITEMS)
+    
 # Переопределяем некоторые функции для SQLite, которые работают неправлено
 from django.db.backends.signals import connection_created
-from django.dispatch import receiver
 
 def sopds_upper(s):
     return s.upper()
diff --git a/sopds/settings.py b/sopds/settings.py
index 39490ed..d600f35 100644
--- a/sopds/settings.py
+++ b/sopds/settings.py
@@ -195,23 +195,4 @@ CONSTANCE_CONFIG_FIELDSETS = {
     '6. Log & PID Files': ('SOPDS_SERVER_LOG', 'SOPDS_SCANNER_LOG', 'SOPDS_SERVER_PID','SOPDS_SCANNER_PID'),
 }
 
-#
-# SIMPLE OPDS SETTINGS
-#
-SOPDS_ROOT_LIB = 'W:\\_Downloads\\_Lib.rus.ec - Официальная\\lib.rus.ec\\'
-#SOPDS_ROOT_LIB = '/mnt/SATA1TB-1/КНИГИ/BOOKS/'
-#SOPDS_ROOT_LIB = '/mnt/nfs/КНИГИ/BOOKS/'
-#SOPDS_ROOT_LIB = os.path.join(BASE_DIR,'opds_catalog\\tests\\data\\')
-#SOPDS_ROOT_LIB = "d:\\BOOKS\\"
-
-SOPDS_AUTH = True
-SOPDS_SCAN_SHED_MIN ='0'
-SOPDS_SCAN_SHED_HOUR ='0,12'
-SOPDS_INPX_ENABLE = True
-
-#Конвертеры EPUB и MOBI
-#SOPDS_FB2TOEPUB = os.path.join(BASE_DIR,'convert/fb2toepub/unix_dist/fb2toepub')
-#SOPDS_FB2TOEPUB = os.path.join(BASE_DIR,'convert/fb2conv/fb2epub')
-#SOPDS_FB2TOMOBI = os.path.join(BASE_DIR,'convert/fb2conv/fb2mobi')
-#SOPDS_FB2TOEPUB = os.path.join(BASE_DIR, 'convert\\fb2epub\\fb2epub.cmd' if sys.platform =='win32' else 'convert/fb2epub/fb2epub' )
 
diff --git a/sopds_web_backend/views.py b/sopds_web_backend/views.py
index 367bb91..797df33 100644
--- a/sopds_web_backend/views.py
+++ b/sopds_web_backend/views.py
@@ -11,7 +11,8 @@ from django.core.urlresolvers import reverse, reverse_lazy
 
 from opds_catalog import models
 from opds_catalog.models import Book, Author, Series, bookshelf, Counter, Catalog, Genre
-from opds_catalog.settings import MAXITEMS, DOUBLES_HIDE, AUTH, VERSION, ALPHABET_MENU, SPLITITEMS, FB2TOEPUB, FB2TOMOBI
+#from opds_catalog.settings import MAXITEMS, DOUBLES_HIDE, AUTH, VERSION, ALPHABET_MENU, SPLITITEMS, FB2TOEPUB, FB2TOMOBI
+from opds_catalog import settings as settings
 from opds_catalog.models import lang_menu
 from opds_catalog.opds_paginator import Paginator as OPDS_Paginator
 
@@ -19,7 +20,7 @@ from sopds_web_backend.settings import HALF_PAGES_LINKS
 
 def sopds_login(function=None, redirect_field_name=REDIRECT_FIELD_NAME, url=None):
     actual_decorator = user_passes_test(
-        lambda u: (u.is_authenticated() if AUTH else True),
+        lambda u: (u.is_authenticated() if settings.AUTH else True),
         login_url=reverse_lazy(url),
         redirect_field_name=redirect_field_name
     ) 
@@ -29,16 +30,16 @@ def sopds_login(function=None, redirect_field_name=REDIRECT_FIELD_NAME, url=None
 
 def sopds_processor(request):
     args={}
-    args['sopds_auth']=AUTH
-    args['sopds_version']=VERSION
-    args['alphabet'] = ALPHABET_MENU
-    args['splititems'] = SPLITITEMS
-    args['fb2tomobi'] = (FB2TOMOBI!="")
-    args['fb2toepub'] = (FB2TOEPUB!="")
-    if ALPHABET_MENU:
+    args['sopds_auth']=settings.AUTH
+    args['sopds_version']=settings.VERSION
+    args['alphabet'] = settings.ALPHABET_MENU
+    args['splititems'] = settings.SPLITITEMS
+    args['fb2tomobi'] = (settings.FB2TOMOBI!="")
+    args['fb2toepub'] = (settings.FB2TOEPUB!="")
+    if settings.ALPHABET_MENU:
         args['lang_menu'] = lang_menu
     
-    if AUTH:
+    if settings.AUTH:
         user=request.user
         if user.is_authenticated():
             result=[]
@@ -136,7 +137,7 @@ def SearchBooksView(request):
                                    
         # Поиск книг на книжной полке            
         elif searchtype == 'u':
-            if AUTH:
+            if settings.AUTH:
                 books = Book.objects.filter(bookshelf__user=request.user).order_by('-bookshelf__readtime')
                 args['breadcrumbs'] = [_('Books'),_('Bookshelf'),request.user.username]
                 #books = bookshelf.objects.filter(user=request.user).select_related('book')              
@@ -175,7 +176,7 @@ def SearchBooksView(request):
         
         # Фильтруем дубликаты и формируем выдачу затребованной страницы
         books_count = books.count()
-        op = OPDS_Paginator(books_count, 0, page_num, MAXITEMS, HALF_PAGES_LINKS)
+        op = OPDS_Paginator(books_count, 0, page_num, settings.MAXITEMS, HALF_PAGES_LINKS)
         items = []
         
         prev_title = ''
@@ -183,7 +184,7 @@ def SearchBooksView(request):
         
         # Начаинам анализ с последнего элемента на предидущей странице, чторбы он "вытянул" с этой страницы
         # свои дубликаты если они есть
-        summary_DOUBLES_HIDE =  DOUBLES_HIDE and (searchtype != 'd')
+        summary_DOUBLES_HIDE =  settings.DOUBLES_HIDE and (searchtype != 'd')
         start = op.d1_first_pos if ((op.d1_first_pos==0) or (not summary_DOUBLES_HIDE)) else op.d1_first_pos-1
         finish = op.d1_last_pos
         
@@ -251,7 +252,7 @@ def SearchSeriesView(request):
             
         # Создаем результирующее множество
         series_count = series.count()
-        op = OPDS_Paginator(series_count, 0, page_num, MAXITEMS, HALF_PAGES_LINKS)        
+        op = OPDS_Paginator(series_count, 0, page_num, settings.MAXITEMS, HALF_PAGES_LINKS)        
         items = []
         for row in series[op.d1_first_pos:op.d1_last_pos+1]:
             #p = {'id':row.id, 'ser':row.ser, 'lang_code': row.lang_code, 'book_count': Book.objects.filter(series=row).count()}
@@ -290,7 +291,7 @@ def SearchAuthorsView(request):
                         
         # Создаем результирующее множество
         authors_count = authors.count()
-        op = OPDS_Paginator(authors_count, 0, page_num, MAXITEMS, HALF_PAGES_LINKS)        
+        op = OPDS_Paginator(authors_count, 0, page_num, settings.MAXITEMS, HALF_PAGES_LINKS)        
         items = []
         
         for row in authors[op.d1_first_pos:op.d1_last_pos+1]:
@@ -334,7 +335,7 @@ def CatalogsView(request):
     books_count = books_list.count()
     
     # Получаем результирующий список
-    op = OPDS_Paginator(catalogs_count, books_count, page_num, MAXITEMS, HALF_PAGES_LINKS)
+    op = OPDS_Paginator(catalogs_count, books_count, page_num, settings.MAXITEMS, HALF_PAGES_LINKS)
     items = []
     
     for row in catalogs_list[op.d1_first_pos:op.d1_last_pos+1]:
-- 
GitLab