diff --git a/cps.py b/cps.py index 180be500..523246b2 100755 --- a/cps.py +++ b/cps.py @@ -22,7 +22,7 @@ except ImportError: if __name__ == '__main__': if web.ub.DEVELOPMENT: - web.app.run(host="0.0.0.0", port=web.ub.config.config_port, debug=True) + web.app.run(port=web.ub.config.config_port, debug=True) else: if gevent_present: web.app.logger.info('Attempting to start gevent') diff --git a/cps/db.py b/cps/db.py index ccc055e9..831b597e 100755 --- a/cps/db.py +++ b/cps/db.py @@ -67,9 +67,9 @@ class Identifiers(Base): val = Column(String) book = Column(Integer, ForeignKey('books.id')) - def __init__(self, val, type, book): + def __init__(self, val, id_type, book): self.val = val - self.type = type + self.type = id_type self.book = book def formatType(self): @@ -209,9 +209,9 @@ class Data(Base): uncompressed_size = Column(Integer) name = Column(String) - def __init__(self, book, format, uncompressed_size, name): + def __init__(self, book, book_format, uncompressed_size, name): self.book = book - self.format = format + self.format = book_format self.uncompressed_size = uncompressed_size self.name = name @@ -264,7 +264,7 @@ class Books(Base): class Custom_Columns(Base): __tablename__ = 'custom_columns' - + id = Column(Integer, primary_key=True) label = Column(String) name = Column(String) @@ -274,7 +274,7 @@ class Custom_Columns(Base): display = Column(String) is_multiple = Column(Boolean) normalized = Column(Boolean) - + def get_display_dict(self): display_dict = ast.literal_eval(self.display) return display_dict @@ -293,7 +293,7 @@ def setup_db(): engine = create_engine('sqlite:///'+ dbpath, echo=False, isolation_level="SERIALIZABLE") try: conn = engine.connect() - except Exception as e: + except Exception: content = ub.session.query(ub.Settings).first() content.config_calibre_dir = None content.db_configured = False @@ -333,15 +333,15 @@ def setup_db(): 'value': Column(String)} cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict) - for id in cc_ids: - if id[1] == 'bool': - setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]], + for cc_id in cc_ids: + if cc_id[1] == 'bool': + setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]], primaryjoin=( - Books.id == cc_classes[id[0]].book), + Books.id == cc_classes[cc_id[0]].book), backref='books')) else: - setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]], - secondary=books_custom_column_links[id[0]], + setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]], + secondary=books_custom_column_links[cc_id[0]], backref='books')) # Base.metadata.create_all(engine) diff --git a/cps/epub.py b/cps/epub.py index 446efe9b..6473d401 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -7,12 +7,13 @@ import os import uploader from iso639 import languages as isoLanguages -def extractCover(zip, coverFile, coverpath, tmp_file_name): + +def extractCover(zipFile, coverFile, coverpath, tmp_file_name): if coverFile is None: return None else: zipCoverPath = os.path.join(coverpath , coverFile).replace('\\','/') - cf = zip.read(zipCoverPath) + cf = zipFile.read(zipCoverPath) prefix = os.path.splitext(tmp_file_name)[0] tmp_cover_name = prefix + '.' + os.path.basename(zipCoverPath) image = open(tmp_cover_name, 'wb') @@ -28,15 +29,15 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): 'dc': 'http://purl.org/dc/elements/1.1/' } - zip = zipfile.ZipFile(tmp_file_path) + epubZip = zipfile.ZipFile(tmp_file_path) - txt = zip.read('META-INF/container.xml') + txt = epubZip.read('META-INF/container.xml') tree = etree.fromstring(txt) cfname = tree.xpath('n:rootfiles/n:rootfile/@full-path', namespaces=ns)[0] - cf = zip.read(cfname) + cf = epubZip.read(cfname) tree = etree.fromstring(cf) - coverpath=os.path.dirname(cfname) + coverpath = os.path.dirname(cfname) p = tree.xpath('/pkg:package/pkg:metadata', namespaces=ns)[0] @@ -57,7 +58,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): epub_metadata['description'] = "" if epub_metadata['language'] == "Unknown": - epub_metadata['language'] == "" + epub_metadata['language'] = "" else: lang = epub_metadata['language'].split('-', 1)[0].lower() if len(lang) == 2: @@ -70,24 +71,24 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover-image']/@href", namespaces=ns) coverfile = None if len(coversection) > 0: - coverfile = extractCover(zip, coversection[0], coverpath, tmp_file_path) + coverfile = extractCover(epubZip, coversection[0], coverpath, tmp_file_path) else: meta_cover = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='cover']/@content", namespaces=ns) if len(meta_cover) > 0: coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='"+meta_cover[0]+"']/@href", namespaces=ns) if len(coversection) > 0: - filetype = coversection[0].rsplit('.',1)[-1] - if filetype == "xhtml" or filetype == "html": #if cover is (x)html format - markup = zip.read(os.path.join(coverpath,coversection[0])) + filetype = coversection[0].rsplit('.', 1)[-1] + if filetype == "xhtml" or filetype == "html": #if cover is (x)html format + markup = epubZip.read(os.path.join(coverpath, coversection[0])) markupTree = etree.fromstring(markup) - #no matter xhtml or html with no namespace + # no matter xhtml or html with no namespace imgsrc = markupTree.xpath("//*[local-name() = 'img']/@src") - #imgsrc maybe startwith "../"" so fullpath join then relpath to cwd + # imgsrc maybe startwith "../"" so fullpath join then relpath to cwd filename = os.path.relpath(os.path.join(os.path.dirname(os.path.join(coverpath, coversection[0])), imgsrc[0])) - coverfile = extractCover(zip, filename, "", tmp_file_path) + coverfile = extractCover(epubZip, filename, "", tmp_file_path) else: - coverfile = extractCover(zip, coversection[0], coverpath, tmp_file_path) - + coverfile = extractCover(epubZip, coversection[0], coverpath, tmp_file_path) + if epub_metadata['title'] is None: title = original_file_name else: diff --git a/cps/fb2.py b/cps/fb2.py index 8e3e39b8..65d44848 100644 --- a/cps/fb2.py +++ b/cps/fb2.py @@ -2,12 +2,11 @@ # -*- coding: utf-8 -*- from lxml import etree -import os import uploader -try: - from io import StringIO -except ImportError as e: - import StringIO +#try: +# from io import StringIO +#except ImportError: +# import StringIO def get_fb2_info(tmp_file_path, original_file_extension): diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index 55341419..09cb7987 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -4,12 +4,11 @@ try: from apiclient import errors except ImportError: pass -import os, time +import os from ub import config from sqlalchemy import * -from sqlalchemy import exc from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import * @@ -104,7 +103,7 @@ def getEbooksFolderId(drive=None): gDriveId.path='/' session.merge(gDriveId) session.commit() - return + return def getFolderInFolder(parentId, folderName, drive=None): if not drive: @@ -113,7 +112,7 @@ def getFolderInFolder(parentId, folderName, drive=None): drive.auth.Refresh() folder= "title = '%s' and '%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % (folderName.replace("'", "\\'"), parentId) fileList = drive.ListFile({'q': folder}).GetList() - return fileList[0] + return fileList[0] def getFile(pathId, fileName, drive=None): if not drive: @@ -165,11 +164,11 @@ def getFileFromEbooksFolder(drive, path, fileName): if drive.auth.access_token_expired: drive.auth.Refresh() if path: - sqlCheckPath=path if path[-1] =='/' else path + '/' + # sqlCheckPath=path if path[-1] =='/' else path + '/' folderId=getFolderId(path, drive) else: folderId=getEbooksFolderId(drive) - + return getFile(folderId, fileName, drive) def copyDriveFileRemote(drive, origin_file_id, copy_title): @@ -195,7 +194,6 @@ def downloadFile(drive, path, filename, output): f.GetContentFile(output) def backupCalibreDbAndOptionalDownload(drive, f=None): - pass if not drive: drive=getDrive() if drive.auth.access_token_expired: @@ -203,15 +201,16 @@ def backupCalibreDbAndOptionalDownload(drive, f=None): metaDataFile="'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId() fileList = drive.ListFile({'q': metaDataFile}).GetList() - + databaseFile=fileList[0] if f: databaseFile.GetContentFile(f) + def copyToDrive(drive, uploadFile, createRoot, replaceFiles, - ignoreFiles=[], - parent=None, prevDir=''): + ignoreFiles=[], + parent=None, prevDir=''): if not drive: drive=getDrive() if drive.auth.access_token_expired: @@ -222,7 +221,7 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles, 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() if len(existingFolder) == 0 and (not isInitial or createRoot): - parent = drive.CreateFile({'title': os.path.basename(uploadFile), 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}], + parent = drive.CreateFile({'title': os.path.basename(uploadFile), 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}], "mimeType": "application/vnd.google-apps.folder" }) parent.Upload() else: @@ -260,7 +259,7 @@ def uploadFileToEbooksFolder(drive, destFile, f): else: existingFolder=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (x, parent['id'])}).GetList() if len(existingFolder) == 0: - parent = drive.CreateFile({'title': x, 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}], + parent = drive.CreateFile({'title': x, 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}], "mimeType": "application/vnd.google-apps.folder" }) parent.Upload() else: @@ -273,20 +272,19 @@ def watchChange(drive, channel_id, channel_type, channel_address, drive=getDrive() if drive.auth.access_token_expired: drive.auth.Refresh() - """Watch for all changes to a user's Drive. - Args: - service: Drive API service instance. - channel_id: Unique string that identifies this channel. - channel_type: Type of delivery mechanism used for this channel. - channel_address: Address where notifications are delivered. - channel_token: An arbitrary string delivered to the target address with - each notification delivered over this channel. Optional. - channel_address: Address where notifications are delivered. Optional. - Returns: - The created channel if successful - Raises: - apiclient.errors.HttpError: if http request to create channel fails. - """ + # Watch for all changes to a user's Drive. + # Args: + # service: Drive API service instance. + # channel_id: Unique string that identifies this channel. + # channel_type: Type of delivery mechanism used for this channel. + # channel_address: Address where notifications are delivered. + # channel_token: An arbitrary string delivered to the target address with + # each notification delivered over this channel. Optional. + # channel_address: Address where notifications are delivered. Optional. + # Returns: + # The created channel if successful + # Raises: + # apiclient.errors.HttpError: if http request to create channel fails. body = { 'id': channel_id, 'type': channel_type, @@ -343,8 +341,8 @@ def stopChannel(drive, channel_id, resource_id): if not drive: drive=getDrive() if drive.auth.access_token_expired: - drive.auth.Refresh() - service=drive.auth.service + drive.auth.Refresh() + # service=drive.auth.service body = { 'id': channel_id, 'resourceId': resource_id @@ -355,16 +353,15 @@ def getChangeById (drive, change_id): if not drive: drive=getDrive() if drive.auth.access_token_expired: - drive.auth.Refresh() - """Print a single Change resource information. - - Args: - service: Drive API service instance. - change_id: ID of the Change resource to retrieve. - """ + drive.auth.Refresh() + # Print a single Change resource information. + # + # Args: + # service: Drive API service instance. + # change_id: ID of the Change resource to retrieve. try: change = drive.auth.service.changes().get(changeId=change_id).execute() return change - except errors.HttpError, error: - web.app.logger.exception(error) + except (errors.HttpError, error): + web.app.logger.exception(error) return None diff --git a/cps/helper.py b/cps/helper.py index 1a89c86c..8522d336 100755 --- a/cps/helper.py +++ b/cps/helper.py @@ -13,6 +13,7 @@ import os import traceback import re import unicodedata + try: from StringIO import StringIO from email.MIMEBase import MIMEBase @@ -43,9 +44,9 @@ import web try: import unidecode - use_unidecode=True + use_unidecode = True except Exception as e: - use_unidecode=False + use_unidecode = False # Global variables global_task = None @@ -80,7 +81,7 @@ def make_mobi(book_id, calibrepath): file_path = os.path.join(calibrepath, book.path, data.name) if os.path.exists(file_path + u".epub"): p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()), - shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Poll process for new output until finished while True: nextline = p.stdout.readline() @@ -93,7 +94,7 @@ def make_mobi(book_id, calibrepath): if not check or check < 2: book.data.append(db.Data( name=book.data[0].name, - format="MOBI", + book_format="MOBI", book=book.id, uncompressed_size=os.path.getsize(file_path + ".mobi") )) @@ -242,7 +243,7 @@ def get_valid_filename(value, replace_whitespace=True): Returns the given string converted to a string that can be used for a clean filename. Limits num characters to 128 max. """ - if value[-1:] ==u'.': + if value[-1:] == u'.': value = value[:-1]+u'_' if use_unidecode: value=(unidecode.unidecode(value)).strip() @@ -251,7 +252,7 @@ def get_valid_filename(value, replace_whitespace=True): value=value.replace(u'ß',u'ss') value = unicodedata.normalize('NFKD', value) re_slugify = re.compile('[\W\s-]', re.UNICODE) - if type(value) is str: #Python3 str, Python2 unicode + if isinstance(value, str): #Python3 str, Python2 unicode value = re_slugify.sub('', value).strip() else: value = unicode(re_slugify.sub('', value).strip()) @@ -266,7 +267,7 @@ def get_sorted_author(value): regexes = ["^(JR|SR)\.?$","^I{1,3}\.?$","^IV\.?$"] combined = "(" + ")|(".join(regexes) + ")" value = value.split(" ") - if re.match(combined,value[-1].upper()): + if re.match(combined, value[-1].upper()): value2 = value[-2] + ", " + " ".join(value[:-2]) + " " + value[-1] else: value2 = value[-1] + ", " + " ".join(value[:-1]) @@ -277,28 +278,29 @@ def update_dir_stucture(book_id, calibrepath): db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) book = db.session.query(db.Books).filter(db.Books.id == book_id).first() path = os.path.join(calibrepath, book.path)#.replace('/',os.path.sep)).replace('\\',os.path.sep) - + authordir = book.path.split('/')[0] new_authordir = get_valid_filename(book.authors[0].name) titledir = book.path.split('/')[1] new_titledir = get_valid_filename(book.title) + " (" + str(book_id) + ")" - + if titledir != new_titledir: new_title_path = os.path.join(os.path.dirname(path), new_titledir) os.rename(path, new_title_path) path = new_title_path book.path = book.path.split('/')[0] + '/' + new_titledir - + if authordir != new_authordir: new_author_path = os.path.join(os.path.join(calibrepath, new_authordir), os.path.basename(path)) os.renames(path, new_author_path) book.path = new_authordir + '/' + book.path.split('/')[1] db.session.commit() + def update_dir_structure_gdrive(book_id): db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) book = db.session.query(db.Books).filter(db.Books.id == book_id).first() - + authordir = book.path.split('/')[0] new_authordir = get_valid_filename(book.authors[0].name) titledir = book.path.split('/')[1] @@ -313,7 +315,7 @@ def update_dir_structure_gdrive(book_id): if authordir != new_authordir: gFile=gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive,None,authordir) - gFile['title']= new_authordir + gFile['title'] = new_authordir gFile.Upload() book.path = new_authordir + '/' + book.path.split('/')[1] @@ -327,41 +329,44 @@ class Updater(threading.Thread): def run(self): global global_task - self.status=1 + self.status = 1 r = requests.get('https://api.github.com/repos/janeczku/calibre-web/zipball/master', stream=True) fname = re.findall("filename=(.+)", r.headers['content-disposition'])[0] - self.status=2 + self.status = 2 z = zipfile.ZipFile(StringIO(r.content)) - self.status=3 + self.status = 3 tmp_dir = gettempdir() z.extractall(tmp_dir) - self.status=4 + self.status = 4 self.update_source(os.path.join(tmp_dir,os.path.splitext(fname)[0]),ub.config.get_main_dir) - self.status=5 + self.status = 5 global_task = 0 db.session.close() db.engine.dispose() ub.session.close() ub.engine.dispose() - self.status=6 + self.status = 6 if web.gevent_server: web.gevent_server.stop() else: - # stop tornado server + # stop tornado server server = IOLoop.instance() server.add_callback(server.stop) - self.status=7 + self.status = 7 def get_update_status(self): return self.status + @classmethod def file_to_list(self, file): return [x.strip() for x in open(file, 'r') if not x.startswith('#EXT')] + @classmethod def one_minus_two(self, one, two): return [x for x in one if x not in set(two)] + @classmethod def reduce_dirs(self, delete_files, new_list): new_delete = [] for file in delete_files: @@ -382,6 +387,7 @@ class Updater(threading.Thread): break return list(set(new_delete)) + @classmethod def reduce_files(self, remove_items, exclude_items): rf = [] for item in remove_items: @@ -389,6 +395,7 @@ class Updater(threading.Thread): rf.append(item) return rf + @classmethod def moveallfiles(self, root_src_dir, root_dst_dir): change_permissions = True if sys.platform == "win32" or sys.platform == "darwin": @@ -431,8 +438,8 @@ class Updater(threading.Thread): # destination files old_list = list() exclude = ( - 'vendor' + os.sep + 'kindlegen.exe', 'vendor' + os.sep + 'kindlegen', os.sep + 'app.db', - os.sep + 'vendor',os.sep + 'calibre-web.log') + 'vendor' + os.sep + 'kindlegen.exe', 'vendor' + os.sep + 'kindlegen', os.sep + 'app.db', + os.sep + 'vendor', os.sep + 'calibre-web.log') for root, dirs, files in os.walk(destination, topdown=True): for name in files: old_list.append(os.path.join(root, name).replace(destination, '')) @@ -462,9 +469,9 @@ class Updater(threading.Thread): else: try: logging.getLogger('cps.web').debug("Delete file " + item_path) - log_from_thread("Delete file " + item_path) + # log_from_thread("Delete file " + item_path) os.remove(item_path) - except Exception as e: + except Exception: logging.getLogger('cps.web').debug("Could not remove:" + item_path) shutil.rmtree(source, ignore_errors=True) diff --git a/cps/static/js/edit_books.js b/cps/static/js/edit_books.js index 0d6665ba..8a1a36d1 100644 --- a/cps/static/js/edit_books.js +++ b/cps/static/js/edit_books.js @@ -1,13 +1,15 @@ /** * Created by SpeedProg on 05.04.2015. */ +/* global Bloodhound */ + /* Takes a prefix, query typeahead callback, Bloodhound typeahead adapter and returns the completions it gets from the bloodhound engine prefixed. */ -function prefixed_source(prefix, query, cb, bh_adapter) { - bh_adapter(query, function(retArray){ +function prefixedSource(prefix, query, cb, bhAdapter) { + bhAdapter(query, function(retArray){ var matches = []; for (var i = 0; i < retArray.length; i++) { var obj = {name : prefix + retArray[i].name}; @@ -16,184 +18,161 @@ function prefixed_source(prefix, query, cb, bh_adapter) { cb(matches); }); } -function get_path(){ - var jsFileLocation = $('script[src*=edit_books]').attr('src'); // the js file path - jsFileLocation = jsFileLocation.replace('/static/js/edit_books.js', ''); // the js folder path +function getPath(){ + var jsFileLocation = $("script[src*=edit_books]").attr("src"); // the js file path + jsFileLocation = jsFileLocation.replace("/static/js/edit_books.js", ""); // the js folder path return jsFileLocation; } var authors = new Bloodhound({ - name: 'authors', - datumTokenizer: function(datum) { + name: "authors", + datumTokenizer(datum) { return [datum.name]; }, queryTokenizer: Bloodhound.tokenizers.whitespace, remote: { - url: get_path()+'/get_authors_json?q=%QUERY' + url: getPath()+"/get_authors_json?q=%QUERY" } }); -function authors_source(query, cb) { - var bh_adapter = authors.ttAdapter(); - - var tokens = query.split("&"); - var current_author = tokens[tokens.length-1].trim(); - - tokens.splice(tokens.length-1, 1); // remove last element - var prefix = ""; - for (var i = 0; i < tokens.length; i++) { - var author = tokens[i].trim(); - prefix += author + " & "; - } - - prefixed_source(prefix, current_author, cb, bh_adapter); -} - - - -var promise = authors.initialize(); - promise.done(function(){ - $("#bookAuthor").typeahead( - { - highlight: true, minLength: 1, - hint: true - }, { - name: 'authors', displayKey: 'name', - source: authors_source - } - ) -}); - var series = new Bloodhound({ - name: 'series', - datumTokenizer: function(datum) { + name: "series", + datumTokenizer(datum) { return [datum.name]; }, - queryTokenizer: function(query) { + queryTokenizer(query) { return [query]; }, remote: { - url: get_path()+'/get_series_json?q=', - replace: function(url, query) { - url_query = url+encodeURIComponent(query); - return url_query; + url: getPath()+"/get_series_json?q=", + replace(url, query) { + return url+encodeURIComponent(query); } } }); -var promise = series.initialize(); - promise.done(function(){ - $("#series").typeahead( - { - highlight: true, minLength: 0, - hint: true - }, { - name: 'series', displayKey: 'name', - source: series.ttAdapter() - } - ) -}); + var tags = new Bloodhound({ - name: 'tags', - datumTokenizer: function(datum) { + name: "tags", + datumTokenizer(datum) { return [datum.name]; }, - queryTokenizer: function(query) { - tokens = query.split(","); + queryTokenizer(query) { + var tokens = query.split(","); tokens = [tokens[tokens.length-1].trim()]; - return tokens + return tokens; }, remote: { - url: get_path()+'/get_tags_json?q=%QUERY' - } -}); - -function tag_source(query, cb) { - var bh_adapter = tags.ttAdapter(); - - var tokens = query.split(","); - var current_tag = tokens[tokens.length-1].trim(); - - tokens.splice(tokens.length-1, 1); // remove last element - var prefix = ""; - for (var i = 0; i < tokens.length; i++) { - var tag = tokens[i].trim(); - prefix += tag + ", "; + url: getPath()+"/get_tags_json?q=%QUERY" } - - prefixed_source(prefix, current_tag, cb, bh_adapter); -} - -var promise = tags.initialize(); - promise.done(function(){ - $("#tags").typeahead( - { - highlight: true, minLength: 0, - hint: true - }, { - name: 'tags', displayKey: 'name', - source: tag_source - } - ) }); var languages = new Bloodhound({ - name: 'languages', - datumTokenizer: function(datum) { + name: "languages", + datumTokenizer(datum) { return [datum.name]; }, - queryTokenizer: function(query) { + queryTokenizer(query) { return [query]; }, remote: { - url: get_path()+'/get_languages_json?q=', - replace: function(url, query) { - url_query = url+encodeURIComponent(query); - return url_query; + url: getPath()+"/get_languages_json?q=", + replace(url, query) { + return url+encodeURIComponent(query); } } }); -function language_source(query, cb) { - var bh_adapter = languages.ttAdapter(); +function sourceSplit(query, cb, split, source) { + var bhAdapter = source.ttAdapter(); - var tokens = query.split(","); - var current_language = tokens[tokens.length-1].trim(); + var tokens = query.split(split); + var currentSource = tokens[tokens.length-1].trim(); tokens.splice(tokens.length-1, 1); // remove last element var prefix = ""; + var newSplit; + if (split === "&"){ + newSplit = " " + split + " "; + }else{ + newSplit = split + " "; + } for (var i = 0; i < tokens.length; i++) { - var tag = tokens[i].trim(); - prefix += tag + ", "; + prefix += tokens[i].trim() + newSplit; } - - prefixed_source(prefix, current_language, cb, bh_adapter); + prefixedSource(prefix, currentSource, cb, bhAdapter); } -var promise = languages.initialize(); - promise.done(function(){ - $("#languages").typeahead( +var promiseAuthors = authors.initialize(); + promiseAuthors.done(function(){ + $("#bookAuthor").typeahead( + { + highlight: true, minLength: 1, + hint: true + }, { + name: "authors", + displayKey: "name", + source(query, cb){ + return sourceSplit(query, cb, "&", authors); //sourceSplit //("&") + } + }); +}); + +var promiseSeries = series.initialize(); + promiseSeries.done(function(){ + $("#series").typeahead( { highlight: true, minLength: 0, hint: true }, { - name: 'languages', displayKey: 'name', - source: language_source + name: "series", + displayKey: "name", + source: series.ttAdapter() } - ) + ); }); -$('form').on('change input typeahead:selected', function(data){ - form = $('form').serialize(); - $.getJSON( get_path()+"/get_matching_tags", form, function( data ) { - $('.tags_click').each(function() { - if ($.inArray(parseInt($(this).children('input').first().val(), 10), data.tags) == -1 ) { - if (!($(this).hasClass('active'))) { - $(this).addClass('disabled'); +var promiseTags = tags.initialize(); + promiseTags.done(function(){ + $("#tags").typeahead( + { + highlight: true, minLength: 0, + hint: true + }, { + name: "tags", + displayKey: "name", + source(query, cb){ + return sourceSplit(query, cb, ",", tags); + } + }); + }); + +var promiseLanguages = languages.initialize(); + promiseLanguages.done(function(){ + $("#languages").typeahead( + { + highlight: true, minLength: 0, + hint: true + }, { + name: "languages", + displayKey: "name", + source(query, cb){ + return sourceSplit(query, cb, ",", languages); //(",") + } + }); + }); + +$("form").on("change input typeahead:selected", function(data){ + var form = $("form").serialize(); + $.getJSON( getPath()+"/get_matching_tags", form, function( data ) { + $(".tags_click").each(function() { + if ($.inArray(parseInt($(this).children("input").first().val(), 10), data.tags) === -1 ) { + if (!($(this).hasClass("active"))) { + $(this).addClass("disabled"); } } else { - $(this).removeClass('disabled'); + $(this).removeClass("disabled"); } }); }); diff --git a/cps/static/js/get_meta.js b/cps/static/js/get_meta.js index 2cec1252..99fce061 100644 --- a/cps/static/js/get_meta.js +++ b/cps/static/js/get_meta.js @@ -4,176 +4,182 @@ * Google Books api document: https://developers.google.com/books/docs/v1/using * Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only) */ + /* global i18nMsg */ $(document).ready(function () { - var msg = i18n_msg; - var douban = 'https://api.douban.com'; - var db_search = '/v2/book/search'; - var db_get_info = '/v2/book/'; - var db_get_info_by_isbn = '/v2/book/isbn/ '; - var db_done = false; + var msg = i18nMsg; + var douban = "https://api.douban.com"; + var dbSearch = "/v2/book/search"; + // var dbGetInfo = "/v2/book/"; + // var db_get_info_by_isbn = "/v2/book/isbn/ "; + var dbDone = false; - var google = 'https://www.googleapis.com/'; - var gg_search = '/books/v1/volumes'; - var gg_get_info = '/books/v1/volumes/'; - var gg_done = false; + var google = "https://www.googleapis.com/"; + var ggSearch = "/books/v1/volumes"; + // var gg_get_info = "/books/v1/volumes/"; + var ggDone = false; - var db_results = []; - var gg_results = []; - var show_flag = 0; - String.prototype.replaceAll = function (s1, s2) {   - return this.replace(new RegExp(s1, "gm"), s2);   + var dbResults = []; + var ggResults = []; + var showFlag = 0; + String.prototype.replaceAll = function (s1, s2) { + return this.replace(new RegExp(s1, "gm"), s2); + }; + + function showResult () { + var book; + var i; + var bookHtml; + showFlag++; + if (showFlag === 1) { + $("#meta-info").html(""); + } + if (ggDone && dbDone) { + if (!ggResults && !dbResults) { + $("#meta-info").html("

