Merge from trunk

This commit is contained in:
Charles Haley
2011-11-03 10:31:54 +01:00
28 changed files with 452 additions and 64 deletions
+1 -1
View File
@@ -1465,7 +1465,7 @@ class StoreVirtualoStore(StoreBase):
actual_plugin = 'calibre.gui2.store.stores.virtualo_plugin:VirtualoStore'
headquarters = 'PL'
formats = ['EPUB', 'PDF']
formats = ['EPUB', 'MOBI', 'PDF']
class StoreWaterstonesUKStore(StoreBase):
name = 'Waterstones UK'
+10 -4
View File
@@ -36,7 +36,8 @@ class ANDROID(USBMS):
0xca2 : [0x100, 0x0227, 0x0226, 0x222],
0xca3 : [0x100, 0x0227, 0x0226, 0x222],
0xca4 : [0x100, 0x0227, 0x0226, 0x222],
0xca9 : [0x100, 0x0227, 0x0226, 0x222]
0xca9 : [0x100, 0x0227, 0x0226, 0x222],
0xcac : [0x100, 0x0227, 0x0226, 0x222],
},
# Eken
@@ -138,8 +139,12 @@ class ANDROID(USBMS):
# Advent
0x0955 : { 0x7100 : [0x9999] }, # This is the same as the Notion Ink Adam
# Kobo
0x2237: { 0x2208 : [0x0226] },
}
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books']
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books',
'sdcard/ebooks']
EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of directories to '
'send e-books to on the device. The first one that exists will '
'be used')
@@ -149,7 +154,7 @@ class ANDROID(USBMS):
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE', 'ARCHOS',
'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC', 'LGE', 'NVIDIA',
'GENERIC-', 'ZTE', 'MID', 'QUALCOMM', 'PANDIGIT', 'HYSTON',
'VIZIO', 'GOOGLE', 'FREESCAL']
'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC']
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
@@ -160,7 +165,8 @@ class ANDROID(USBMS):
'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK',
'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612',
'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A',
'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI']
'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI',
'UMS', '.K080']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
+31 -17
View File
@@ -61,18 +61,25 @@ class KOBO(USBMS):
' ebook file itself. With this option, calibre will send a '
'separate cover image to the reader, useful if you '
'have modified the cover.'),
_('Upload Black and White Covers')
_('Upload Black and White Covers'),
_('Show expired books') +
':::'+_('A bug in an earlier version left non kepubs book records'
' in the datbase. With this option Calibre will show the '
'expired records and allow you to delete them with '
'the new delete logic.'),
]
EXTRA_CUSTOMIZATION_DEFAULT = [
', '.join(['tags']),
True,
True,
True
]
OPT_COLLECTIONS = 0
OPT_UPLOAD_COVERS = 1
OPT_UPLOAD_GRAYSCALE_COVERS = 2
OPT_SHOW_EXPIRED_BOOK_RECORDS = 3
def initialize(self):
USBMS.initialize(self)
@@ -232,18 +239,23 @@ class KOBO(USBMS):
self.dbversion = result[0]
debug_print("Database Version: ", self.dbversion)
opts = self.settings()
if self.dbversion >= 16:
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility from content where ' \
'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)'
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
elif self.dbversion < 16 and self.dbversion >= 14:
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, "-1" as Accessibility from content where ' \
'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)'
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
elif self.dbversion < 14 and self.dbversion >= 8:
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
'ImageID, ReadStatus, ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility from content where ' \
'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)'
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
else:
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility from content where BookID is Null'
@@ -343,21 +355,23 @@ class KOBO(USBMS):
# Kobo does not delete the Book row (ie the row where the BookID is Null)
# The next server sync should remove the row
cursor.execute('delete from content where BookID = ?', t)
try:
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0, ___ExpirationStatus=3 ' \
'where BookID is Null and ContentID =?',t)
except Exception as e:
if 'no such column' not in str(e):
raise
if ContentType == 6:
try:
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0, ___ExpirationStatus=3 ' \
'where BookID is Null and ContentID =?',t)
except Exception as e:
if 'no such column' not in str(e):
raise
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\' ' \
'where BookID is Null and ContentID =?',t)
try:
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \
'where BookID is Null and ContentID =?',t)
except Exception as e:
if 'no such column' not in str(e):
raise
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\' ' \
'where BookID is Null and ContentID =?',t)
else:
cursor.execute('delete from content where BookID is Null and ContentID =?',t)
connection.commit()
+52
View File
@@ -1,3 +1,4 @@
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
@@ -9,6 +10,8 @@
#include <fcntl.h>
#define MARKER ".created_by_calibre_mount_helper"
#define DEV "/dev/"
#define MEDIA "/media/"
#define False 0
#define True 1
@@ -33,6 +36,43 @@ void ensure_root() {
}
}
int check_args(const char *dev, const char *mp) {
char buffer[PATH_MAX+1];
if (dev == NULL || strlen(dev) < strlen(DEV) || mp == NULL || strlen(mp) < strlen(MEDIA)) {
fprintf(stderr, "Invalid arguments\n");
return False;
}
if (exists(mp)) {
if (realpath(mp, buffer) == NULL) {
fprintf(stderr, "Unable to resolve mount path\n");
return False;
}
if (strncmp(MEDIA, buffer, strlen(MEDIA)) != 0) {
fprintf(stderr, "Trying to operate on a mount point not under /media is not allowed\n");
return False;
}
}
if (strncmp(MEDIA, mp, strlen(MEDIA)) != 0) {
fprintf(stderr, "Trying to operate on a mount point not under /media is not allowed\n");
return False;
}
if (realpath(dev, buffer) == NULL) {
fprintf(stderr, "Unable to resolve dev path\n");
return False;
}
if (strncmp(DEV, buffer, strlen(DEV)) != 0) {
fprintf(stderr, "Trying to operate on a dev node not under /dev\n");
return False;
}
return True;
}
int do_mount(const char *dev, const char *mp) {
char options[1000], marker[2000];
#ifdef __NetBSD__
@@ -44,6 +84,7 @@ int do_mount(const char *dev, const char *mp) {
fprintf(stderr, "Specified device node does not exist\n");
return EXIT_FAILURE;
}
if (!exists(mp)) {
if (mkdir(mp, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != 0) {
errsv = errno;
@@ -211,6 +252,17 @@ int main(int argc, char** argv)
}
action = argv[1]; dev = argv[2]; mp = argv[3];
/* Ensure that PATH only contains system directories to prevent execution of
arbitrary executables as root */
if (setenv("PATH",
"/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin\0",
1) != 0) {
fprintf(stderr, "Failed to restrict PATH env var, aborting.\n");
exit(EXIT_FAILURE);
}
if (!check_args(dev, mp)) exit(EXIT_FAILURE);
if (strncmp(action, "mount", 5) == 0) {
status = do_mount(dev, mp);
} else if (strncmp(action, "eject", 5) == 0) {
+9 -2
View File
@@ -560,14 +560,21 @@ class PRST1(USBMS):
cursor = connection.cursor()
periodical_schema = \
"'http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0'"
# Setting this to the SONY periodical schema apparently causes errors
# with some periodicals, therefore set it to null, since the special
# periodical navigation doesn't work anyway.
periodical_schema = 'null'
query = '''
UPDATE books
SET conforms_to = 'http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0',
SET conforms_to = %s,
periodical_name = ?,
description = ?,
publication_date = ?
WHERE _id = ?
'''
'''%periodical_schema
t = (name, None, pubdate, book.bookId,)
cursor.execute(query, t)
+4 -2
View File
@@ -109,14 +109,16 @@ class HTMLFile(object):
try:
with open(self.path, 'rb') as f:
src = f.read()
src = f.read(4096)
self.is_binary = level > 0 and not bool(self.HTML_PAT.search(src))
if not self.is_binary:
src += f.read()
except IOError as err:
msg = 'Could not read from file: %s with error: %s'%(self.path, as_unicode(err))
if level == 0:
raise IOError(msg)
raise IgnoreFile(msg, err.errno)
self.is_binary = level > 0 and not bool(self.HTML_PAT.search(src[:4096]))
if not self.is_binary:
if not encoding:
encoding = xml_to_unicode(src[:4096], verbose=verbose)[-1]
+3 -1
View File
@@ -15,7 +15,6 @@ from calibre.customize.conversion import OutputFormatPlugin, \
OptionRecommendation
from calibre.ebooks.metadata.opf2 import OPF
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks.pdf.writer import PDFWriter, ImagePDFWriter, PDFMetadata
from calibre.ebooks.pdf.pageoptions import UNITS, PAPER_SIZES, \
ORIENTATIONS
@@ -90,6 +89,7 @@ class PDFOutput(OutputFormatPlugin):
self.convert_text(oeb_book)
def convert_images(self, images):
from calibre.ebooks.pdf.writer import ImagePDFWriter
self.write(ImagePDFWriter, images)
def get_cover_data(self):
@@ -105,6 +105,7 @@ class PDFOutput(OutputFormatPlugin):
self.cover_data = None
def convert_text(self, oeb_book):
from calibre.ebooks.pdf.writer import PDFWriter
self.log.debug('Serializing oeb input to disk for processing...')
self.get_cover_data()
@@ -119,6 +120,7 @@ class PDFOutput(OutputFormatPlugin):
self.write(PDFWriter, [s.path for s in opf.spine])
def write(self, Writer, items):
from calibre.ebooks.pdf.writer import PDFMetadata
writer = Writer(self.opts, self.log, cover_data=self.cover_data)
close = False
+3 -3
View File
@@ -587,7 +587,6 @@ class CoversModel(QAbstractListModel): # {{{
return 1
return pmap.width()*pmap.height()
def clear_failed(self):
good = []
pmap = {}
@@ -729,7 +728,8 @@ class CoversWidget(QWidget): # {{{
except Empty:
break
self.covers_view.clear_failed()
if self.continue_processing:
self.covers_view.clear_failed()
if self.worker.error is not None:
error_dialog(self, _('Download failed'),
@@ -759,7 +759,7 @@ class CoversWidget(QWidget): # {{{
self.continue_processing = False
def cancel(self):
self.continue_processing = False
self.cleanup()
self.abort.set()
def cover_pixmap(self):
@@ -34,10 +34,11 @@ class VirtualoStore(BasicStoreConfig, StorePlugin):
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://virtualo.pl/c2/?q=' + urllib.quote(query)
def search(self, query, max_results=12, timeout=60):
url = 'http://virtualo.pl/?q=' + urllib.quote(query) + '&f=format_id:4,6,3'
br = browser()
drm_pattern = re.compile("ADE")
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
@@ -46,26 +47,28 @@ class VirtualoStore(BasicStoreConfig, StorePlugin):
if counter <= 0:
break
id = ''.join(data.xpath('.//table/tr[2]/td[1]/a/@href'))
id = ''.join(data.xpath('.//table/tr[1]/td[1]/a/@href'))
if not id:
continue
price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()'))
cover_url = ''.join(data.xpath('.//table/tr[2]/td[1]/a/img/@src'))
cover_url = ''.join(data.xpath('.//table/tr[1]/td[1]/a/img/@src'))
title = ''.join(data.xpath('.//div[@class="title"]/a/text()'))
author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()'))
formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()'))
formats = re.sub(r'(, )?ONLINE(, )?', '', formats)
drm = drm_pattern.search(formats)
formats = re.sub(r'(, )?ADE(, )?', '', formats)
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.cover_url = cover_url.split('.jpg')[0] + '.jpg'
s.title = title.strip() + ' ' + formats
s.author = author.strip()
s.price = price + ''
s.detail_item = 'http://virtualo.pl' + id.strip()
s.detail_item = 'http://virtualo.pl' + id.strip().split('http://')[0]
s.formats = formats.upper().strip()
s.drm = SearchResult.DRM_UNKNOWN
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
yield s
+1 -1
View File
@@ -156,7 +156,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS,
body.append(HR())
body.append(DIV(
A(_('Switch to the full interface (non-mobile interface)'),
href="/browse",
href=prefix+"/browse",
style="text-decoration: none; color: blue",
title=_('The full interface gives you many more features, '
'but it may not work well on a small screen')),
+1 -1
View File
@@ -259,7 +259,7 @@ The following functions are available in addition to those described in single-f
yy : the year as two digit number (00 to 99).
yyyy : the year as four digit number.
h : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm)
hh : the minutes with a leading 0 (00 to 11 or 00 to 23, depending on am/pm)
hh : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm)
m : the minutes without a leading 0 (0 to 59)
mm : the minutes with a leading 0 (00 to 59)
s : the seconds without a leading 0 (0 to 59)
+4
View File
@@ -194,4 +194,8 @@ class SpooledTemporaryFile(tempfile.SpooledTemporaryFile):
tempfile.SpooledTemporaryFile.__init__(self, max_size=max_size, suffix=suffix,
prefix=prefix, dir=dir, mode=mode, bufsize=bufsize)
def better_mktemp(*args, **kwargs):
fd, path = tempfile.mkstemp(*args, **kwargs)
os.close(fd)
return path
+3 -1
View File
@@ -235,7 +235,9 @@ def format_date(dt, format, assume_utc=False, as_utc=False):
return ''
return function_index[s[0]](s)
return re.sub('(s{1,2})|(m{1,2})|(h{1,2})|(ap)|(AP)|(d{1,4}|M{1,4}|(?:yyyy|yy))', repl_func, format)
return re.sub(
'(s{1,2})|(m{1,2})|(h{1,2})|(ap)|(AP)|(d{1,4}|M{1,4}|(?:yyyy|yy))',
repl_func, format)
def replace_months(datestr, clang):
# Replace months by english equivalent for parse_date
+1 -1
View File
@@ -748,7 +748,7 @@ class BuiltinFormatDate(BuiltinFormatterFunction):
'yy : the year as two digit number (00 to 99). '
'yyyy : the year as four digit number. '
'h : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm) '
'hh : the minutes with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) '
'hh : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) '
'm : the minutes without a leading 0 (0 to 59) '
'mm : the minutes with a leading 0 (00 to 59) '
's : the seconds without a leading 0 (0 to 59) '
+1 -1
View File
@@ -121,7 +121,7 @@ _extra_lang_codes = {
'en_TH' : _('English (Thailand)'),
'en_TR' : _('English (Turkey)'),
'en_CY' : _('English (Cyprus)'),
'en_CZ' : _('English (Czechoslovakia)'),
'en_CZ' : _('English (Czech Republic)'),
'en_PK' : _('English (Pakistan)'),
'en_HR' : _('English (Croatia)'),
'en_ID' : _('English (Indonesia)'),