Merge from trunk

This commit is contained in:
Charles Haley 2010-09-02 09:30:05 +01:00
commit 652238538e
10 changed files with 180 additions and 64 deletions

View File

@ -0,0 +1,30 @@
from calibre.web.feeds.news import BasicNewsRecipe
class WinnipegFreePress(BasicNewsRecipe):
title = u'Winnipeg Free Press'
__author__ = 'buyo'
description = 'News from Winnipeg, Manitoba, Canada'
oldest_article = 1
max_articles_per_feed = 15
category = 'News, Winnipeg, Canada'
cover_url = 'http://media.winnipegfreepress.com/designimages/winnipegfreepress_WFP.gif'
no_stylesheets = True
encoding = 'UTF-8'
remove_javascript = True
use_embedded_content = False
language = 'en_CA'
feeds = [(u'Breaking News', u'http://www.winnipegfreepress.com/rss?path=/breakingnews'),
(u'Local News',u'http://www.winnipegfreepress.com/rss?path=/local'),
(u'Breaking Business News',u'http://www.winnipegfreepress.com/rss?path=/business/finance'),
(u'Business',u'http://www.winnipegfreepress.com/rss?path=/business'),
(u'Editorials',u'http://www.winnipegfreepress.com/rss?path=/opinion/editorials'),
(u'Views from the West',u'http://www.winnipegfreepress.com/rss?path=/opinion/westview'),
(u'Life & Style',u'http://www.winnipegfreepress.com/rss?path=/life'),
(u'Food & Drink',u'http://www.winnipegfreepress.com/rss?path=/life/food')
]
keep_only_tags = [
dict(name='div', attrs={'id':'article_header'}),
dict(name='div', attrs={'class':'article'}),
]

View File

@ -41,7 +41,7 @@ class ANDROID(USBMS):
0x502 : { 0x3203 : [0x0100]}, 0x502 : { 0x3203 : [0x0100]},
# Dell # Dell
0x413c : { 0xb007 : [0x0100]}, 0x413c : { 0xb007 : [0x0100, 0x0224]},
# Eken? # Eken?
0x040d : { 0x0851 : [0x0001]}, 0x040d : { 0x0851 : [0x0001]},

View File

@ -50,6 +50,8 @@ class JETBOOK(USBMS):
def filename_callback(self, fname, mi): def filename_callback(self, fname, mi):
fileext = os.path.splitext(os.path.basename(fname))[1] fileext = os.path.splitext(os.path.basename(fname))[1]
if fileext.lower() not in ('txt', 'pdf', 'fb2'):
return fname
title = mi.title if mi.title else 'Unknown' title = mi.title if mi.title else 'Unknown'
title = title.replace(' ', '_') title = title.replace(' ', '_')
au = mi.format_authors() au = mi.format_authors()

View File

@ -23,7 +23,7 @@ class Book(MetaInformation):
'uuid', 'device_collections', 'uuid', 'device_collections',
] ]
def __init__(self, prefix, lpath, title, authors, mime, date, ContentType, thumbnail_name, other=None): def __init__(self, prefix, lpath, title, authors, mime, date, ContentType, thumbnail_name, size=None, other=None):
MetaInformation.__init__(self, '') MetaInformation.__init__(self, '')
self.device_collections = [] self.device_collections = []
@ -42,10 +42,8 @@ class Book(MetaInformation):
else: else:
self.authors = [authors] self.authors = [authors]
self.mime = mime self.mime = mime
try:
self.size = os.path.getsize(self.path) self.size = size # will be set later if None
except OSError:
self.size = 0
try: try:
if ContentType == '6': if ContentType == '6':
self.datetime = time.strptime(date, "%Y-%m-%dT%H:%M:%S.%f") self.datetime = time.strptime(date, "%Y-%m-%dT%H:%M:%S.%f")

View File

