Merge from trunk

This commit is contained in:
Charles Haley 2011-03-12 09:30:22 +00:00
commit 8bc2d8f8ac
74 changed files with 57649 additions and 45341 deletions

View File

@ -19,6 +19,117 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.7.49
date: 2011-03-11
new features:
- title: "News download: More flexible news downlaod scheduling. You can now schedule by days of the week, days of the month and an interval, which can be as small as an hour for news sources that change rapidly"
- title: "Improved support for dragging and dropping cover images directly from web browsers into calibre."
description: >
"You can drop the images onto the cover in calibre and it will be replaced. Tested on a number of OS/browser combinations, but I am sure there a still a few for which it wont work."
- title: "Add shortcuts of Alt+Left and Alt+Right for the next and previous buttons in the edit metadata dialog."
tickets: [9360]
- title: "When adding a GUI plugin, prompt the user for where the plugin should be displayed"
- title: "Conversion: When using the Level x Table of Contents options, support the case when the level 1,2,3 items are spread over multiple HTML files."
- title: "Support for the Optimus V"
- title: "FB2 Input: Support for tables"
tickets: [9302]
- title: "Display a checkmark/cross next to 'true' and 'false' items in custom columns. Controlled via Preferences->Add a custom column"
- title: "Catalog generation: Reuse cover from existing catalog, allows the use of a custom cover for catalogs"
- title: "When setting covers in calibre, resize to fit within a maximum size of (1200, 1600), to prevent slowdowns due to extra large covers. This size can be controlled via Preferences->Tweaks."
tickets: [9277]
bug fixes:
- title: "Fix long standing bug that caused errors when saving books to disk if the book metadata has certain chinese/russian characters on windows. The fix required some changes to how unicode paths are handled in calibre, so it might have broken something else. If so, please open a ticket."
tickets: [7250]
- title: "Custom recipes: Store custom recipes in the calibre config directory instead of the library database. This allows scheduling of custom recipes to work with multiple libraries. Note that you may have to re-schedule any existing custom recipes."
- title: "Restore the ability to do search and replace on ISBN. Use the 'identifiers' field with type isbn to do this"
- title: "Fix amazon metadata download plugin not working with ISBN-13 and social metadata not downloading if the supplied ISBN 10 is not for an edition available on Amazon"
- title: "Workaround for openlibrary blocking the user agent used by calibre, preventing cover downloads from that site"
- title: "FB2 Output: Add sequence to metadata. Fix bugs with author names. Fix bug where <empty-line/> elements were put inside <p> tags."
- title: "Conversion pipeline: If the input HTML document uses uppercase tag and attribute names, convert them to lowercase"
- title: "RTF Input: Fix space after unicode quote character being incorrectly removed"
tickets: [9343]
- title: "Fix regression that broke the ebook-device command line program in the previous release"
- title: "Fix custom columns with numbers not allowing entry of positive numbers of 64-bit machines"
tickets: [9283]
- title: "Fix regression that caused focus to be lost when editing metadata in the device view"
tickets: [9323]
- title: "CHM Input: If an input encoding is specified, use it rather than trying to detect the encoding of the text in the CHM file."
tickets: [9173]
- title: "Fix regression that caused the viewer to forget its window size and other attributes when launched from within calibre, after calibre is restarted."
tickets: [9326]
- title: "News download: Fix regression that caused the delay parameter in recipes to not actually delay downloads."
tickets: [9332]
- title: "Conversion pipeline: When converting the :first-letter pseudo CSS selector to a <span> follow W3C rules for handling leading punctuation characters."
tickets: [9319]
- title: "Fix regression that caused clicking saved searches in the Tag Browser to not work"
- title: "Comic Input: Fix conversion failing when output profile is set to Tablet Output"
- title: "Replace leading periods in all path components generated by calibre with underscores"
- title: "Search and replace preferences: Prevent very long strings from causing the wizard button to get pushed off the screen"
- title: "Content server: Fix regression that caused various metadata to be missing in the book details view."
ticckets: [8929]
- title: "Apple driver: Ignore invalid EPUBs when sending to iTunes"
improved recipes:
- golem.de
- gulli.de
- La Nacion
- Ming Pao
- evz.ro
- Kompiuterra
- NRC Handelsblad (EPUB)
- The Leduc - Wetaskiwin Pipestone Flyer
new recipes:
- title: "Various Romanian news sources"
author: Silviu Cotoara
- title: "Salt Lake City Tribune"
author: Charles Holbert
- title: "Bay Citizen and Oakland North"
author: noah
- title: "Nikkei Business and JB Press"
author: Ado Nishimura
- title: "El Pais Babelia"
author: oneillpt
- title: "Komchadluek"
author: ballsai
- version: 0.7.48 - version: 0.7.48
date: 2011-03-04 date: 2011-03-04

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.48' __version__ = '0.7.49'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -92,7 +92,7 @@ class TXT2TXTZ(FileTypePlugin):
'containing Markdown or Textile references to images. The referenced ' 'containing Markdown or Textile references to images. The referenced '
'images as well as the TXT file are added to the archive.') 'images as well as the TXT file are added to the archive.')
version = numeric_version version = numeric_version
file_types = set(['txt']) file_types = set(['txt', 'text'])
supported_platforms = ['windows', 'osx', 'linux'] supported_platforms = ['windows', 'osx', 'linux']
on_import = True on_import = True

