IGN: Almost working read only web interface

This commit is contained in:
Kovid Goyal 2008-11-03 14:32:18 -08:00
parent 65d1083f9d
commit 57d5bbd82d
14 changed files with 686 additions and 24 deletions

View File

@ -2,7 +2,7 @@ from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
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'

View File

@ -103,6 +103,19 @@
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5" >
<property name="maximumSize" >
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="text" >
<string>See the &lt;a href="http://calibre.kovidgoyal.net/user_manual/gui.html#the-search-interface">User Manual&lt;/a> for more help</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >

View File

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,104 @@
/**
* Version: 1.0 Alpha-1
* Build Date: 13-Nov-2007
* Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
* License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
* Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
*/
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}};
Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
return-1;};Date.getDayNumberFromName=function(name){var n=Date.CultureInfo.dayNames,m=Date.CultureInfo.abbreviatedDayNames,o=Date.CultureInfo.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
return-1;};Date.isLeapYear=function(year){return(((year%4===0)&&(year%100!==0))||(year%400===0));};Date.getDaysInMonth=function(year,month){return[31,(Date.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};Date.getTimezoneOffset=function(s,dst){return(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST[s.toUpperCase()]:Date.CultureInfo.abbreviatedTimeZoneStandard[s.toUpperCase()];};Date.getTimezoneAbbreviation=function(offset,dst){var n=(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST:Date.CultureInfo.abbreviatedTimeZoneStandard,p;for(p in n){if(n[p]===offset){return p;}}
return null;};Date.prototype.clone=function(){return new Date(this.getTime());};Date.prototype.compareTo=function(date){if(isNaN(this)){throw new Error(this);}
if(date instanceof Date&&!isNaN(date)){return(this>date)?1:(this<date)?-1:0;}else{throw new TypeError(date);}};Date.prototype.equals=function(date){return(this.compareTo(date)===0);};Date.prototype.between=function(start,end){var t=this.getTime();return t>=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(value<min||value>max){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;i<dx.length;i++){$D[dx[i]]=$D[dx[i].substring(0,3)]=df(i);}
var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
return this.moveToMonth(n,this._orient);};};for(var j=0;j<mx.length;j++){$D[mx[j]]=$D[mx[j].substring(0,3)]=mf(j);}
var ef=function(j){return function(){if(j.substring(j.length-1)!="s"){j+="s";}
return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$D[de]=$D[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}}());Date.prototype.toJSONString=function(){return this.toString("yyyy-MM-ddThh:mm:ssZ");};Date.prototype.toShortDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortDatePattern);};Date.prototype.toLongDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.longDatePattern);};Date.prototype.toShortTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortTimePattern);};Date.prototype.toLongTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.longTimePattern);};Date.prototype.getOrdinal=function(){switch(this.getDate()){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};
(function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
break;}
return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
rx.push(r[0]);s=r[1];}
return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
try{r=(px[i].call(this,s));}catch(e){r=null;}
if(r){return r;}}
throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
rx.push(r[0]);s=r[1];}
return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
s=q[1];}
if(!r){throw new $P.Exception(s);}
if(q){throw new $P.Exception(q[1]);}
if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
rx=[[r[0]],r[1]];if(r[1].length>0&&!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;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
if(rx[1].length<best[1].length){best=rx;}
if(best[1].length===0){break;}}
if(best[0].length===0){return best;}
if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
best[1]=q[1];}
return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
return rx;};Date.Grammar={};Date.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=((s.length==3)?Date.getMonthNumberFromName(s):(Number(s)-1));};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<Date.CultureInfo.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];var now=new Date();this.year=now.getFullYear();this.month=now.getMonth();this.day=1;this.hour=0;this.minute=0;this.second=0;for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
this.hour=(this.meridian=="p"&&this.hour<13)?this.hour+12:this.hour;if(this.day>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;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
if(this.now){return new Date();}
var today=Date.today();var method=null;var expression=!!(this.days!=null||this.orient||this.operator);if(expression){var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(this.weekday){this.unit="day";gap=(Date.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
if(this.month){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
if(!this.unit){this.unit="day";}
if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
if(this.unit=="week"){this.unit="day";this.value=this.value*7;}
this[this.unit+"s"]=this.value*orient;}
return today.add(this);}else{if(this.meridian&&this.hour){this.hour=(this.hour<13&&this.meridian=="p")?this.hour+12:this.hour;}
if(this.weekday&&!this.day){this.day=(today.addDays((Date.getDayNumberFromName(this.weekday)-today.getDay()))).getDate();}
if(this.month&&!this.day){this.day=1;}
return today.set(this);}}};var _=Date.Parsing.Operators,g=Date.Grammar,t=Date.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=Date.CultureInfo.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
fn=_C[keys]=_.any.apply(null,px);}
return fn;};g.ctoken2=function(key){return _.rtoken(Date.CultureInfo.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.mm,g.ss],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^(\+|\-)?\s*\d\d\d\d?/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^(\+|\-)\s*\d\d\d\d/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[Date.CultureInfo.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw Date.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["yyyy-MM-ddTHH:mm:ss","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
return g._start.call({},s);};}());Date._parse=Date.parse;Date.parse=function(s){var r=null;if(!s){return null;}
try{r=Date.Grammar.start.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};Date.getParseFunction=function(fx){var fn=Date.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return Date.getParseFunction(fx)(s);};

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

View File

@ -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;
}