@ -94,19 +94,19 @@ class KOBO(USBMS):
idx = bl_cache.get(lpath, None) idx = bl_cache.get(lpath, None)
if idx is not None: if idx is not None:
bl_cache[lpath] = None
if ImageID is not None: if ImageID is not None:
imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - NickelBookCover.parsed') imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - NickelBookCover.parsed')
#print "Image name Normalized: " + imagename #print "Image name Normalized: " + imagename
if imagename is not None: if imagename is not None:
bl[idx].thumbnail = ImageWrapper(imagename) bl[idx].thumbnail = ImageWrapper(imagename)
bl_cache[lpath] = None
if ContentType != '6': if ContentType != '6':
if self.update_metadata_item(bl[idx]): if self.update_metadata_item(bl[idx]):
# print 'update_metadata_item returned true' # print 'update_metadata_item returned true'
changed = True changed = True
bl[idx].device_collections = playlist_map.get(lpath, []) bl[idx].device_collections = playlist_map.get(lpath, [])
else: else:
book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID) book = self.book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
# print 'Update booklist' # print 'Update booklist'
if bl.add_book(book, replace_metadata=False): if bl.add_book(book, replace_metadata=False):
changed = True changed = True
@ -316,10 +316,10 @@ class KOBO(USBMS):
lpath = lpath[1:] lpath = lpath[1:]
#print "path: " + lpath #print "path: " + lpath
#book = self.book_class(prefix, lpath, other=info) #book = self.book_class(prefix, lpath, other=info)
lpath = self.normalize_path(prefix + lpath)
book = Book(prefix, lpath, '', '', '', '', '', '', other=info) book = Book(prefix, lpath, '', '', '', '', '', '', other=info)
if book.size is None: if book.size is None:
book.size = os.stat(self.normalize_path(path)).st_size book.size = os.stat(self.normalize_path(path)).st_size
book._new_book = True # Must be before add_book
booklists[blist].add_book(book, replace_metadata=True) booklists[blist].add_book(book, replace_metadata=True)
self.report_progress(1.0, _('Adding books to device metadata listing...')) self.report_progress(1.0, _('Adding books to device metadata listing...'))
@ -380,3 +380,19 @@ class KOBO(USBMS):
return USBMS.get_file(self, path, *args, **kwargs) return USBMS.get_file(self, path, *args, **kwargs)
@classmethod
def book_from_path(cls, prefix, lpath, title, authors, mime, date, ContentType, ImageID):
from calibre.ebooks.metadata import MetaInformation
if cls.settings().read_metadata or cls.MUST_READ_METADATA:
mi = cls.metadata_from_path(cls.normalize_path(os.path.join(prefix, lpath)))
else:
from calibre.ebooks.metadata.meta import metadata_from_filename
mi = metadata_from_filename(cls.normalize_path(os.path.basename(lpath)),
cls.build_template_regexp())
if mi is None:
mi = MetaInformation(os.path.splitext(os.path.basename(lpath))[0],
[_('Unknown')])
size = os.stat(cls.normalize_path(os.path.join(prefix, lpath))).st_size
book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID, size=size, other=mi)
return book

View File