"+ msg.no_result +"

"); + return; + } + } + if (ggDone && ggResults.length > 0) { + for (i = 0; i < ggResults.length; i++) { + book = ggResults[i]; + var bookCover; + if (book.volumeInfo.imageLinks) { + bookCover = book.volumeInfo.imageLinks.thumbnail; + } else { + bookCover = "/static/generic_cover.jpg"; + } + bookHtml = "
  • " + + "\"Cover\"" + + "
    " + + "

    " + book.title + "

    " + + "

    " + msg.author + ":" + book.author + "

    " + + "

    " + msg.publisher + ":" + book.publisher + "

    " + + "

    " + msg.description + ":" + book.summary + "

    " + + "

    " + msg.source + ":Douban Books

    " + + "
    " + + "
  • "; + $("#book-list").append(bookHtml); + } + dbDone = false; + } } - gg_search_book = function (title) { - title = title.replaceAll(/\s+/, '+'); - var url = google + gg_search + '?q=' + title; + function ggSearchBook (title) { + title = title.replaceAll(/\s+/, "+"); + var url = google + ggSearch + "?q=" + title; $.ajax({ - url: url, + url, type: "GET", dataType: "jsonp", - jsonp: 'callback', - success: function (data) { - gg_results = data.items; + jsonp: "callback", + success (data) { + ggResults = data.items; }, - complete: function () { - gg_done = true; - show_result(); + complete () { + ggDone = true; + showResult(); } }); } - get_meta = function (source, id) { + function getMeta (source, id) { var meta; - if (source == 'google') {; - meta = gg_results[id]; - $('#description').val(meta.volumeInfo.description); - $('#bookAuthor').val(meta.volumeInfo.authors.join(' & ')); - $('#book_title').val(meta.volumeInfo.title); + var tags; + if (source === "google") { + meta = ggResults[id]; + $("#description").val(meta.volumeInfo.description); + $("#bookAuthor").val(meta.volumeInfo.authors.join(" & ")); + $("#book_title").val(meta.volumeInfo.title); if (meta.volumeInfo.categories) { - var tags = meta.volumeInfo.categories.join(','); - $('#tags').val(tags); + tags = meta.volumeInfo.categories.join(","); + $("#tags").val(tags); } if (meta.volumeInfo.averageRating) { - $('#rating').val(Math.round(meta.volumeInfo.averageRating)); + $("#rating").val(Math.round(meta.volumeInfo.averageRating)); } return; } - if (source == 'douban') { - meta = db_results[id]; - $('#description').val(meta.summary); - $('#bookAuthor').val(meta.author.join(' & ')); - $('#book_title').val(meta.title); - var tags = ''; + if (source === "douban") { + meta = dbResults[id]; + $("#description").val(meta.summary); + $("#bookAuthor").val(meta.author.join(" & ")); + $("#book_title").val(meta.title); + tags = ""; for (var i = 0; i < meta.tags.length; i++) { - tags = tags + meta.tags[i].title + ','; + tags = tags + meta.tags[i].title + ","; } - $('#tags').val(tags); - $('#rating').val(Math.round(meta.rating.average / 2)); + $("#tags").val(tags); + $("#rating").val(Math.round(meta.rating.average / 2)); return; } } - do_search = function (keyword) { - show_flag = 0; - $('#meta-info').text(msg.loading); - var keyword = $('#keyword').val(); - if (keyword) { - db_search_book(keyword); - gg_search_book(keyword); - } - } - db_search_book = function (title) { - var url = douban + db_search + '?q=' + title + '&fields=all&count=10'; + function dbSearchBook (title) { + var url = douban + dbSearch + "?q=" + title + "&fields=all&count=10"; $.ajax({ - url: url, + url, type: "GET", dataType: "jsonp", - jsonp: 'callback', - success: function (data) { - db_results = data.books; + jsonp: "callback", + success (data) { + dbResults = data.books; }, - error: function () { - $('#meta-info').html('

    '+ msg.search_error+'!

    '); + error () { + $("#meta-info").html("

    "+ msg.search_error+"!

    "); }, - complete: function () { - db_done = true; - show_result(); + complete () { + dbDone = true; + showResult(); } }); } - show_result = function () { - show_flag++; - if (show_flag == 1) { - $('#meta-info').html(''); - } - if (gg_done && db_done) { - if (!gg_results && !db_results) { - $('#meta-info').html('

    '+ msg.no_result +'

    '); - return; - } - } - if (gg_done && gg_results.length > 0) { - for (var i = 0; i < gg_results.length; i++) { - var book = gg_results[i]; - var book_cover; - if (book.volumeInfo.imageLinks) { - book_cover = book.volumeInfo.imageLinks.thumbnail; - } else { - book_cover = '/static/generic_cover.jpg'; - } - var book_html = '
  • ' + - 'Cover' + - '
    ' + - '

    ' + book.volumeInfo.title + '

    ' + - '

    '+ msg.author +':' + book.volumeInfo.authors + '

    ' + - '

    '+ msg.publisher + ':' + book.volumeInfo.publisher + '

    ' + - '

    '+ msg.description + ':' + book.volumeInfo.description + '

    ' + - '

    '+ msg.source + ':Google Books

    ' + - '
    ' + - '
  • '; - $("#book-list").append(book_html); - } - gg_done = false; - } - if (db_done && db_results.length > 0) { - for (var i = 0; i < db_results.length; i++) { - var book = db_results[i]; - var book_html = '
  • ' + - 'Cover' + - '
    ' + - '

    ' + book.title + '

    ' + - '

    ' + msg.author + ':' + book.author + '

    ' + - '

    ' + msg.publisher + ':' + book.publisher + '

    ' + - '

    ' + msg.description + ':' + book.summary + '

    ' + - '

    ' + msg.source + ':Douban Books

    ' + - '
    ' + - '
  • '; - $("#book-list").append(book_html); - } - db_done = false; + function doSearch (keyword) { + showFlag = 0; + $("#meta-info").text(msg.loading); + // var keyword = $("#keyword").val(); + if (keyword) { + dbSearchBook(keyword); + ggSearchBook(keyword); } } - $('#do-search').click(function () { - var keyword = $('#keyword').val(); + $("#do-search").click(function () { + var keyword = $("#keyword").val(); if (keyword) { - do_search(keyword); + doSearch(keyword); } }); - $('#get_meta').click(function () { - var book_title = $('#book_title').val(); - if (book_title) { - $('#keyword').val(book_title); - do_search(book_title); + $("#get_meta").click(function () { + var bookTitle = $("#book_title").val(); + if (bookTitle) { + $("#keyword").val(bookTitle); + doSearch(bookTitle); } }); diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 7a498f36..d759a3b8 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -3,13 +3,47 @@ var updateTimerID; var updateText; $(function() { - $('.discover .row').isotope({ + + function restartTimer() { + $("#spinner").addClass("hidden"); + $("#RestartDialog").modal("hide"); + } + + function updateTimer() { + $.ajax({ + dataType: "json", + url: window.location.pathname+"/../../get_updater_status", + success(data) { + // console.log(data.status); + $("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]); + if (data.status >6){ + clearInterval(updateTimerID); + $("#spinner2").hide(); + $("#UpdateprogressDialog #updateFinished").removeClass("hidden"); + $("#check_for_update").removeClass("hidden"); + $("#perform_update").addClass("hidden"); + } + }, + error() { + // console.log('Done'); + clearInterval(updateTimerID); + $("#spinner2").hide(); + $("#UpdateprogressDialog #Updatecontent").html(updateText[7]); + $("#UpdateprogressDialog #updateFinished").removeClass("hidden"); + $("#check_for_update").removeClass("hidden"); + $("#perform_update").addClass("hidden"); + }, + timeout:2000 + }); + } + + $(".discover .row").isotope({ // options - itemSelector : '.book', - layoutMode : 'fitRows' + itemSelector : ".book", + layoutMode : "fitRows" }); - $('.load-more .row').infinitescroll({ + $(".load-more .row").infinitescroll({ debug: false, navSelector : ".pagination", // selector for the paged navigation (it will be hidden) @@ -20,109 +54,74 @@ $(function() { extraScrollPx: 300, // selector for all items you'll retrieve }, function(data){ - $('.load-more .row').isotope( 'appended', $(data), null ); + $(".load-more .row").isotope( "appended", $(data), null ); }); - $('#sendbtn').click(function(){ + $("#sendbtn").click(function(){ var $this = $(this); - $this.text('Please wait...'); - $this.addClass('disabled'); + $this.text("Please wait..."); + $this.addClass("disabled"); }); $("#restart").click(function() { $.ajax({ - dataType: 'json', + dataType: "json", url: window.location.pathname+"/../../shutdown", data: {"parameter":0}, - success: function(data) { - $('#spinner').show(); + success(data) { + $("#spinner").show(); displaytext=data.text; setTimeout(restartTimer, 3000);} }); }); $("#shutdown").click(function() { $.ajax({ - dataType: 'json', + dataType: "json", url: window.location.pathname+"/../../shutdown", data: {"parameter":1}, - success: function(data) { + success(data) { return alert(data.text);} }); }); $("#check_for_update").click(function() { - var button_text = $("#check_for_update").html(); - $("#check_for_update").html('...'); + var buttonText = $("#check_for_update").html(); + $("#check_for_update").html("..."); $.ajax({ - dataType: 'json', + dataType: "json", url: window.location.pathname+"/../../get_update_status", - success: function(data) { - $("#check_for_update").html(button_text); - if (data.status == true) { - $("#check_for_update").addClass('hidden'); - $("#perform_update").removeClass('hidden'); - $("#update_info").removeClass('hidden'); - $("#update_info").find('span').html(data.commit); + success(data) { + $("#check_for_update").html(buttonText); + if (data.status === true) { + $("#check_for_update").addClass("hidden"); + $("#perform_update").removeClass("hidden"); + $("#update_info").removeClass("hidden"); + $("#update_info").find("span").html(data.commit); } } }); }); $("#restart_database").click(function() { $.ajax({ - dataType: 'json', + dataType: "json", url: window.location.pathname+"/../../shutdown", data: {"parameter":2} }); }); $("#perform_update").click(function() { - $('#spinner2').show(); + $("#spinner2").show(); $.ajax({ type: "POST", - dataType: 'json', + dataType: "json", data: { start: "True"}, url: window.location.pathname+"/../../get_updater_status", - success: function(data) { - updateText=data.text + success(data) { + updateText=data.text; $("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]); - console.log(data.status); + // console.log(data.status); updateTimerID=setInterval(updateTimer, 2000);} }); }); -}); - - -function restartTimer() { - $('#spinner').hide(); - $('#RestartDialog').modal('hide'); -} -function updateTimer() { - $.ajax({ - dataType: 'json', - url: window.location.pathname+"/../../get_updater_status", - success: function(data) { - console.log(data.status); - $("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]); - if (data.status >6){ - clearInterval(updateTimerID); - $('#spinner2').hide(); - $('#UpdateprogressDialog #updateFinished').removeClass('hidden'); - $("#check_for_update").removeClass('hidden'); - $("#perform_update").addClass('hidden'); - } - }, - error: function() { - console.log('Done'); - clearInterval(updateTimerID); - $('#spinner2').hide(); - $("#UpdateprogressDialog #Updatecontent").html(updateText[7]); - $('#UpdateprogressDialog #updateFinished').removeClass('hidden'); - $("#check_for_update").removeClass('hidden'); - $("#perform_update").addClass('hidden'); - }, - timeout:2000 + $(window).resize(function(event) { + $(".discover .row").isotope("reLayout"); }); -} - - -$(window).resize(function(event) { - $('.discover .row').isotope('reLayout'); -}); +}); \ No newline at end of file diff --git a/cps/static/js/shelforder.js b/cps/static/js/shelforder.js index ea970f9a..0bf693f0 100644 --- a/cps/static/js/shelforder.js +++ b/cps/static/js/shelforder.js @@ -1,4 +1,6 @@ -Sortable.create(sortTrue, { +/* global Sortable,sortTrue */ + +var sortable = Sortable.create(sortTrue, { group: "sorting", sort: true }); @@ -9,7 +11,7 @@ function sendData(path){ var maxElements; var tmp=[]; - elements=Sortable.utils.find(sortTrue,"div"); + elements=sortable.utils.find(sortTrue,"div"); maxElements=elements.length; var form = document.createElement("form"); diff --git a/cps/templates/book_edit.html b/cps/templates/book_edit.html index 474cfaae..b94ddbb2 100644 --- a/cps/templates/book_edit.html +++ b/cps/templates/book_edit.html @@ -106,7 +106,7 @@ {{_('Get metadata')}} - {{_('Back')}} + {{_('Back')}} {% endif %} @@ -138,7 +138,7 @@ {% block js %}