Compare commits

..
118 Commits
Author SHA1 Message Date
Ozzie Isaacs aba88ae53a Update setup.cfg 2019-07-15 21:04:08 +02:00
Ozzie Isaacs 67d0ddb180 Update optional requirements 2019-07-15 21:03:35 +02:00
Ozzie Isaacs b1bb1cfdfa Changed optional dependencies 2019-07-15 20:21:59 +02:00
Ozzieisaacs 37736e11d5 Updated dependencies 2019-07-13 20:32:39 +02:00
Ozzieisaacs 792367e35e Version update 2019-07-13 20:27:32 +02:00
Ozzieisaacs b1cb7123a3 Fix for #959 2019-07-11 20:37:03 +02:00
Ozzieisaacs f79d549910 Fix duplicate user and email (now case insensitive #948)
Fix sorting in comics (#950)
Fix log error on Calibre converter error (#953)
Fix long running tasks (#954)
2019-07-06 14:35:12 +02:00
Ozzieisaacs 66283c542f Merge remote-tracking branch 'dutch/master' 2019-06-22 19:29:39 +02:00
Ozzie Isaacs c1d5f77fe8 Wiki link fixed 2019-06-20 10:38:32 +02:00
Heimen Stoffels d9f69ca264 Updated Dutch translation 2019-06-17 22:38:32 +02:00
Ozzieisaacs cd546eb6d4 Update comic reader js 2019-06-17 19:48:17 +02:00
Ozzieisaacs e67d707867 Fix for #588
Merge remote-tracking branch 'linuxserver/master'
2019-06-15 11:58:55 +02:00
Ozzieisaacs 3f5c6c1fa5 Fix #937 2019-06-13 20:01:37 +02:00
Ozzieisaacs f5e3ed26b9 Fix for #935 2019-06-10 19:31:13 +02:00
Ozzieisaacs c81d4edb7d Fix for #923 2019-06-10 16:38:29 +02:00
Ozzieisaacs f736a15c12 Fix for restart of newer versions of tornado during update
Fix for updater with Beta Versions
2019-06-02 18:47:25 +02:00
Ozzieisaacs 87ca05f129 Merge remote-tracking branch 'setup/feature/setuptools-integration' 2019-05-31 16:24:13 +02:00
Luke Murphy b3a286c0b5 Add initial setuptools integration 2019-05-31 11:54:05 +02:00
Ozzieisaacs 26e45f1f57 Merge remote-tracking branch 'comic-reader/master' 2019-05-31 08:43:51 +02:00
Ozzieisaacs df5d15d1a2 Merge remote-tracking branch 'comicapi/master' 2019-05-30 10:43:28 +02:00
Ozzieisaacs ecedf92783 Update version
Make custom columns visible (#904)
2019-05-30 10:21:11 +02:00
Ozzieisaacs d106ada9ed Changed version
Update translation
2019-05-30 09:07:52 +02:00
Ozzieisaacs ed91048a63 Fix for magic link with python 3 2019-05-26 11:31:09 +02:00
Ozzieisaacs e6ff2f1d90 Fix file not found loading-icon.gif 2019-05-16 19:42:59 +02:00
subdiox 867aa2f0bd Beautify bitjs 2019-05-14 04:28:06 +09:00
subdiox 7982ed877c Downgrade bitjs to es5 branch 2019-05-13 14:53:25 +09:00
Ozzieisaacs 1030e195a5 Fix #916 2019-05-11 13:03:38 +02:00
subdiox c0d136ccd8 Fix slow loading 2019-05-10 08:16:03 +09:00
Ozzieisaacs 479b4b7d82 Update translations, integrated update of Italian translation 2019-05-08 20:24:10 +02:00
Ozzieisaacs a42ebdc096 Fix for #897 2019-05-07 19:54:13 +02:00
Ozzieisaacs 49ba221e85 Merge remote-tracking branch 'direction' 2019-05-07 18:54:27 +02:00
Ozzieisaacs cb0403a924 Merge remote-tracking branch 'adv_search/master' 2019-05-06 20:48:46 +02:00
Marvin Marx a2c7741e21 Fix "Internal Server Error" on advanced search
Custom boolean columns return that error if calibre does not have a
custom_column_1 in the DB, as this is queried in the removed line.
However the value is completely unused anyway -> removing.
2019-05-05 17:58:45 +02:00
Ozzieisaacs 55bb8d4590 Fix #900 2019-05-01 17:47:54 +02:00
Ozzieisaacs b80bfa5260 Improvement for #897 2019-04-29 19:01:30 +02:00
Ozzieisaacs f941908f73 Workaround for #889 2019-04-23 21:32:48 +02:00
subdiox c2bfb29726 Add reading direction settings to readcbr page 2019-04-21 18:20:15 +09:00
subdiox 204de4aef6 Fix an issue that fullscreen doens't work on some browsers 2019-04-21 18:02:02 +09:00
subdiox 8b6d165d64 Fix Japanese translation 2019-04-21 17:53:40 +09:00
Ozzieisaacs 6a7b8c7870 Update Version info
Fix #816
Clarified licences for isolanguages.py and reverseproxy.py
2019-04-21 08:48:51 +02:00
Ozzieisaacs cc5c2c0f65 Update Version info 2019-04-20 12:30:33 +02:00
Ozzieisaacs c748716ad2 Fix for opds feed of unread/read books 2019-04-19 12:07:46 +02:00
Ozzieisaacs 544405f5aa Update search for cyrillic letters #877 2019-04-19 06:54:12 +02:00
Ozzieisaacs 67736fe187 Catch errors on loading pickle file
Fix for cover images without Pillow
2019-04-18 21:17:59 +02:00
Ozzieisaacs 89516fc2d6 Make PIL optional #885 2019-04-17 20:45:08 +02:00
Ozzieisaacs e1e79a73e8 Fix #884 2019-04-17 20:14:24 +02:00
Ozzieisaacs 0b8624b542 Deleted load metadata from douban, as API seems to be shut down (#858) 2019-04-17 19:55:32 +02:00
Ozzieisaacs 91ebe33879 Merge remote-tracking branch 'image_formats/dev' 2019-04-15 20:57:25 +02:00
Ozzieisaacs 55bdd13fb1 Fix #877 2019-04-14 17:54:18 +02:00
Ozzieisaacs 4552208c02 Fix #848 2019-04-14 16:37:57 +02:00
Ozzieisaacs 436c2671d1 Fix delete domain entries
Fix error handling smtp.authentication error
2019-04-14 14:08:49 +02:00
Ozzieisaacs a669eacde9 Merge remote-tracking branch 'proxy/patch-1' 2019-04-14 11:19:36 +02:00
Ozzieisaacs 5188c62bea Merge remote-tracking branch 'translation/loc-hu' 2019-04-13 16:45:05 +02:00
Ozzieisaacs e754ce313c Merge remote-tracking branch 'pycountry/pycountry' 2019-04-13 16:34:04 +02:00
Ozzieisaacs dc933f3d66 Merge remote-tracking branch 'pdf.js/epubjs'
Updated pdf.js
2019-04-13 15:02:01 +02:00
Iris W 029d299067 updated comicapi version to fix installation error 2019-04-09 15:22:03 -04:00
Barnabás Nagy afc79416b2 HU Translation updates
Translation of the new untranslated strings
2019-04-06 23:38:59 +02:00
Barnabás Nagy 93720c9fdf Merge pull request #1 from janeczku/master
Synch the master
2019-04-06 23:06:00 +02:00
Iris W b7e30644ab updated comicapi requirement—libunrar no longer necessary (CBR parsing support was always disabled anyway) 2019-04-03 15:36:05 -04:00
Josh Max cdfb6bf3e3 Use HTTP_X_FORWARDED_HOST instead of HTTP_X_FORWARDED_SERVER
Use HTTP_X_FORWARDED_HOST since HTTP_X_FORWARDED_SERVER holds the hostname of the proxy server on HAProxy/Traefik.
2019-04-03 01:25:29 -07:00
Iris Wildthyme cbdc9876b2 comicapi dependency now pippable 2019-04-02 20:58:23 -04:00
Iris Wildthyme 05d0f12608 redid comic importing support to handle metadata properly using comicapi from comictagger. needs work to automate installation 2019-04-02 18:52:45 -04:00
Daniel Pavel 858c80bb20 'except' style fix 2019-03-29 17:06:26 +02:00
Daniel Pavel f7e640fd98 support pycountry if iso-639 is not available 2019-03-29 07:00:10 +02:00
Daniel Pavel 439ce19b5d Updated epub.js to v0.3.66 2019-03-28 07:23:45 +02:00
Ozzieisaacs 4b301a7961 Additional fix for #845 (in case of error) 2019-03-26 18:19:39 +01:00
Yijun Zhao 6c493df2f5 support issue: #843 2019-03-26 14:34:51 +08:00
Ozzieisaacs 8923e71220 Remove debug logging 2019-03-24 15:18:01 +01:00
Ozzieisaacs 843a93fe28 Updater fix for slow servers 2019-03-24 15:15:36 +01:00
OzzieIsaacs fa36072699 Fix uk translation-file 2019-03-24 08:18:41 +01:00
OzzieIsaacs 8ab9365688 Prevent error 500 for cover of not exisiting book 2019-03-23 20:29:53 +01:00
Chad Bergeron 169ace777a Fixed infinite scroll for read and unread displays (#855) 2019-03-21 19:18:44 +01:00
Elifcan Cakmak ec2103a3d0 fixed a bug that affects allowing password change for users (#854) 2019-03-20 13:06:11 +01:00
Ozzieisaacs a0a15b4598 Fix max authors shown 2019-03-10 19:26:39 +01:00
Ozzieisaacs 76d40411dd Translation updated 2019-03-10 08:04:31 +01:00
Ozzieisaacs 8612dce97c Merge remote-tracking branch 'author/shorten-authors' 2019-03-09 20:14:16 +01:00
Ozzieisaacs 0b0e62fdf2 Merge remote-tracking branch 'dialog/master'
Fix for #833
2019-03-09 20:02:17 +01:00
Ozzieisaacs 6f80a63ca6 Fix for #827 2019-03-09 07:05:43 +01:00
Andrew Sullivan 3dfc2f2580 fixing issue #833 2019-03-08 20:44:45 -05:00
Ozzieisaacs 5b0a0e2139 Fix #835 2019-03-08 16:32:56 +01:00
Ozzie Isaacs 0744e84ba9 Fix for sqlalchemy version problem
In version 1.3.0 the follwoing error is thrown: Textual SQL expression 'xxx.xxx' should be explicitly declared as text
2019-03-07 20:50:58 +01:00
Ozzieisaacs 6f0b3bbda0 Fix for #812 2019-03-05 21:28:55 +01:00
Ozzieisaacs 466af21548 Code Cosmetics 2019-03-05 16:31:09 +01:00
Ozzieisaacs 6269a83c3d Fix mismerge user logon with python3 in opds feed 2019-03-03 20:51:19 +01:00
Ozzieisaacs 11b770b3fb Fix for #830
Fix user with umlauts
2019-03-03 08:41:50 +01:00
Ozzieisaacs fa276fd0d3 Add scrollbar to metadata load screen
Small UI improvements in metadata load screen
2019-03-01 21:00:09 +01:00
Ozzieisaacs 361a1243d7 Improvements #828 2019-02-27 19:30:13 +01:00
Virgil Grigoras bd7b0175a1 Fix remaining shown by codacy 2019-02-26 17:24:13 +01:00
Virgil Grigoras f5cf62f209 Fix issues shown by codacy 2019-02-26 11:17:06 +01:00
Virgil Grigoras 15aa49e163 shorten authors in author / discover / index / search and shelf 2019-02-25 19:27:59 +01:00
Virgil Grigoras 3272b4ca1e configuration field for max_authors 2019-02-25 19:26:43 +01:00
Virgil Grigoras d3ed0317bb styling and javascript for hiding authors 2019-02-25 19:25:08 +01:00
Virgil Grigoras e5bc1ae020 Add new database field for storing max_authors 2019-02-25 19:21:49 +01:00
Ozzieisaacs fad62a0909 translation update 2019-02-24 19:23:51 +01:00
Ozzieisaacs f6fbe2e135 Fix #766
Update pdf upload
Translation of uploadprogress dialog
2019-02-24 19:23:51 +01:00
Ozzieisaacs 17b7333992 Merge remote-tracking branch 'caliblur/master' 2019-02-20 20:00:08 +01:00
leram84 ae279c5123 fixed description read-more button 2019-02-20 12:57:38 -05:00
Ozzieisaacs ae7c4908f9 Fix #767 2019-02-20 18:04:55 +01:00
Ozzieisaacs ea1a882353 Merge remote-tracking branch 'origin/master' 2019-02-20 18:04:40 +01:00
hexeth 9a0e8fefb7 re-add loader code 2019-02-19 13:56:49 -08:00
Matt c07cb23ef2 Merge branch 'master' of git://github.com/janeczku/calibre-web 2019-02-19 13:47:53 -08:00
hexeth 5a863541de Fix object object 2019-02-19 13:44:40 -08:00
Ozzie Isaacs 4c80db4324 Revert bootstrap update 3.4.0 -> 3.3.7 2019-02-19 19:32:00 +01:00
Ozzieisaacs 2505f0c1e4 Merge remote-tracking branch 'filename/patch-1'
Fixes for Google Drive
2019-02-18 20:52:58 +01:00
Fabian Wüthrich 7fb9bf8889 Truncate single quote in upload file name 2019-02-18 13:35:30 +01:00
Ozzieisaacs 237502ae6b Output of errormessage for invalid CALIBRE_PORT value 2019-02-16 10:18:32 +01:00
Ozzieisaacs 56826f67fc progressbar during upload thanks to jim3ma 2019-02-16 10:05:51 +01:00
Ozzieisaacs 0fa45f5800 Merge remote-tracking branch 'github/master'
Improvement for #814
2019-02-16 08:03:42 +01:00
Ozzie Isaacs 3593f904dc Fixed spelling error readme 2019-02-13 19:55:56 +01:00
lorek123 7f3e98b477 Merge branch 'master' of github.com:lorek123/calibre-web 2019-02-10 18:30:22 +01:00
lorek123 83a06fa6c9 Merge branch 'master' of github.com:lorek123/calibre-web 2019-02-10 18:18:13 +01:00
lorek123 d508b909d1 update js and css libs, especially to fix xss in Jquery and Bootstrap 2019-02-10 18:17:33 +01:00
Lorek 6842a8ac91 update js and css libs, especially to fix xss in Jquery and Bootstrap 2019-02-10 18:04:15 +01:00
Ozzie Isaacs 47b5a5652b Update converter section 2019-02-10 07:54:57 +01:00
Ozzieisaacs d58d792f56 Catch Imagemagick policy error 2019-02-09 12:58:43 +01:00
Ozzieisaacs 6fc4b318e8 cover folders beautified 2019-02-03 20:29:16 +01:00
Ozzieisaacs 6661be91ff Update readme 2019-02-03 17:29:27 +01:00
Ozzieisaacs ea98323c06 Fixes Google Drive
Improved user guidance for Google Drive
Improved Errorhandling for Google Drive
Update french translation
Added ukranian translation
Improvements on updater: calibre-web.log1, calibre-web.log2, gdrive_credentials, settings.yaml, gdrive.db, .git are no longer deleted upon update
2019-02-03 16:38:27 +01:00
326 changed files with 123857 additions and 75032 deletions
+3
View File
@@ -0,0 +1,3 @@
include cps/static/*
include cps/templates/*
include cps/translations/*
+69 -7
View File
@@ -35,6 +35,7 @@ logger = logging.getLogger("book_formats")
try:
from wand.image import Image
from wand import version as ImageVersion
from wand.exceptions import PolicyError
use_generic_pdf_cover = False
except (ImportError, RuntimeError) as e:
logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
@@ -61,6 +62,13 @@ except ImportError as e:
logger.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e)
use_fb2_meta = False
try:
from PIL import Image
from PIL import __version__ as PILversion
use_PIL = True
except ImportError:
use_PIL = False
def process(tmp_file_path, original_file_name, original_file_extension):
meta = None
@@ -100,7 +108,7 @@ def default_meta(tmp_file_path, original_file_name, original_file_extension):
def pdf_meta(tmp_file_path, original_file_name, original_file_extension):
if use_pdf_meta:
pdf = PdfFileReader(open(tmp_file_path, 'rb'))
pdf = PdfFileReader(open(tmp_file_path, 'rb'), strict=False)
doc_info = pdf.getDocumentInfo()
else:
doc_info = None
@@ -130,12 +138,58 @@ def pdf_preview(tmp_file_path, tmp_dir):
if use_generic_pdf_cover:
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:
img.compression_quality = 88
img.save(filename=os.path.join(tmp_dir, cover_file_name))
return cover_file_name
if use_PIL:
try:
input1 = PdfFileReader(open(tmp_file_path, 'rb'), strict=False)
page0 = input1.getPage(0)
xObject = page0['/Resources']['/XObject'].getObject()
for obj in xObject:
if xObject[obj]['/Subtype'] == '/Image':
size = (xObject[obj]['/Width'], xObject[obj]['/Height'])
data = xObject[obj]._data # xObject[obj].getData()
if xObject[obj]['/ColorSpace'] == '/DeviceRGB':
mode = "RGB"
else:
mode = "P"
if '/Filter' in xObject[obj]:
if xObject[obj]['/Filter'] == '/FlateDecode':
img = Image.frombytes(mode, size, data)
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.png"
img.save(filename=os.path.join(tmp_dir, cover_file_name))
return cover_file_name
# img.save(obj[1:] + ".png")
elif xObject[obj]['/Filter'] == '/DCTDecode':
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jpg"
img = open(cover_file_name, "wb")
img.write(data)
img.close()
return cover_file_name
elif xObject[obj]['/Filter'] == '/JPXDecode':
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jp2"
img = open(cover_file_name, "wb")
img.write(data)
img.close()
return cover_file_name
else:
img = Image.frombytes(mode, size, data)
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.png"
img.save(filename=os.path.join(tmp_dir, cover_file_name))
return cover_file_name
except Exception as ex:
print(ex)
try:
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jpg"
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
except PolicyError as ex:
logger.warning('Pdf extraction forbidden by Imagemagick policy: %s', ex)
return None
except Exception as ex:
logger.warning('Cannot extract cover image, using default: %s', ex)
return None
def get_versions():
if not use_generic_pdf_cover:
@@ -152,4 +206,12 @@ def get_versions():
XVersion = 'v'+'.'.join(map(str, lxmlversion))
else:
XVersion = _(u'not installed')
return {'Image Magick': IVersion, 'PyPdf': PVersion, 'lxml':XVersion, 'Wand Version': WVersion}
if use_PIL:
PILVersion = 'v' + PILversion
else:
PILVersion = _(u'not installed')
return {'Image Magick': IVersion,
'PyPdf': PVersion,
'lxml':XVersion,
'Wand': WVersion,
'Pillow': PILVersion}
Regular → Executable
+82 -19
View File
@@ -17,41 +17,104 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import zipfile
import tarfile
import os
import uploader
import logging
from iso639 import languages as isoLanguages
logger = logging.getLogger("book_formats")
try:
from comicapi.comicarchive import ComicArchive, MetaDataStyle
use_comic_meta = True
except ImportError as e:
logger.warning('cannot import comicapi, extracting comic metadata will not work: %s', e)
import zipfile
import tarfile
use_comic_meta = False
def extractCover(tmp_file_name, original_file_extension):
if original_file_extension.upper() == '.CBZ':
cf = zipfile.ZipFile(tmp_file_name)
compressed_name = cf.namelist()[0]
cover_data = cf.read(compressed_name)
elif original_file_extension.upper() == '.CBT':
cf = tarfile.TarFile(tmp_file_name)
compressed_name = cf.getnames()[0]
cover_data = cf.extractfile(compressed_name).read()
if use_comic_meta:
archive = ComicArchive(tmp_file_name)
cover_data = None
ext = os.path.splitext(archive.getPageName(0))
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg' or extension == '.jpeg':
cover_data = archive.getPage(0)
else:
if original_file_extension.upper() == '.CBZ':
cf = zipfile.ZipFile(tmp_file_name)
for name in cf.namelist():
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg':
cover_data = cf.read(name)
break
elif original_file_extension.upper() == '.CBT':
cf = tarfile.TarFile(tmp_file_name)
for name in cf.getnames():
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg':
cover_data = cf.extractfile(name).read()
break
prefix = os.path.dirname(tmp_file_name)
tmp_cover_name = prefix + '/cover' + os.path.splitext(compressed_name)[1]
image = open(tmp_cover_name, 'wb')
image.write(cover_data)
image.close()
if cover_data:
tmp_cover_name = prefix + '/cover' + extension
image = open(tmp_cover_name, 'wb')
image.write(cover_data)
image.close()
else:
tmp_cover_name = None
return tmp_cover_name
def get_comic_info(tmp_file_path, original_file_name, original_file_extension):
if use_comic_meta:
archive = ComicArchive(tmp_file_path)
if archive.seemsToBeAComicArchive():
if archive.hasMetadata(MetaDataStyle.CIX):
style = MetaDataStyle.CIX
elif archive.hasMetadata(MetaDataStyle.CBI):
style = MetaDataStyle.CBI
else:
style = None
coverfile = extractCover(tmp_file_path, original_file_extension)
if style is not None:
loadedMetadata = archive.readMetadata(style)
return uploader.BookMeta(
lang = loadedMetadata.language
if len(lang) == 2:
loadedMetadata.language = isoLanguages.get(part1=lang).name
elif len(lang) == 3:
loadedMetadata.language = isoLanguages.get(part3=lang).name
else:
loadedMetadata.language = ""
return uploader.BookMeta(
file_path=tmp_file_path,
extension=original_file_extension,
title=loadedMetadata.title or original_file_name,
author=" & ".join([credit["person"] for credit in loadedMetadata.credits if credit["role"] == "Writer"]) or u"Unknown",
cover=extractCover(tmp_file_path, original_file_extension),
description=loadedMetadata.comments or "",
tags="",
series=loadedMetadata.series or "",
series_id=loadedMetadata.issue or "",
languages=loadedMetadata.language)
else:
return uploader.BookMeta(
file_path=tmp_file_path,
extension=original_file_extension,
title=original_file_name,
author=u"Unknown",
cover=coverfile,
cover=extractCover(tmp_file_path, original_file_extension),
description="",
tags="",
series="",
+8 -3
View File
@@ -27,6 +27,7 @@ import ast
from ub import config
import ub
import sys
import unidecode
session = None
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
@@ -46,7 +47,7 @@ def title_sort(title):
def lcase(s):
return s.lower()
return unidecode.unidecode(s.lower())
def ucase(s):
@@ -112,6 +113,8 @@ class Identifiers(Base):
return u"Google Books"
elif self.type == "kobo":
return u"Kobo"
if self.type == "lubimyczytac":
return u"Lubimyczytac"
else:
return self.type
@@ -130,6 +133,8 @@ class Identifiers(Base):
return u"https://books.google.com/books?id={0}".format(self.val)
elif self.type == "kobo":
return u"https://www.kobo.com/ebook/{0}".format(self.val)
elif self.type == "lubimyczytac":
return u" http://lubimyczytac.pl/ksiazka/{0}".format(self.val)
elif self.type == "url":
return u"{0}".format(self.val)
else:
@@ -355,8 +360,8 @@ def setup_db():
ub.session.commit()
config.loadSettings()
conn.connection.create_function('title_sort', 1, title_sort)
conn.connection.create_function('lower', 1, lcase)
conn.connection.create_function('upper', 1, ucase)
# conn.connection.create_function('lower', 1, lcase)
# conn.connection.create_function('upper', 1, ucase)
if not cc_classes:
cc = conn.execute("SELECT id, datatype FROM custom_columns")
+1 -1
View File
@@ -21,7 +21,7 @@ import zipfile
from lxml import etree
import os
import uploader
from iso639 import languages as isoLanguages
import isoLanguages
def extractCover(zipFile, coverFile, coverpath, tmp_file_name):
+28 -32
View File
@@ -20,7 +20,7 @@
try:
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from pydrive.auth import RefreshError
from pydrive.auth import RefreshError, InvalidConfigError
from apiclient import errors
gdrive_support = True
except ImportError:
@@ -31,12 +31,9 @@ from ub import config
import cli
import shutil
from flask import Response, stream_with_context
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *
import web
class Singleton:
@@ -166,7 +163,10 @@ def getDrive(drive=None, gauth=None):
# Save the current credentials to a file
return GoogleDrive(gauth)
if drive.auth.access_token_expired:
drive.auth.Refresh()
try:
drive.auth.Refresh()
except RefreshError as e:
web.app.logger.error("Google Drive error: " + e.message)
return drive
def listRootFolders():
@@ -184,7 +184,7 @@ def getFolderInFolder(parentId, folderName, drive):
# drive = getDrive(drive)
query=""
if folderName:
query = "title = '%s' and " % folderName.replace("'", "\\'")
query = "title = '%s' and " % folderName.replace("'", r"\'")
folder = query + "'%s' in parents and mimeType = 'application/vnd.google-apps.folder'" \
" and trashed = false" % parentId
fileList = drive.ListFile({'q': folder}).GetList()
@@ -211,7 +211,7 @@ def getEbooksFolderId(drive=None):
def getFile(pathId, fileName, drive):
metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", "\\'"))
metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", r"\'"))
fileList = drive.ListFile({'q': metaDataFile}).GetList()
if fileList.__len__() == 0:
return None
@@ -246,7 +246,7 @@ def getFolderId(path, drive):
dbChange = True
currentFolderId = currentFolder['id']
else:
currentFolderId= None
currentFolderId = None
break
if dbChange:
session.commit()
@@ -268,16 +268,9 @@ def getFileFromEbooksFolder(path, fileName):
return None
'''def copyDriveFileRemote(drive, origin_file_id, copy_title):
drive = getDrive(drive)
copied_file = {'title': copy_title}
try:
file_data = drive.auth.service.files().copy(
fileId = origin_file_id, body=copied_file).execute()
return drive.CreateFile({'id': file_data['id']})
except errors.HttpError as error:
print ('An error occurred: %s' % error)
return None'''
def moveGdriveFileRemote(origin_file_id, new_title):
origin_file_id['title']= new_title
origin_file_id.Upload()
# Download metadata.db from gdrive
@@ -289,9 +282,10 @@ def downloadFile(path, filename, output):
def moveGdriveFolderRemote(origin_file, target_folder):
drive = getDrive(Gdrive.Instance().drive)
previous_parents = ",".join([parent["id"] for parent in origin_file.get('parents')])
children = drive.auth.service.children().list(folderId=previous_parents).execute()
gFileTargetDir = getFileFromEbooksFolder(None, target_folder)
if not gFileTargetDir:
# Folder is not exisiting, create, and move folder
# Folder is not existing, create, and move folder
gFileTargetDir = drive.CreateFile(
{'title': target_folder, 'parents': [{"kind": "drive#fileLink", 'id': getEbooksFolderId()}],
"mimeType": "application/vnd.google-apps.folder"})
@@ -301,13 +295,10 @@ def moveGdriveFolderRemote(origin_file, target_folder):
addParents=gFileTargetDir['id'],
removeParents=previous_parents,
fields='id, parents').execute()
# if previous_parents has no childs anymore, delete originfileparent
# is not working correctly, because of slow update on gdrive -> could cause trouble in gdrive.db
# (nonexisting folder has id)
# children = drive.auth.service.children().list(folderId=previous_parents).execute()
# if not len(children['items']):
# drive.auth.service.files().delete(fileId=previous_parents).execute()
# if previous_parents has no childs anymore, delete original fileparent
if len(children['items']) == 1:
deleteDatabaseEntry(previous_parents)
drive.auth.service.files().delete(fileId=previous_parents).execute()
def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
@@ -320,7 +311,7 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
parent = getEbooksFolder(drive)
if os.path.isdir(os.path.join(prevDir,uploadFile)):
existingFolder = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
(os.path.basename(uploadFile), parent['id'])}).GetList()
(os.path.basename(uploadFile).replace("'", r"\'"), parent['id'])}).GetList()
if len(existingFolder) == 0 and (not isInitial or createRoot):
parent = drive.CreateFile({'title': os.path.basename(uploadFile),
'parents': [{"kind": "drive#fileLink", 'id': parent['id']}],
@@ -335,11 +326,11 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
else:
if os.path.basename(uploadFile) not in ignoreFiles:
existingFiles = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
(os.path.basename(uploadFile), parent['id'])}).GetList()
(os.path.basename(uploadFile).replace("'", r"\'"), parent['id'])}).GetList()
if len(existingFiles) > 0:
driveFile = existingFiles[0]
else:
driveFile = drive.CreateFile({'title': os.path.basename(uploadFile),
driveFile = drive.CreateFile({'title': os.path.basename(uploadFile).replace("'", r"\'"),
'parents': [{"kind":"drive#fileLink", 'id': parent['id']}], })
driveFile.SetContentFile(os.path.join(prevDir, uploadFile))
driveFile.Upload()
@@ -352,7 +343,7 @@ def uploadFileToEbooksFolder(destFile, f):
for i, x in enumerate(splitDir):
if i == len(splitDir)-1:
existingFiles = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
(x, parent['id'])}).GetList()
(x.replace("'", r"\'"), parent['id'])}).GetList()
if len(existingFiles) > 0:
driveFile = existingFiles[0]
else:
@@ -361,7 +352,7 @@ def uploadFileToEbooksFolder(destFile, f):
driveFile.Upload()
else:
existingFolder = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
(x, parent['id'])}).GetList()
(x.replace("'", r"\'"), parent['id'])}).GetList()
if len(existingFolder) == 0:
parent = drive.CreateFile({'title': x, 'parents': [{"kind": "drive#fileLink", 'id': parent['id']}],
"mimeType": "application/vnd.google-apps.folder"})
@@ -454,6 +445,10 @@ def getChangeById (drive, change_id):
except (errors.HttpError) as error:
web.app.logger.info(error.message)
return None
except Exception as e:
web.app.logger.info(e)
return None
# Deletes the local hashes database to force search for new folder names
def deleteDatabaseOnChange():
@@ -468,9 +463,10 @@ def updateGdriveCalibreFromLocal():
# update gdrive.db on edit of books title
def updateDatabaseOnEdit(ID,newPath):
sqlCheckPath = newPath if newPath[-1] == '/' else newPath + u'/'
storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first()
if storedPathName:
storedPathName.path = newPath
storedPathName.path = sqlCheckPath
session.commit()
+132 -57
View File
@@ -22,30 +22,27 @@
import db
import ub
from flask import current_app as app
# import logging
from tempfile import gettempdir
import sys
import io
import os
import re
import unicodedata
# from io import BytesIO
import worker
import time
from flask import send_from_directory, make_response, redirect, abort
from flask_babel import gettext as _
from flask_login import current_user
from babel.dates import format_datetime
from datetime import datetime
# import threading
from babel.units import format_unit
from datetime import datetime, timedelta
import shutil
import requests
# import zipfile
try:
import gdriveutils as gd
except ImportError:
pass
import web
# import server
import random
import subprocess
@@ -55,6 +52,12 @@ try:
except ImportError:
use_unidecode = False
try:
from PIL import Image
use_PIL = True
except ImportError:
use_PIL = False
# Global variables
# updater_thread = None
global_WorkerThread = worker.WorkerThread()
@@ -128,7 +131,7 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
text += "Sincerely\r\n\r\n"
text += "Your Calibre-Web team"
global_WorkerThread.add_email(_(u'Get Started with Calibre-Web'),None, None, ub.get_mail_settings(),
e_mail, user_name, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
e_mail, None, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
return
def check_send_to_kindle(entry):
@@ -146,8 +149,8 @@ def check_send_to_kindle(entry):
bookformats.append({'format':'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
if 'AZW' in ele.format:
bookformats.append({'format':'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
if 'AZW3' in ele.format:
bookformats.append({'format':'Azw3','convert':0,'text':_('Send %(format)s to Kindle',format='Azw3')})
'''if 'AZW3' in ele.format:
bookformats.append({'format':'Azw3','convert':0,'text':_('Send %(format)s to Kindle',format='Azw3')})'''
else:
formats = list()
for ele in iter(entry.data):
@@ -156,18 +159,16 @@ def check_send_to_kindle(entry):
bookformats.append({'format': 'Mobi','convert':0,'text':_('Send %(format)s to Kindle',format='Mobi')})
if 'AZW' in formats:
bookformats.append({'format': 'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
if 'AZW3' in formats:
bookformats.append({'format': 'Azw3','convert':0,'text':_('Send %(format)s to Kindle',format='Azw3')})
if 'PDF' in formats:
bookformats.append({'format': 'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
if ub.config.config_ebookconverter >= 1:
if 'EPUB' in formats and not 'MOBI' in formats:
bookformats.append({'format': 'Mobi','convert':1,
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Mobi')})
if ub.config.config_ebookconverter == 2:
'''if ub.config.config_ebookconverter == 2:
if 'EPUB' in formats and not 'AZW3' in formats:
bookformats.append({'format': 'Azw3','convert':1,
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Azw3')})
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Azw3')})'''
return bookformats
else:
app.logger.error(u'Cannot find book entry %d', entry.id)
@@ -177,7 +178,7 @@ def check_send_to_kindle(entry):
# Check if a reader is existing for any of the book formats, if not, return empty list, otherwise return
# list with supported formats
def check_read_formats(entry):
EXTENSIONS_READER = {'TXT', 'PDF', 'EPUB', 'ZIP', 'CBZ', 'TAR', 'CBT', 'RAR', 'CBR'}
EXTENSIONS_READER = {'TXT', 'PDF', 'EPUB', 'CBZ', 'CBT', 'CBR'}
bookformats = list()
if len(entry.data):
for ele in iter(entry.data):
@@ -235,7 +236,10 @@ def get_valid_filename(value, replace_whitespace=True):
value = value[:128]
if not value:
raise ValueError("Filename cannot be empty")
return value
if sys.version_info.major == 3:
return value
else:
return value.decode('utf-8')
def get_sorted_author(value):
@@ -324,11 +328,11 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
# Rename all files from old names to new names
if authordir != new_authordir or titledir != new_titledir:
try:
new_name = get_valid_filename(localbook.title) + ' - ' + get_valid_filename(new_authordir)
path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path))
for file_format in localbook.data:
path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path))
new_name = get_valid_filename(localbook.title) + ' - ' + get_valid_filename(new_authordir)
os.renames(os.path.join(path_name, file_format.name + '.' + file_format.format.lower()),
os.path.join(path_name,new_name + '.' + file_format.format.lower()))
os.path.join(path_name, new_name + '.' + file_format.format.lower()))
file_format.name = new_name
except OSError as ex:
web.app.logger.error("Rename file in path " + path + " to " + new_name + ": " + str(ex))
@@ -341,6 +345,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
def update_dir_structure_gdrive(book_id, first_author):
error = False
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
path = book.path
authordir = book.path.split('/')[0]
if first_author:
@@ -348,40 +353,39 @@ def update_dir_structure_gdrive(book_id, first_author):
else:
new_authordir = get_valid_filename(book.authors[0].name)
titledir = book.path.split('/')[1]
new_titledir = get_valid_filename(book.title) + " (" + str(book_id) + ")"
new_titledir = get_valid_filename(book.title) + u" (" + str(book_id) + u")"
if titledir != new_titledir:
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
if gFile:
gFile['title'] = new_titledir
gFile.Upload()
book.path = book.path.split('/')[0] + '/' + new_titledir
book.path = book.path.split('/')[0] + u'/' + new_titledir
path = book.path
gd.updateDatabaseOnEdit(gFile['id'], book.path) # only child folder affected
else:
error = _(u'File %(file)s not found on Google Drive', file=book.path) # file not found
if authordir != new_authordir:
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), new_titledir)
if gFile:
gd.moveGdriveFolderRemote(gFile,new_authordir)
book.path = new_authordir + '/' + book.path.split('/')[1]
gd.moveGdriveFolderRemote(gFile, new_authordir)
book.path = new_authordir + u'/' + book.path.split('/')[1]
path = book.path
gd.updateDatabaseOnEdit(gFile['id'], book.path)
else:
error = _(u'File %(file)s not found on Google Drive', file=authordir) # file not found
# Rename all files from old names to new names
# ToDo: Rename also all bookfiles with new author name and new title name
'''
if authordir != new_authordir or titledir != new_titledir:
for format in book.data:
# path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path))
new_name = get_valid_filename(book.title) + ' - ' + get_valid_filename(book)
format.name = new_name
if gFile:
pass
else:
error = _(u'File %(file)s not found on Google Drive', file=format.name) # file not found
break'''
new_name = get_valid_filename(book.title) + u' - ' + get_valid_filename(new_authordir)
for file_format in book.data:
gFile = gd.getFileFromEbooksFolder(path, file_format.name + u'.' + file_format.format.lower())
if not gFile:
error = _(u'File %(file)s not found on Google Drive', file=file_format.name) # file not found
break
gd.moveGdriveFileRemote(gFile, new_name + u'.' + file_format.format.lower())
file_format.name = new_name
return error
@@ -427,6 +431,8 @@ def delete_book(book, calibrepath, book_format):
def get_book_cover(cover_path):
if ub.config.config_use_google_drive:
try:
if not web.is_gdrive_ready():
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
path=gd.get_cover_via_gdrive(cover_path)
if path:
return redirect(path)
@@ -434,7 +440,7 @@ def get_book_cover(cover_path):
web.app.logger.error(cover_path + '/cover.jpg not found on Google Drive')
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
except Exception as e:
web.app.logger.error("Error Message: "+e.message)
web.app.logger.error("Error Message: " + e.message)
web.app.logger.exception(e)
# traceback.print_exc()
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
@@ -442,27 +448,71 @@ def get_book_cover(cover_path):
return send_from_directory(os.path.join(ub.config.config_calibre_dir, cover_path), "cover.jpg")
# saves book cover to gdrive or locally
def save_cover(url, book_path):
# saves book cover from url
def save_cover_from_url(url, book_path):
img = requests.get(url)
if img.headers.get('content-type') != 'image/jpeg':
web.app.logger.error("Cover is no jpg file, can't save")
return False
return save_cover(img, book_path)
def save_cover_from_filestorage(filepath, saved_filename, img):
if hasattr(img,'_content'):
f = open(os.path.join(filepath, saved_filename), "wb")
f.write(img._content)
f.close()
else:
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
if not os.path.exists(filepath):
try:
os.makedirs(filepath)
except OSError:
web.app.logger.error(u"Failed to create path for cover")
return False
try:
img.save(os.path.join(filepath, saved_filename))
except OSError:
web.app.logger.error(u"Failed to store cover-file")
return False
except IOError:
web.app.logger.error(u"Cover-file is not a valid image file")
return False
return True
# saves book cover to gdrive or locally
def save_cover(img, book_path):
content_type = img.headers.get('content-type')
if use_PIL:
if content_type not in ('image/jpeg', 'image/png', 'image/webp'):
web.app.logger.error("Only jpg/jpeg/png/webp files are supported as coverfile")
return False
# convert to jpg because calibre only supports jpg
if content_type in ('image/png', 'image/webp'):
if hasattr(img,'stream'):
imgc = Image.open(img.stream)
else:
imgc = Image.open(io.BytesIO(img.content))
im = imgc.convert('RGB')
tmp_bytesio = io.BytesIO()
im.save(tmp_bytesio, format='JPEG')
img._content = tmp_bytesio.getvalue()
else:
if content_type not in ('image/jpeg'):
web.app.logger.error("Only jpg/jpeg files are supported as coverfile")
return False
if ub.config.config_use_google_drive:
tmpDir = gettempdir()
f = open(os.path.join(tmpDir, "uploaded_cover.jpg"), "wb")
f.write(img.content)
f.close()
gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
web.app.logger.info("Cover is saved on Google Drive")
return True
if save_cover_from_filestorage(tmpDir, "uploaded_cover.jpg", img) is True:
gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'),
os.path.join(tmpDir, "uploaded_cover.jpg"))
web.app.logger.info("Cover is saved on Google Drive")
return True
else:
return False
else:
return save_cover_from_filestorage(os.path.join(ub.config.config_calibre_dir, book_path), "cover.jpg", img)
f = open(os.path.join(ub.config.config_calibre_dir, book_path, "cover.jpg"), "wb")
f.write(img.content)
f.close()
web.app.logger.info("Cover is saved")
return True
def do_download_file(book, book_format, data, headers):
@@ -487,7 +537,6 @@ def do_download_file(book, book_format, data, headers):
def check_unrar(unrarLocation):
error = False
if os.path.exists(unrarLocation):
@@ -518,23 +567,49 @@ def json_serial(obj):
if isinstance(obj, (datetime)):
return obj.isoformat()
if isinstance(obj, (timedelta)):
return {
'__type__': 'timedelta',
'days': obj.days,
'seconds': obj.seconds,
'microseconds': obj.microseconds,
}
raise TypeError ("Type %s not serializable" % type(obj))
# helper function for displaying the runtime of tasks
def format_runtime(runtime):
retVal = ""
if runtime.days:
retVal = format_unit(runtime.days, 'duration-day', length="long", locale=web.get_locale()) + ', '
mins, seconds = divmod(runtime.seconds, 60)
hours, minutes = divmod(mins, 60)
# ToDo: locale.number_symbols._data['timeSeparator'] -> localize time separator ?
if hours:
retVal += '{:d}:{:02d}:{:02d}s'.format(hours, minutes, seconds)
elif minutes:
retVal += '{:2d}:{:02d}s'.format(minutes, seconds)
else:
retVal += '{:2d}s'.format(seconds)
return retVal
# helper function to apply localize status information in tasklist entries
def render_task_status(tasklist):
#helper function to apply localize status information in tasklist entries
renderedtasklist=list()
# task2 = task
for task in tasklist:
if task['user'] == current_user.nickname or current_user.role_admin():
# task2 = copy.deepcopy(task) # = task
if task['formStarttime']:
task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=web.get_locale())
# task2['formStarttime'] = ""
else:
if 'starttime' not in task:
task['starttime'] = ""
if 'formRuntime' not in task:
task['runtime'] = ""
else:
task['runtime'] = format_runtime(task['formRuntime'])
# localize the task status
if isinstance( task['stat'], int ):
if task['stat'] == worker.STAT_WAITING:
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2019 pwr
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
from iso639 import languages, __version__
get = languages.get
except ImportError:
from pycountry import languages as pyc_languages
try:
import pkg_resources
__version__ = pkg_resources.get_distribution('pycountry').version + ' (PyCountry)'
del pkg_resources
except (ImportError, Exception):
__version__ = "? (PyCountry)"
def _copy_fields(l):
l.part1 = l.alpha_2
l.part3 = l.alpha_3
return l
def get(name=None, part1=None, part3=None):
if (part3 is not None):
return _copy_fields(pyc_languages.get(alpha_3=part3))
if (part1 is not None):
return _copy_fields(pyc_languages.get(alpha_2=part1))
if (name is not None):
return _copy_fields(pyc_languages.get(name=name))
+33 -13
View File
@@ -1,21 +1,41 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2018 cervinko, janeczku, OzzieIsaacs
# Flask License
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Copyright © 2010 by the Pallets team, cervinko, janeczku, OzzieIsaacs
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# Some rights reserved.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Redistribution and use in source and binary forms of the software as
# well as documentation, with or without modification, are permitted
# provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# Inspired by http://flask.pocoo.org/snippets/35/
class ReverseProxied(object):
"""Wrap the application in this middleware and configure the
@@ -49,7 +69,7 @@ class ReverseProxied(object):
scheme = environ.get('HTTP_X_SCHEME', '')
if scheme:
environ['wsgi.url_scheme'] = scheme
servr = environ.get('HTTP_X_FORWARDED_SERVER', '')
servr = environ.get('HTTP_X_FORWARDED_HOST', '')
if servr:
environ['HTTP_HOST'] = servr
return self.app(environ, start_response)
+5 -4
View File
@@ -103,6 +103,7 @@ class server:
ssl_options=ssl)
http_server.listen(web.ub.config.config_port)
self.wsgiserver=IOLoop.instance()
web.py3_gevent_link = self.wsgiserver
self.wsgiserver.start()
# wait for stop signal
self.wsgiserver.close(True)
@@ -142,10 +143,10 @@ class server:
# ToDo: Somehow caused by circular import under python3 refactor
if sys.version_info > (3, 0):
if not self.wsgiserver:
if gevent_present:
self.wsgiserver = web.py3_gevent_link
else:
self.wsgiserver = IOLoop.instance()
# if gevent_present:
self.wsgiserver = web.py3_gevent_link
#else:
# self.wsgiserver = IOLoop.instance()
if self.wsgiserver:
if gevent_present:
self.wsgiserver.close()
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -152,11 +152,11 @@ body {
max-width: 70%;
}
#prev {
#left {
left: 40px;
}
#next {
#right {
right: 40px;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 415 B

Before

Width:  |  Height:  |  Size: 883 B

After

Width:  |  Height:  |  Size: 883 B

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 408 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 426 B

After

Width:  |  Height:  |  Size: 426 B

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 158 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 199 B

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 304 B

Before

Width:  |  Height:  |  Size: 193 B

After

Width:  |  Height:  |  Size: 193 B

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 296 B

Before

Width:  |  Height:  |  Size: 193 B

After

Width:  |  Height:  |  Size: 193 B

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 296 B

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 199 B

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 304 B

Before

Width:  |  Height:  |  Size: 326 B

After

Width:  |  Height:  |  Size: 326 B

Before

Width:  |  Height:  |  Size: 326 B

After

Width:  |  Height:  |  Size: 326 B

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 403 B

Before

Width:  |  Height:  |  Size: 933 B

After

Width:  |  Height:  |  Size: 933 B

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 179 B

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 266 B

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 301 B

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 583 B

Before

Width:  |  Height:  |  Size: 175 B

After

Width:  |  Height:  |  Size: 175 B

Before

Width:  |  Height:  |  Size: 276 B

After

Width:  |  Height:  |  Size: 276 B

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 731 B

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 359 B

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Before

Width:  |  Height:  |  Size: 290 B

After

Width:  |  Height:  |  Size: 290 B

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 174 B

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 260 B

Before

Width:  |  Height:  |  Size: 259 B

After

Width:  |  Height:  |  Size: 259 B

Before

Width:  |  Height:  |  Size: 425 B

After

Width:  |  Height:  |  Size: 425 B

Before

Width:  |  Height:  |  Size: 107 B

After

Width:  |  Height:  |  Size: 107 B

Before

Width:  |  Height:  |  Size: 152 B

After

Width:  |  Height:  |  Size: 152 B

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 295 B

Before

Width:  |  Height:  |  Size: 550 B

After

Width:  |  Height:  |  Size: 550 B

Before

Width:  |  Height:  |  Size: 242 B

After

Width:  |  Height:  |  Size: 242 B

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 398 B

Before

Width:  |  Height:  |  Size: 238 B

After

Width:  |  Height:  |  Size: 238 B

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 396 B

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 245 B

Before

Width:  |  Height:  |  Size: 405 B

After

Width:  |  Height:  |  Size: 405 B

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 246 B

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 403 B

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 321 B

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 586 B

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 257 B

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 464 B

Before

Width:  |  Height:  |  Size: 309 B

After

Width:  |  Height:  |  Size: 309 B

Before

Width:  |  Height:  |  Size: 653 B

After

Width:  |  Height:  |  Size: 653 B

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 246 B

Before

Width:  |  Height:  |  Size: 456 B

After

Width:  |  Height:  |  Size: 456 B

Before

Width:  |  Height:  |  Size: 243 B

After

Width:  |  Height:  |  Size: 243 B

Before

Width:  |  Height:  |  Size: 458 B

After

Width:  |  Height:  |  Size: 458 B

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 225 B

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 225 B

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 331 B

Before

Width:  |  Height:  |  Size: 384 B

After

Width:  |  Height:  |  Size: 384 B

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 859 B

Before

Width:  |  Height:  |  Size: 177 B

After

Width:  |  Height:  |  Size: 177 B

Before

Width:  |  Height:  |  Size: 394 B

After

Width:  |  Height:  |  Size: 394 B

Some files were not shown because too many files have changed in this diff Show More