Added name of book emailed to task list

Implemented emailing of books from gdrive (converting not implemented yet)
pull/570/head
Ozzie Isaacs 6 years ago
parent dbf55d6f41
commit ff1b479188

@ -135,7 +135,7 @@ class EMailThread(threading.Thread):
return self.UIqueue return self.UIqueue
def add_email(self, data, settings, recipient, user_name): def add_email(self, data, settings, recipient, user_name, type):
# if more than 50 entries in the list, clean the list # if more than 50 entries in the list, clean the list
addLock = threading.Lock() addLock = threading.Lock()
addLock.acquire() addLock.acquire()
@ -144,7 +144,7 @@ class EMailThread(threading.Thread):
# progress, runtime, and status = 0 # progress, runtime, and status = 0
self.queue.append({'data':data, 'settings':settings, 'recipent':recipient, 'starttime': 0, self.queue.append({'data':data, 'settings':settings, 'recipent':recipient, 'starttime': 0,
'status': STAT_WAITING}) 'status': STAT_WAITING})
self.UIqueue.append({'user': user_name, 'formStarttime': '', 'progress': " 0 %", 'type': 'E-Mail', self.UIqueue.append({'user': user_name, 'formStarttime': '', 'progress': " 0 %", 'type': type,
'runtime': '0 s', 'status': _('Waiting') }) 'runtime': '0 s', 'status': _('Waiting') })
# access issue # access issue
self.last=len(self.queue) self.last=len(self.queue)