View File

@ -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 += '<td>{0}&nbsp;<span class="sort_indicator" id="{1}_sort">↑</span></td>'
.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 = '<i>{0}</i>'.format(book.attr("title")) + '<br /><span class="subtitle">';
var id = book.attr("id");
var comments = $.trim(book.text()).replace(/\n\n/, '<br/>');
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 += '<a title="Download in '+formats[i]+' format" class="format" href="'+format_url(formats[i], id, book.attr("title"))+'">'+formats[i]+'</a>, ';
}
title = title.slice(0, title.length-2);
title += '&nbsp;({0} MB)&nbsp;'.format(size);
}
if (tags) title += '[{0}]'.format(tags);
title += '<img style="display:none" alt="" src="/get/cover/{0}" /></span>'.format(id);
title += '<p class="comments">{0}</p>'.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, '&nbsp;')+'<br />';
}
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 += '&#9733;'}
// 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, '&nbsp;');
// Render series cell
var series = book.attr("series")
if (series) {
series += '&nbsp;[{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 += '<td class="{0}">{1}</td>'.format(cmap[i], cells[cmap[i]]);
}
return '<tr id="{0}">{1}</tr>'.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();
});

View File

@ -0,0 +1,49 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" version="XHTML 1.1" xml:lang="en">
<head>
<title>calibre library</title>
<link rel="stylesheet" type="text/css" href="/static/gui.css" charset="utf-8" />
<script type="text/javascript" src="/static/date.js" charset="utf-8"></script>
<script type="text/javascript" src="/static/jquery.js" charset="utf-8"></script>
<script type="text/javascript" src="/static/gui.js" charset="utf-8"></script>
<link rel="icon" href="http://calibre.kovidgoyal.net/chrome/site/favicon.ico" type="image/x-icon" />
</head>
<body>
<div id="banner">
<img src="/static/calibre.png" alt="calibre" />
</div>
<div id="search_box">
<form name="search_form" onsubmit="search();return false;" action="./" method="get">
<input value="" id="s" type="text" />
<input type="image" src="/static/btn_search_box.png" width="27" height="24" id="go" alt="Search" title="Search" />
</form>
</div>
<div id="count_bar">
<span id="left"><img src="/static/first.png" alt="Show first set of books" />&nbsp;<img src="/static/previous.png" alt="Show previous set of books" />&nbsp;</span><span id="count">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;<span id="right"><img src="/static/next.png" alt="Show first set of books" />&nbsp;<img src="/static/last.png" alt="Show previous set of books" /></span>
</div>
<div id="main">
<table id="book_list" summary="Book list" cellspacing="0" cellpadding="0">
<thead>
<tr></tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="loading">
<div>
<img align="top" src="/static/loading.gif" alt="Loading..." />&nbsp;<span id="loading_msg">Loading&hellip;</span>
</div>
</div>
<div id="cover_pane">
<img alt="Cover" src="" />
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B