View File

@ -35,7 +35,7 @@ class ANDROID(USBMS):
# Motorola # Motorola
0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100], 0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100],
0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216], 0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216],
0x4286 : [0x216], 0x42b3 : [0x216] }, 0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216] },
# Sony Ericsson # Sony Ericsson
0xfce : { 0xd12e : [0x0100]}, 0xfce : { 0xd12e : [0x0100]},
@ -96,7 +96,8 @@ class ANDROID(USBMS):
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE', 'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE',
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H', 'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H',
'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD', '7'] 'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD',
'7', 'A956']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7'] 'A70S', 'A101IT', '7']

View File

@ -224,7 +224,7 @@ class TREKSTOR(USBMS):
FORMATS = ['epub', 'txt', 'pdf'] FORMATS = ['epub', 'txt', 'pdf']
VENDOR_ID = [0x1e68] VENDOR_ID = [0x1e68]
PRODUCT_ID = [0x0041] PRODUCT_ID = [0x0041, 0x0042]
BCD = [0x0002] BCD = [0x0002]
EBOOK_DIR_MAIN = 'Ebooks' EBOOK_DIR_MAIN = 'Ebooks'

View File

@ -65,7 +65,6 @@ class TXTInput(InputFormatPlugin):
txt = '' txt = ''
log.debug('Reading text from file...') log.debug('Reading text from file...')
length = 0 length = 0
# [(u'path', mime),]
# Extract content from zip archive. # Extract content from zip archive.
if file_ext == 'txtz': if file_ext == 'txtz':
@ -73,7 +72,7 @@ class TXTInput(InputFormatPlugin):
zf.extractall('.') zf.extractall('.')
for x in walk('.'): for x in walk('.'):
if os.path.splitext(x)[1].lower() == '.txt': if os.path.splitext(x)[1].lower() in ('.txt', '.text'):
with open(x, 'rb') as tf: with open(x, 'rb') as tf:
txt += tf.read() + '\n\n' txt += tf.read() + '\n\n'
else: else:

View File

@ -340,6 +340,7 @@ class FileIconProvider(QFileIconProvider):
'rar' : 'rar', 'rar' : 'rar',
'zip' : 'zip', 'zip' : 'zip',
'txt' : 'txt', 'txt' : 'txt',
'text' : 'txt',
'prc' : 'mobi', 'prc' : 'mobi',
'azw' : 'mobi', 'azw' : 'mobi',
'mobi' : 'mobi', 'mobi' : 'mobi',

View File

