diff --git a/cps/book_formats.py b/cps/book_formats.py index d295452a..64ec86e8 100644 --- a/cps/book_formats.py +++ b/cps/book_formats.py @@ -99,7 +99,7 @@ def pdf_preview(tmp_file_path, tmp_dir): return None else: cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jpg" - with Image(filename=tmp_file_path +"[0]", resolution=150) as img: + with Image(filename=tmp_file_path + "[0]", resolution=150) as img: img.compression_quality = 88 img.save(filename=os.path.join(tmp_dir, cover_file_name)) return cover_file_name diff --git a/cps/db.py b/cps/db.py index 67526e2c..f6ee790e 100755 --- a/cps/db.py +++ b/cps/db.py @@ -32,29 +32,29 @@ def title_sort(title): Base = declarative_base() books_authors_link = Table('books_authors_link', Base.metadata, - Column('book', Integer, ForeignKey('books.id'), primary_key=True), - Column('author', Integer, ForeignKey('authors.id'), primary_key=True) - ) + Column('book', Integer, ForeignKey('books.id'), primary_key=True), + Column('author', Integer, ForeignKey('authors.id'), primary_key=True) + ) books_tags_link = Table('books_tags_link', Base.metadata, - Column('book', Integer, ForeignKey('books.id'), primary_key=True), - Column('tag', Integer, ForeignKey('tags.id'), primary_key=True) - ) + Column('book', Integer, ForeignKey('books.id'), primary_key=True), + Column('tag', Integer, ForeignKey('tags.id'), primary_key=True) + ) books_series_link = Table('books_series_link', Base.metadata, - Column('book', Integer, ForeignKey('books.id'), primary_key=True), - Column('series', Integer, ForeignKey('series.id'), primary_key=True) - ) + Column('book', Integer, ForeignKey('books.id'), primary_key=True), + Column('series', Integer, ForeignKey('series.id'), primary_key=True) + ) books_ratings_link = Table('books_ratings_link', Base.metadata, - Column('book', Integer, ForeignKey('books.id'), primary_key=True), - Column('rating', Integer, ForeignKey('ratings.id'), primary_key=True) - ) + Column('book', Integer, ForeignKey('books.id'), primary_key=True), + Column('rating', Integer, ForeignKey('ratings.id'), primary_key=True) + ) books_languages_link = Table('books_languages_link', Base.metadata, - Column('book', Integer, ForeignKey('books.id'), primary_key=True), - Column('lang_code', Integer, ForeignKey('languages.id'), primary_key=True) - ) + Column('book', Integer, ForeignKey('books.id'), primary_key=True), + Column('lang_code', Integer, ForeignKey('languages.id'), primary_key=True) + ) class Identifiers(Base): @@ -227,7 +227,7 @@ class Books(Base): identifiers = relationship('Identifiers', backref='books') def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover, - authors, tags): # ToDO check Authors and tags necessary + authors, tags): self.title = title self.sort = sort self.author_sort = author_sort diff --git a/cps/fb2.py b/cps/fb2.py index aa887068..205f69ce 100644 --- a/cps/fb2.py +++ b/cps/fb2.py @@ -6,7 +6,7 @@ import os import uploader import StringIO -# ToDo: Check usage of original_file_name + def get_fb2_info(tmp_file_path, original_file_extension): ns = { @@ -20,37 +20,35 @@ def get_fb2_info(tmp_file_path, original_file_extension): authors = tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:author', namespaces=ns) def get_author(element): - last_name=element.xpath('fb:last-name/text()', namespaces=ns) + last_name = element.xpath('fb:last-name/text()', namespaces=ns) if len(last_name): - last_name=last_name[0] + last_name = last_name[0] else: - last_name=u'' - middle_name=element.xpath('fb:middle-name/text()', namespaces=ns) + last_name = u'' + middle_name = element.xpath('fb:middle-name/text()', namespaces=ns) if len(middle_name): - middle_name=middle_name[0] + middle_name = middle_name[0] else: - middle_name=u'' - first_name=element.xpath('fb:first-name/text()', namespaces=ns) + middle_name = u'' + first_name = element.xpath('fb:first-name/text()', namespaces=ns) if len(first_name): - first_name=first_name[0] + first_name = first_name[0] else: - first_name=u'' - return first_name + ' ' + middle_name + ' ' + last_name + first_name = u'' + return first_name + ' ' + middle_name + ' ' + last_name author = unicode(", ".join(map(get_author, authors))) title = tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:book-title/text()', namespaces=ns) if len(title): - title=unicode(title[0]) + title = unicode(title[0]) else: - title=u'' + title = u'' description = tree.xpath('/fb:FictionBook/fb:description/fb:publish-info/fb:book-name/text()', namespaces=ns) if len(description): - description=unicode(description[0]) + description = unicode(description[0]) else: - description=u'' - - + description = u'' return uploader.BookMeta( file_path=tmp_file_path, diff --git a/cps/helper.py b/cps/helper.py index a0867660..f9251147 100755 --- a/cps/helper.py +++ b/cps/helper.py @@ -22,6 +22,11 @@ from email.generator import Generator from flask_babel import gettext as _ import subprocess import shutil +try: + import unidecode + use_unidecode=True +except: + use_unidecode=False def update_download(book_id, user_id): check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id == @@ -203,7 +208,7 @@ def get_attachment(file_path): return attachment except IOError: traceback.print_exc() - message = (_('The requested file could not be read. Maybe wrong permissions?')) # ToDo: What is this? + app.logger.error = (u'The requested file could not be read. Maybe wrong permissions?') return None @@ -212,47 +217,54 @@ def get_valid_filename(value, replace_whitespace=True): Returns the given string converted to a string that can be used for a clean filename. Limits num characters to 128 max. """ - value = value[:128] - # re_slugify = re.compile('[^\w\s-]', re.UNICODE) - value = unicodedata.normalize('NFKD', value) - re_slugify = re.compile('[^\w\s-]', re.UNICODE) - value = unicode(re_slugify.sub('', value).strip()) + if value[-1:] ==u'.': + value = value[:-1]+u'_' + if use_unidecode: + value=(unidecode.unidecode(value)).strip() + else: + value=value.replace('§','SS') + value=value.replace('ß','ss') + value = unicodedata.normalize('NFKD', value) + re_slugify = re.compile('[\W\s-]', re.UNICODE) + value = unicode(re_slugify.sub('', value).strip()) if replace_whitespace: - value = re.sub('[\s]+', '_', value, flags=re.U) - value = value.replace(u"\u00DF", "ss") + #*+:\"/<>? werden durch _ ersetzt + value = re.sub('[\*\+:\\\"/<>\?]+', '_', value, flags=re.U) + + value = value[:128] return value +def get_sorted_author(value): + regexes = ["^(JR|SR)\.?$","^I{1,3}\.?$","^IV\.?$"] + combined = "(" + ")|(".join(regexes) + ")" + value = value.split(" ") + if re.match(combined,value[-1].upper()): + value2 = value[-2] + ", " + " ".join(value[:-2]) + " " + value[-1] + else: + value2 = value[-1] + ", " + " ".join(value[:-1]) + return value2 -def get_normalized_author(value): - """ - Normalizes sorted author name - """ - value = unicodedata.normalize('NFKD', value) - value = re.sub('[^\w,\s]', '', value, flags=re.U) - value = " ".join(value.split(", ")[::-1]) - return value - def update_dir_stucture(book_id, calibrepath): db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) book = db.session.query(db.Books).filter(db.Books.id == book_id).first() - path = os.path.join(calibrepath, book.path) + path = os.path.join(calibrepath, book.path)#.replace('/',os.path.sep)).replace('\\',os.path.sep) - authordir = book.path.split(os.sep)[0] - new_authordir = get_valid_filename(book.authors[0].name, False) - titledir = book.path.split(os.sep)[1] - new_titledir = get_valid_filename(book.title, False) + " (" + str(book_id) + ")" + authordir = book.path.split('/')[0] + new_authordir = get_valid_filename(book.authors[0].name) + titledir = book.path.split('/')[1] + new_titledir = get_valid_filename(book.title) + " (" + str(book_id) + ")" if titledir != new_titledir: new_title_path = os.path.join(os.path.dirname(path), new_titledir) os.rename(path, new_title_path) path = new_title_path - book.path = book.path.split(os.sep)[0] + os.sep + new_titledir + book.path = book.path.split('/')[0] + '/' + new_titledir if authordir != new_authordir: new_author_path = os.path.join(os.path.join(calibrepath, new_authordir), os.path.basename(path)) - os.renames(path, new_author_path) - book.path = new_authordir + os.sep + book.path.split(os.sep)[1] + os.rename(path, new_author_path) + book.path = new_authordir + '/' + book.path.split('/')[1] db.session.commit() diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 2b70e17b..31c167ee 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -2,7 +2,7 @@ {% block body %}

