From 57d5bbd82dc44a0d9a33017d9190053b6be81840 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 3 Nov 2008 14:32:18 -0800 Subject: [PATCH] IGN: Almost working read only web interface --- setup.py | 18 +- src/calibre/gui2/dialogs/search.ui | 13 + src/calibre/library/server.py | 86 +++-- src/calibre/library/static/bg_search_box.png | Bin 0 -> 471 bytes src/calibre/library/static/btn_search_box.png | Bin 0 -> 335 bytes src/calibre/library/static/calibre.png | Bin 0 -> 27150 bytes src/calibre/library/static/date.js | 104 +++++++ src/calibre/library/static/first.png | Bin 0 -> 248 bytes src/calibre/library/static/gui.css | 147 +++++++++ src/calibre/library/static/gui.js | 293 ++++++++++++++++++ src/calibre/library/static/index.html | 49 +++ src/calibre/library/static/last.png | Bin 0 -> 255 bytes src/calibre/library/static/next.png | Bin 0 -> 267 bytes src/calibre/library/static/previous.png | Bin 0 -> 262 bytes 14 files changed, 686 insertions(+), 24 deletions(-) create mode 100644 src/calibre/library/static/bg_search_box.png create mode 100644 src/calibre/library/static/btn_search_box.png create mode 100644 src/calibre/library/static/calibre.png create mode 100644 src/calibre/library/static/date.js create mode 100644 src/calibre/library/static/first.png create mode 100644 src/calibre/library/static/gui.css create mode 100644 src/calibre/library/static/gui.js create mode 100644 src/calibre/library/static/index.html create mode 100644 src/calibre/library/static/last.png create mode 100644 src/calibre/library/static/next.png create mode 100644 src/calibre/library/static/previous.png diff --git a/setup.py b/setup.py index 948db8039c..35a2ae176d 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import sys, re, os, shutil, cStringIO, tempfile, subprocess +import sys, re, os, shutil, cStringIO, tempfile, subprocess, time sys.path.append('src') iswindows = re.search('win(32|64)', sys.platform) isosx = 'darwin' in sys.platform @@ -146,7 +146,7 @@ if __name__ == '__main__': metadata_sqlite = 'library/metadata_sqlite.sql', jquery = 'gui2/viewer/jquery.js', jquery_scrollTo = 'gui2/viewer/jquery_scrollTo.js', - ) + ) DEST = os.path.join('src', APPNAME, 'resources.py') @@ -165,6 +165,15 @@ if __name__ == '__main__': print 'WARNING: Could not find Qt transations' return data + def get_static_resources(self): + sdir = os.path.join('src', 'calibre', 'library', 'static') + resources, max = {}, 0 + for f in os.listdir(sdir): + resources[f] = open(os.path.join(sdir, f), 'rb').read() + mtime = os.stat(os.path.join(sdir, f)).st_mtime + max = mtime if mtime > max else max + return resources, max + def run(self): data, dest, RESOURCES = {}, self.DEST, self.RESOURCES for key in RESOURCES: @@ -173,12 +182,15 @@ if __name__ == '__main__': RESOURCES[key] = os.path.join('src', APPNAME, path) translations = self.get_qt_translations() RESOURCES.update(translations) - if newer([dest], RESOURCES.values()): + static, smax = self.get_static_resources() + if newer([dest], RESOURCES.values()) or os.stat(dest).st_mtime < smax: print 'Compiling resources...' with open(dest, 'wb') as f: for key in RESOURCES: data = open(RESOURCES[key], 'rb').read() f.write(key + ' = ' + repr(data)+'\n\n') + f.write('server_resources = %s\n\n'%repr(static)) + f.write('build_time = "%s"\n\n'%time.strftime('%d %m %Y %H%M%S')) else: print 'Resources are up to date' diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui index f1ba9e60ae..f5813c18ee 100644 --- a/src/calibre/gui2/dialogs/search.ui +++ b/src/calibre/gui2/dialogs/search.ui @@ -103,6 +103,19 @@ + + + + + 16777215 + 30 + + + + See the <a href="http://calibre.kovidgoyal.net/user_manual/gui.html#the-search-interface">User Manual</a> for more help + + + diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index 6840b6aad4..9b044f478e 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' HTTP server for remote access to the calibre database. ''' -import sys, textwrap, cStringIO, mimetypes, operator, os +import sys, textwrap, cStringIO, mimetypes, operator, os, re from datetime import datetime import cherrypy from PIL import Image @@ -16,6 +16,11 @@ from calibre.constants import __version__, __appname__ from calibre.utils.config import StringConfig, Config from calibre.utils.genshi.template import MarkupTemplate from calibre import fit_image +from calibre.resources import jquery, server_resources, build_time +from calibre.library.database2 import LibraryDatabase2, FIELD_MAP + +build_time = datetime.strptime(build_time, '%d %m %Y %H%M%S') +server_resources['jquery.js'] = jquery def expose(func): @@ -37,7 +42,7 @@ class LibraryServer(object): author_sort="${r[12]}" authors="${authors}" rating="${r[4]}" - timestamp="${r[5].ctime()}" + timestamp="${r[5].strftime('%Y/%m/%d %H:%M:%S')}" size="${r[6]}" isbn="${r[14] if r[14] else ''}" formats="${r[13] if r[13] else ''}" @@ -100,6 +105,7 @@ class LibraryServer(object): self.opts = opts cherrypy.config.update({ + 'server.socket_host': '0.0.0.0', 'server.socket_port': opts.port, 'server.socket_timeout': opts.timeout, #seconds 'server.thread_pool': opts.thread_pool, # number of threads @@ -108,7 +114,7 @@ class LibraryServer(object): [global] engine.autoreload_on = %(autoreload)s tools.gzip.on = True - tools.gzip.mime_types = ['text/html', 'text/plain', 'text/xml'] + tools.gzip.mime_types = ['text/html', 'text/plain', 'text/xml', 'text/javascript', 'text/css'] ''')%dict(autoreload=opts.develop) def start(self): @@ -121,7 +127,7 @@ class LibraryServer(object): cherrypy.response.headers['Content-Type'] = 'image/jpeg' path = getattr(cover, 'name', None) if path and os.path.exists(path): - updated = datetime.fromutctimestamp(os.stat(path).st_mtime) + updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) if not thumbnail: return cover.read() @@ -149,25 +155,28 @@ class LibraryServer(object): cherrypy.response.headers['Content-Type'] = mt path = getattr(fmt, 'name', None) if path and os.path.exists(path): - updated = datetime.fromutctimestamp(os.stat(path).st_mtime) + updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) return fmt.read() - def sort(self, items, field): + def sort(self, items, field, order): field = field.lower().strip() if field == 'author': field = 'authors' - if field not in ('title', 'authors', 'rating'): + if field == 'date': + field = 'timestamp' + if field not in ('title', 'authors', 'rating', 'timestamp', 'tags', 'size', 'series'): raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field) - cmpf = cmp if field == 'rating' else lambda x, y: cmp(x.lower(), y.lower()) - field = {'title':11, 'authors':12, 'rating':4}[field] + cmpf = cmp if field in ('rating', 'size', 'timestamp') else \ + lambda x, y: cmp(x.lower() if x else '', y.lower() if y else '') + field = FIELD_MAP[field] getter = operator.itemgetter(field) - items.sort(cmp=lambda x, y: cmpf(getter(x), getter(y))) + items.sort(cmp=lambda x, y: cmpf(getter(x), getter(y)), reverse=not order) def last_modified(self, updated): lm = updated.strftime('day, %d month %Y %H:%M:%S GMT') day ={0:'Sun', 1:'Mon', 2:'Tue', 3:'Wed', 4:'Thu', 5:'Fri', 6:'Sat'} - lm = lm.replace('day', day[int(lm.strftime('%w'))]) + lm = lm.replace('day', day[int(updated.strftime('%w'))]) month = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul', 8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'} return lm.replace('month', month[updated.month]) @@ -175,6 +184,7 @@ class LibraryServer(object): @expose def stanza(self): + ' Feeds to read calibre books on a ipod with stanza.' books = [] for record in iter(self.db): if 'EPUB' in record['formats'].upper(): @@ -193,11 +203,14 @@ class LibraryServer(object): updated=updated, id='urn:calibre:main').render('xml') @expose - def library(self, start='0', num='50', sort=None, search=None): + def library(self, start='0', num='50', sort=None, search=None, _=None, order='ascending'): ''' + Serves metadata from the calibre database as XML. + :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax - :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results + :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results + :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching ''' try: start = int(start) @@ -207,33 +220,40 @@ class LibraryServer(object): num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num) - ids = self.db.data.parse(search) if search else self.db.data.universal_set() + order = order.lower().strip() == 'ascending' + ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() ids = sorted(ids) items = [r for r in iter(self.db) if r[0] in ids] if sort is not None: - self.sort(items, sort) + self.sort(items, sort, order) book, books = MarkupTemplate(self.BOOK), [] for record in items[start:start+num]: - authors = ' & '.join([i.replace('|', ',') for i in record[2].split(',')]) + authors = '|'.join([i.replace('|', ',') for i in record[2].split(',')]) books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8')) updated = self.db.last_modified() cherrypy.response.headers['Content-Type'] = 'text/xml' cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) return self.LIBRARY.generate(books=books, start=start, updated=updated, - total=self.db.count()).render('xml') + total=len(ids)).render('xml') @expose def index(self): - return 'Hello, World!' + 'The / URL' + return self.static('index.html') @expose def get(self, what, id): + 'Serves files, covers, thumbnails from the calibre database' try: id = int(id) except ValueError: - raise cherrypy.HTTPError(400, 'id:%s not an integer'%id) + id = id.rpartition('_')[-1].partition('.')[0] + match = re.search(r'\d+', id) + if not match: + raise cherrypy.HTTPError(400, 'id:%s not an integer'%id) + id = int(match.group()) if not self.db.has_id(id): raise cherrypy.HTTPError(400, 'id:%d does not exist in database'%id) if what == 'thumb': @@ -242,6 +262,31 @@ class LibraryServer(object): return self.get_cover(id) return self.get_format(id, what) + @expose + def static(self, name): + 'Serves static content' + name = name.lower() + cherrypy.response.headers['Content-Type'] = { + 'js' : 'text/javascript', + 'css' : 'text/css', + 'png' : 'image/png', + 'gif' : 'image/gif', + 'html' : 'text/html', + '' : 'application/octet-stream', + }[name.rpartition('.')[-1].lower()] + cherrypy.response.headers['Last-Modified'] = self.last_modified(build_time) + if self.opts.develop and name in ('gui.js', 'gui.css', 'index.html'): + path = os.path.join(os.path.dirname(__file__), 'static', name) + lm = datetime.fromtimestamp(os.stat(path).st_mtime) + cherrypy.response.headers['Last-Modified'] = self.last_modified(lm) + return open(path, 'rb').read() + else: + if server_resources.has_key(name): + return server_resources[name] + raise cherrypy.HTTPError(404, '%s not found'%name) + + + def config(defaults=None): desc=_('Settings to control the calibre content server') c = Config('server', desc) if defaults is None else StringConfig(defaults, desc) @@ -256,7 +301,7 @@ def config(defaults=None): help=_('The hostname of the machine the server is running on. Used when generating the stanza feeds. Default is %default')) c.add_opt('develop', ['--develop'], default=False, - help='Development mode. Server automatically restarts on file changes.') + help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.') return c def option_parser(): @@ -267,7 +312,6 @@ def main(args=sys.argv): opts, args = parser.parse_args(args) cherrypy.log.screen = True from calibre.utils.config import prefs - from calibre.library.database2 import LibraryDatabase2 db = LibraryDatabase2(prefs['library_path'], row_factory=True) server = LibraryServer(db, opts) server.start() diff --git a/src/calibre/library/static/bg_search_box.png b/src/calibre/library/static/bg_search_box.png new file mode 100644 index 0000000000000000000000000000000000000000..83f87e42b96f01122ac32d4d791ada9148391fd8 GIT binary patch literal 471 zcmeAS@N?(olHy`uVBq!ia0vp^CxKX=gBeIx)l0Ghse}NZ5Z49w8!9jPJoj$}XP)lQKW~5c?%lLA#{d8S2O2;O zkX&@^I#3;Ffk$L9koEv$x0Bg+Ai=T%$8;bKb{WIF{6DXOoMoOajv*0;-`?=%Yf#{E zy~y-QION4Yac!$o4NvdO(&86{=l)<2G(NJgK=6};LeFuFZtnECjp0nc-D*$Y-}bjs z@um*Hq)4$+!(zK+w{zkh(|&bH-V|97EN{3x?x=@U%oXz$Pn0KqE?wCYsr7KW*N>$_ zbt_xqflQ6gDf{oMrA=HB6t+m2Bl2C1w$Zha`-e7jKYl6v)$8x_C;g0-su51P+~LlW z9n#Ik>#p2g!zToEv1*BHL`iZ{YGO&MZVHfKFfuT(&^0vFH82V>GO{u>vobK#HL$QU zFfcx028t1|hTQy=%(P0l26Lbapq>yz3oAntD-$DK19PB;^DG+LKn)C@u6{1-oD!M< Dg4CzT literal 0 HcmV?d00001 diff --git a/src/calibre/library/static/btn_search_box.png b/src/calibre/library/static/btn_search_box.png new file mode 100644 index 0000000000000000000000000000000000000000..00302ed5eb49dbdff6ecf002cefdc14e7e44a06f GIT binary patch literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^(m*W1!VDyT=FL6<;sp4FxK6!Ylz-m-=<7}M@7MqT z|3B@FF;E5wx`dLa0V&P`kH}&m?E%JaC$sH9f@KAc=|CE6BE!EM*ExWkG*1`D5Q)pl z1%U#*txSxf>}<~iSUAcO0$d-pX&G!#DwdRRnYfdQ=fs{13(O#u=NMg|5Jx`u|j21X%9MplOARwm}U1{PKZ2LH?>s$d#& b^HVa@DhX&<*rw+P)WG2B>gTe~DWM4fpkrb| literal 0 HcmV?d00001 diff --git a/src/calibre/library/static/calibre.png b/src/calibre/library/static/calibre.png new file mode 100644 index 0000000000000000000000000000000000000000..f42e4926cab58d97b12c7864a22bee74a6ae8047 GIT binary patch literal 27150 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:(this=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;} +var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);} +if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);} +if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);} +if(x.hour||x.hours){this.addHours(x.hour||x.hours);} +if(x.month||x.months){this.addMonths(x.month||x.months);} +if(x.year||x.years){this.addYears(x.year||x.years);} +if(x.day||x.days){this.addDays(x.day||x.days);} +return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(valuemax){throw new RangeError(value+" is not a valid value for "+name+".");} +return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;} +if(!x.second&&x.second!==0){x.second=-1;} +if(!x.minute&&x.minute!==0){x.minute=-1;} +if(!x.hour&&x.hour!==0){x.hour=-1;} +if(!x.day&&x.day!==0){x.day=-1;} +if(!x.month&&x.month!==0){x.month=-1;} +if(!x.year&&x.year!==0){x.year=-1;} +if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());} +if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());} +if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());} +if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());} +if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());} +if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());} +if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());} +if(x.timezone){this.setTimezone(x.timezone);} +if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);} +return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;} +var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}} +return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();}; +Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;} +return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;} +if(!last&&q[1].length===0){last=true;} +if(!last){var qx=[];for(var j=0;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} +if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} +if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");} +var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});} +return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;} +for(var i=0;ibK-%Htz|9I9N{=$E5*P_GOygH4s nx=Uv|Mh5FHZQ0!_@_|*&M0{o3g)P^B4r1_h^>bP0l+XkK^JiFr literal 0 HcmV?d00001 diff --git a/src/calibre/library/static/gui.css b/src/calibre/library/static/gui.css new file mode 100644 index 0000000000..10446d53ea --- /dev/null +++ b/src/calibre/library/static/gui.css @@ -0,0 +1,147 @@ +body { + background-color: white; +} + +#banner { + position: absolute; + left: 5px; top: 0px; +} + +/* +Search bar +*/ +#search_box { + width: 201px; + height: 31px; + background: url(/static/bg_search_box.png); + top: 5px; right: 20px; + position: absolute; +} +#search_box #s { + float: left; + padding: 0; + margin: 6px 0 0 6px; + border-width: 0px; + font-size: 16px; + width: 159px; + background: transparent; +} +#search_box #go { + float: right; + margin: 3px 4px 0 0; +} + +/* +Count bar +*/ +#count_bar { + position: absolute; + right: 30px; + top: 80px; + font-size:smaller; + padding-bottom: 5px; +} + +#count_bar * img { + cursor: pointer; +} + +#count { cursor: default;} + +/* +Styles for the book list +*/ +#main { + width:95%; + overflow: auto; + border: solid thin black; + position: absolute; + top: 115px; left: 10px; + z-index: 1; +} + +table#book_list thead tr td { + width: 100%; + padding-right: 1em; padding-left: 1em; + text-align: center; + font-weight: bold; + font-size: 130%; + border-bottom: thick solid black; + border-top: thick solid black; + cursor: pointer; + font-family: serif; + padding-top: 0.5ex; padding-bottom: 0.5ex; +} + +table#book_list tbody tr td { + padding-right: 1em; padding-left: 1em; + /*border-bottom: thin solid black;*/ + padding-bottom: 0.7ex; padding-top: 0.7ex; + margin: 0pt; + cursor: pointer; + +} + +table#book_list * .sort_indicator { + visibility:hidden; + color: #9f9f9f; +} + +table#book_list * .rating { + color: #3fbbe4; +} + +table#book_list * span.subtitle { + font-size: smaller; +} + +table#book_list * a.format { + text-decoration: none; + color: blue; + font-family: monospace; +} + +table#book_list * a.format:hover { + color: red; +} + +table#book_list * a.format:visited { + color: blue; +} + +table#book_list * .comments { + font-size: smaller; + display: none; +} +/* +Loading message +*/ +#loading { + top: 10px; left: 10px; + position: absolute; + font-size: 160%; font-family: monospace; + text-align: center; + visibility: hidden; + z-index: 10000; + background-color: #aaaaaa; + opacity: 0.8; + +} + +#loading div { + top: 50%; position: relative; +} + +#cover_pane { + overflow: auto; + position: absolute; + visibility: hidden; + text-align: right; + z-index: 2; + margin: 0pt; padding: 0pt; border-width: 0pt; +} + +#cover_pane img { + border: solid thin black; + z-index: 10; +} diff --git a/src/calibre/library/static/gui.js b/src/calibre/library/static/gui.js new file mode 100644 index 0000000000..c2d9cbcab8 --- /dev/null +++ b/src/calibre/library/static/gui.js @@ -0,0 +1,293 @@ +/* COLUMNS */ +var cmap = ['title', 'authors', 'rating', 'date', 'series']; +/* COLUMNS END */ +var column_titles = { + 'title' : 'Title', + 'authors' : 'Author(s)', + 'rating' : 'Rating', + 'date' : 'Date', + 'tags' : 'Tags', + 'series' : 'Series', +} + +String.prototype.format = function() { + var pattern = /\{\d+\}/g; + var args = arguments; + return this.replace(pattern, function(capture){ return args[capture.match(/\d+/)]; }); +} + +var last_search = ''; +var last_sort = null; +var last_sort_order = null; +var last_start = 0; +var last_num = 20; +var total = 0; +var current_library_request = null; + +////////////////////////////// GET BOOK LIST ////////////////////////////// + +var LIBRARY_FETCH_TIMEOUT = 10000; // milliseconds + +function create_table_headers() { + var thead = $('table#book_list thead tr'); + var titles = ''; + for (i = 0; i < cmap.length; i++) { + titles += '{0} ' + .format(column_titles[cmap[i]], cmap[i]); + } + thead.html(titles); +} + + +function format_url(format, id, title) { + return '/get/'+format.toLowerCase() + '/'+title + '_' + id+'.'+format.toLowerCase(); +} + +function render_book(book) { + // Render title cell + var title = '{0}'.format(book.attr("title")) + '
'; + var id = book.attr("id"); + var comments = $.trim(book.text()).replace(/\n\n/, '
'); + var formats = new Array(); + var size = (parseFloat(book.attr('size'))/(1024*1024)).toFixed(1); + var tags = book.attr('tags').replace(/,/g, ', '); + formats = book.attr("formats").split(","); + if (formats.length > 0) { + for (i=0; i < formats.length; i++) { + title += ''+formats[i]+', '; + } + title = title.slice(0, title.length-2); + title += ' ({0} MB) '.format(size); + } + if (tags) title += '[{0}]'.format(tags); + title += '
'.format(id); + title += '

{0}

'.format(comments) + // Render authors cell + var _authors = new Array(); + var authors = ''; + _authors = book.attr('authors').split('|'); + for (i = 0; i < _authors.length; i++) { + authors += jQuery.trim(_authors[i]).replace(/ /g, ' ')+'
'; + } + if (authors) { authors = authors.slice(0, authors.length-6); } + + // Render rating cell + var _rating = parseFloat(book.attr('rating'))/2.; + var rating = ''; + for (i = 0; i < _rating; i++) { rating += '★'} + + // Render date cell + var _date = Date.parseExact(book.attr('timestamp'), 'yyyy/MM/dd HH:mm:ss'); + var date = _date.toString('d MMM yyyy').replace(/ /g, ' '); + + // Render series cell + var series = book.attr("series") + if (series) { + series += ' [{0}]'.format(book.attr('series_index')); + } + + var cells = { + 'title' : title, + 'authors' : authors, + 'rating' : rating, + 'date' : date, + 'series' : series + }; + + var row = ''; + for (i = 0; i < cmap.length; i++) { + row += '{1}'.format(cmap[i], cells[cmap[i]]); + } + return '{1}'.format(id, row); +} + +function fetch_library_books(start, num, timeout, sort, order, search) { + // null, 0, false are False + data = {"start":start+'', "num":num+''}; + if (sort) { data["sort"] = sort; } + if (search) { data["search"] = search; } + if (order) { data['order'] = order; } + last_num = num; + last_start = start; + last_search = search; + last_sort = sort; + last_sort_order = order; + + if (current_library_request != null) { + current_library_request.abort(); + current_library_request = null; + } + + $('#loading').css('visibility', 'visible'); + + current_library_request = $.ajax({ + type: "GET", + url: "/library", + data: data, + cache: false, + timeout: timeout, //milliseconds + dataType: "xml", + + error : function(XMLHttpRequest, textStatus, errorThrown) { + alert('Error: '+textStatus+'\n\n'+errorThrown); + }, + + success : function(xml, textStatus) { + var library = $(xml).find('library'); + total = parseInt(library.attr('total')); + var num = parseInt(library.attr('num')); + var start = parseInt(library.attr('start')); + update_count_bar(start, num, total); + var display = ''; + library.find('book').each( function() { + var book = $(this); + var row = render_book(book); + display += row+'\n\n'; + }); + $("#book_list tbody").html(display); + $("#book_list tbody tr").mouseover(function() { + var row = $(this); + var cover = row.find('img').attr('src'); + row.css('background-color', "#fff2a8"); + row.find('.comments').css('display', 'inherit'); + $('#cover_pane img').attr('src', cover); + $('#cover_pane').css('visibility', 'visible'); + row.bind('mouseout', function(){ + row.css('background-color', "white"); + row.find('.comments').css('display', 'none'); + $('#book_list tbody tr:even()').css('background-color', '#eeeeee'); + row.unbind('mouseout'); + }); + }); + $('#book_list').mouseout(function(){ + $('#cover_pane').css('visibility', 'hidden') + }); + + layout(); + $('#book_list tbody tr:even()').css('background-color', '#eeeeee'); + }, + + complete : function(XMLHttpRequest, textStatus) { + current_library_request = null; + document.getElementById('main').scrollTop = 0; + $('#loading').css('visibility', 'hidden'); + } + + }); + +} + + +////////////////////////////// COUNT BAR ////////////////////////////// + +function update_count_bar(start, num, total) { + var cb = $('#count_bar'); + cb.find('#count').html('Books {0} to {1} of {2}'.format(start+1, start+num, total)); + var left = cb.find('#left'); + left.css('opacity', (start <= 0) ? 0.3 : 1); + var right = cb.find('#right'); + right.css('opacity', (start + num >= total) ? 0.3 : 1); + +} + +function setup_count_bar() { + $('#count_bar * img:eq(0)').click(function(){ + if (last_start > 0) { + fetch_library_books(0, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, last_search); + } + }); + + $('#count_bar * img:eq(1)').click(function(){ + if (last_start > 0) { + var new_start = last_start - last_num; + if (new_start < 0) { + new_start = 0; + } + fetch_library_books(new_start, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, last_search); + } + }); + + $('#count_bar * img:eq(2)').click(function(){ + if (last_start + last_num < total) { + var new_start = last_start + last_num; + fetch_library_books(new_start, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, last_search); + } + }); + + $('#count_bar * img:eq(3)').click(function(){ + if (total - last_num > 0) { + fetch_library_books(total - last_num, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, last_search); + } + }); +} + +////////////////////////////// SEARCH ///////////////////////////////////////// + +function search() { + var search = $.trim($('#search_box * #s').val()); + fetch_library_books(last_start, last_num, LIBRARY_FETCH_TIMEOUT, + last_sort, last_sort_order, search); +} + + +/////////////////////////// SORTING ///////////////////////////////////// + +function setup_sorting() { + $('table#book_list thead tr td').mouseover(function() { + this.style.backgroundColor = "#fff2a8"; + }); + + $('table#book_list thead tr td').mouseout(function() { + this.style.backgroundColor = "inherit"; + }); + + for (i = 0; i < cmap.length; i++) { + $('table#book_list span#{0}_sort'.format(cmap[i])).parent().click(function() { + var sort_indicator = $($(this).find('span')); + var cell = $(sort_indicator.parent()); + var id = sort_indicator.attr("id"); + var col = id.slice(0, id.indexOf("_")); + var order = 'ascending'; + var html = '↑'; + + if (sort_indicator.html() == '↑') { + order = 'descending'; html = '↓'; + } + sort_indicator.html(html); + $('#book_list * .sort_indicator').css('visibility', 'hidden'); + sort_indicator.css('visibility', 'visible'); + fetch_library_books(last_start, last_num, LIBRARY_FETCH_TIMEOUT, col, order, last_search); + }); + } +} + +///////////////////////// STARTUP //////////////////////////////////////// + +function layout() { + var main = $('#main'); var cb = $('#count_bar'); + main.css('height', ($(window).height() - main.offset().top - 20)+'px') + main.css('width', ($(window).width() - main.offset().left - 15)+'px') + cb.css('right', '20px'); + cb.css('top', (main.offset().top - cb.height()-5)+'px'); + $('#loading').css('height', ($(window).height()-20)+'px'); + $('#loading').css('width', ($(window).width()-20)+'px'); + var cover = $('#cover_pane'); + cover.css('width', (main.width()/2.0)+'px') + cover.css('height', main.height()+'px') + cover.css('left', (main.offset().left + main.width()/2.0)+'px'); + cover.css('top', main.offset().top+'px'); +} + +$(function() { + // document is ready + create_table_headers(); + + // Setup widgets + setup_sorting(); + setup_count_bar(); + $('#search_box * #s').val(''); + $(window).resize(layout); + + $($('#book_list * span#date_sort').parent()).click(); + +}); \ No newline at end of file diff --git a/src/calibre/library/static/index.html b/src/calibre/library/static/index.html new file mode 100644 index 0000000000..45c902a080 --- /dev/null +++ b/src/calibre/library/static/index.html @@ -0,0 +1,49 @@ + + + + + calibre library + + + + + + + + + + + +
+ Show first set of books Show previous set of books              Show first set of books Show previous set of books +
+ +
+ + + + + + + +
+
+ +
+
+ Loading... Loading… +
+
+ +
+ Cover +
+ + diff --git a/src/calibre/library/static/last.png b/src/calibre/library/static/last.png new file mode 100644 index 0000000000000000000000000000000000000000..3967fa4e529b8b67d8ba9d545fc1e12bd1a3b595 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhY)RhkE)4%caKYZ?lYt_f1s;*b zKvlvZ%*Zfnjs#GUy~NYkmHisGAQ!8$t7FIvpiqyei(^Q|t={vyc@H@Vv^?Z@77t)? zIp?W)z)(?k1_vkSPUflt#+DL?2NRey8atkNoSS6%^Pq~g)Zg8+=C%s_S?!wLwP;1b zLF+^ylM4{B` ti*ctPA9~Qt`^`4sGW+w7MXP+X_j%ls-2Bfw=n~Lf44$rjF6*2UngC+lTJZn? literal 0 HcmV?d00001 diff --git a/src/calibre/library/static/next.png b/src/calibre/library/static/next.png new file mode 100644 index 0000000000000000000000000000000000000000..fed82945ce04fbd483f4955a37f8464e6e750c99 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhEa{HEjtmUfZd~z?Faq)=OI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i>J^>lFzskoK&=l_3u=8X&r4HAi6 zhYugV-58=RP=9b)!P8Syc^DXvv>aK_@JQNW4(E|GH%u5<1casHr8<@~ew0)wa|-7X zaPYC1lg@CYy0L;=VD$kOMy8I=hWUFMDZl2)i=5@O&`5R>83 zXDC#f(#G7tkaQ?Q&gTe~ HDWM4fhB;FP literal 0 HcmV?d00001 diff --git a/src/calibre/library/static/previous.png b/src/calibre/library/static/previous.png new file mode 100644 index 0000000000000000000000000000000000000000..457d873c55134bf80785fc65c5a99ec2d97a4e79 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhEa{HEjtmUfZd~z?Faq)=OI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i>J@N{tuskoK&=l_3u=8X&r4G|75 z6%`hGm2wK};&v9Ls&+67IQUqI7%~^C3EXFR#5F^~fnj4p;0u-`dmVBb^cV#kPIQGf zR4@rtKQ0n>U^pmpn&G37g4xmMjtvY=JAZNt+-4{g7WjW?sf0qq1Y3@v5dI_29BLS6 zCp9n}5|a{NAZ3ul$e|G9*nU;4fgwprAk!fF0E5B-hP&BfpAPMby#RD0gQu&X%Q~lo FCIAteRUQBU literal 0 HcmV?d00001