@ -9,6 +9,7 @@ import os
from ub import config from ub import config
import cli import cli
import shutil import shutil
from flask import Response, stream_with_context
from sqlalchemy import * from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
@ -281,18 +282,18 @@ def moveGdriveFolderRemote(origin_file, target_folder):
# drive.auth.service.files().delete(fileId=previous_parents).execute() # drive.auth.service.files().delete(fileId=previous_parents).execute()
def downloadFile(path, filename, output): #def downloadFile(path, filename, output):
f = getFileFromEbooksFolder(path, filename) # f = getFileFromEbooksFolder(path, filename)
f.GetContentFile(output) # return f.GetContentFile(output)
# ToDo: Check purpose Parameter f ??, purpose of function ?
def backupCalibreDbAndOptionalDownload(drive, f=None): def backupCalibreDbAndOptionalDownload(drive):
drive = getDrive(drive) drive = getDrive(drive)
metaDataFile = "'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId() metaDataFile = "'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId()
fileList = drive.ListFile({'q': metaDataFile}).GetList() fileList = drive.ListFile({'q': metaDataFile}).GetList()
databaseFile = fileList[0] #databaseFile = fileList[0]
if f: #if f:
databaseFile.GetContentFile(f) # databaseFile.GetContentFile(f)
def copyToDrive(drive, uploadFile, createRoot, replaceFiles, def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
@ -446,7 +447,7 @@ def deleteDatabaseOnChange():
session.commit() session.commit()
def updateGdriveCalibreFromLocal(): def updateGdriveCalibreFromLocal():
backupCalibreDbAndOptionalDownload(Gdrive.Instance().drive) # backupCalibreDbAndOptionalDownload(Gdrive.Instance().drive)
copyToDrive(Gdrive.Instance().drive, config.config_calibre_dir, False, True) copyToDrive(Gdrive.Instance().drive, config.config_calibre_dir, False, True)
for x in os.listdir(config.config_calibre_dir): for x in os.listdir(config.config_calibre_dir):
if os.path.isdir(os.path.join(config.config_calibre_dir, x)): if os.path.isdir(os.path.join(config.config_calibre_dir, x)):
@ -463,3 +464,47 @@ def updateDatabaseOnEdit(ID,newPath):
def deleteDatabaseEntry(ID): def deleteDatabaseEntry(ID):
session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete() session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete()
session.commit() session.commit()
# Gets cover file from gdrive
def get_cover_via_gdrive(cover_path):
df = getFileFromEbooksFolder(cover_path, 'cover.jpg')
if df:
if not session.query(PermissionAdded).filter(PermissionAdded.gdrive_id == df['id']).first():
df.GetPermissions()
df.InsertPermission({
'type': 'anyone',
'value': 'anyone',
'role': 'reader',
'withLink': True})
permissionAdded = PermissionAdded()
permissionAdded.gdrive_id = df['id']
session.add(permissionAdded)
session.commit()
return df.metadata.get('webContentLink')
else:
return None
# Creates chunks for downloading big files
def partial(total_byte_len, part_size_limit):
s = []
for p in range(0, total_byte_len, part_size_limit):
last = min(total_byte_len - 1, p + part_size_limit - 1)
s.append([p, last])
return s
# downloads files in chunks from gdrive
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
def stream():
for byte in s:
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:
yield content
else:
web.app.logger.info('An error occurred: %s' % resp)
return
return Response(stream_with_context(stream()), headers=headers)

@ -14,6 +14,7 @@ import unicodedata
from io import BytesIO from io import BytesIO
import converter import converter
import asyncmail import asyncmail
import time
try: try:
from StringIO import StringIO from StringIO import StringIO
@ -29,6 +30,7 @@ except ImportError as e:
from email import encoders from email import encoders
from email.utils import formatdate from email.utils import formatdate
from email.utils import make_msgid from email.utils import make_msgid
from flask import send_from_directory, make_response, redirect, abort
from flask_babel import gettext as _ from flask_babel import gettext as _
import threading import threading
import shutil import shutil
@ -86,13 +88,14 @@ def send_test_mail(kindle_mail, user_name):
msg['Subject'] = _(u'Calibre-web test email') msg['Subject'] = _(u'Calibre-web test email')
text = _(u'This email has been sent via calibre web.') text = _(u'This email has been sent via calibre web.')
msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8')) msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8'))
global_eMailThread.add_email(msg,ub.get_mail_settings(),kindle_mail, user_name) global_eMailThread.add_email(msg,ub.get_mail_settings(),kindle_mail, user_name, _('Test E-Mail'))
return # send_raw_email(kindle_mail, msg) return # send_raw_email(kindle_mail, msg)
def send_mail(book_id, kindle_mail, calibrepath, user_id): def send_mail(book_id, kindle_mail, calibrepath, user_id):
"""Send email with attachments""" """Send email with attachments"""
# create MIME message # create MIME message
result= None
msg = MIMEMultipart() msg = MIMEMultipart()
msg['Subject'] = _(u'Send to Kindle') msg['Subject'] = _(u'Send to Kindle')
msg['Message-Id'] = make_msgid('calibre-web') msg['Message-Id'] = make_msgid('calibre-web')
@ -107,48 +110,72 @@ def send_mail(book_id, kindle_mail, calibrepath, user_id):
for entry in data: for entry in data:
if entry.format == "MOBI": if entry.format == "MOBI":
formats["mobi"] = os.path.join(calibrepath, book.path, entry.name + ".mobi") formats["mobi"] = entry.name + ".mobi" # os.path.join(calibrepath, book.path, entry.name + ".mobi")
if entry.format == "EPUB": if entry.format == "EPUB":
formats["epub"] = os.path.join(calibrepath, book.path, entry.name + ".epub") formats["epub"] = entry.name + ".epub" # os.path.join(calibrepath, book.path, entry.name + ".epub")
if entry.format == "PDF": if entry.format == "PDF":
formats["pdf"] = os.path.join(calibrepath, book.path, entry.name + ".pdf") formats["pdf"] = entry.name + ".pdf" # os.path.join(calibrepath, book.path, entry.name + ".pdf")
if len(formats) == 0: if len(formats) == 0:
return _("Could not find any formats suitable for sending by email") return _("Could not find any formats suitable for sending by email")
if 'mobi' in formats: if 'mobi' in formats:
msg.attach(get_attachment(formats['mobi'])) result = get_attachment(calibrepath, book.path, formats['mobi'])
if result:
msg.attach(result)
elif 'epub' in formats: elif 'epub' in formats:
data, resultCode = make_mobi(book.id, calibrepath) data, resultCode = make_mobi(book.id, calibrepath)
if resultCode == RET_SUCCESS: if resultCode == RET_SUCCESS:
msg.attach(get_attachment(data)) result = get_attachment(calibrepath, book.path, data) # toDo check data
if result:
msg.attach(result)
else: else:
app.logger.error = data app.logger.error = data
return data # _("Could not convert epub to mobi") return data # _("Could not convert epub to mobi")
elif 'pdf' in formats: elif 'pdf' in formats:
msg.attach(get_attachment(formats['pdf'])) result = get_attachment(calibrepath, book.path, formats['pdf'])
if result:
msg.attach(result)
else: else:
return _("Could not find any formats suitable for sending by email") return _("Could not find any formats suitable for sending by email")
global_eMailThread.add_email(msg,ub.get_mail_settings(),kindle_mail, user_id) if result:
return None # send_raw_email(kindle_mail, msg) global_eMailThread.add_email(msg,ub.get_mail_settings(),kindle_mail, user_id, _(u"E-Mail: %s" % book.title))
return None # send_raw_email(kindle_mail, msg)
else:
return _('The requested file could not be read. Maybe wrong permissions?')
def get_attachment(file_path): def get_attachment(calibrepath, bookpath, filename):
"""Get file as MIMEBase message""" """Get file as MIMEBase message"""
try: if ub.config.config_use_google_drive:
file_ = open(file_path, 'rb') df = gd.getFileFromEbooksFolder(bookpath, filename)
attachment = MIMEBase('application', 'octet-stream') if df:
attachment.set_payload(file_.read()) # tmpDir = gettempdir()
file_.close() datafile = os.path.join(calibrepath, bookpath, filename)
encoders.encode_base64(attachment) if not os.path.exists(os.path.join(calibrepath, bookpath)):
os.makedirs(os.path.join(calibrepath, bookpath))
attachment.add_header('Content-Disposition', 'attachment', df.GetContentFile(datafile)
filename=os.path.basename(file_path)) file_ = open(datafile, 'rb')
return attachment data = file_.read()
except IOError: file_.close()
traceback.print_exc() os.remove(datafile)
app.logger.error = u'The requested file could not be read. Maybe wrong permissions?' else:
return None return None
else:
try:
file_ = open(os.path.join(calibrepath, bookpath, filename), 'rb')
data = file_.read()
file_.close()
except IOError:
traceback.print_exc()
app.logger.error = u'The requested file could not be read. Maybe wrong permissions?'
return None
attachment = MIMEBase('application', 'octet-stream')
attachment.set_payload(data)
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment',
filename=filename)
return attachment
def get_valid_filename(value, replace_whitespace=True): def get_valid_filename(value, replace_whitespace=True):
@ -309,6 +336,60 @@ def delete_book(book, calibrepath):
return delete_book_gdrive(book) return delete_book_gdrive(book)
else: else:
return delete_book_file(book, calibrepath) return delete_book_file(book, calibrepath)
def get_book_cover(cover_path):
if ub.config.config_use_google_drive:
try:
path=gd.get_cover_via_gdrive(cover_path)
if path:
return redirect(path)
else:
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.exception(e)
# traceback.print_exc()
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
else:
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):
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
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()
uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
web.app.logger.info("Cover is saved on gdrive")
return true
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):
if ub.config.config_use_google_drive:
startTime = time.time()
df = gd.getFileFromEbooksFolder(book.path, data.name + "." + book_format)
web.app.logger.debug(time.time() - startTime)
if df:
return gd.do_gdrive_download(df, headers)
else:
abort(404)
else:
response = make_response(send_from_directory(os.path.join(ub.config.config_calibre_dir, book.path), data.name + "." + book_format))
response.headers = headers
return response
################################## ##################################