@ -5,15 +5,16 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os import os, shutil
from functools import partial from functools import partial
from PyQt4.Qt import QMenu, Qt from PyQt4.Qt import QMenu, Qt, QInputDialog
from calibre import isbytestring from calibre import isbytestring
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.gui2 import gprefs, warning_dialog, Dispatcher from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
question_dialog
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
class LibraryUsageStats(object): class LibraryUsageStats(object):
@ -66,6 +67,13 @@ class LibraryUsageStats(object):
loc = loc[:-1] loc = loc[:-1]
return loc.split('/')[-1] return loc.split('/')[-1]
def rename(self, location, newloc):
newloc = self.canonicalize_path(newloc)
stats = self.stats.pop(location, None)
if stats is not None:
self.stats[newloc] = stats
self.write_stats()
class ChooseLibraryAction(InterfaceAction): class ChooseLibraryAction(InterfaceAction):
@ -80,7 +88,7 @@ class ChooseLibraryAction(InterfaceAction):
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
self.stats = LibraryUsageStats() self.stats = LibraryUsageStats()
self.create_action(spec=(_('Switch to library...'), 'lt.png', None, self.create_action(spec=(_('Switch/create library...'), 'lt.png', None,
None), attr='action_choose') None), attr='action_choose')
self.action_choose.triggered.connect(self.choose_library, self.action_choose.triggered.connect(self.choose_library,
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
@ -90,7 +98,13 @@ class ChooseLibraryAction(InterfaceAction):
self.quick_menu = QMenu(_('Quick switch')) self.quick_menu = QMenu(_('Quick switch'))
self.quick_menu_action = self.choose_menu.addMenu(self.quick_menu) self.quick_menu_action = self.choose_menu.addMenu(self.quick_menu)
self.qs_separator = self.choose_menu.addSeparator() self.rename_menu = QMenu(_('Rename library'))
self.rename_menu_action = self.choose_menu.addMenu(self.rename_menu)
self.delete_menu = QMenu(_('Delete library'))
self.delete_menu_action = self.choose_menu.addMenu(self.delete_menu)
self.rename_separator = self.choose_menu.addSeparator()
self.switch_actions = [] self.switch_actions = []
for i in range(5): for i in range(5):
ac = self.create_action(spec=('', None, None, None), ac = self.create_action(spec=('', None, None, None),
@ -123,9 +137,15 @@ class ChooseLibraryAction(InterfaceAction):
ac.setVisible(False) ac.setVisible(False)
self.quick_menu.clear() self.quick_menu.clear()
self.qs_locations = [i[1] for i in locations] self.qs_locations = [i[1] for i in locations]
self.rename_menu.clear()
self.delete_menu.clear()
for name, loc in locations: for name, loc in locations:
self.quick_menu.addAction(name, Dispatcher(partial(self.switch_requested, self.quick_menu.addAction(name, Dispatcher(partial(self.switch_requested,
loc))) loc)))
self.rename_menu.addAction(name, Dispatcher(partial(self.rename_requested,
name, loc)))
self.delete_menu.addAction(name, Dispatcher(partial(self.delete_requested,
name, loc)))
for i, x in enumerate(locations[:len(self.switch_actions)]): for i, x in enumerate(locations[:len(self.switch_actions)]):
name, loc = x name, loc = x
@ -134,12 +154,50 @@ class ChooseLibraryAction(InterfaceAction):
ac.setVisible(True) ac.setVisible(True)
self.quick_menu_action.setVisible(bool(locations)) self.quick_menu_action.setVisible(bool(locations))
self.rename_menu_action.setVisible(bool(locations))
self.delete_menu_action.setVisible(bool(locations))
def location_selected(self, loc): def location_selected(self, loc):
enabled = loc == 'library' enabled = loc == 'library'
self.qaction.setEnabled(enabled) self.qaction.setEnabled(enabled)
def rename_requested(self, name, location):
loc = location.replace('/', os.sep)
base = os.path.dirname(loc)
newname, ok = QInputDialog.getText(self.gui, _('Rename') + ' ' + name,
'<p>'+_('Choose a new name for the library <b>%s</b>. ')%name +
'<p>'+_('Note that the actual library folder will be renamed.'),
text=name)
newname = unicode(newname)
if not ok or not newname or newname == name:
return
newloc = os.path.join(base, newname)
if os.path.exists(newloc):
return error_dialog(self.gui, _('Already exists'),
_('The folder %s already exists. Delete it first.') %
newloc, show=True)
os.rename(loc, newloc)
self.stats.rename(location, newloc)
self.build_menus()
def delete_requested(self, name, location):
loc = location.replace('/', os.sep)
if not question_dialog(self.gui, _('Are you sure?'), '<p>'+
_('All files from %s will be '
'<b>permanently deleted</b>. Are you sure?') % loc,
show_copy_button=False):
return
exists = self.gui.library_view.model().db.exists_at(loc)
if exists:
try:
shutil.rmtree(loc, ignore_errors=True)
except:
pass
self.stats.remove(location)
self.build_menus()
def switch_requested(self, location): def switch_requested(self, location):
if not self.change_library_allowed(): if not self.change_library_allowed():
return return

View File

@ -5,6 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os
from functools import partial from functools import partial
from threading import Thread from threading import Thread
@ -13,6 +14,7 @@ from PyQt4.Qt import QMenu, QToolButton
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2 import error_dialog, Dispatcher from calibre.gui2 import error_dialog, Dispatcher
from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2.dialogs.progress import ProgressDialog
from calibre.utils.config import prefs
class Worker(Thread): class Worker(Thread):
@ -38,6 +40,13 @@ class Worker(Thread):
self.done() self.done()
def add_formats(self, id, paths, newdb, replace=True):
for path in paths:
fmt = os.path.splitext(path)[-1].replace('.', '').upper()
with open(path, 'rb') as f:
newdb.add_format(id, fmt, f, index_is_id=True,
notify=False, replace=replace)
def doit(self): def doit(self):
from calibre.library.database2 import LibraryDatabase2 from calibre.library.database2 import LibraryDatabase2
newdb = LibraryDatabase2(self.loc) newdb = LibraryDatabase2(self.loc)
@ -49,12 +58,18 @@ class Worker(Thread):
else: fmts = fmts.split(',') else: fmts = fmts.split(',')
paths = [self.db.format_abspath(x, fmt, index_is_id=True) for fmt in paths = [self.db.format_abspath(x, fmt, index_is_id=True) for fmt in
fmts] fmts]
newdb.import_book(mi, paths, notify=False, import_hooks=False) added = False
co = self.db.conversion_options(x, 'PIPE') if prefs['add_formats_to_existing']:
if co is not None: identical_book_list = newdb.find_identical_books(mi)
newdb.set_conversion_options(x, 'PIPE', co) if identical_book_list: # books with same author and nearly same title exist in newdb
added = True
for identical_book in identical_book_list:
self.add_formats(identical_book, paths, newdb, replace=False)
if not added:
newdb.import_book(mi, paths, notify=False, import_hooks=False)
co = self.db.conversion_options(x, 'PIPE')
if co is not None:
newdb.set_conversion_options(x, 'PIPE', co)
class CopyToLibraryAction(InterfaceAction): class CopyToLibraryAction(InterfaceAction):

View File

@ -1,7 +1,7 @@
''' '''
UI for adding books to the database and saving books to disk UI for adding books to the database and saving books to disk
''' '''
import os, shutil, time, re import os, shutil, time
from Queue import Queue, Empty from Queue import Queue, Empty
from threading import Thread from threading import Thread
@ -94,14 +94,6 @@ class DBAdder(Thread): # {{{
self.daemon = True self.daemon = True
self.input_queue = Queue() self.input_queue = Queue()
self.output_queue = Queue() self.output_queue = Queue()
self.fuzzy_title_patterns = [(re.compile(pat), repl) for pat, repl in
[
(r'[\[\](){}<>\'";,:#]', ''),
(r'^(the|a|an) ', ''),
(r'[-._]', ' '),
(r'\s+', ' ')
]
]
self.merged_books = set([]) self.merged_books = set([])
def run(self): def run(self):
@ -138,33 +130,6 @@ class DBAdder(Thread): # {{{
fmts[-1] = fmt fmts[-1] = fmt
return fmts return fmts
def fuzzy_title(self, title):
title = title.strip().lower()
for pat, repl in self.fuzzy_title_patterns:
title = pat.sub(repl, title)
return title
def find_identical_books(self, mi):
identical_book_ids = set([])
if mi.authors:
try:
query = u' and '.join([u'author:"=%s"'%(a.replace('"', '')) for a in
mi.authors])
except ValueError:
return identical_book_ids
try:
book_ids = self.db.data.parse(query)
except:
import traceback
traceback.print_exc()
return identical_book_ids
for book_id in book_ids:
fbook_title = self.db.title(book_id, index_is_id=True)
fbook_title = self.fuzzy_title(fbook_title)
mbook_title = self.fuzzy_title(mi.title)
if fbook_title == mbook_title:
identical_book_ids.add(book_id)
return identical_book_ids
def add(self, id, opf, cover, name): def add(self, id, opf, cover, name):
formats = self.ids.pop(id) formats = self.ids.pop(id)
@ -191,7 +156,7 @@ class DBAdder(Thread): # {{{
orig_formats = formats orig_formats = formats
formats = [f for f in formats if not f.lower().endswith('.opf')] formats = [f for f in formats if not f.lower().endswith('.opf')]
if prefs['add_formats_to_existing']: if prefs['add_formats_to_existing']:
identical_book_list = self.find_identical_books(mi) identical_book_list = self.db.find_identical_books(mi)
if identical_book_list: # books with same author and nearly same title exist in db if identical_book_list: # books with same author and nearly same title exist in db
self.merged_books.add(mi.title) self.merged_books.add(mi.title)

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
''' '''
The database used to store ebook metadata The database used to store ebook metadata
''' '''
import os, sys, shutil, cStringIO, glob, time, functools, traceback import os, sys, shutil, cStringIO, glob, time, functools, traceback, re
from itertools import repeat from itertools import repeat
from math import floor from math import floor
@ -550,6 +550,43 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return bool(self.conn.get('SELECT id FROM books where title=?', (title,), all=False)) return bool(self.conn.get('SELECT id FROM books where title=?', (title,), all=False))
return False return False
def find_identical_books(self, mi):
fuzzy_title_patterns = [(re.compile(pat), repl) for pat, repl in
[
(r'[\[\](){}<>\'";,:#]', ''),
(r'^(the|a|an) ', ''),
(r'[-._]', ' '),
(r'\s+', ' ')
]
]
def fuzzy_title(title):
title = title.strip().lower()
for pat, repl in fuzzy_title_patterns:
title = pat.sub(repl, title)
return title
identical_book_ids = set([])
if mi.authors:
try:
query = u' and '.join([u'author:"=%s"'%(a.replace('"', '')) for a in
mi.authors])
except ValueError:
return identical_book_ids
try:
book_ids = self.data.parse(query)
except:
import traceback
traceback.print_exc()
return identical_book_ids
for book_id in book_ids:
fbook_title = self.title(book_id, index_is_id=True)
fbook_title = fuzzy_title(fbook_title)
mbook_title = fuzzy_title(mi.title)
if fbook_title == mbook_title:
identical_book_ids.add(book_id)
return identical_book_ids
def has_cover(self, index, index_is_id=False): def has_cover(self, index, index_is_id=False):
id = index if index_is_id else self.id(index) id = index if index_is_id else self.id(index)
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')

View File

@ -81,12 +81,7 @@ def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=30,
for x in to: for x in to:
return sendmail_direct(from_, x, msg, timeout, localhost, verbose) return sendmail_direct(from_, x, msg, timeout, localhost, verbose)
import smtplib import smtplib
class SMTP_SSL(smtplib.SMTP_SSL): # Workaround for bug in smtplib.py cls = smtplib.SMTP if encryption == 'TLS' else smtplib.SMTP_SSL
def _get_socket(self, host, port, timeout):
smtplib.SMTP_SSL._get_socket(self, host, port, timeout)
return self.sock
cls = smtplib.SMTP if encryption == 'TLS' else SMTP_SSL
timeout = None # Non-blocking sockets sometimes don't work timeout = None # Non-blocking sockets sometimes don't work
port = int(port) port = int(port)
s = cls(timeout=timeout, local_hostname=localhost) s = cls(timeout=timeout, local_hostname=localhost)