@ -11,7 +11,6 @@ from PyQt4.Qt import QWizard, QWizardPage, QIcon, QPixmap, Qt, QThread, \
pyqtSignal pyqtSignal
from calibre.gui2 import error_dialog, choose_dir, gprefs from calibre.gui2 import error_dialog, choose_dir, gprefs
from calibre.constants import filesystem_encoding
from calibre.library.add_to_library import find_folders_under, \ from calibre.library.add_to_library import find_folders_under, \
find_books_in_folder, hash_merge_format_collections find_books_in_folder, hash_merge_format_collections
@ -122,20 +121,19 @@ class WelcomePage(WizardPage, WelcomeWidget):
x = unicode(self.opt_root_folder.text()).strip() x = unicode(self.opt_root_folder.text()).strip()
if not x: if not x:
return None return None
return os.path.abspath(x.encode(filesystem_encoding)) return os.path.abspath(x)
def get_one_per_folder(self): def get_one_per_folder(self):
return self.opt_one_per_folder.isChecked() return self.opt_one_per_folder.isChecked()
def validatePage(self): def validatePage(self):
x = self.get_root_folder() x = self.get_root_folder()
xu = x.decode(filesystem_encoding)
if x and os.access(x, os.R_OK) and os.path.isdir(x): if x and os.access(x, os.R_OK) and os.path.isdir(x):
gprefs['add wizard root folder'] = xu gprefs['add wizard root folder'] = x
gprefs['add wizard one per folder'] = self.get_one_per_folder() gprefs['add wizard one per folder'] = self.get_one_per_folder()
return True return True
error_dialog(self, _('Invalid root folder'), error_dialog(self, _('Invalid root folder'),
xu + _('is not a valid root folder'), show=True) x + _('is not a valid root folder'), show=True)
return False return False
# }}} # }}}

View File

