@ -137,12 +137,14 @@ class Singleton:
@Singleton
class Gauth :
def __init__ ( self ) :
self . auth = GoogleAuth ( settings_file = ' settings.yaml ' )
self . auth = GoogleAuth ( settings_file = ' settings.yaml ' )
@Singleton
class Gdrive :
def __init__ ( self ) :
self . drive = gdriveutils . getDrive ( Gauth . Instance ( ) . auth )
self . drive = gdriveutils . getDrive ( Gauth . Instance ( ) . auth )
class ReverseProxied ( object ) :
""" Wrap the application in this middleware and configure the
@ -223,7 +225,7 @@ lm.anonymous_user = ub.Anonymous
app . secret_key = ' A0Zr98j/3yX R~XHH!jmN]LWX/,?RT '
db . setup_db ( )
if config . config_log_level == logging . DEBUG :
if config . config_log_level == logging . DEBUG :
logging . getLogger ( " sqlalchemy.engine " ) . addHandler ( file_handler )
logging . getLogger ( " sqlalchemy.engine " ) . setLevel ( logging . INFO )
logging . getLogger ( " sqlalchemy.pool " ) . addHandler ( file_handler )
@ -235,6 +237,7 @@ if config.config_log_level == logging.DEBUG :
def is_gdrive_ready ( ) :
return os . path . exists ( ' settings.yaml ' ) and os . path . exists ( ' gdrive_credentials ' )
@babel.localeselector
def get_locale ( ) :
# if a user is logged in, use the locale from the user settings
@ -274,22 +277,26 @@ def load_user_from_header(header_val):
return user
return
def check_auth ( username , password ) :
user = ub . session . query ( ub . User ) . filter ( ub . User . nickname == username ) . first ( )
return bool ( user and check_password_hash ( user . password , password ) )
def authenticate ( ) :
return Response (
' Could not verify your access level for that URL. \n '
' You have to login with proper credentials ' , 401 ,
{ ' WWW-Authenticate ' : ' Basic realm= " Login Required " ' } )
def updateGdriveCalibreFromLocal ( ) :
gdriveutils . backupCalibreDbAndOptionalDownload ( Gdrive . Instance ( ) . drive )
gdriveutils . copyToDrive ( Gdrive . Instance ( ) . drive , config . config_calibre_dir , False , True )
for x in os . listdir ( config . config_calibre_dir ) :
if os . path . isdir ( os . path . join ( config . config_calibre_dir , x ) ) :
shutil . rmtree ( os . path . join ( config . config_calibre_dir , x ) )
if os . path . isdir ( os . path . join ( config . config_calibre_dir , x ) ) :
shutil . rmtree ( os . path . join ( config . config_calibre_dir , x ) )
def requires_basic_auth_if_no_ano ( f ) :
@wraps ( f )
@ -383,24 +390,27 @@ 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 [ : 15 ] , " % Y % m %d % H % M % S " )
return format_date ( formatdate , format = ' medium ' , locale = get_locale ( ) )
return format_date ( formatdate , format = ' medium ' , locale = get_locale ( ) )
@app.template_filter ( ' strftime ' )
def timestamptodate ( date , fmt = None ) :
date = datetime . datetime . fromtimestamp (
date = datetime . datetime . fromtimestamp (
int ( date ) / 1000
)
native = date . replace ( tzinfo = None )
if fmt :
time_format = fmt
time_format = fmt
else :
time_format = ' %d % m % Y - % H: % S '
time_format = ' %d % m % Y - % H: % S '
return native . strftime ( time_format )
def admin_required ( f ) :
"""
Checks if current_user . role == 1
@ -484,19 +494,27 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
del_elements = [ ]
for c_elements in db_book_object :
found = False
if db_type == ' custom ' :
type_elements = c_elements . value
else :
type_elements = c_elements . name
for inp_element in input_elements :
if inp_element == c_elements . name :
if inp_element == type_elements :
found = True
break
# if the element was not found in the new list, add it to remove list
if not found :
del_elements . append ( c_elements )
# 2. search for elements that need to be added
# 2. search for elements that need to be added
add_elements = [ ]
for inp_element in input_elements :
found = False
for c_elements in db_book_object :
if inp_element == c_elements . name :
if db_type == ' custom ' :
type_elements = c_elements . value
else :
type_elements = c_elements . name
if inp_element == type_elements :
found = True
break
if not found :
@ -511,6 +529,8 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
if len ( add_elements ) > 0 :
if db_type == ' languages ' :
db_filter = db_object . lang_code
elif db_type == ' custom ' :
db_filter = db_object . value
else :
db_filter = db_object . name
for add_element in add_elements :
@ -520,17 +540,17 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
if new_element is None :
if db_type == ' author ' :
new_element = db_object ( add_element , add_element , " " )
else :
if db_type == ' series ' :
new_element = db_object ( add_element , add_element )
else : # db_type should be tag, or languages
new_element = db_object ( add_element )
elif db_type == ' series ' :
new_element = db_object ( add_element , add_element )
elif db_type == ' custom ' :
new_element = db_object ( value = add_element )
else : # db_type should be tag, or languages
new_element = db_object ( add_element )
db_session . add ( new_element )
new_element = db . session . query ( db_object ) . filter ( db_filter == add_element ) . first ( )
# add element to book
db_book_object . append ( new_element )
def render_title_template ( * args , * * kwargs ) :
return render_template ( instance = config . config_calibre_web_title , * args , * * kwargs )
@ -642,6 +662,7 @@ def feed_best_rated():
response . headers [ " Content-Type " ] = " application/xml "
return response
@app.route ( " /opds/hot " )
@requires_basic_auth_if_no_ano
def feed_hot ( ) :
@ -781,47 +802,49 @@ def partial(total_byte_len, part_size_limit):
s . append ( [ p , last ] )
return s
def do_gdrive_download ( df , headers ) :
total_size = int ( df . metadata . get ( ' fileSize ' ) )
download_url = df . metadata . get ( ' downloadUrl ' )
s = partial ( total_size , 1024 * 1024 ) # I'm downloading BIG files, so 100M chunk size is fine for me
s = partial ( total_size , 1024 * 1024 ) # I'm downloading BIG files, so 100M chunk size is fine for me
def stream ( ) :
for byte in s :
headers = { " Range " : ' bytes= %s - %s ' % ( byte [ 0 ] , byte [ 1 ] ) }
headers = { " Range " : ' bytes= %s - %s ' % ( byte [ 0 ] , byte [ 1 ] ) }
resp , content = df . auth . Get_Http_Object ( ) . request ( download_url , headers = headers )
if resp . status == 206 :
if resp . status == 206 :
yield content
else :
app . logger . info ( ' An error occurred: %s ' % resp )
return
return Response ( stream_with_context ( stream ( ) ) , headers = headers )
@app.route ( " /opds/download/<book_id>/<book_format>/ " )
@requires_basic_auth_if_no_ano
@download_required
def get_opds_download_link ( book_id , book_format ) :
startTime = time . time ( )
startTime = time . time ( )
book_format = book_format . split ( " . " ) [ 0 ]
book = db . session . query ( db . Books ) . filter ( db . Books . id == book_id ) . first ( )
data = db . session . query ( db . Data ) . filter ( db . Data . book == book . id ) . filter ( db . Data . format == book_format . upper ( ) ) . first ( )
app . logger . info ( data . name )
app . logger . info ( data . name )
if current_user . is_authenticated :
helper . update_download ( book_id , int ( current_user . id ) )
file_name = book . title
if len ( book . authors ) > 0 :
file_name = book . authors [ 0 ] . name + ' _ ' + file_name
file_name = helper . get_valid_filename ( file_name )
headers = Headers ( )
headers = Headers ( )
headers [ " Content-Disposition " ] = " attachment; filename*=UTF-8 ' ' %s . %s " % ( quote ( file_name . encode ( ' utf8 ' ) ) , book_format )
app . logger . info ( time . time ( ) - startTime )
startTime = time . time ( )
app . logger . info ( time . time ( ) - startTime )
startTime = time . time ( )
if config . config_use_google_drive :
app . logger . info ( time . time ( ) - startTime )
df = gdriveutils . getFileFromEbooksFolder ( Gdrive . Instance ( ) . drive , book . path , data . name + " . " + book_format )
df = gdriveutils . getFileFromEbooksFolder ( Gdrive . Instance ( ) . drive , book . path , data . name + " . " + book_format )
return do_gdrive_download ( df , headers )
else :
response = make_response ( send_from_directory ( os . path . join ( config . config_calibre_dir , book . path ) , data . name + " . " + book_format ) )
response . headers = headers
response . headers = headers
return response
@ -856,11 +879,12 @@ def get_tags_json():
query = request . args . get ( ' q ' )
# entries = db.session.execute("select name from tags where name like '%" + query + "%'")
entries = db . session . query ( db . Tags ) . filter ( db . Tags . name . like ( " % " + query + " % " ) ) . all ( )
# for x in entries:
# for x in entries:
# alfa = dict(name=x.name)
json_dumps = json . dumps ( [ dict ( name = r . name ) for r in entries ] )
return json_dumps
@app.route ( " /get_update_status " , methods = [ ' GET ' ] )
@login_required_if_no_ano
def get_update_status ( ) :
@ -880,7 +904,8 @@ def get_update_status():
status [ ' status ' ] = False
return json . dumps ( status )
@app.route ( " /get_updater_status " , methods = [ ' GET ' , ' POST ' ] )
@app.route ( " /get_updater_status " , methods = [ ' GET ' , ' POST ' ] )
@login_required
@admin_required
def get_updater_status ( ) :
@ -888,7 +913,7 @@ def get_updater_status():
if request . method == " POST " :
commit = request . form . to_dict ( )
if " start " in commit and commit [ ' start ' ] == ' True ' :
text = {
text = {
" 1 " : _ ( u ' Requesting update package ' ) ,
" 2 " : _ ( u ' Downloading update package ' ) ,
" 3 " : _ ( u ' Unzipping update package ' ) ,
@ -1038,7 +1063,7 @@ def author_list():
@app.route ( " /author/<int:book_id> " , defaults = { ' page ' : 1 } )
@app.route ( " /author/<int:book_id>/<int:page> ' " )
@login_required_if_no_ano
def author ( book_id , page ) :
def author ( book_id , page ) :
entries , random , pagination = fill_indexpage ( page , db . Books , db . Books . authors . any ( db . Authors . id == book_id ) ,
db . Books . timestamp . desc ( ) )
name = db . session . query ( db . Authors ) . filter ( db . Authors . id == book_id ) . first ( ) . name
@ -1069,7 +1094,7 @@ def series_list():
def series ( book_id , page ) :
entries , random , pagination = fill_indexpage ( page , db . Books , db . Books . series . any ( db . Series . id == book_id ) ,
db . Books . series_index )
name = db . session . query ( db . Series ) . filter ( db . Series . id == book_id ) . first ( ) . name
name = db . session . query ( db . Series ) . filter ( db . Series . id == book_id ) . first ( ) . name
if entries :
return render_title_template ( ' index.html ' , random = random , pagination = pagination , entries = entries ,
title = _ ( u " Series: %(serie)s " , serie = name ) )
@ -1143,7 +1168,7 @@ def category(book_id, page):
entries , random , pagination = fill_indexpage ( page , db . Books , db . Books . tags . any ( db . Tags . id == book_id ) ,
db . Books . timestamp . desc ( ) )
name = db . session . query ( db . Tags ) . filter ( db . Tags . id == book_id ) . first ( ) . name
name = db . session . query ( db . Tags ) . filter ( db . Tags . id == book_id ) . first ( ) . name
return render_title_template ( ' index.html ' , random = random , entries = entries , pagination = pagination ,
title = _ ( u " Category: %(name)s " , name = name ) )
@ -1154,17 +1179,18 @@ def toggle_read(book_id):
book = ub . session . query ( ub . ReadBook ) . filter ( ub . and_ ( ub . ReadBook . user_id == int ( current_user . id ) ,
ub . ReadBook . book_id == book_id ) ) . first ( )
if book :
book . is_read = not book . is_read
book . is_read = not book . is_read
else :
readBook = ub . ReadBook ( )
readBook . user_id = int ( current_user . id )
readBook = ub . ReadBook ( )
readBook . user_id = int ( current_user . id )
readBook . book_id = book_id
readBook . is_read = True
book = readBook
readBook . is_read = True
book = readBook
ub . session . merge ( book )
ub . session . commit ( )
return " "
@app.route ( " /book/<int:book_id> " )
@login_required_if_no_ano
def show_book ( book_id ) :
@ -1184,26 +1210,24 @@ def show_book(book_id):
tmpcc = db . session . query ( db . Custom_Columns ) . filter ( db . Custom_Columns . datatype . notin_ ( db . cc_exceptions ) ) . all ( )
if config . config_columns_to_ignore :
cc = [ ]
cc = [ ]
for col in tmpcc :
r = re . compile ( config . config_columns_to_ignore )
r = re . compile ( config . config_columns_to_ignore )
if r . match ( col . label ) :
cc . append ( col )
else :
cc = tmpcc
cc = tmpcc
book_in_shelfs = [ ]
shelfs = ub . session . query ( ub . BookShelf ) . filter ( ub . BookShelf . book_id == book_id ) . all ( )
for entry in shelfs :
book_in_shelfs . append ( entry . shelf )
#return render_title_template('detail.html', entry=entries, cc=cc,
# title=entries.title, books_shelfs=book_in_shelfs)
if not current_user . is_anonymous ( ) :
matching_have_read_book = ub . session . query ( ub . ReadBook ) . filter ( ub . and_ ( ub . ReadBook . user_id == int ( current_user . id ) ,
matching_have_read_book = ub . session . query ( ub . ReadBook ) . filter ( ub . and_ ( ub . ReadBook . user_id == int ( current_user . id ) ,
ub . ReadBook . book_id == book_id ) ) . all ( )
have_read = len ( matching_have_read_book ) > 0 and matching_have_read_book [ 0 ] . is_read
have_read = len ( matching_have_read_book ) > 0 and matching_have_read_book [ 0 ] . is_read
else :
have_read = None
have_read = None
return render_title_template ( ' detail.html ' , entry = entries , cc = cc ,
title = entries . title , books_shelfs = book_in_shelfs , have_read = have_read )
@ -1255,33 +1279,58 @@ def stats():
return render_title_template ( ' stats.html ' , bookcounter = counter , authorcounter = authors , versions = versions ,
categorycounter = categorys , seriecounter = series , title = _ ( u " Statistics " ) )
@app.route ( " /delete/<int:book_id>/ " )
@login_required
def delete_book ( book_id ) :
if current_user . role_delete_books ( ) :
book = db . session . query ( db . Books ) . filter ( db . Books . id == book_id ) . first ( )
if book :
# check if only this book links to:
# author, language, series, tags,
modify_database_object ( [ u ' ' ] , book . authors , db . Authors , db . session , ' author ' )
modify_database_object ( [ u ' ' ] , book . tags , db . Tags , db . session , ' tags ' )
modify_database_object ( [ u ' ' ] , book . series , db . Series , db . session , ' series ' )
modify_database_object ( [ u ' ' ] , book . languages , db . Languages , db . session , ' languages ' )
modify_database_object ( [ u ' ' ] , book . publishers , db . Publishers , db . session , ' series ' )
# custom colums open
ub . session . query ( db . Books ) . filter ( db . Books . id == book_id ) . delete ( )
#return redirect(url_for('index'))
else :
app . logger . info ( ' Book with id " ' + book_id + ' " could not be deleted ' )
# book not found
return redirect ( url_for ( ' index ' ) )
@app.route ( " /gdrive/authenticate " )
@login_required
@admin_required
def authenticate_google_drive ( ) :
authUrl = Gauth . Instance ( ) . auth . GetAuthUrl ( )
authUrl = Gauth . Instance ( ) . auth . GetAuthUrl ( )
return redirect ( authUrl )
@app.route ( " /gdrive/callback " )
def google_drive_callback ( ) :
auth_code = request . args . get ( ' code ' )
credentials = Gauth . Instance ( ) . auth . flow . step2_exchange ( auth_code )
with open ( ' gdrive_credentials ' , ' w ' ) as f :
with open ( ' gdrive_credentials ' , ' w ' ) as f :
f . write ( credentials . to_json ( ) )
return redirect ( url_for ( ' configuration ' ) )
@app.route ( " /gdrive/watch/subscribe " )
@login_required
@admin_required
def watch_gdrive ( ) :
if not config . config_google_drive_watch_changes_response :
address = ' %s gdrive/watch/callback ' % config . config_google_drive_calibre_url_base
notification_id = str ( uuid4 ( ) )
notification_id = str ( uuid4 ( ) )
result = gdriveutils . watchChange ( Gdrive . Instance ( ) . drive , notification_id ,
' web_hook ' , address , gdrive_watch_callback_token , current_milli_time ( ) + 604800 * 1000 )
print ( result )
settings = ub . session . query ( ub . Settings ) . first ( )
settings . config_google_drive_watch_changes_response = json . dumps ( result )
settings . config_google_drive_watch_changes_response = json . dumps ( result )
ub . session . merge ( settings )
ub . session . commit ( )
settings = ub . session . query ( ub . Settings ) . first ( )
@ -1291,49 +1340,51 @@ def watch_gdrive():
return redirect ( url_for ( ' configuration ' ) )
@app.route ( " /gdrive/watch/revoke " )
@login_required
@admin_required
def revoke_watch_gdrive ( ) :
last_watch_response = config . config_google_drive_watch_changes_response
last_watch_response = config . config_google_drive_watch_changes_response
if last_watch_response :
try :
gdriveutils . stopChannel ( Gdrive . Instance ( ) . drive , last_watch_response [ ' id ' ] , last_watch_response [ ' resourceId ' ] )
except HttpError :
pass
settings = ub . session . query ( ub . Settings ) . first ( )
settings . config_google_drive_watch_changes_response = None
settings . config_google_drive_watch_changes_response = None
ub . session . merge ( settings )
ub . session . commit ( )
config . loadSettings ( )
return redirect ( url_for ( ' configuration ' ) )
@app.route ( " /gdrive/watch/callback " , methods = [ ' GET ' , ' POST ' ] )
def on_received_watch_confirmation ( ) :
app . logger . info ( request . headers )
app . logger . info ( request . headers )
if request . headers . get ( ' X-Goog-Channel-Token ' ) == gdrive_watch_callback_token \
and request . headers . get ( ' X-Goog-Resource-State ' ) == ' change ' \
and request . data :
and request . headers . get ( ' X-Goog-Resource-State ' ) == ' change ' \
and request . data :
data = request . data
data = request . data
def updateMetaData ( ) :
app . logger . info ( ' Change received from gdrive ' )
app . logger . info ( data )
app . logger . info ( ' Change received from gdrive ' )
app . logger . info ( data )
try :
j = json . loads ( data )
app . logger . info ( ' Getting change details ' )
response = gdriveutils . getChangeById ( Gdrive . Instance ( ) . drive , j [ ' id ' ] )
app . logger . info ( response )
j = json . loads ( data )
app . logger . info ( ' Getting change details ' )
response = gdriveutils . getChangeById ( Gdrive . Instance ( ) . drive , j [ ' id ' ] )
app . logger . info ( response )
if response :
dbpath = os . path . join ( config . config_calibre_dir , " metadata.db " )
if not response [ ' deleted ' ] and response [ ' file ' ] [ ' title ' ] == ' metadata.db ' and response [ ' file ' ] [ ' md5Checksum ' ] != md5 ( dbpath ) :
tmpDir = tempfile . gettempdir ( )
app . logger . info ( ' Database file updated ' )
copyfile ( dbpath , os . path . join ( tmpDir , " metadata.db_ " + str ( current_milli_time ( ) ) ) )
app . logger . info ( ' Backing up existing and downloading updated metadata.db ' )
tmpDir = tempfile . gettempdir ( )
app . logger . info ( ' Database file updated ' )
copyfile ( dbpath , os . path . join ( tmpDir , " metadata.db_ " + str ( current_milli_time ( ) ) ) )
app . logger . info ( ' Backing up existing and downloading updated metadata.db ' )
gdriveutils . downloadFile ( Gdrive . Instance ( ) . drive , None , " metadata.db " , os . path . join ( tmpDir , " tmp_metadata.db " ) )
app . logger . info ( ' Setting up new DB ' )
app . logger . info ( ' Setting up new DB ' )
os . rename ( os . path . join ( tmpDir , " tmp_metadata.db " ) , dbpath )
db . setup_db ( )
except Exception as e :
@ -1373,6 +1424,7 @@ def shutdown():
return json . dumps ( { } )
abort ( 404 )
@app.route ( " /update " )
@login_required
@admin_required
@ -1473,20 +1525,21 @@ def advanced_search():
def get_cover_via_gdrive ( cover_path ) :
df = gdriveutils . getFileFromEbooksFolder ( Gdrive . Instance ( ) . drive , cover_path , ' cover.jpg ' )
df = gdriveutils . getFileFromEbooksFolder ( Gdrive . Instance ( ) . drive , cover_path , ' cover.jpg ' )
if not gdriveutils . session . query ( gdriveutils . PermissionAdded ) . filter ( gdriveutils . PermissionAdded . gdrive_id == df [ ' id ' ] ) . first ( ) :
df . GetPermissions ( )
df . InsertPermission ( {
' type ' : ' anyone ' ,
' value ' : ' anyone ' ,
' role ' : ' reader ' ,
' withLink ' : True } )
permissionAdded = gdriveutils . PermissionAdded ( )
permissionAdded . gdrive_id = df [ ' id ' ]
' withLink ' : True } )
permissionAdded = gdriveutils . PermissionAdded ( )
permissionAdded . gdrive_id = df [ ' id ' ]
gdriveutils . session . add ( permissionAdded )
gdriveutils . session . commit ( )
return df . metadata . get ( ' webContentLink ' )
@app.route ( " /cover/<path:cover_path> " )
@login_required_if_no_ano
def get_cover ( cover_path ) :
@ -1508,9 +1561,10 @@ def feed_get_cover(book_id):
else :
return send_from_directory ( os . path . join ( config . config_calibre_dir , book . path ) , " cover.jpg " )
def render_read_books ( page , are_read , as_xml = False ) :
readBooks = ub . session . query ( ub . ReadBook ) . filter ( ub . ReadBook . user_id == int ( current_user . id ) ) . filter ( ub . ReadBook . is_read == True ) . all ( )
readBookIds = [ x . book_id for x in readBooks ]
readBooks = ub . session . query ( ub . ReadBook ) . filter ( ub . ReadBook . user_id == int ( current_user . id ) ) . filter ( ub . ReadBook . is_read == True ) . all ( )
readBookIds = [ x . book_id for x in readBooks ]
if are_read :
db_filter = db . Books . id . in_ ( readBookIds )
else :
@ -1524,9 +1578,10 @@ def render_read_books(page, are_read, as_xml=False):
response . headers [ " Content-Type " ] = " application/xml "
return response
else :
name = u ' Read Books ' if are_read else u ' Unread Books '
name = u ' Read Books ' if are_read else u ' Unread Books '
return render_title_template ( ' index.html ' , random = random , entries = entries , pagination = pagination ,
title = _ ( name , name = name ) )
title = _ ( name , name = name ) )
@app.route ( " /opds/readbooks/ " )
@login_required_if_no_ano
@ -1536,12 +1591,14 @@ def feed_read_books():
off = 0
return render_read_books ( int ( off ) / ( int ( config . config_books_per_page ) ) + 1 , True , True )
@app.route ( " /readbooks/ " , defaults = { ' page ' : 1 } )
@app.route ( " /readbooks/<int:page> ' " )
@login_required_if_no_ano
def read_books ( page ) :
return render_read_books ( page , True )
@app.route ( " /opds/unreadbooks/ " )
@login_required_if_no_ano
def feed_unread_books ( ) :
@ -1550,12 +1607,14 @@ def feed_unread_books():
off = 0
return render_read_books ( int ( off ) / ( int ( config . config_books_per_page ) ) + 1 , False , True )
@app.route ( " /unreadbooks/ " , defaults = { ' page ' : 1 } )
@app.route ( " /unreadbooks/<int:page> ' " )
@login_required_if_no_ano
def unread_books ( page ) :
return render_read_books ( page , False )
@app.route ( " /read/<int:book_id>/<book_format> " )
@login_required_if_no_ano
def read_book ( book_id , book_format ) :
@ -1579,7 +1638,7 @@ def read_book(book_id, book_format):
try :
os . makedirs ( newDir )
except OSError as exception :
if not exception . errno == errno . EEXIST :
if not exception . errno == errno . EEXIST :
raise
if fileName :
fd = open ( os . path . join ( newDir , fileName ) , " wb " )
@ -1613,6 +1672,7 @@ def read_book(book_id, book_format):
flash ( _ ( u " Error opening eBook. File does not exist or file is not accessible: " ) , category = " error " )
return redirect ( url_for ( " index " ) )
@app.route ( " /download/<int:book_id>/<book_format> " )
@login_required_if_no_ano
@download_required
@ -1628,28 +1688,30 @@ def get_download_link(book_id, book_format):
if len ( book . authors ) > 0 :
file_name = book . authors [ 0 ] . name + ' _ ' + file_name
file_name = helper . get_valid_filename ( file_name )
headers = Headers ( )
headers = Headers ( )
try :
headers [ " Content-Type " ] = mimetypes . types_map [ ' . ' + book_format ]
except KeyError :
headers [ " Content-Type " ] = " application/octet-stream "
headers [ " Content-Disposition " ] = " attachment; filename*=UTF-8 ' ' %s . %s " % ( quote ( file_name . encode ( ' utf-8 ' ) ) , book_format )
if config . config_use_google_drive :
df = gdriveutils . getFileFromEbooksFolder ( Gdrive . Instance ( ) . drive , book . path , ' %s . %s ' % ( data . name , book_format ) )
df = gdriveutils . getFileFromEbooksFolder ( Gdrive . Instance ( ) . drive , book . path , ' %s . %s ' % ( data . name , book_format ) )
return do_gdrive_download ( df , headers )
else :
response = make_response ( send_from_directory ( os . path . join ( config . config_calibre_dir , book . path ) , data . name + " . " + book_format ) )
response . headers = headers
response . headers = headers
return response
else :
abort ( 404 )
@app.route ( " /download/<int:book_id>/<book_format>/<anyname> " )
@login_required_if_no_ano
@download_required
def get_download_link_ext ( book_id , book_format , anyname ) :
return get_download_link ( book_id , book_format )
@app.route ( ' /register ' , methods = [ ' GET ' , ' POST ' ] )
def register ( ) :
if not config . config_public_reg :
@ -1745,7 +1807,7 @@ def add_to_shelf(shelf_id, book_id):
app . logger . info ( " Sorry you are not allowed to add a book to the the shelf: %s " % shelf . name )
return redirect ( url_for ( ' index ' ) )
maxOrder = ub . session . query ( func . max ( ub . BookShelf . order ) ) . filter ( ub . BookShelf . shelf == shelf_id ) . first ( )
book_in_shelf = ub . session . query ( ub . BookShelf ) . filter ( ub . BookShelf . shelf == shelf_id ,
book_in_shelf = ub . session . query ( ub . BookShelf ) . filter ( ub . BookShelf . shelf == shelf_id ,
ub . BookShelf . book_id == book_id ) . first ( )
if book_in_shelf :
app . logger . info ( " Book is already part of the shelf: %s " % shelf . name )
@ -1761,7 +1823,7 @@ def add_to_shelf(shelf_id, book_id):
flash ( _ ( u " Book has been added to shelf: %(sname)s " , sname = shelf . name ) , category = " success " )
return redirect ( request . environ [ " HTTP_REFERER " ] )
else :
app . logger . info ( " User is not allowed to edit public shelfs " )
app . logger . info ( " User is not allowed to edit public shelfs " )
return redirect ( url_for ( ' index ' ) )
@ -2016,30 +2078,30 @@ def configuration_helper(origin):
if content . config_calibre_dir != to_save [ " config_calibre_dir " ] :
content . config_calibre_dir = to_save [ " config_calibre_dir " ]
db_change = True
# # Google drive setup
create_new_yaml = False
# Google drive setup
create_new_yaml = False
if " config_google_drive_client_id " in to_save :
if content . config_google_drive_client_id != to_save [ " config_google_drive_client_id " ] :
content . config_google_drive_client_id = to_save [ " config_google_drive_client_id " ]
create_new_yaml = True
create_new_yaml = True
if " config_google_drive_client_secret " in to_save :
if content . config_google_drive_client_secret != to_save [ " config_google_drive_client_secret " ] :
content . config_google_drive_client_secret = to_save [ " config_google_drive_client_secret " ]
create_new_yaml = True
create_new_yaml = True
if " config_google_drive_calibre_url_base " in to_save :
if content . config_google_drive_calibre_url_base != to_save [ " config_google_drive_calibre_url_base " ] :
content . config_google_drive_calibre_url_base = to_save [ " config_google_drive_calibre_url_base " ]
create_new_yaml = True
create_new_yaml = True
if ( " config_use_google_drive " in to_save and not content . config_use_google_drive ) or ( " config_use_google_drive " not in to_save and content . config_use_google_drive ) :
content . config_use_google_drive = " config_use_google_drive " in to_save
db_change = True
if not content . config_use_google_drive :
create_new_yaml = False
create_new_yaml = False
if create_new_yaml :
with open ( ' settings.yaml ' , ' w ' ) as f :
with open ( ' gdrive_template.yaml ' , ' r ' ) as t :
f . write ( t . read ( ) % { ' client_id ' : content . config_google_drive_client_id , ' client_secret ' : content . config_google_drive_client_secret ,
" redirect_uri " : content . config_google_drive_calibre_url_base + ' gdrive/callback ' } )
with open ( ' gdrive_template.yaml ' , ' r ' ) as t :
f . write ( t . read ( ) % { ' client_id ' : content . config_google_drive_client_id , ' client_secret ' : content . config_google_drive_client_secret ,
" redirect_uri " : content . config_google_drive_calibre_url_base + ' gdrive/callback ' } )
if " config_google_drive_folder " in to_save :
if content . config_google_drive_folder != to_save [ " config_google_drive_folder " ] :
content . config_google_drive_folder = to_save [ " config_google_drive_folder " ]
@ -2082,6 +2144,8 @@ def configuration_helper(origin):
content . config_default_role = content . config_default_role + ub . ROLE_UPLOAD
if " edit_role " in to_save :
content . config_default_role = content . config_default_role + ub . ROLE_EDIT
if " delete_role " in to_save :
content . config_default_role = content . config_default_role + ub . ROLE_DELETE_BOOKS
if " passwd_role " in to_save :
content . config_default_role = content . config_default_role + ub . ROLE_PASSWD
if " passwd_role " in to_save :
@ -2176,6 +2240,8 @@ def new_user():
if " upload_role " in to_save :
content . role = content . role + ub . ROLE_UPLOAD
if " edit_role " in to_save :
content . role = content . role + ub . ROLE_DELETE_BOOKS
if " delete_role " in to_save :
content . role = content . role + ub . ROLE_EDIT
if " passwd_role " in to_save :
content . role = content . role + ub . ROLE_PASSWD
@ -2190,7 +2256,7 @@ def new_user():
ub . session . rollback ( )
flash ( _ ( u " Found an existing account for this email address or nickname. " ) , category = " error " )
else :
content . role = config . config_default_role
content . role = config . config_default_role
return render_title_template ( " user_edit.html " , new_user = 1 , content = content , translations = translations ,
languages = languages , title = _ ( u " Add new user " ) )
@ -2279,6 +2345,11 @@ def edit_user(user_id):
elif " edit_role " not in to_save and content . role_edit ( ) :
content . role = content . role - ub . ROLE_EDIT
if " delete_role " in to_save and not content . role_delete_books ( ) :
content . role = content . role + ub . ROLE_DELETE_BOOKS
elif " delete_role " not in to_save and content . role_delete_books ( ) :
content . role = content . role - ub . ROLE_DELETE_BOOKS
if " passwd_role " in to_save and not content . role_passwd ( ) :
content . role = content . role + ub . ROLE_PASSWD
elif " passwd_role " not in to_save and content . role_passwd ( ) :
@ -2384,16 +2455,22 @@ def edit_book(book_id):
input_authors = to_save [ " author_name " ] . split ( ' & ' )
input_authors = map ( lambda it : it . strip ( ) , input_authors )
# we have all author names now
author0_before_edit = book . authors [ 0 ] . name
if input_authors == [ ' ' ] :
input_authors = [ _ ( u ' unknown ' ) ] # prevent empty Author
if book . authors :
author0_before_edit = book . authors [ 0 ] . name
else :
author0_before_edit = db . Authors ( _ ( u ' unknown ' ) , ' ' , 0 )
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_sorted_author ( input_authors [ 0 ] )
if book . authors :
if author0_before_edit != book . authors [ 0 ] . name :
edited_books_id . add ( book . id )
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 " ] )
if config . config_use_google_drive :
tmpDir = tempfile . gettempdir ( )
tmpDir = tempfile . gettempdir ( )
f = open ( os . path . join ( tmpDir , " uploaded_cover.jpg " ) , " wb " )
f . write ( img . content )
f . close ( )
@ -2402,7 +2479,7 @@ def edit_book(book_id):
f = open ( os . path . join ( config . config_calibre_dir , book . path , " cover.jpg " ) , " wb " )
f . write ( img . content )
f . close ( )
book . has_cover = 1
book . has_cover = 1
if book . series_index != to_save [ " series_index " ] :
book . series_index = to_save [ " series_index " ]
@ -2510,7 +2587,8 @@ def edit_book(book_id):
else :
input_tags = to_save [ cc_string ] . split ( ' , ' )
input_tags = map ( lambda it : it . strip ( ) , input_tags )
input_tags = [ x for x in input_tags if x != ' ' ]
modify_database_object ( input_tags , getattr ( book , cc_string ) , db . cc_classes [ c . id ] , db . session , ' custom ' )
''' input_tags = [x for x in input_tags if x != ' ' ]
# we have all author names now
# 1. search for tags to remove
del_tags = [ ]
@ -2552,7 +2630,7 @@ def edit_book(book_id):
new_tag = db . session . query ( db . cc_classes [ c . id ] ) . filter (
db . cc_classes [ c . id ] . value == add_tag ) . first ( )
# add tag to book
getattr ( book , cc_string ) . append ( new_tag )
getattr ( book , cc_string ) . append ( new_tag ) '''
db . session . commit ( )
author_names = [ ]
@ -2644,7 +2722,7 @@ def upload():
db_author = db . Authors ( author , helper . get_sorted_author ( author ) , " " )
db . session . add ( db_author )
# add language actually one value in list
# add language actually one value in list
input_language = meta . languages
db_language = None
if input_language != " " :
@ -2656,7 +2734,7 @@ def upload():
db_language = db . Languages ( input_language )
db . session . add ( db_language )
# combine path and normalize path from windows systems
path = os . path . join ( author_dir , title_dir ) . replace ( ' \\ ' , ' / ' )
path = os . path . join ( author_dir , title_dir ) . replace ( ' \\ ' , ' / ' )
db_book = db . Books ( title , " " , db_author . sort , datetime . datetime . now ( ) , datetime . datetime ( 101 , 1 , 1 ) , 1 ,
datetime . datetime . now ( ) , path , has_cover , db_author , [ ] , db_language )
db_book . authors . append ( db_author )
@ -2666,15 +2744,13 @@ def upload():
db_book . data . append ( db_data )
db . session . add ( db_book )
db . session . flush ( ) # flush content get db_book.id avalible
# add comment
db . session . flush ( ) # flush content get db_book.id avalible
# add comment
upload_comment = Markup ( meta . description ) . unescape ( )
db_comment = None
if upload_comment != " " :
db_comment = db . Comments ( upload_comment , db_book . id )
db . session . add ( db_comment )
db . session . add ( db . Comments ( upload_comment , db_book . id ) )
db . session . commit ( )
if db_language is not None : # display Full name instead of iso639.part3
if db_language is not None : # display Full name instead of iso639.part3
db_book . languages [ 0 ] . language_name = _ ( meta . languages )
author_names = [ ]
for author in db_book . authors :
@ -2691,6 +2767,7 @@ def upload():
else :
return redirect ( url_for ( " index " ) )
def start_gevent ( ) :
from gevent . wsgi import WSGIServer
global gevent_server