{{_('User list')}}

- +
@@ -30,9 +30,9 @@ {% endif %} {% endfor %}
{{_('Nickname')}} {{_('Email')}}
-
{{_('Add new user')}}
+
{{_('Add new user')}}

{{_('SMTP mail settings')}}

- +
@@ -51,10 +51,10 @@
{{_('SMTP hostname')}} {{_('SMTP port')}}
-
{{_('Change SMTP settings')}}
+
{{_('Change SMTP settings')}}

{{_('Configuration')}}

- +
@@ -76,6 +76,7 @@
{{_('Configuration')}}

{{_('Administration')}}

{% if not development %} +

{{_('Current commit timestamp')}}: {{commit}}

{{_('Restart Calibre-web')}}
{{_('Stop Calibre-web')}}
{{_('Check for update')}}
diff --git a/cps/templates/detail.html b/cps/templates/detail.html index 4fa37a1d..8775f079 100644 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -70,8 +70,8 @@

{% endif %} - {% if entry.pubdate != '0101-01-01 00:00:00' %} -

{{_('Publishing date')}}: {{entry.pubdate[:10]}}

+ {% if entry.pubdate[:10] != '0101-01-01' %} +

{{_('Publishing date')}}: {{entry.pubdate|formatdate}}

{% endif %} {% if cc|length > 0 %}

