From 4c6f5096be4065469a4cac062c90b30cf8686d62 Mon Sep 17 00:00:00 2001 From: Ren Wei Date: Sat, 1 Aug 2020 15:12:20 +0800 Subject: [PATCH 01/18] get metadata, typo, if no result from google, it will display the noresult msg, regardless of the others results. --- cps/static/js/get_meta.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/static/js/get_meta.js b/cps/static/js/get_meta.js index 3ad5ffd8..e27608ab 100644 --- a/cps/static/js/get_meta.js +++ b/cps/static/js/get_meta.js @@ -74,8 +74,8 @@ $(function () { $("#meta-info").html(""); } if ((ggDone === 3 || (ggDone === 1 && ggResults.length === 0)) && - (dbDone === 3 || (ggDone === 1 && dbResults.length === 0)) && - (cvDone === 3 || (ggDone === 1 && cvResults.length === 0))) { + (dbDone === 3 || (dbDone === 1 && dbResults.length === 0)) && + (cvDone === 3 || (cvDone === 1 && cvResults.length === 0))) { $("#meta-info").html("

" + msg.no_result + "

"); return; } From e7eb5b6ea653f2d48d38569d57ae41715c5c04ef Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 29 Aug 2020 12:24:12 -0400 Subject: [PATCH 02/18] Improvements to task processing * Moves clean up to separate function for organization * moves the `self.dequeued.append(item)` immediately after `self.queue.get()` so that we don't wait for it to show up (cherry picked from commit bc9372e88f0c8855694431f811702d7fb899a97e) --- cps/services/worker.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/cps/services/worker.py b/cps/services/worker.py index 05307b52..14c94df6 100644 --- a/cps/services/worker.py +++ b/cps/services/worker.py @@ -82,33 +82,37 @@ class WorkerThread(threading.Thread): tasks = self.queue.to_list() + self.dequeued return sorted(tasks, key=lambda x: x.num) + def cleanup_tasks(self): + with self.doLock: + dead = [] + alive = [] + for x in self.dequeued: + (dead if x.task.dead else alive).append(x) + + # if the ones that we need to keep are within the trigger, do nothing else + delta = len(self.dequeued) - len(dead) + if delta > TASK_CLEANUP_TRIGGER: + ret = alive + else: + # otherwise, lop off the oldest dead tasks until we hit the target trigger + ret = sorted(dead, key=lambda x: x.task.end_time)[-TASK_CLEANUP_TRIGGER:] + alive + + self.dequeued = sorted(ret, key=lambda x: x.num) # Main thread loop starting the different tasks def run(self): main_thread = _get_main_thread() while main_thread.is_alive(): + # this blocks until something is available item = self.queue.get() - with self.doLock: - # once we hit our trigger, start cleaning up dead tasks - if len(self.dequeued) > TASK_CLEANUP_TRIGGER: - dead = [] - alive = [] - for x in self.dequeued: - (dead if x.task.dead else alive).append(x) - - # if the ones that we need to keep are within the trigger, do nothing else - delta = len(self.dequeued) - len(dead) - if delta > TASK_CLEANUP_TRIGGER: - ret = alive - else: - # otherwise, lop off the oldest dead tasks until we hit the target trigger - ret = sorted(dead, key=lambda x: x.task.end_time)[-TASK_CLEANUP_TRIGGER:] + alive - - self.dequeued = sorted(ret, key=lambda x: x.num) # add to list so that in-progress tasks show up self.dequeued.append(item) + # once we hit our trigger, start cleaning up dead tasks + if len(self.dequeued) > TASK_CLEANUP_TRIGGER: + self.cleanup_tasks() + # sometimes tasks (like Upload) don't actually have work to do and are created as already finished if item.task.stat is STAT_WAITING: # CalibreTask.start() should wrap all exceptions in it's own error handling From 62dd29d2f330e4a157d3da7a4806e603f9069980 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sun, 30 Aug 2020 08:49:53 +0200 Subject: [PATCH 03/18] Renamed email to mail due to naming conflict in python2 --- cps/helper.py | 2 +- cps/services/worker.py | 1 - cps/tasks/convert.py | 2 +- cps/tasks/{email.py => mail.py} | 0 4 files changed, 2 insertions(+), 3 deletions(-) rename cps/tasks/{email.py => mail.py} (100%) diff --git a/cps/helper.py b/cps/helper.py index fe938127..c2fc3ec0 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -64,7 +64,7 @@ from . import gdriveutils as gd from .constants import STATIC_DIR as _STATIC_DIR from .subproc_wrapper import process_wait from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS -from .tasks.email import TaskEmail +from .tasks.mail import TaskEmail log = logger.create() diff --git a/cps/services/worker.py b/cps/services/worker.py index 14c94df6..543d438f 100644 --- a/cps/services/worker.py +++ b/cps/services/worker.py @@ -11,7 +11,6 @@ except ImportError: from datetime import datetime, timedelta from collections import namedtuple -from cps import calibre_db from cps import logger log = logger.create() diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index 6a0ac402..ba905047 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -14,7 +14,7 @@ from cps import logger, config from cps.subproc_wrapper import process_open from flask_babel import gettext as _ -from cps.tasks.email import TaskEmail +from cps.tasks.mail import TaskEmail from cps import gdriveutils log = logger.create() diff --git a/cps/tasks/email.py b/cps/tasks/mail.py similarity index 100% rename from cps/tasks/email.py rename to cps/tasks/mail.py From 1a9a436cbe4066035f8cd5aee4e649846f356790 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sun, 30 Aug 2020 13:43:08 +0200 Subject: [PATCH 04/18] Fixed wrong translation string Removed unused variables from callback in server.py Update Testresults --- cps/server.py | 2 +- cps/services/worker.py | 2 +- cps/tasks/convert.py | 2 +- cps/tasks/mail.py | 2 ++ test/Calibre-Web TestSummary.html | 12 ++++++++---- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cps/server.py b/cps/server.py index d27fe239..ed5b63c7 100644 --- a/cps/server.py +++ b/cps/server.py @@ -194,7 +194,7 @@ class WebServer(object): os.execv(sys.executable, arguments) return True - def _killServer(self, ignored_signum, ignored_frame): + def _killServer(self, __, ___): self.stop() def stop(self, restart=False): diff --git a/cps/services/worker.py b/cps/services/worker.py index 543d438f..b72c0636 100644 --- a/cps/services/worker.py +++ b/cps/services/worker.py @@ -8,7 +8,7 @@ try: import queue except ImportError: import Queue as queue -from datetime import datetime, timedelta +from datetime import datetime from collections import namedtuple from cps import logger diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index ba905047..a9cbfbd2 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -193,7 +193,7 @@ class TaskConvert(CalibreTask): ele = ele.decode('utf-8') log.debug(ele.strip('\n')) if not ele.startswith('Traceback') and not ele.startswith(' File'): - error_message = _("Calibre failed with error: %(error)s", ele.strip('\n')) + error_message = _("Calibre failed with error: %(error)s", error=ele.strip('\n')) return check, error_message @property diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 3b2db6eb..f83cc4fa 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -175,6 +175,8 @@ class TaskEmail(CalibreTask): text = e.smtp_error.decode('utf-8').replace("\n", '. ') elif hasattr(e, "message"): text = e.message + elif hasattr(e, "args"): + text = '\n'.join(e.args) else: log.exception(e) text = '' diff --git a/test/Calibre-Web TestSummary.html b/test/Calibre-Web TestSummary.html index 98894833..10e2e0ee 100755 --- a/test/Calibre-Web TestSummary.html +++ b/test/Calibre-Web TestSummary.html @@ -36,12 +36,16 @@
-

Start Time: 2020-08-29 11:15:36

+ +

Start Time: 2020-08-30 15:47:09

+
-

Stop Time: 2020-08-29 12:34:46

+ +

Stop Time: 2020-08-30 17:06:27

+
@@ -356,7 +360,7 @@
Traceback (most recent call last):
-  File "/home/matthias/Entwicklung/calibre-web-test/test/test_cover_edit_books.py", line 89, in test_upload_jpg
+  File "/home/matthias/Entwicklung/calibre-web-test/test/test_cover_edit_books.py", line 91, in test_upload_jpg
     self.assertTrue(self.check_element_on_page((By.ID, 'flash_alert')))
 AssertionError: False is not true
@@ -920,7 +924,7 @@ AssertionError: False is not true
Traceback (most recent call last):
-  File "/home/matthias/Entwicklung/calibre-web-test/test/test_edit_books.py", line 734, in test_upload_cover_hdd
+  File "/home/matthias/Entwicklung/calibre-web-test/test/test_edit_books.py", line 735, in test_upload_cover_hdd
     self.assertTrue(False, "Browser-Cache Problem: Old Cover is displayed instead of New Cover")
 AssertionError: False is not true : Browser-Cache Problem: Old Cover is displayed instead of New Cover
From 9b9e29a3b6f6b71b5ed546d87fafa53911f5764c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 30 Aug 2020 13:29:42 -0400 Subject: [PATCH 05/18] Implement worker thread as a daemon --- cps/services/worker.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/cps/services/worker.py b/cps/services/worker.py index 14c94df6..92c5fa3d 100644 --- a/cps/services/worker.py +++ b/cps/services/worker.py @@ -59,7 +59,7 @@ class WorkerThread(threading.Thread): threading.Thread.__init__(self) self.dequeued = list() - + self.daemon = True self.doLock = threading.Lock() self.queue = ImprovedQueue() self.num = 0 @@ -101,24 +101,23 @@ class WorkerThread(threading.Thread): # Main thread loop starting the different tasks def run(self): - main_thread = _get_main_thread() - while main_thread.is_alive(): - # this blocks until something is available - item = self.queue.get() - with self.doLock: - # add to list so that in-progress tasks show up - self.dequeued.append(item) - - # once we hit our trigger, start cleaning up dead tasks - if len(self.dequeued) > TASK_CLEANUP_TRIGGER: - self.cleanup_tasks() - - # sometimes tasks (like Upload) don't actually have work to do and are created as already finished - if item.task.stat is STAT_WAITING: - # CalibreTask.start() should wrap all exceptions in it's own error handling - item.task.start(self) - - self.queue.task_done() + # this blocks until something is available + item = self.queue.get() + + with self.doLock: + # add to list so that in-progress tasks show up + self.dequeued.append(item) + + # once we hit our trigger, start cleaning up dead tasks + if len(self.dequeued) > TASK_CLEANUP_TRIGGER: + self.cleanup_tasks() + + # sometimes tasks (like Upload) don't actually have work to do and are created as already finished + if item.task.stat is STAT_WAITING: + # CalibreTask.start() should wrap all exceptions in it's own error handling + item.task.start(self) + + self.queue.task_done() class CalibreTask: From b0a055a8709558e3a92f59bc96560fa4a6a1b8bb Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 30 Aug 2020 13:31:59 -0400 Subject: [PATCH 06/18] Rename email.py -> mail.py to avoid shadowing standard email module --- cps/helper.py | 2 +- cps/tasks/convert.py | 2 +- cps/tasks/{email.py => mail.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename cps/tasks/{email.py => mail.py} (100%) diff --git a/cps/helper.py b/cps/helper.py index 624a3b54..a37fbb15 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -64,7 +64,7 @@ from . import gdriveutils as gd from .constants import STATIC_DIR as _STATIC_DIR from .subproc_wrapper import process_wait from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS -from .tasks.email import TaskEmail +from .tasks.mail import TaskEmail log = logger.create() diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index 5060c29e..09263964 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -14,7 +14,7 @@ from cps import logger, config from cps.subproc_wrapper import process_open from flask_babel import gettext as _ -from cps.tasks.email import TaskEmail +from cps.tasks.mail import TaskEmail from cps import gdriveutils log = logger.create() diff --git a/cps/tasks/email.py b/cps/tasks/mail.py similarity index 100% rename from cps/tasks/email.py rename to cps/tasks/mail.py From ef49e2b5b30ff5c0bc3439e1fbd7447cb279128e Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 30 Aug 2020 13:49:45 -0400 Subject: [PATCH 07/18] Revert daemon in favor of timeout --- cps/services/worker.py | 47 ++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/cps/services/worker.py b/cps/services/worker.py index 92c5fa3d..e434528f 100644 --- a/cps/services/worker.py +++ b/cps/services/worker.py @@ -3,6 +3,7 @@ from __future__ import division, print_function, unicode_literals import threading import abc import uuid +import time try: import queue @@ -59,7 +60,7 @@ class WorkerThread(threading.Thread): threading.Thread.__init__(self) self.dequeued = list() - self.daemon = True + self.doLock = threading.Lock() self.queue = ImprovedQueue() self.num = 0 @@ -101,23 +102,33 @@ class WorkerThread(threading.Thread): # Main thread loop starting the different tasks def run(self): - # this blocks until something is available - item = self.queue.get() - - with self.doLock: - # add to list so that in-progress tasks show up - self.dequeued.append(item) - - # once we hit our trigger, start cleaning up dead tasks - if len(self.dequeued) > TASK_CLEANUP_TRIGGER: - self.cleanup_tasks() - - # sometimes tasks (like Upload) don't actually have work to do and are created as already finished - if item.task.stat is STAT_WAITING: - # CalibreTask.start() should wrap all exceptions in it's own error handling - item.task.start(self) - - self.queue.task_done() + main_thread = _get_main_thread() + while main_thread.is_alive(): + try: + # this blocks until something is available. This can cause issues when the main thread dies - this + # thread will remain alive. We implement a timeout to unblock every second which allows us to check if + # the main thread is still alive. + # We don't use a daemon here because we don't want the tasks to just be abruptly halted, leading to + # possible file / database corruption + item = self.queue.get(timeout=1) + except queue.Empty as ex: + time.sleep(1) + continue + + with self.doLock: + # add to list so that in-progress tasks show up + self.dequeued.append(item) + + # once we hit our trigger, start cleaning up dead tasks + if len(self.dequeued) > TASK_CLEANUP_TRIGGER: + self.cleanup_tasks() + + # sometimes tasks (like Upload) don't actually have work to do and are created as already finished + if item.task.stat is STAT_WAITING: + # CalibreTask.start() should wrap all exceptions in it's own error handling + item.task.start(self) + + self.queue.task_done() class CalibreTask: From 62e7ab8c2b76fa9a10a404ccbd61588bc25193e7 Mon Sep 17 00:00:00 2001 From: Dawid Gliwka Date: Sun, 30 Aug 2020 21:14:53 +0200 Subject: [PATCH 08/18] Update polish translation --- cps/translations/pl/LC_MESSAGES/messages.mo | Bin 36568 -> 47985 bytes cps/translations/pl/LC_MESSAGES/messages.po | 183 ++++++++++---------- 2 files changed, 91 insertions(+), 92 deletions(-) diff --git a/cps/translations/pl/LC_MESSAGES/messages.mo b/cps/translations/pl/LC_MESSAGES/messages.mo index 6f3c5f96207ae14e47a12775fcd77ef0f3711545..97f8f1940e7c94282a45b1256f065050713fe85f 100644 GIT binary patch literal 47985 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 w=z=nbE;ceom983hLD;N>laBHK2Ltteb^rhX delta 9083 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\n" "Language: pl\n" "Language-Team: \n" @@ -41,11 +41,11 @@ msgstr "Wykonano wyłączenie serwera, proszę zamknąć okno" #: cps/admin.py:103 msgid "Reconnect successful" -msgstr "" +msgstr "Ponowne połączenie zakończono sukcesem" #: cps/admin.py:106 msgid "Unknown command" -msgstr "" +msgstr "Nieznane polecenie" # ??? #: cps/admin.py:116 cps/editbooks.py:564 cps/editbooks.py:576 @@ -79,63 +79,63 @@ msgstr "Zezwalaj" #: cps/admin.py:510 msgid "client_secrets.json Is Not Configured For Web Application" -msgstr "" +msgstr "client_secrets.json nie został skonfigurowany dla aplikacji webowej" #: cps/admin.py:549 msgid "Logfile Location is not Valid, Please Enter Correct Path" -msgstr "" +msgstr "Lokalizacja pliku dziennika jest nieprawidłowa, wprowadź poprawną ścieżkę" #: cps/admin.py:554 msgid "Access Logfile Location is not Valid, Please Enter Correct Path" -msgstr "" +msgstr "Lokalizacja pliku dziennika dostępu jest nieprawidłowa, wprowadź poprawną ścieżkę" #: cps/admin.py:580 msgid "Please Enter a LDAP Provider, Port, DN and User Object Identifier" -msgstr "" +msgstr "Wprowadź dostawcę LDAP, port, nazwę wyróżniającą i identyfikator obiektu użytkownika" #: cps/admin.py:593 #, python-format msgid "LDAP Group Object Filter Needs to Have One \"%s\" Format Identifier" -msgstr "" +msgstr "Filtr obiektów grupy LDAP musi mieć jeden identyfikator formatu \"% s\"" #: cps/admin.py:596 msgid "LDAP Group Object Filter Has Unmatched Parenthesis" -msgstr "" +msgstr "Filtr obiektów grupy LDAP ma niedopasowany nawias" #: cps/admin.py:600 #, python-format msgid "LDAP User Object Filter needs to Have One \"%s\" Format Identifier" -msgstr "" +msgstr "Filtr obiektów użytkownika LDAP musi mieć jeden identyfikator formatu \"% s\"" #: cps/admin.py:603 msgid "LDAP User Object Filter Has Unmatched Parenthesis" -msgstr "" +msgstr "Filtr obiektów użytkownika LDAP ma niedopasowany nawias" #: cps/admin.py:607 msgid "LDAP Certificate Location is not Valid, Please Enter Correct Path" -msgstr "" +msgstr "Lokalizacja certyfikatu LDAP jest nieprawidłowa, wprowadź poprawną ścieżkę" #: cps/admin.py:628 msgid "Keyfile Location is not Valid, Please Enter Correct Path" -msgstr "" +msgstr "Lokalizacja pliku klucza jest nieprawidłowa, wprowadź poprawną ścieżkę" #: cps/admin.py:632 msgid "Certfile Location is not Valid, Please Enter Correct Path" -msgstr "" +msgstr "Lokalizacja pliku certyfikatu jest nieprawidłowa, wprowadź poprawną ścieżkę" #: cps/admin.py:694 cps/admin.py:793 cps/admin.py:885 cps/admin.py:934 #: cps/shelf.py:100 cps/shelf.py:161 cps/shelf.py:202 cps/shelf.py:260 #: cps/shelf.py:309 cps/shelf.py:338 cps/shelf.py:368 cps/shelf.py:392 msgid "Settings DB is not Writeable" -msgstr "" +msgstr "Baza danych ustawień nie jest zapisywalna" #: cps/admin.py:706 msgid "DB Location is not Valid, Please Enter Correct Path" -msgstr "" +msgstr "Lokalizacja bazy danych jest nieprawidłowa, wprowadź poprawną ścieżkę" #: cps/admin.py:708 msgid "DB is not Writeable" -msgstr "" +msgstr "Baza danych nie jest zapisywalna" #: cps/admin.py:741 msgid "Basic Configuration" @@ -173,7 +173,7 @@ msgstr "Nie można usunąć użytkownika. Brak na serwerze innego konta z prawam #: cps/admin.py:811 msgid "No admin user remaining, can't remove admin role" -msgstr "" +msgstr "Nie można odebrać praw administratora. Brak na serwerze innego konta z prawami administratora" #: cps/admin.py:847 cps/web.py:1618 msgid "Found an existing account for this e-mail address." @@ -221,7 +221,7 @@ msgstr "Zaktualizowano ustawienia serwera poczty e-mail" #: cps/admin.py:959 msgid "User not found" -msgstr "" +msgstr "Nie znaleziono użytkownika" # ??? #: cps/admin.py:994 @@ -293,7 +293,7 @@ msgstr "Błąd ogólny" #: cps/admin.py:1062 msgid "Update File Could Not be Saved in Temp Dir" -msgstr "" +msgstr "Plik aktualizacji nie mógł zostać zapisany w katalogu tymczasowym" #: cps/converter.py:32 msgid "not configured" @@ -301,15 +301,15 @@ msgstr "nie skonfigurowane" #: cps/converter.py:34 msgid "Execution permissions missing" -msgstr "" +msgstr "Brak uprawnienia do wykonywania pliku" #: cps/editbooks.py:242 msgid "Book Format Successfully Deleted" -msgstr "" +msgstr "Plik książki w wybranym formacie został usunięty" #: cps/editbooks.py:245 msgid "Book Successfully Deleted" -msgstr "" +msgstr "Książka została usunięta" #: cps/editbooks.py:254 cps/editbooks.py:549 cps/web.py:1641 cps/web.py:1682 #: cps/web.py:1744 @@ -347,7 +347,7 @@ msgstr "Nie można zapisać pliku %(file)s." #: cps/editbooks.py:507 cps/editbooks.py:870 #, python-format msgid "Database error: %(error)s." -msgstr "" +msgstr "Błąd bazy danych: %(error)s." #: cps/editbooks.py:511 #, python-format @@ -374,12 +374,12 @@ msgstr "Wysłana książka prawdopodobnie istnieje w bibliotece, rozważ zmianę #: cps/editbooks.py:786 #, python-format msgid "Failed to Move File %(file)s: %(error)s" -msgstr "" +msgstr "Nie udało się przenieść pliku %(file)s:%(error)s" #: cps/editbooks.py:842 #, python-format msgid "Failed to Move Cover File %(file)s: %(error)s" -msgstr "" +msgstr "Nie udało się przenieść pliku okładki %(file)s:%(error)s" #: cps/editbooks.py:856 #, python-format @@ -474,22 +474,22 @@ msgstr "Żądany plik nie mógł zostać odczytany. Sprawdź uprawnienia?" #: cps/helper.py:300 #, python-format msgid "Deleting book %(id)s failed, path has subfolders: %(path)s" -msgstr "" +msgstr "Usuwanie książki %(id)s zakończyło się błędem, ścieżka posiada podkatalogi: %(path)s" #: cps/helper.py:310 #, python-format msgid "Deleting book %(id)s failed: %(message)s" -msgstr "" +msgstr "Usuwanie książki %(id)s zakończyło się błędem: %(message)s" #: cps/helper.py:320 #, python-format msgid "Deleting book %(id)s, book path not valid: %(path)s" -msgstr "" +msgstr "Usuwanie książki %(id)s, ścieżka książki jest niepoprawna: %(path)s" #: cps/helper.py:355 #, python-format msgid "Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s" -msgstr "Zmiana nazwy tytułu z: „%(src)s” na „%(dest)s” zakończyła się niepowodzeniem z błędem: %(error)s" +msgstr "Zmiana nazwy tytułu z: „%(src)s” na „%(dest)s” zakończyła się błędem: %(error)s" #: cps/helper.py:365 #, python-format @@ -513,11 +513,11 @@ msgstr "Nie znaleziono ścieżki do książki %(path)s na Google Drive" #: cps/helper.py:550 msgid "Error Downloading Cover" -msgstr "" +msgstr "Błąd przy pobieraniu okładki" #: cps/helper.py:553 msgid "Cover Format Error" -msgstr "" +msgstr "Błędny format okładki" #: cps/helper.py:569 msgid "Failed to create path for cover" @@ -525,7 +525,7 @@ msgstr "Nie udało się utworzyć ścieżki dla okładki" #: cps/helper.py:574 msgid "Cover-file is not a valid image file, or could not be stored" -msgstr "" +msgstr "Plik okładki nie jest poprawnym plikiem obrazu lub nie mógł zostać zapisany" #: cps/helper.py:585 msgid "Only jpg/jpeg/png/webp files are supported as coverfile" @@ -537,11 +537,11 @@ msgstr "Jako plik okładki dopuszczalne są jedynie pliki jpg/jpeg" #: cps/helper.py:648 msgid "Unrar binary file not found" -msgstr "" +msgstr "Plik wykonywalny programu unrar nie znaleziony" #: cps/helper.py:662 msgid "Error excecuting UnRar" -msgstr "" +msgstr "Błąd przy wykonywaniu unrar" #: cps/helper.py:718 msgid "Waiting" @@ -581,7 +581,7 @@ msgstr "Nieznane zadanie: " #: cps/kobo_auth.py:130 msgid "PLease access calibre-web from non localhost to get valid api_endpoint for kobo device" -msgstr "Aby uzyskać prawidłowy api_endpoint dla urządzenia kobo, należy skorzystać z dostępu do calibre-web spoza localhost" +msgstr "Aby uzyskać prawidłowy api_endpoint dla urządzenia Kobo, należy skorzystać z dostępu do calibre-web spoza localhost" #: cps/kobo_auth.py:133 cps/kobo_auth.py:153 msgid "Kobo Setup" @@ -617,26 +617,26 @@ msgstr "zalogowałeś się jako: '%(nickname)s'" #: cps/oauth_bb.py:235 #, python-format msgid "Link to %(oauth)s Succeeded" -msgstr "" +msgstr "Łączenie z %(oauth)s zakończono sukcesem" #: cps/oauth_bb.py:241 msgid "Login failed, No User Linked With OAuth Account" -msgstr "" +msgstr "Błąd logowania, użytkownik niepołączony z kontem OAuth" #: cps/oauth_bb.py:283 #, python-format msgid "Unlink to %(oauth)s Succeeded" -msgstr "" +msgstr "Rozłączanie z %(oauth)s zakończono sukcesem" #: cps/oauth_bb.py:287 #, python-format msgid "Unlink to %(oauth)s Failed" -msgstr "" +msgstr "Rozłączanie z %(oauth)s zakończono porażką" #: cps/oauth_bb.py:290 #, python-format msgid "Not Linked to %(oauth)s." -msgstr "" +msgstr "Brak połączonych %(oauth)s." #: cps/oauth_bb.py:318 msgid "GitHub Oauth error, please retry later." @@ -698,12 +698,12 @@ msgstr "Niestety nie możesz usunąć książki z tej półki %(sname)s" #: cps/shelf.py:240 cps/shelf.py:284 #, python-format msgid "A public shelf with the name '%(title)s' already exists." -msgstr "" +msgstr "Publiczna półka o nazwie '%(title)s' już istnieje." #: cps/shelf.py:249 cps/shelf.py:294 #, python-format msgid "A private shelf with the name '%(title)s' already exists." -msgstr "" +msgstr "Prywatna półka o nazwie '%(title)s' już istnieje." #: cps/shelf.py:256 #, python-format @@ -716,7 +716,7 @@ msgstr "Wystąpił błąd" #: cps/shelf.py:264 cps/shelf.py:266 cps/templates/layout.html:144 msgid "Create a Shelf" -msgstr "utwórz półkę" +msgstr "Utwórz półkę" #: cps/shelf.py:306 #, python-format @@ -855,11 +855,11 @@ msgstr "Pokaż menu formatu plików" #: cps/ub.py:109 cps/web.py:1246 msgid "Archived Books" -msgstr "" +msgstr "Zarchiwizowane książki" #: cps/ub.py:111 msgid "Show archived books" -msgstr "" +msgstr "Pokaż zarchiwizowane książki" #: cps/updater.py:294 cps/updater.py:305 cps/updater.py:406 cps/updater.py:420 msgid "Unexpected data while reading update information" @@ -893,23 +893,23 @@ msgstr "Kliknij przycisk poniżej, aby zaktualizować do najnowszej stabilnej we #: cps/web.py:319 #, python-format msgid "Error: %(ldaperror)s" -msgstr "" +msgstr "Błąd: %(ldaperror)s" #: cps/web.py:323 msgid "Error: No user returned in response of LDAP server" -msgstr "" +msgstr "Błąd. LDAP nie zwrócił żadnego użytkownika" #: cps/web.py:371 msgid "Failed to Create at Least One LDAP User" -msgstr "" +msgstr "Błąd przy tworzeniu przynajmniej jednego użytkownika LDAP" #: cps/web.py:374 msgid "At Least One LDAP User Not Found in Database" -msgstr "" +msgstr "Przynajmniej jeden użytkownik LDAP nie został znaleziony w bazie danych" #: cps/web.py:376 msgid "User Successfully Imported" -msgstr "" +msgstr "Użytkownik pomyślnie zaimportowany" #: cps/web.py:622 msgid "Recently Added Books" @@ -1009,7 +1009,7 @@ msgstr "szukaj" #: cps/web.py:1210 #, python-format msgid "Custom Column No.%(column)d is not existing in calibre database" -msgstr "" +msgstr "Niestandardowa kolumna No.%(column)d nie istnieje w bazie calibre" #: cps/web.py:1301 #, python-format @@ -1053,12 +1053,12 @@ msgstr "Nie można aktywować uwierzytelniania LDAP" #: cps/web.py:1401 #, python-format msgid "Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known" -msgstr "" +msgstr "Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known" #: cps/web.py:1407 #, python-format msgid "Could not login: %(message)s" -msgstr "" +msgstr "Nie można zalogować: %(message)s" #: cps/web.py:1411 cps/web.py:1435 msgid "Wrong Username or Password" @@ -1110,7 +1110,7 @@ msgstr "Czytaj książkę" #: cps/worker.py:313 #, python-format msgid "Calibre ebook-convert %(tool)s not found" -msgstr "" +msgstr "Nie znaleziono narzędzia calibre %(tool)s do konwertowania" #: cps/worker.py:373 #, python-format @@ -1120,12 +1120,12 @@ msgstr "Konwertowanie nie powiodło się: %(error)s" #: cps/worker.py:406 #, python-format msgid "Kepubify-converter failed: %(error)s" -msgstr "" +msgstr "Kepubify-converter spowodowało błąd: %(error)s" #: cps/worker.py:430 #, python-format msgid "Converted file not found or more than one file in folder %(folder)s" -msgstr "" +msgstr "Konwertowany plik nie został znaleziony, lub więcej niż jeden plik w folderze %(folder)s" #: cps/templates/admin.html:9 msgid "Users" @@ -1188,7 +1188,7 @@ msgstr "Usuń" #: cps/templates/admin.html:23 msgid "Public Shelf" -msgstr "" +msgstr "Półka publiczna" #: cps/templates/admin.html:44 msgid "Add New User" @@ -1196,7 +1196,7 @@ msgstr "Dodaj nowego użytkownika" #: cps/templates/admin.html:46 cps/templates/admin.html:47 msgid "Import LDAP Users" -msgstr "" +msgstr "Importuj użytkowników LDAP" #: cps/templates/admin.html:54 msgid "E-mail Server Settings" @@ -1415,23 +1415,23 @@ msgstr "Opis" #: cps/templates/book_edit.html:66 msgid "Identifiers" -msgstr "" +msgstr "Identyfikatory" #: cps/templates/book_edit.html:70 cps/templates/book_edit.html:308 msgid "Identifier Type" -msgstr "" +msgstr "Rodzaj identyfikatora" #: cps/templates/book_edit.html:71 cps/templates/book_edit.html:309 msgid "Identifier Value" -msgstr "" +msgstr "Wartość identyfikatora" #: cps/templates/book_edit.html:72 cps/templates/book_edit.html:310 msgid "Remove" -msgstr "" +msgstr "Usuń" #: cps/templates/book_edit.html:76 msgid "Add Identifier" -msgstr "" +msgstr "Dodaj identyfikator" #: cps/templates/book_edit.html:80 cps/templates/search_form.html:33 msgid "Tags" @@ -1509,11 +1509,11 @@ msgstr "i z dysku twardego" #: cps/templates/book_edit.html:209 msgid "Important Kobo Note: deleted books will remain on any paired Kobo device." -msgstr "" +msgstr "Ważne dla Kobo: usunięte książki pozostaną na każdym połączonym urządzeniu Kobo." #: cps/templates/book_edit.html:210 msgid "Books must first be archived and the device synced before a book can safely be deleted." -msgstr "" +msgstr "Książki muszą najpierw zostać zarchiwizowane a urządzenie zsynchronizowane, zanim książka będzie mogła zostać bezpiecznie usunięta." #: cps/templates/book_edit.html:232 msgid "Keyword" @@ -1644,7 +1644,7 @@ msgstr "Włącz wysyłanie" #: cps/templates/config_edit.html:169 msgid "Allowed Upload Fileformats" -msgstr "" +msgstr "Dozwolone formaty przesyłania" #: cps/templates/config_edit.html:175 msgid "Enable Anonymous Browsing" @@ -1656,7 +1656,7 @@ msgstr "Włącz publiczną rejestrację" #: cps/templates/config_edit.html:184 msgid "Use E-Mail as Username" -msgstr "" +msgstr "Użyj e-maila jako nazwy użytkownika" #: cps/templates/config_edit.html:189 msgid "Enable Magic Link Remote Login" @@ -1720,7 +1720,7 @@ msgstr "Port serwera LDAP" #: cps/templates/config_edit.html:255 msgid "LDAP Encryption" -msgstr "" +msgstr "Szyfrowanie LDAP" #: cps/templates/config_edit.html:257 cps/templates/config_view_edit.html:61 #: cps/templates/email_edit.html:21 @@ -1729,31 +1729,31 @@ msgstr "Brak" #: cps/templates/config_edit.html:258 msgid "TLS" -msgstr "" +msgstr "TLS" #: cps/templates/config_edit.html:259 msgid "SSL" -msgstr "" +msgstr "SSL" #: cps/templates/config_edit.html:264 msgid "LDAP Certificate Path" -msgstr "" +msgstr "Ścieżka certyfikatu LDAP" #: cps/templates/config_edit.html:269 msgid "LDAP Authentication" -msgstr "" +msgstr "Uwierzytelnianie LDAP" #: cps/templates/config_edit.html:271 msgid "Anonymous" -msgstr "" +msgstr "Anonim" #: cps/templates/config_edit.html:272 msgid "Unauthenticated" -msgstr "" +msgstr "Nieuwierzytelniony" #: cps/templates/config_edit.html:273 msgid "Simple" -msgstr "" +msgstr "Proste" #: cps/templates/config_edit.html:278 msgid "LDAP Administrator Username" @@ -1777,19 +1777,19 @@ msgstr "Serwer LDAP to OpenLDAP?" #: cps/templates/config_edit.html:300 msgid "Following Settings are Needed For User Import" -msgstr "" +msgstr "Następujące ustawienia są niezbędne dla zaimportowania użytkowników" #: cps/templates/config_edit.html:302 msgid "LDAP Group Object Filter" -msgstr "" +msgstr "Filtr obiektów grupy LDAP" #: cps/templates/config_edit.html:306 msgid "LDAP Group Name" -msgstr "" +msgstr "Nazwa grupy LDAP" #: cps/templates/config_edit.html:310 msgid "LDAP Group Members Field" -msgstr "" +msgstr "Pola członków grupy LDAP" #: cps/templates/config_edit.html:319 #, python-format @@ -1812,15 +1812,15 @@ msgstr "Zewnętrzne pliki" #: cps/templates/config_edit.html:348 msgid "Path to Calibre E-Book Converter" -msgstr "" +msgstr "Ścieżka do konwertera Calibre" #: cps/templates/config_edit.html:356 msgid "Calibre E-Book Converter Settings" -msgstr "" +msgstr "Ustawienia konwertera calibre" #: cps/templates/config_edit.html:359 msgid "Path to Kepubify E-Book Converter" -msgstr "" +msgstr "Ścieżka do konwertera Kepubify" #: cps/templates/config_edit.html:367 msgid "Location of Unrar binary" @@ -1849,7 +1849,7 @@ msgstr "Liczba autorów do pokazania przed ukryciem (0=wyłącza ukrywanie)" #: cps/templates/config_view_edit.html:39 cps/templates/readcbr.html:112 msgid "Theme" -msgstr "Motyw (Theme)" +msgstr "Motyw" #: cps/templates/config_view_edit.html:41 msgid "Standard Theme" @@ -1961,15 +1961,15 @@ msgstr "Przeczytana" #: cps/templates/detail.html:209 msgid "Restore from archive" -msgstr "" +msgstr "Przywróć z archiwum" #: cps/templates/detail.html:209 msgid "Add to archive" -msgstr "" +msgstr "Dodaj do archiwum" #: cps/templates/detail.html:210 msgid "Archived" -msgstr "" +msgstr "Zarchiwizowane" #: cps/templates/detail.html:221 msgid "Description:" @@ -1983,7 +1983,7 @@ msgstr "Dodaj do półki" #: cps/templates/feed.xml:79 cps/templates/layout.html:137 #: cps/templates/layout.html:141 cps/templates/search.html:20 msgid "(Public)" -msgstr "" +msgstr "(publiczna)" #: cps/templates/detail.html:276 msgid "Edit Metadata" @@ -2003,7 +2003,7 @@ msgstr "Hasło SMTP" #: cps/templates/email_edit.html:38 msgid "Attachment Size Limit" -msgstr "" +msgstr "Limit rozmiaru załącznika" #: cps/templates/email_edit.html:46 msgid "Save and Send Test E-mail" @@ -2113,11 +2113,11 @@ msgstr "Ksiązki sortowane według formatu" #: cps/templates/index.xml:111 cps/templates/layout.html:135 msgid "Shelves" -msgstr "" +msgstr "Półki" #: cps/templates/index.xml:115 msgid "Books organized in shelves" -msgstr "" +msgstr "Książki ułożone na półkach" #: cps/templates/layout.html:29 msgid "Home" @@ -2581,4 +2581,3 @@ msgstr "Generuj Kobo Auth URL" #: cps/templates/user_edit.html:178 msgid "Do you really want to delete the Kobo Token?" msgstr "Czy na pewno chcesz usunąć Token Kobo?" - From 173484c30e7b00e77b74f4a285292ebb5f114672 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Tue, 1 Sep 2020 19:25:57 +0200 Subject: [PATCH 09/18] Fix #1002, #1581 (Public shelfs not accessible as guest user) --- cps/shelf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/shelf.py b/cps/shelf.py index 37cfc02a..19a350d5 100644 --- a/cps/shelf.py +++ b/cps/shelf.py @@ -30,7 +30,7 @@ from sqlalchemy.sql.expression import func from sqlalchemy.exc import OperationalError, InvalidRequestError from . import logger, ub, searched_ids, calibre_db -from .web import render_title_template +from .web import login_required_if_no_ano, render_title_template shelf = Blueprint('shelf', __name__) @@ -341,7 +341,7 @@ def delete_shelf(shelf_id): @shelf.route("/shelf/", defaults={'shelf_type': 1}) @shelf.route("/shelf//") -@login_required +@login_required_if_no_ano def show_shelf(shelf_type, shelf_id): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() From feacbe8ebd35803ebd9ed88cc931564cfc9bcb86 Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sat, 5 Sep 2020 10:24:32 +0200 Subject: [PATCH 10/18] Possible Fix for database crash after adding new format and accessing calibre database afterwards --- cps/db.py | 4 +++- cps/tasks/convert.py | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cps/db.py b/cps/db.py index a5a01aca..40ba1753 100644 --- a/cps/db.py +++ b/cps/db.py @@ -48,6 +48,7 @@ try: except ImportError: use_unidecode = False +Session = None cc_exceptions = ['datetime', 'comments', 'composite', 'series'] cc_classes = {} @@ -409,6 +410,7 @@ class CalibreDB(): def setup_db(self, config, app_db_path): self.config = config self.dispose() + global Session if not config.config_calibre_dir: config.invalidate() @@ -506,7 +508,7 @@ class CalibreDB(): backref='books')) Session = scoped_session(sessionmaker(autocommit=False, - autoflush=False, + autoflush=True, bind=self.engine)) self.session = Session() return True diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index a9cbfbd2..2b679fc0 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -53,6 +53,7 @@ class TaskConvert(CalibreTask): def _convert_ebook_format(self): error_message = None + local_session = db.Session() file_path = self.file_path book_id = self.bookid format_old_ext = u'.' + self.settings['old_book_format'].lower() @@ -92,17 +93,13 @@ class TaskConvert(CalibreTask): new_format = db.Data(name=cur_book.data[0].name, book_format=self.settings['new_book_format'].upper(), book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext)) - - cur_book.data.append(new_format) - try: - # db.session.merge(cur_book) - calibre_db.session.commit() + local_session.merge(new_format) + local_session.commit() except SQLAlchemyError as e: - calibre_db.session.rollback() + local_session.rollback() log.error("Database error: %s", e) return - self.results['path'] = cur_book.path self.results['title'] = cur_book.title if config.config_use_google_drive: From eaed53e25b9c9c9cd437d2bcd6709830ccbc6577 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sat, 5 Sep 2020 18:23:14 +0200 Subject: [PATCH 11/18] Fix for author edit error (2 same sort_authors lead maybe to choose wrong one) --- cps/db.py | 9 ++++++--- cps/editbooks.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cps/db.py b/cps/db.py index 40ba1753..2cf1d98b 100644 --- a/cps/db.py +++ b/cps/db.py @@ -593,13 +593,16 @@ class CalibreDB(): sort_authors = entry.author_sort.split('&') authors_ordered = list() error = False + ids = [a.id for a in entry.authors] for auth in sort_authors: + results = self.session.query(Authors).filter(Authors.sort == auth.lstrip().strip()).all() # ToDo: How to handle not found authorname - result = self.session.query(Authors).filter(Authors.sort == auth.lstrip().strip()).first() - if not result: + if not len(results): error = True break - authors_ordered.append(result) + for r in results: + if r.id in ids: + authors_ordered.append(r) if not error: entry.authors = authors_ordered return entry diff --git a/cps/editbooks.py b/cps/editbooks.py index 1de44d98..602d8fc0 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -684,6 +684,7 @@ def edit_book(book_id): if modif_date: book.last_modified = datetime.utcnow() + calibre_db.session.merge(book) calibre_db.session.commit() if config.config_use_google_drive: gdriveutils.updateGdriveCalibreFromLocal() From 34d3225984d4e13f09f81cc002a0e30ef4922fc6 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sat, 5 Sep 2020 18:46:11 +0200 Subject: [PATCH 12/18] Errorhandling edit identifier --- cps/editbooks.py | 19 ++++++++++++------- cps/templates/book_edit.html | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index 602d8fc0..0c5f5889 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -154,8 +154,11 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session): input_identifiers is a list of read-to-persist Identifiers objects. db_identifiers is a list of already persisted list of Identifiers objects.""" changed = False - input_dict = dict([ (identifier.type.lower(), identifier) for identifier in input_identifiers ]) - db_dict = dict([ (identifier.type.lower(), identifier) for identifier in db_identifiers ]) + error = False + input_dict = dict([(identifier.type.lower(), identifier) for identifier in input_identifiers]) + if len(input_identifiers) != len(input_dict): + error = True + db_dict = dict([(identifier.type.lower(), identifier) for identifier in db_identifiers ]) # delete db identifiers not present in input or modify them with input val for identifier_type, identifier in db_dict.items(): if identifier_type not in input_dict.keys(): @@ -170,7 +173,7 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session): if identifier_type not in db_dict.keys(): db_session.add(identifier) changed = True - return changed + return changed, error @editbook.route("/ajax/delete/") @login_required @@ -652,10 +655,12 @@ def edit_book(book_id): # Handle book comments/description modif_date |= edit_book_comments(to_save["description"], book) - # Handle identifiers + # Handle identifiers input_identifiers = identifier_list(to_save, book) - modif_date |= modify_identifiers(input_identifiers, book.identifiers, calibre_db.session) - + modification, warning = modify_identifiers(input_identifiers, book.identifiers, calibre_db.session) + if warning: + flash(_("Identifiers are not Case Sensitive, Overwriting Old Identifier"), category="warning") + modif_date |= modification # Handle book tags modif_date |= edit_book_tags(to_save['tags'], book) @@ -728,7 +733,7 @@ def identifier_list(to_save, book): val_key = id_val_prefix + type_key[len(id_type_prefix):] if val_key not in to_save.keys(): continue - result.append( db.Identifiers(to_save[val_key], type_value, book.id) ) + result.append(db.Identifiers(to_save[val_key], type_value, book.id)) return result @editbook.route("/upload", methods=["GET", "POST"]) diff --git a/cps/templates/book_edit.html b/cps/templates/book_edit.html index be6d4a67..0936d551 100644 --- a/cps/templates/book_edit.html +++ b/cps/templates/book_edit.html @@ -65,7 +65,7 @@ {% for identifier in book.identifiers %} - + From d3bde0408f87e9e9abf7174ed8a4ef3724ebd708 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sat, 5 Sep 2020 18:47:28 +0200 Subject: [PATCH 13/18] Improvederror handling for editing identifier --- cps/editbooks.py | 19 ++++++++++++------- cps/templates/book_edit.html | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index 93ed4acb..cf1eb4c3 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -151,8 +151,11 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session): input_identifiers is a list of read-to-persist Identifiers objects. db_identifiers is a list of already persisted list of Identifiers objects.""" changed = False - input_dict = dict([ (identifier.type.lower(), identifier) for identifier in input_identifiers ]) - db_dict = dict([ (identifier.type.lower(), identifier) for identifier in db_identifiers ]) + error = False + input_dict = dict([(identifier.type.lower(), identifier) for identifier in input_identifiers]) + if len(input_identifiers) != len(input_dict): + error = True + db_dict = dict([(identifier.type.lower(), identifier) for identifier in db_identifiers ]) # delete db identifiers not present in input or modify them with input val for identifier_type, identifier in db_dict.items(): if identifier_type not in input_dict.keys(): @@ -167,7 +170,7 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session): if identifier_type not in db_dict.keys(): db_session.add(identifier) changed = True - return changed + return changed, error @editbook.route("/delete//", defaults={'book_format': ""}) @@ -616,10 +619,12 @@ def edit_book(book_id): # Handle book comments/description modif_date |= edit_book_comments(to_save["description"], book) - # Handle identifiers + # Handle identifiers input_identifiers = identifier_list(to_save, book) - modif_date |= modify_identifiers(input_identifiers, book.identifiers, calibre_db.session) - + modification, warning = modify_identifiers(input_identifiers, book.identifiers, calibre_db.session) + if warning: + flash(_("Identifiers are not Case Sensitive, Overwriting Old Identifier"), category="warning") + modif_date |= modification # Handle book tags modif_date |= edit_book_tags(to_save['tags'], book) @@ -691,7 +696,7 @@ def identifier_list(to_save, book): val_key = id_val_prefix + type_key[len(id_type_prefix):] if val_key not in to_save.keys(): continue - result.append( db.Identifiers(to_save[val_key], type_value, book.id) ) + result.append(db.Identifiers(to_save[val_key], type_value, book.id)) return result @editbook.route("/upload", methods=["GET", "POST"]) diff --git a/cps/templates/book_edit.html b/cps/templates/book_edit.html index 96620a35..5c081101 100644 --- a/cps/templates/book_edit.html +++ b/cps/templates/book_edit.html @@ -67,7 +67,7 @@
{{_('Remove')}}
{% for identifier in book.identifiers %} - + From 393869e538accd45843afc67168fd095c64730a4 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 6 Sep 2020 10:27:10 +0200 Subject: [PATCH 14/18] Test revert global Session --- cps/db.py | 3 --- cps/tasks/convert.py | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cps/db.py b/cps/db.py index 2cf1d98b..a519d5bf 100644 --- a/cps/db.py +++ b/cps/db.py @@ -48,7 +48,6 @@ try: except ImportError: use_unidecode = False -Session = None cc_exceptions = ['datetime', 'comments', 'composite', 'series'] cc_classes = {} @@ -410,7 +409,6 @@ class CalibreDB(): def setup_db(self, config, app_db_path): self.config = config self.dispose() - global Session if not config.config_calibre_dir: config.invalidate() @@ -680,7 +678,6 @@ class CalibreDB(): conn.create_function("title_sort", 1, _title_sort) def dispose(self): - # global session old_session = self.session self.session = None diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index 2b679fc0..d0b59dc8 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -53,7 +53,7 @@ class TaskConvert(CalibreTask): def _convert_ebook_format(self): error_message = None - local_session = db.Session() + # local_session = db.Session() file_path = self.file_path book_id = self.bookid format_old_ext = u'.' + self.settings['old_book_format'].lower() @@ -94,10 +94,10 @@ class TaskConvert(CalibreTask): book_format=self.settings['new_book_format'].upper(), book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext)) try: - local_session.merge(new_format) - local_session.commit() + calibre_db.session.merge(new_format) + calibre_db.session.commit() except SQLAlchemyError as e: - local_session.rollback() + calibre_db.session.rollback() log.error("Database error: %s", e) return self.results['path'] = cur_book.path From e32b017431f46f6d828abe5793ec68d6b6bafc6a Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 6 Sep 2020 10:59:34 +0200 Subject: [PATCH 15/18] Revert "Test revert global Session" This reverts commit 393869e538accd45843afc67168fd095c64730a4. --- cps/db.py | 3 +++ cps/tasks/convert.py | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cps/db.py b/cps/db.py index a519d5bf..2cf1d98b 100644 --- a/cps/db.py +++ b/cps/db.py @@ -48,6 +48,7 @@ try: except ImportError: use_unidecode = False +Session = None cc_exceptions = ['datetime', 'comments', 'composite', 'series'] cc_classes = {} @@ -409,6 +410,7 @@ class CalibreDB(): def setup_db(self, config, app_db_path): self.config = config self.dispose() + global Session if not config.config_calibre_dir: config.invalidate() @@ -678,6 +680,7 @@ class CalibreDB(): conn.create_function("title_sort", 1, _title_sort) def dispose(self): + # global session old_session = self.session self.session = None diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index d0b59dc8..2b679fc0 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -53,7 +53,7 @@ class TaskConvert(CalibreTask): def _convert_ebook_format(self): error_message = None - # local_session = db.Session() + local_session = db.Session() file_path = self.file_path book_id = self.bookid format_old_ext = u'.' + self.settings['old_book_format'].lower() @@ -94,10 +94,10 @@ class TaskConvert(CalibreTask): book_format=self.settings['new_book_format'].upper(), book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext)) try: - calibre_db.session.merge(new_format) - calibre_db.session.commit() + local_session.merge(new_format) + local_session.commit() except SQLAlchemyError as e: - calibre_db.session.rollback() + local_session.rollback() log.error("Database error: %s", e) return self.results['path'] = cur_book.path From f49688fdb955df45e9b43b8513ed7d801ea13f99 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 6 Sep 2020 19:31:03 +0200 Subject: [PATCH 16/18] Fix #1461 (parse Amazon_* identifiers) --- cps/db.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/cps/db.py b/cps/db.py index 2cf1d98b..2dfd92c1 100644 --- a/cps/db.py +++ b/cps/db.py @@ -103,41 +103,49 @@ class Identifiers(Base): # return {self.type: self.val} def formatType(self): - if self.type == "amazon": + format_type = self.type.lower() + if format_type == 'amazon': return u"Amazon" - elif self.type == "isbn": + elif format_type.startswith("amazon_"): + return u"Amazon.{0}".format(format_type[7:]) + elif format_type == "isbn": return u"ISBN" - elif self.type == "doi": + elif format_type == "doi": return u"DOI" - elif self.type == "goodreads": + elif format_type == "douban": + return u"Douban" + elif format_type == "goodreads": return u"Goodreads" - elif self.type == "google": + elif format_type == "google": return u"Google Books" - elif self.type == "kobo": + elif format_type == "kobo": return u"Kobo" - if self.type == "lubimyczytac": + if format_type == "lubimyczytac": return u"Lubimyczytac" else: return self.type def __repr__(self): - if self.type == "amazon" or self.type == "asin": + format_type = self.type.lower() + if format_type == "amazon" or format_type == "asin": return u"https://amzn.com/{0}".format(self.val) - elif self.type == "isbn": + elif format_type.startswith('amazon_'): + return u"https://amazon.{0}/{1}".format(format_type[7:], self.val) + elif format_type == "isbn": return u"https://www.worldcat.org/isbn/{0}".format(self.val) - elif self.type == "doi": + elif format_type == "doi": return u"https://dx.doi.org/{0}".format(self.val) - elif self.type == "goodreads": + elif format_type == "goodreads": return u"https://www.goodreads.com/book/show/{0}".format(self.val) - elif self.type == "douban": + elif format_type == "douban": return u"https://book.douban.com/subject/{0}".format(self.val) - elif self.type == "google": + elif format_type == "google": return u"https://books.google.com/books?id={0}".format(self.val) - elif self.type == "kobo": + elif format_type == "kobo": return u"https://www.kobo.com/ebook/{0}".format(self.val) - elif self.type == "lubimyczytac": + elif format_type == "lubimyczytac": return u" https://lubimyczytac.pl/ksiazka/{0}".format(self.val) - elif self.type == "url": + elif format_type == "url": return u"{0}".format(self.val) else: return u"" From e012726cd48e5f66e9921f9ea1353b5320a33c2e Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 6 Sep 2020 19:31:31 +0200 Subject: [PATCH 17/18] Fix #1461 (parse Amazon_* identifiers) --- cps/db.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/cps/db.py b/cps/db.py index 95b5d366..fb1d5c57 100644 --- a/cps/db.py +++ b/cps/db.py @@ -100,41 +100,49 @@ class Identifiers(Base): self.book = book def formatType(self): - if self.type == "amazon": + format_type = self.type.lower() + if format_type == 'amazon': return u"Amazon" - elif self.type == "isbn": + elif format_type.startswith("amazon_"): + return u"Amazon.{0}".format(format_type[7:]) + elif format_type == "isbn": return u"ISBN" - elif self.type == "doi": + elif format_type == "doi": return u"DOI" - elif self.type == "goodreads": + elif format_type == "douban": + return u"Douban" + elif format_type == "goodreads": return u"Goodreads" - elif self.type == "google": + elif format_type == "google": return u"Google Books" - elif self.type == "kobo": + elif format_type == "kobo": return u"Kobo" - if self.type == "lubimyczytac": + if format_type == "lubimyczytac": return u"Lubimyczytac" else: return self.type def __repr__(self): - if self.type == "amazon" or self.type == "asin": + format_type = self.type.lower() + if format_type == "amazon" or format_type == "asin": return u"https://amzn.com/{0}".format(self.val) - elif self.type == "isbn": + elif format_type.startswith('amazon_'): + return u"https://amazon.{0}/{1}".format(format_type[7:], self.val) + elif format_type == "isbn": return u"https://www.worldcat.org/isbn/{0}".format(self.val) - elif self.type == "doi": + elif format_type == "doi": return u"https://dx.doi.org/{0}".format(self.val) - elif self.type == "goodreads": + elif format_type == "goodreads": return u"https://www.goodreads.com/book/show/{0}".format(self.val) - elif self.type == "douban": + elif format_type == "douban": return u"https://book.douban.com/subject/{0}".format(self.val) - elif self.type == "google": + elif format_type == "google": return u"https://books.google.com/books?id={0}".format(self.val) - elif self.type == "kobo": + elif format_type == "kobo": return u"https://www.kobo.com/ebook/{0}".format(self.val) - elif self.type == "lubimyczytac": + elif format_type == "lubimyczytac": return u" https://lubimyczytac.pl/ksiazka/{0}".format(self.val) - elif self.type == "url": + elif format_type == "url": return u"{0}".format(self.val) else: return u"" From 4b7a37cf7dd87efa0d33f75a4a423f29813cbf07 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Mon, 7 Sep 2020 18:19:54 +0200 Subject: [PATCH 18/18] Bugfix empty email attachment size --- cps/templates/email_edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/templates/email_edit.html b/cps/templates/email_edit.html index bce3bc21..9025a654 100644 --- a/cps/templates/email_edit.html +++ b/cps/templates/email_edit.html @@ -37,7 +37,7 @@
- +
{{_('Remove')}}