@ -27,7 +27,6 @@ except ImportError:
import mimetypes import mimetypes
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import textwrap
from flask import (Flask, render_template, request, Response, redirect, from flask import (Flask, render_template, request, Response, redirect,
url_for, send_from_directory, make_response, g, flash, url_for, send_from_directory, make_response, g, flash,
abort, Markup, stream_with_context) abort, Markup, stream_with_context)
@ -70,7 +69,7 @@ import sys
import re import re
import db import db
from shutil import move, copyfile from shutil import move, copyfile
import shutil # import shutil
import gdriveutils import gdriveutils
import converter import converter
import tempfile import tempfile
@ -842,36 +841,11 @@ def feed_shelf(book_id):
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8" response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response return response
def partial(total_byte_len, part_size_limit):
s = []
for p in range(0, total_byte_len, part_size_limit):
last = min(total_byte_len - 1, p + part_size_limit - 1)
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
def stream():
for byte in s:
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:
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>/") @app.route("/opds/download/<book_id>/<book_format>/")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
@download_required @download_required
def get_opds_download_link(book_id, book_format): def get_opds_download_link(book_id, book_format):
startTime = time.time()
book_format = book_format.split(".")[0] book_format = book_format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() 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() data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper()).first()
@ -888,16 +862,15 @@ def get_opds_download_link(book_id, book_format):
headers["Content-Type"] = mimetypes.types_map['.' + book_format] headers["Content-Type"] = mimetypes.types_map['.' + book_format]
except KeyError: except KeyError:
headers["Content-Type"] = "application/octet-stream" headers["Content-Type"] = "application/octet-stream"
app.logger.info(time.time() - startTime) return helper.do_download_file(book, book_format, data, headers)
startTime = time.time() #if config.config_use_google_drive:
if config.config_use_google_drive: # app.logger.info(time.time() - startTime)
app.logger.info(time.time() - startTime) # df = gdriveutils.getFileFromEbooksFolder(book.path, data.name + "." + book_format)
df = gdriveutils.getFileFromEbooksFolder(book.path, data.name + "." + book_format) # return do_gdrive_download(df, headers)
return do_gdrive_download(df, headers) #else:
else: # response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format))
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
return response
@app.route("/ajax/book/<string:uuid>") @app.route("/ajax/book/<string:uuid>")
@ -1652,7 +1625,7 @@ def on_received_watch_confirmation():
gdriveutils.downloadFile(None, "metadata.db", os.path.join(tmpDir, "tmp_metadata.db")) gdriveutils.downloadFile(None, "metadata.db", os.path.join(tmpDir, "tmp_metadata.db"))
app.logger.info('Setting up new DB') app.logger.info('Setting up new DB')
# prevent error on windows, as os.rename does on exisiting files # prevent error on windows, as os.rename does on exisiting files
shutil.move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath) move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
db.setup_db() db.setup_db()
except Exception as e: except Exception as e:
app.logger.info(e.message) app.logger.info(e.message)
@ -1823,44 +1796,11 @@ def advanced_search():
series=series, title=_(u"search"), page="advsearch") series=series, title=_(u"search"), page="advsearch")
def get_cover_via_gdrive(cover_path):
df = gdriveutils.getFileFromEbooksFolder(cover_path, 'cover.jpg')
if df:
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']
gdriveutils.session.add(permissionAdded)
gdriveutils.session.commit()
return df.metadata.get('webContentLink')
else:
return None
@app.route("/cover/<path:cover_path>") @app.route("/cover/<path:cover_path>")
@login_required_if_no_ano @login_required_if_no_ano
def get_cover(cover_path): def get_cover(cover_path):
if config.config_use_google_drive: return helper.get_book_cover(cover_path)
try:
path=get_cover_via_gdrive(cover_path)
if path:
return redirect(path)
else:
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:
app.logger.error("Error Message: "+e.message)
app.logger.exception(e)
# traceback.print_exc()
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
else:
return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg")
@app.route("/show/<book_id>/<book_format>") @app.route("/show/<book_id>/<book_format>")
@login_required_if_no_ano @login_required_if_no_ano
@ -1888,10 +1828,7 @@ def serve_book(book_id, book_format):
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_get_cover(book_id): def feed_get_cover(book_id):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
if config.config_use_google_drive: return helper.get_book_cover(book.path)
return redirect(get_cover_via_gdrive(book.path))
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): def render_read_books(page, are_read, as_xml=False):
@ -2019,16 +1956,17 @@ def get_download_link(book_id, book_format):
except KeyError: except KeyError:
headers["Content-Type"] = "application/octet-stream" headers["Content-Type"] = "application/octet-stream"
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf-8')), book_format) headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf-8')), book_format)
if config.config_use_google_drive: return helper.do_download_file(book, book_format, data, headers)
df = gdriveutils.getFileFromEbooksFolder(book.path, '%s.%s' % (data.name, book_format)) #if config.config_use_google_drive:
if df: # df = gdriveutils.getFileFromEbooksFolder(book.path, '%s.%s' % (data.name, book_format))
return do_gdrive_download(df, headers) # if df:
else: # return do_gdrive_download(df, headers)
abort(404) # else:
else: # abort(404)
response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)) #else:
response.headers = headers # response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format))
return response # response.headers = headers
# return response
else: else:
abort(404) abort(404)
@ -3158,7 +3096,7 @@ def edit_book(book_id):
if not error: if not error:
if to_save["cover_url"]: if to_save["cover_url"]:
if save_cover(to_save["cover_url"], book.path) is true: if helper.save_cover(to_save["cover_url"], book.path) is true:
book.has_cover = 1 book.has_cover = 1
else: else:
flash(_(u"Cover is not a jpg file, can't save"), category="error") flash(_(u"Cover is not a jpg file, can't save"), category="error")
@ -3315,28 +3253,6 @@ def edit_book(book_id):
return redirect(url_for('show_book', book_id=book.id)) return redirect(url_for('show_book', book_id=book.id))
def save_cover(url, book_path):
img = requests.get(url)
if img.headers.get('content-type') != 'image/jpeg':
app.logger.error("Cover is no jpg file, can't save")
return false
if config.config_use_google_drive:
tmpDir = tempfile.gettempdir()
f = open(os.path.join(tmpDir, "uploaded_cover.jpg"), "wb")
f.write(img.content)
f.close()
gdriveutils.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
app.logger.info("Cover is saved on gdrive")
return true
f = open(os.path.join(config.config_calibre_dir, book_path, "cover.jpg"), "wb")
f.write(img.content)
f.close()
app.logger.info("Cover is saved")
return true
@app.route("/upload", methods=["GET", "POST"]) @app.route("/upload", methods=["GET", "POST"])
@login_required_if_no_ano @login_required_if_no_ano
@upload_required @upload_required

Loading…
Cancel
Save