diff --git a/cps/templates/index.html b/cps/templates/index.html index a59ae6b3..7ab46a7b 100755 --- a/cps/templates/index.html +++ b/cps/templates/index.html @@ -6,7 +6,7 @@

{% for entry in random %} -
+
{% if entry.has_cover %} @@ -41,7 +41,7 @@

{{title}}

{% for entry in entries %} -
+
diff --git a/cps/templates/layout.html b/cps/templates/layout.html index e9157b10..7e63b93b 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -80,16 +80,16 @@ {% endif %} {% endif %} {% if g.user.role_admin() %} -
  • {{_('Admin')}}
  • +
  • {{_('Admin')}}
  • {% endif %} -
  • {{g.user.nickname}}
  • +
  • {{g.user.nickname}}
  • {% if not g.user.is_anonymous() %} -
  • {{_('Logout')}}
  • +
  • {{_('Logout')}}
  • {% endif %} {% endif %} {% if g.allow_registration and not g.user.is_authenticated %} -
  • {{_('Login')}}
  • -
  • {{_('Register')}}
  • +
  • {{_('Login')}}
  • +
  • {{_('Register')}}
  • {% endif %}
    @@ -98,17 +98,17 @@ {% for message in get_flashed_messages(with_categories=True) %} {%if message[0] == "error" %}
    -
    {{ message[1] }}
    +
    {{ message[1] }}
    {%endif%} {%if message[0] == "info" %}
    -
    {{ message[1] }}
    +
    {{ message[1] }}
    {%endif%} {%if message[0] == "success" %}
    -
    {{ message[1] }}
    +
    {{ message[1] }}
    {%endif%} {% endfor %} @@ -119,25 +119,25 @@
    {% if error %} diff --git a/cps/templates/osd.xml b/cps/templates/osd.xml index 35c1147c..77709478 100644 --- a/cps/templates/osd.xml +++ b/cps/templates/osd.xml @@ -2,7 +2,7 @@ {{instance}} {{instance}} - {{_('instanceCalibre Web ebook catalog')}} + {{_('Calibre Web ebook catalog')}} Janeczku https://github.com/janeczku/calibre-web {{_('Linked libraries')}} -
    {{_('Calibre DB dir')}} {{_('Log Level')}}
    +
    @@ -30,7 +30,7 @@
    {{_('Program library')}}

    {{_('Calibre library statistics')}}

    - +
    diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index 74469124..b9cff3eb 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -27,7 +27,7 @@ @@ -108,7 +108,7 @@ {% endif %} {% if not profile %} - {{_('Back')}} + {{_('Back')}} {% endif %} diff --git a/cps/translations/de/LC_MESSAGES/messages.po b/cps/translations/de/LC_MESSAGES/messages.po index 7ba0050e..3fb5cc32 100644 --- a/cps/translations/de/LC_MESSAGES/messages.po +++ b/cps/translations/de/LC_MESSAGES/messages.po @@ -81,7 +81,7 @@ msgstr "Beliebte Bücher (die meisten Downloads)" #: cps/web.py:813 msgid "Best rated books" -msgstr "" +msgstr "Best bewertete Bücher" #: cps/templates/index.xml:36 cps/web.py:822 msgid "Random Books" @@ -94,7 +94,7 @@ msgstr "Autorenliste" #: cps/web.py:846 #, python-format msgid "Author: %(name)s" -msgstr "" +msgstr "Autor: %(name)s" #: cps/web.py:848 cps/web.py:876 cps/web.py:975 cps/web.py:1216 cps/web.py:2103 msgid "Error opening eBook. File does not exist or file is not accessible:" @@ -143,7 +143,7 @@ msgstr "Server wird runtergefahren, bitte Fenster schließen" #: cps/web.py:1055 msgid "Update done" -msgstr "" +msgstr "Update durchgeführt" #: cps/web.py:1128 cps/web.py:1141 msgid "search" @@ -470,11 +470,11 @@ msgstr "Stoppe Calibre-web" #: cps/templates/admin.html:81 msgid "Check for update" -msgstr "" +msgstr "Suche nach Update" #: cps/templates/admin.html:82 msgid "Perform Update" -msgstr "" +msgstr "Update durchführen" #: cps/templates/admin.html:93 msgid "Do you really want to restart Calibre-web?" @@ -584,7 +584,7 @@ msgstr "Öffentliche Registrierung aktivieren" #: cps/templates/config_edit.html:52 msgid "Default Settings for new users" -msgstr "" +msgstr "Default Einstellungen für neue Benutzer" #: cps/templates/config_edit.html:55 cps/templates/user_edit.html:80 msgid "Admin user" @@ -625,7 +625,7 @@ msgstr "Sprache" #: cps/templates/detail.html:74 msgid "Publishing date" -msgstr "" +msgstr "Herausgabedatum" #: cps/templates/detail.html:106 msgid "Description:" @@ -699,11 +699,11 @@ msgstr "Beliebte Bücher" #: cps/templates/index.xml:19 msgid "Popular publications from this catalog based on Downloads." -msgstr "" +msgstr "Beliebte Publikationen aus dieser Bibliothek basierend auf Downloadzahlen" #: cps/templates/index.xml:22 cps/templates/layout.html:127 msgid "Best rated Books" -msgstr "" +msgstr "Best bewertete Bücher" #: cps/templates/index.xml:26 msgid "Popular publications from this catalog based on Rating." @@ -804,8 +804,8 @@ msgid "Remember me" msgstr "Merken" #: cps/templates/osd.xml:5 -msgid "instanceCalibre Web ebook catalog" -msgstr "" +msgid "Calibre Web ebook catalog" +msgstr "Calibre Web Ebook Katalog" #: cps/templates/read.html:136 msgid "Reflow text when sidebars are open." @@ -909,11 +909,11 @@ msgstr "Autoren in dieser Bibliothek" #: cps/templates/stats.html:45 msgid "Categories in this Library" -msgstr "" +msgstr "Kategorien in dieser Bibliothek" #: cps/templates/stats.html:49 msgid "Series in this Library" -msgstr "" +msgstr "Serien in dieser Bibliothek" #: cps/templates/user_edit.html:23 msgid "Kindle E-Mail" @@ -937,7 +937,7 @@ msgstr "Zeige Auswahl Beliebte Bücher" #: cps/templates/user_edit.html:53 msgid "Show best rated books" -msgstr "" +msgstr "Zeige am besten bewertete Bücher" #: cps/templates/user_edit.html:57 msgid "Show language selection" diff --git a/cps/ub.py b/cps/ub.py index bebeedab..f5207f06 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -144,7 +144,6 @@ class UserBase: else: return False - def __repr__(self): return '' % self.nickname @@ -164,10 +163,6 @@ class User(UserBase, Base): downloads = relationship('Downloads', backref='user', lazy='dynamic') locale = Column(String(2), default="en") sidebar_view = Column(Integer, default=1) - #language_books = Column(Integer, default=1) - #series_books = Column(Integer, default=1) - #category_books = Column(Integer, default=1) - #hot_books = Column(Integer, default=1) default_language = Column(String(3), default="all") @@ -184,10 +179,6 @@ class Anonymous(AnonymousUserMixin, UserBase): self.role = data.role self.sidebar_view = data.sidebar_view self.default_language = data.default_language - #self.language_books = data.language_books - #self.series_books = data.series_books - #self.category_books = data.category_books - #self.hot_books = data.hot_books self.default_language = data.default_language self.locale = data.locale self.anon_browse = settings.config_anonbrowse diff --git a/cps/web.py b/cps/web.py index da601c10..efecfe56 100755 --- a/cps/web.py +++ b/cps/web.py @@ -25,6 +25,7 @@ import zipfile from werkzeug.security import generate_password_hash, check_password_hash from babel import Locale as LC from babel import negotiate_locale +from babel.dates import format_date from functools import wraps import base64 from sqlalchemy.sql import * @@ -279,6 +280,12 @@ def mimetype_filter(val): s = 'application/octet-stream' return s +@app.template_filter('formatdate') +def formatdate(val): + conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', val) + formatdate = datetime.datetime.strptime(conformed_timestamp[:-5], "%Y%m%d %H%M%S") + return format_date(formatdate, format='medium',locale=get_locale()) + def admin_required(f): """ @@ -658,10 +665,9 @@ def get_opds_download_link(book_id, format): data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first() if current_user.is_authenticated: helper.update_download(book_id, int(current_user.id)) - author = helper.get_normalized_author(book.author_sort) file_name = book.title - if len(author) > 0: - file_name = author + '-' + file_name + if len(book.authors) > 0: + file_name = book.authors[0].name + '-' + file_name file_name = helper.get_valid_filename(file_name) response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format)) response.headers["Content-Disposition"] = "attachment; filename=\"%s.%s\"" % (data.name, format) @@ -1228,10 +1234,9 @@ def get_download_link(book_id, format): # collect downloaded books only for registered user and not for anonymous user if current_user.is_authenticated: helper.update_download(book_id, int(current_user.id)) - author = helper.get_normalized_author(book.author_sort) file_name = book.title - if len(author) > 0: - file_name = author + '-' + file_name + if len(book.authors) > 0: + file_name = book.authors[0].name + '-' + file_name file_name = helper.get_valid_filename(file_name) response = make_response( send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format)) @@ -1239,13 +1244,7 @@ def get_download_link(book_id, format): response.headers["Content-Type"] = mimetypes.types_map['.' + format] except: pass - response.headers["Content-Disposition"] = \ - "attachment; " \ - "filename={utf_filename}.{suffix};" \ - "filename*=UTF-8''{utf_filename}.{suffix}".format( - utf_filename=file_name.encode('utf-8'), - suffix=format - ) + response.headers["Content-Disposition"] = "attachment; filename=\"%s.%s\"" % (file_name.encode('utf-8'), format) return response else: abort(404) @@ -1599,6 +1598,7 @@ def basic_configuration(): def configuration_helper(origin): global global_task + commit='$Format:%cI$' reboot_required = False db_change = False success = False @@ -1659,16 +1659,16 @@ def configuration_helper(origin): logging.getLogger("book_formats").setLevel(config.config_log_level) except e: flash(e, category="error") - return render_title_template("config_edit.html", content=config, origin=origin, + return render_title_template("config_edit.html", content=config, origin=origin, commit=commit, title=_(u"Basic Configuration")) if db_change: reload(db) if not db.setup_db(): flash(_(u'DB location is not valid, please enter correct path'), category="error") - return render_title_template("config_edit.html", content=config, origin=origin, + return render_title_template("config_edit.html", content=config, origin=origin, commit=commit, title=_(u"Basic Configuration")) if reboot_required: - # db.engine.dispose() # ToDo verify correct + # db.engine.dispose() # ToDo verify correct ub.session.close() ub.engine.dispose() # stop tornado server @@ -1678,7 +1678,7 @@ def configuration_helper(origin): app.logger.info('Reboot required, restarting') if origin: success = True - return render_title_template("config_edit.html", origin=origin, success=success, content=config, + return render_title_template("config_edit.html", origin=origin, success=success, content=config, commit=commit, title=_(u"Basic Configuration")) @@ -1927,7 +1927,7 @@ def edit_book(book_id): modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author') if author0_before_edit != book.authors[0].name: edited_books_id.add(book.id) - book.author_sort=helper.get_normalized_author(input_authors[0]) # ToDo: wrong sorting + book.author_sort=helper.get_sorted_author(input_authors[0]) if to_save["cover_url"] and os.path.splitext(to_save["cover_url"])[1].lower() == ".jpg": img = requests.get(to_save["cover_url"]) @@ -2155,9 +2155,10 @@ def upload(): if is_author: db_author = is_author else: - db_author = db.Authors(author, helper.get_normalized_author(author), "") # TODO: WRONG Sorting Author function + db_author = db.Authors(author, helper.get_sorted_author(author), "") db.session.add(db_author) - path = os.path.join(author_dir, title_dir) + # combine path and normalize path from windows systems + path = os.path.join(author_dir, title_dir).replace('\\','/') db_book = db.Books(title, "", db_author.sort, datetime.datetime.now(), datetime.datetime(101, 01, 01), 1, datetime.datetime.now(), path, has_cover, db_author, []) db_book.authors.append(db_author) diff --git a/readme.md b/readme.md index 4629a47f..102d4b2e 100755 --- a/readme.md +++ b/readme.md @@ -11,7 +11,7 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d - full graphical setup - User management - Admin interface -- User Interface in english, french, german, simplified chinese, spanish +- User Interface in english, french, german, polish, simplified chinese, spanish - OPDS feed for eBook reader apps - Filter and search by titles, authors, tags, series and language - Create custom book collection (shelves)
    {{bookcounter}}