@ -8,7 +8,6 @@ __docformat__ = 'restructuredtext en'
import os import os
from hashlib import sha1 from hashlib import sha1
from calibre.constants import filesystem_encoding
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
def find_folders_under(root, db, add_root=True, # {{{ def find_folders_under(root, db, add_root=True, # {{{
@ -17,21 +16,13 @@ def find_folders_under(root, db, add_root=True, # {{{
Find all folders under the specified root path, ignoring any folders under Find all folders under the specified root path, ignoring any folders under
the library path of db the library path of db
root must be a bytestring in filesystem_encoding
If follow_links is True, follow symbolic links. WARNING; this can lead to If follow_links is True, follow symbolic links. WARNING; this can lead to
infinite recursion. infinite recursion.
cancel_callback must be a no argument callable that returns True to cancel cancel_callback must be a no argument callable that returns True to cancel
the search the search
''' '''
assert not isinstance(root, unicode) # root must be in filesystem encoding
lp = db.library_path lp = db.library_path
if isinstance(lp, unicode):
try:
lp = lp.encode(filesystem_encoding)
except:
lp = None
if lp: if lp:
lp = os.path.abspath(lp) lp = os.path.abspath(lp)

View File

@ -147,6 +147,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def __init__(self, library_path, row_factory=False, default_prefs=None, def __init__(self, library_path, row_factory=False, default_prefs=None,
read_only=False): read_only=False):
try:
if isbytestring(library_path):
library_path = library_path.decode(filesystem_encoding)
except:
traceback.print_exc()
self.field_metadata = FieldMetadata() self.field_metadata = FieldMetadata()
self._library_id_ = None self._library_id_ = None
# Create the lock to be used to guard access to the metadata writer # Create the lock to be used to guard access to the metadata writer
@ -160,8 +165,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.dbpath = os.path.join(library_path, 'metadata.db') self.dbpath = os.path.join(library_path, 'metadata.db')
self.dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', self.dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH',
self.dbpath) self.dbpath)
if isinstance(self.dbpath, unicode) and not iswindows:
self.dbpath = self.dbpath.encode(filesystem_encoding)
if read_only and os.path.exists(self.dbpath): if read_only and os.path.exists(self.dbpath):
# Work on only a copy of metadata.db to ensure that # Work on only a copy of metadata.db to ensure that
@ -489,12 +492,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
authors = self.authors(id, index_is_id=True) authors = self.authors(id, index_is_id=True)
if not authors: if not authors:
authors = _('Unknown') authors = _('Unknown')
author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace') author = ascii_filename(authors.split(',')[0]
title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace') )[:self.PATH_LIMIT].decode('ascii', 'replace')
title = ascii_filename(self.title(id, index_is_id=True)
)[:self.PATH_LIMIT].decode('ascii', 'replace')
while author[-1] in (' ', '.'): while author[-1] in (' ', '.'):
author = author[:-1] author = author[:-1]
if not author: if not author:
author = ascii_filename(_('Unknown')).decode(filesystem_encoding, 'replace') author = ascii_filename(_('Unknown')).decode(
'ascii', 'replace')
path = author + '/' + title + ' (%d)'%id path = author + '/' + title + ' (%d)'%id
return path return path
@ -505,8 +511,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
authors = self.authors(id, index_is_id=True) authors = self.authors(id, index_is_id=True)
if not authors: if not authors:
authors = _('Unknown') authors = _('Unknown')
author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace') author = ascii_filename(authors.split(',')[0]
title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace') )[:self.PATH_LIMIT].decode('ascii', 'replace')
title = ascii_filename(self.title(id, index_is_id=True)
)[:self.PATH_LIMIT].decode('ascii', 'replace')
name = title + ' - ' + author name = title + ' - ' + author
while name.endswith('.'): while name.endswith('.'):
name = name[:-1] name = name[:-1]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -266,9 +266,11 @@ magick_DrawingWand_fontsize_setter(magick_DrawingWand *self, PyObject *val, void
// DrawingWand.stroke_color {{{ // DrawingWand.stroke_color {{{
static PyObject * static PyObject *
magick_DrawingWand_stroke_color_getter(magick_DrawingWand *self, void *closure) { magick_DrawingWand_stroke_color_getter(magick_DrawingWand *self, void *closure) {
NULL_CHECK(NULL)
magick_PixelWand *pw; magick_PixelWand *pw;
PixelWand *wand = NewPixelWand(); PixelWand *wand;
NULL_CHECK(NULL)
wand = NewPixelWand();
if (wand == NULL) return PyErr_NoMemory(); if (wand == NULL) return PyErr_NoMemory();
DrawGetStrokeColor(self->wand, wand); DrawGetStrokeColor(self->wand, wand);
@ -281,13 +283,14 @@ magick_DrawingWand_stroke_color_getter(magick_DrawingWand *self, void *closure)
static int static int
magick_DrawingWand_stroke_color_setter(magick_DrawingWand *self, PyObject *val, void *closure) { magick_DrawingWand_stroke_color_setter(magick_DrawingWand *self, PyObject *val, void *closure) {
magick_PixelWand *pw;
NULL_CHECK(-1) NULL_CHECK(-1)
if (val == NULL) { if (val == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete DrawingWand stroke color"); PyErr_SetString(PyExc_TypeError, "Cannot delete DrawingWand stroke color");
return -1; return -1;
} }
magick_PixelWand *pw;
pw = (magick_PixelWand*)val; pw = (magick_PixelWand*)val;
if (!IsPixelWand(pw->wand)) { PyErr_SetString(PyExc_TypeError, "Invalid PixelWand"); return -1; } if (!IsPixelWand(pw->wand)) { PyErr_SetString(PyExc_TypeError, "Invalid PixelWand"); return -1; }
@ -302,9 +305,11 @@ magick_DrawingWand_stroke_color_setter(magick_DrawingWand *self, PyObject *val,
// DrawingWand.fill_color {{{ // DrawingWand.fill_color {{{
static PyObject * static PyObject *
magick_DrawingWand_fill_color_getter(magick_DrawingWand *self, void *closure) { magick_DrawingWand_fill_color_getter(magick_DrawingWand *self, void *closure) {
NULL_CHECK(NULL)
magick_PixelWand *pw; magick_PixelWand *pw;
PixelWand *wand = NewPixelWand(); PixelWand *wand;
NULL_CHECK(NULL)
wand = NewPixelWand();
if (wand == NULL) return PyErr_NoMemory(); if (wand == NULL) return PyErr_NoMemory();
DrawGetFillColor(self->wand, wand); DrawGetFillColor(self->wand, wand);
@ -317,13 +322,14 @@ magick_DrawingWand_fill_color_getter(magick_DrawingWand *self, void *closure) {
static int static int
magick_DrawingWand_fill_color_setter(magick_DrawingWand *self, PyObject *val, void *closure) { magick_DrawingWand_fill_color_setter(magick_DrawingWand *self, PyObject *val, void *closure) {
magick_PixelWand *pw;
NULL_CHECK(-1) NULL_CHECK(-1)
if (val == NULL) { if (val == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete DrawingWand fill color"); PyErr_SetString(PyExc_TypeError, "Cannot delete DrawingWand fill color");
return -1; return -1;
} }
magick_PixelWand *pw;
pw = (magick_PixelWand*)val; pw = (magick_PixelWand*)val;
if (!IsPixelWand(pw->wand)) { PyErr_SetString(PyExc_TypeError, "Invalid PixelWand"); return -1; } if (!IsPixelWand(pw->wand)) { PyErr_SetString(PyExc_TypeError, "Invalid PixelWand"); return -1; }