Merge from trunk

This commit is contained in:
Charles Haley 2010-07-02 10:20:09 +01:00
commit 62c9c54f1f
31 changed files with 408 additions and 329 deletions

View File

@ -7,7 +7,7 @@ class AdvancedUserRecipe1277228948(BasicNewsRecipe):
__author__ = 'rty'
__version__ = '1.0'
language = 'zh_CN'
language = 'zh'
pubisher = 'www.chinapressusa.com'
description = 'Overseas Chinese Network Newspaper in the USA'
category = 'News in Chinese, USA'

View File

@ -0,0 +1,50 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1277305250(BasicNewsRecipe):
title = u'infzm - China Southern Weekly'
oldest_article = 14
max_articles_per_feed = 100
feeds = [(u'\u5357\u65b9\u5468\u672b-\u70ed\u70b9\u65b0\u95fb', u'http://www.infzm.com/rss/home/rss2.0.xml'),
(u'\u5357\u65b9\u5468\u672b-\u7ecf\u6d4e\u65b0\u95fb', u'http://www.infzm.com/rss/economic.xml'),
(u'\u5357\u65b9\u5468\u672b-\u6587\u5316\u65b0\u95fb', u'http://www.infzm.com/rss/culture.xml'),
(u'\u5357\u65b9\u5468\u672b-\u751f\u6d3b\u65f6\u5c1a', u'http://www.infzm.com/rss/lifestyle.xml'),
(u'\u5357\u65b9\u5468\u672b-\u89c2\u70b9', u'http://www.infzm.com/rss/opinion.xml')
]
__author__ = 'rty'
__version__ = '1.0'
language = 'zh'
pubisher = 'http://www.infzm.com'
description = 'Chinese Weekly Tabloid'
category = 'News, China'
remove_javascript = True
use_embedded_content = False
no_stylesheets = True
#encoding = 'GB2312'
encoding = 'UTF-8'
conversion_options = {'linearize_tables':True}
masthead_url = 'http://i50.tinypic.com/2qmfb7l.jpg'
extra_css = '''
@font-face { font-family: "DroidFont", serif, sans-serif; src: url(res:///system/fonts/DroidSansFallback.ttf); }\n
body {
margin-right: 8pt;
font-family: 'DroidFont', serif;}
.detailContent {font-family: 'DroidFont', serif, sans-serif}
'''
keep_only_tags = [
dict(name='div', attrs={'id':'detailContent'}),
]
remove_tags = [
dict(name='div', attrs={'id':['detailTools', 'detailSideL', 'pageNum']}),
]
remove_tags_after = [
dict(name='div', attrs={'id':'pageNum'}),
]
def preprocess_html(self, soup):
for item in soup.findAll(color=True):
del item['font']
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -40,6 +40,7 @@ class LinuxFreeze(Command):
'/usr/bin/pdftohtml',
'/usr/lib/libwmflite-0.2.so.7',
'/usr/lib/liblcms.so.1',
'/usr/lib/libstlport.so.5.1',
'/tmp/calibre-mount-helper',
'/usr/lib/libunrar.so',
'/usr/lib/libchm.so.0',

View File

@ -342,13 +342,6 @@ def detect_ncpus():
return ans
def launch(path_or_url):
from PyQt4.QtCore import QUrl
from PyQt4.QtGui import QDesktopServices
if os.path.exists(path_or_url):
path_or_url = 'file:'+path_or_url
QDesktopServices.openUrl(QUrl(path_or_url))
relpath = os.path.relpath
_spat = re.compile(r'^the\s+|^a\s+|^an\s+', re.IGNORECASE)
def english_sort(x, y):

View File

@ -30,6 +30,7 @@ every time you add an HTML file to the library.\
with TemporaryDirectory('_plugin_html2zip') as tdir:
recs =[('debug_pipeline', tdir, OptionRecommendation.HIGH)]
recs.append(['keep_ligatures', True, OptionRecommendation.HIGH])
if self.site_customization and self.site_customization.strip():
recs.append(['input_encoding', self.site_customization.strip(),
OptionRecommendation.HIGH])
@ -444,7 +445,7 @@ from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
from calibre.devices.nook.driver import NOOK
from calibre.devices.prs505.driver import PRS505
from calibre.devices.android.driver import ANDROID, S60
from calibre.devices.nokia.driver import N770, N810, E71X
from calibre.devices.nokia.driver import N770, N810, E71X, E52
from calibre.devices.eslick.driver import ESLICK, EBK52
from calibre.devices.nuut2.driver import NUUT2
from calibre.devices.iriver.driver import IRIVER_STORY
@ -519,6 +520,7 @@ plugins += [
S60,
N770,
E71X,
E52,
N810,
COOL_ER,
ESLICK,

View File

@ -13,14 +13,14 @@ from calibre.devices.errors import UserFeedback
from calibre.devices.usbms.deviceconfig import DeviceConfig
from calibre.devices.interface import DevicePlugin
from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata import MetaInformation, authors_to_string
from calibre.ebooks.metadata.epub import set_metadata
from calibre.library.server.utils import strftime
from calibre.utils.config import config_dir
from calibre.utils.date import isoformat, now, parse_date
from calibre.utils.localization import get_lang
from calibre.utils.logging import Log
from calibre.utils.zipfile import ZipFile, safe_replace
from calibre.utils.zipfile import ZipFile
from PIL import Image as PILImage
@ -84,7 +84,7 @@ class ITUNES(DriverBase):
name = 'Apple device interface'
gui_name = 'Apple device'
icon = I('devices/ipad.png')
description = _('Communicate with iBooks through iTunes.')
description = _('Communicate with iTunes/iBooks.')
supported_platforms = ['osx','windows']
author = 'GRiker'
#: The version of this plugin as a 3-tuple (major, minor, revision)
@ -93,7 +93,6 @@ class ITUNES(DriverBase):
OPEN_FEEDBACK_MESSAGE = _(
'Apple device detected, launching iTunes, please wait ...')
# Product IDs:
# 0x1291 iPod Touch
# 0x1292 iPhone 3G
@ -1242,8 +1241,7 @@ class ITUNES(DriverBase):
if DEBUG:
self.log.info(" ITUNES._create_new_book()")
#this_book = Book(metadata.title, metadata.author[0])
this_book = Book(metadata.title, ' & '.join(metadata.author))
this_book = Book(metadata.title, authors_to_string(metadata.author))
this_book.datetime = time.gmtime()
this_book.db_id = None
this_book.device_collections = []
@ -1729,7 +1727,6 @@ class ITUNES(DriverBase):
return thumb_data
thumb_path = book_path.rpartition('.')[0] + '.jpg'
format = book_path.rpartition('.')[2].lower()
if isosx:
title = book.name()
elif iswindows:
@ -2548,8 +2545,7 @@ class ITUNES(DriverBase):
if isosx:
if lb_added:
lb_added.album.set(metadata.title)
#lb_added.artist.set(metadata.authors[0])
lb_added.artist.set(' & '.join(metadata.authors))
lb_added.artist.set(authors_to_string(metadata.authors))
lb_added.composer.set(metadata.uuid)
lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
lb_added.enabled.set(True)
@ -2560,8 +2556,7 @@ class ITUNES(DriverBase):
if db_added:
db_added.album.set(metadata.title)
#db_added.artist.set(metadata.authors[0])
db_added.artist.set(' & '.join(metadata.authors))
db_added.artist.set(authors_to_string(metadata.authors))
db_added.composer.set(metadata.uuid)
db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
db_added.enabled.set(True)
@ -2592,13 +2587,13 @@ class ITUNES(DriverBase):
if DEBUG:
self.log.info(" using Series name as Genre")
if lb_added:
lb_added.sort_name.set("%s %03d" % (metadata.series, metadata.series_index))
lb_added.sort_name.set("%s %04f" % (metadata.series, metadata.series_index))
lb_added.genre.set(metadata.series)
lb_added.episode_ID.set(metadata.series)
lb_added.episode_number.set(metadata.series_index)
if db_added:
db_added.sort_name.set("%s %03d" % (metadata.series, metadata.series_index))
db_added.sort_name.set("%s %04f" % (metadata.series, metadata.series_index))
db_added.genre.set(metadata.series)
db_added.episode_ID.set(metadata.series)
db_added.episode_number.set(metadata.series_index)
@ -2618,8 +2613,7 @@ class ITUNES(DriverBase):
elif iswindows:
if lb_added:
lb_added.Album = metadata.title
#lb_added.Artist = metadata.authors[0]
lb_added.Artist = ' & '.join(metadata.authors)
lb_added.Artist = authors_to_string(metadata.authors)
lb_added.Composer = metadata.uuid
lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
lb_added.Enabled = True
@ -2630,8 +2624,7 @@ class ITUNES(DriverBase):
if db_added:
db_added.Album = metadata.title
#db_added.Artist = metadata.authors[0]
db_added.Artist = ' & '.join(metadata.authors)
db_added.Artist = authors_to_string(metadata.authors)
db_added.Composer = metadata.uuid
db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
db_added.Enabled = True
@ -2666,7 +2659,7 @@ class ITUNES(DriverBase):
if DEBUG:
self.log.info(" using Series name as Genre")
if lb_added:
lb_added.SortName = "%s %03d" % (metadata.series, metadata.series_index)
lb_added.SortName = "%s %04f" % (metadata.series, metadata.series_index)
lb_added.Genre = metadata.series
lb_added.EpisodeID = metadata.series
try:
@ -2674,7 +2667,7 @@ class ITUNES(DriverBase):
except:
pass
if db_added:
db_added.SortName = "%s %03d" % (metadata.series, metadata.series_index)
db_added.SortName = "%s %04f" % (metadata.series, metadata.series_index)
db_added.Genre = metadata.series
db_added.EpisodeID = metadata.series
try:

View File

@ -67,3 +67,24 @@ class E71X(USBMS):
VENDOR_NAME = 'NOKIA'
WINDOWS_MAIN_MEM = 'S60'
class E52(USBMS):
name = 'Nokia E52 device interface'
gui_name = 'Nokia E52'
description = _('Communicate with the Nokia E52')
author = 'David Ignjic'
supported_platforms = ['windows', 'linux', 'osx']
VENDOR_ID = [0x421]
PRODUCT_ID = [0x1CD]
BCD = [0x100]
FORMATS = ['mobi', 'prc']
EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = True
VENDOR_NAME = 'NOKIA'
WINDOWS_MAIN_MEM = 'S60'

View File

@ -0,0 +1,15 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from calibre.customize import Plugin
class CoverDownload(Plugin):
supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal'
type = _('Cover download')

View File

@ -15,7 +15,6 @@ from calibre.utils.config import OptionParser
from calibre.ebooks.metadata.fetch import MetadataSource
from calibre.utils.date import parse_date, utcnow
DOUBAN_API_KEY = None
NAMESPACES = {
'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/',
'atom' : 'http://www.w3.org/2005/Atom',
@ -35,13 +34,15 @@ date = XPath("descendant::db:attribute[@name='pubdate']")
creator = XPath("descendant::db:attribute[@name='author']")
tag = XPath("descendant::db:tag")
CALIBRE_DOUBAN_API_KEY = '0bd1672394eb1ebf2374356abec15c3d'
class DoubanBooks(MetadataSource):
name = 'Douban Books'
description = _('Downloads metadata from Douban.com')
supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on
author = 'Li Fanxi <lifanxi@freemindworld.com>' # The author of this plugin
version = (1, 0, 0) # The version number of this plugin
version = (1, 0, 1) # The version number of this plugin
def fetch(self):
try:
@ -65,7 +66,7 @@ class Query(object):
type = "search"
def __init__(self, title=None, author=None, publisher=None, isbn=None,
max_results=20, start_index=1):
max_results=20, start_index=1, api_key=''):
assert not(title is None and author is None and publisher is None and \
isbn is None)
assert (int(max_results) < 21)
@ -89,16 +90,16 @@ class Query(object):
if self.type == "isbn":
self.url = self.ISBN_URL + q
if DOUBAN_API_KEY is not None:
self.url = self.url + "?apikey=" + DOUBAN_API_KEY
if api_key != '':
self.url = self.url + "?apikey=" + api_key
else:
self.url = self.SEARCH_URL+urlencode({
'q':q,
'max-results':max_results,
'start-index':start_index,
})
if DOUBAN_API_KEY is not None:
self.url = self.url + "&apikey=" + DOUBAN_API_KEY
if api_key != '':
self.url = self.url + "&apikey=" + api_key
def __call__(self, browser, verbose):
if verbose:
@ -177,7 +178,7 @@ class ResultList(list):
d = None
return d
def populate(self, entries, browser, verbose=False):
def populate(self, entries, browser, verbose=False, api_key=''):
for x in entries:
try:
id_url = entry_id(x)[0].text
@ -186,8 +187,8 @@ class ResultList(list):
report(verbose)
mi = MetaInformation(title, self.get_authors(x))
try:
if DOUBAN_API_KEY is not None:
id_url = id_url + "?apikey=" + DOUBAN_API_KEY
if api_key != '':
id_url = id_url + "?apikey=" + api_key
raw = browser.open(id_url).read()
feed = etree.fromstring(raw)
x = entry(feed)[0]
@ -203,12 +204,16 @@ class ResultList(list):
self.append(mi)
def search(title=None, author=None, publisher=None, isbn=None,
verbose=False, max_results=40):
verbose=False, max_results=40, api_key=None):
br = browser()
start, entries = 1, []
if api_key is None:
api_key = CALIBRE_DOUBAN_API_KEY
while start > 0 and len(entries) <= max_results:
new, start = Query(title=title, author=author, publisher=publisher,
isbn=isbn, max_results=max_results, start_index=start)(br, verbose)
new, start = Query(title=title, author=author, publisher=publisher,
isbn=isbn, max_results=max_results, start_index=start, api_key=api_key)(br, verbose)
if not new:
break
entries.extend(new)
@ -216,7 +221,7 @@ def search(title=None, author=None, publisher=None, isbn=None,
entries = entries[:max_results]
ans = ResultList()
ans.populate(entries, br, verbose)
ans.populate(entries, br, verbose, api_key)
return ans
def option_parser():

View File

@ -1069,8 +1069,10 @@ class OPFCreator(MetaInformation):
dc_attrs={'id':__appname__+'_id'}))
if getattr(self, 'pubdate', None) is not None:
a(DC_ELEM('date', self.pubdate.isoformat()))
a(DC_ELEM('language', self.language if self.language else
get_lang().replace('_', '-')))
lang = self.language
if not lang or lang.lower() == 'und':
lang = get_lang().replace('_', '-')
a(DC_ELEM('language', lang))
if self.comments:
a(DC_ELEM('description', self.comments))
if self.publisher:

View File

@ -8,7 +8,7 @@ from functools import partial
from calibre import prints
from calibre.constants import plugins
from calibre.ebooks.metadata import MetaInformation, string_to_authors, authors_to_string
from calibre.ebooks.metadata import MetaInformation, string_to_authors
pdfreflow, pdfreflow_error = plugins['pdfreflow']
@ -56,66 +56,10 @@ def get_metadata(stream, cover=True):
get_quick_metadata = partial(get_metadata, cover=False)
import cStringIO
from threading import Thread
from calibre.utils.pdftk import set_metadata as pdftk_set_metadata
from calibre.utils.podofo import set_metadata as podofo_set_metadata, Unavailable
from calibre.utils.podofo import set_metadata as podofo_set_metadata
def set_metadata(stream, mi):
stream.seek(0)
try:
return podofo_set_metadata(stream, mi)
except Unavailable:
pass
try:
return pdftk_set_metadata(stream, mi)
except:
pass
set_metadata_pypdf(stream, mi)
class MetadataWriter(Thread):
def __init__(self, out_pdf, buf):
self.out_pdf = out_pdf
self.buf = buf
Thread.__init__(self)
self.daemon = True
def run(self):
try:
self.out_pdf.write(self.buf)
except RuntimeError:
pass
def set_metadata_pypdf(stream, mi):
# Use a StringIO object for the pdf because we will want to over
# write it later and if we are working on the stream directly it
# could cause some issues.
from pyPdf import PdfFileReader, PdfFileWriter
raw = cStringIO.StringIO(stream.read())
orig_pdf = PdfFileReader(raw)
title = mi.title if mi.title else orig_pdf.documentInfo.title
author = authors_to_string(mi.authors) if mi.authors else orig_pdf.documentInfo.author
out_pdf = PdfFileWriter(title=title, author=author)
out_str = cStringIO.StringIO()
writer = MetadataWriter(out_pdf, out_str)
for page in orig_pdf.pages:
out_pdf.addPage(page)
writer.start()
writer.join(10) # Wait 10 secs for writing to complete
out_pdf.killed = True
writer.join()
if out_pdf.killed:
print 'Failed to set metadata: took too long'
return
stream.seek(0)
stream.truncate()
out_str.seek(0)
stream.write(out_str.read())
stream.seek(0)
return podofo_set_metadata(stream, mi)

View File

@ -1,18 +1,18 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" The GUI """
import os
import os, sys
from threading import RLock
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
QByteArray, QTranslator, QCoreApplication, QThread, \
QEvent, QTimer, pyqtSignal, QDate
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
QIcon, QApplication, QDialog, QPushButton
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \
QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
QIcon, QApplication, QDialog, QPushButton, QUrl
ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500'
from calibre import islinux, iswindows, isosx, isfreebsd
from calibre.constants import islinux, iswindows, isosx, isfreebsd, isfrozen
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
from calibre.utils.localization import set_qt_translator
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
@ -579,6 +579,22 @@ class Application(QApplication):
_store_app = None
def open_url(qurl):
paths = os.environ.get('LD_LIBRARY_PATH',
'').split(os.pathsep)
paths = [x for x in paths if x]
if isfrozen and islinux and paths:
npaths = [x for x in paths if x != sys.frozen_path]
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(npaths)
QDesktopServices.openUrl(qurl)
if isfrozen and islinux and paths:
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(paths)
def open_local_file(path):
url = QUrl.fromLocalFile(path)
open_url(url)
def is_ok_to_use_qt():
global gui_thread, _store_app
if (islinux or isfreebsd) and ':' not in os.environ.get('DISPLAY', ''):

View File

@ -5,17 +5,18 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import shutil, os, datetime, sys, time
import shutil, os, datetime, time
from functools import partial
from PyQt4.Qt import QInputDialog, pyqtSignal, QModelIndex, QThread, Qt, \
SIGNAL, QPixmap, QTimer, QDesktopServices, QUrl, QDialog
SIGNAL, QPixmap, QTimer, QDialog
from calibre import strftime
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import prefs, dynamic
from calibre.gui2 import error_dialog, Dispatcher, gprefs, choose_files, \
choose_dir, warning_dialog, info_dialog, question_dialog, config
choose_dir, warning_dialog, info_dialog, question_dialog, config, \
open_local_file
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString
from calibre.utils.filenames import ascii_filename
from calibre.gui2.widgets import IMAGE_EXTENSIONS
@ -25,7 +26,7 @@ from calibre.gui2.dialogs.tag_list_editor import TagListEditor
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook, \
fetch_scheduled_recipe, generate_catalog
from calibre.constants import preferred_encoding, filesystem_encoding, \
isosx, isfrozen, islinux
isosx
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2.dialogs.confirm_delete import confirm
@ -645,6 +646,8 @@ class EditMetadataAction(object): # {{{
if x.exception is None:
self.library_view.model().refresh_ids(
x.updated, cr)
if self.cover_flow:
self.cover_flow.dataChanged()
if x.failures:
details = ['%s: %s'%(title, reason) for title,
reason in x.failures.values()]
@ -689,7 +692,6 @@ class EditMetadataAction(object): # {{{
if rows:
current = self.library_view.currentIndex()
m = self.library_view.model()
m.refresh_cover_cache(map(m.id, rows))
if self.cover_flow:
self.cover_flow.dataChanged()
m.current_changed(current, previous)
@ -711,6 +713,8 @@ class EditMetadataAction(object): # {{{
self.library_view.model().resort(reset=False)
self.library_view.model().research()
self.tags_view.recount()
if self.cover_flow:
self.cover_flow.dataChanged()
# Merge books {{{
def merge_books(self, safe_merge=False):
@ -917,7 +921,7 @@ class SaveToDiskAction(object): # {{{
_('Could not save some books') + ', ' +
_('Click the show details button to see which ones.'),
u'\n\n'.join(failures), show=True)
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
open_local_file(path)
def books_saved(self, job):
if job.failed:
@ -1183,15 +1187,7 @@ class ViewAction(object): # {{{
self.job_manager.launch_gui_app(viewer,
kwargs=dict(args=args))
else:
paths = os.environ.get('LD_LIBRARY_PATH',
'').split(os.pathsep)
paths = [x for x in paths if x]
if isfrozen and islinux and paths:
npaths = [x for x in paths if x != sys.frozen_path]
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(npaths)
QDesktopServices.openUrl(QUrl.fromLocalFile(name))#launch(name)
if isfrozen and islinux and paths:
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(paths)
open_local_file(name)
time.sleep(2) # User feedback
finally:
self.unsetCursor()
@ -1237,11 +1233,11 @@ class ViewAction(object): # {{{
return
for row in rows:
path = self.library_view.model().db.abspath(row.row())
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
open_local_file(path)
def view_folder_for_id(self, id_):
path = self.library_view.model().db.abspath(id_, index_is_id=True)
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
open_local_file(path)
def view_book(self, triggered):
rows = self.current_view().selectionModel().selectedRows()

View File

@ -15,7 +15,7 @@ from calibre.ebooks.metadata import MetaInformation
from calibre.constants import preferred_encoding, filesystem_encoding
from calibre.utils.config import prefs
class DuplicatesAdder(QThread):
class DuplicatesAdder(QThread): # {{{
# Add duplicate books
def __init__(self, parent, db, duplicates, db_adder):
QThread.__init__(self, parent)
@ -34,9 +34,9 @@ class DuplicatesAdder(QThread):
self.emit(SIGNAL('added(PyQt_PyObject)'), count)
count += 1
self.emit(SIGNAL('adding_done()'))
# }}}
class RecursiveFind(QThread):
class RecursiveFind(QThread): # {{{
def __init__(self, parent, db, root, single):
QThread.__init__(self, parent)
@ -79,7 +79,9 @@ class RecursiveFind(QThread):
if not self.canceled:
self.emit(SIGNAL('found(PyQt_PyObject)'), self.books)
class DBAdder(Thread):
# }}}
class DBAdder(Thread): # {{{
def __init__(self, db, ids, nmap):
self.db, self.ids, self.nmap = db, dict(**ids), dict(**nmap)
@ -219,8 +221,9 @@ class DBAdder(Thread):
self.db.add_format(id, fmt, f, index_is_id=True,
notify=False, replace=replace)
# }}}
class Adder(QObject):
class Adder(QObject): # {{{
ADD_TIMEOUT = 600 # seconds
@ -410,6 +413,7 @@ class Adder(QObject):
return getattr(getattr(self, 'db_adder', None), 'infos',
[])
# }}}
###############################################################################
############################## END ADDER ######################################

View File

@ -9,14 +9,14 @@ import os, collections
from PyQt4.Qt import QLabel, QPixmap, QSize, QWidget, Qt, pyqtSignal, \
QVBoxLayout, QScrollArea, QPropertyAnimation, QEasingCurve, \
QSizePolicy, QPainter, QRect, pyqtProperty, QDesktopServices, QUrl
QSizePolicy, QPainter, QRect, pyqtProperty
from calibre import fit_image, prepare_string_for_xml
from calibre.gui2.widgets import IMAGE_EXTENSIONS
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.constants import preferred_encoding
from calibre.library.comments import comments_to_html
from calibre.gui2 import config
from calibre.gui2 import config, open_local_file
# render_rows(data) {{{
WEIGHTS = collections.defaultdict(lambda : 100)
@ -294,7 +294,7 @@ class BookDetails(QWidget): # {{{
id_, fmt = val.split(':')
self.view_specific_format.emit(int(id_), fmt)
elif typ == 'devpath':
QDesktopServices.openUrl(QUrl.fromLocalFile(val))
open_local_file(val)
def mouseReleaseEvent(self, ev):

View File

@ -31,6 +31,8 @@ from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \
config as email_config
from calibre.devices.apple.driver import ITUNES_ASYNC
from calibre.devices.folder_device.driver import FOLDER_DEVICE
from calibre.ebooks.metadata.meta import set_metadata
from calibre.constants import DEBUG
# }}}
@ -304,6 +306,21 @@ class DeviceManager(Thread): # {{{
def _upload_books(self, files, names, on_card=None, metadata=None):
'''Upload books to device: '''
if metadata and files and len(metadata) == len(files):
for f, mi in zip(files, metadata):
if isinstance(f, unicode):
ext = f.rpartition('.')[-1].lower()
if ext:
try:
if DEBUG:
prints('Setting metadata in:', mi.title, 'at:',
f, file=sys.__stdout__)
with open(f, 'r+b') as stream:
set_metadata(stream, mi, stream_type=ext)
except:
if DEBUG:
prints(traceback.format_exc(), file=sys.__stdout__)
return self.device.upload_books(files, names, on_card,
metadata=metadata, end_session=False)
@ -1145,7 +1162,6 @@ class DeviceMixin(object): # {{{
_files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids(ids,
settings.format_map,
set_metadata=True,
specific_format=specific_format,
exclude_auto=do_auto_convert)
if do_auto_convert:

View File

@ -5,11 +5,11 @@ __docformat__ = 'restructuredtext en'
import textwrap, os, re
from PyQt4.QtCore import QCoreApplication, SIGNAL, QModelIndex, QUrl, QTimer, Qt
from PyQt4.QtGui import QDialog, QPixmap, QGraphicsScene, QIcon, QDesktopServices
from PyQt4.QtCore import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt
from PyQt4.QtGui import QDialog, QPixmap, QGraphicsScene, QIcon
from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo
from calibre.gui2 import dynamic
from calibre.gui2 import dynamic, open_local_file
from calibre import fit_image
from calibre.library.comments import comments_to_html
@ -49,12 +49,12 @@ class BookInfo(QDialog, Ui_BookInfo):
def open_book_path(self, path):
if os.sep in unicode(path):
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
open_local_file(path)
else:
format = unicode(path)
path = self.view.model().db.format_abspath(self.current_row, format)
if path is not None:
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
open_local_file(path)
def next(self):
@ -123,6 +123,7 @@ class BookInfo(QDialog, Ui_BookInfo):
for key in info.keys():
if key == 'id': continue
txt = info[key]
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
if key != _('Path'):
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
self.text.setText(u'<table>'+rows+'</table>')

View File

@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, re, time, textwrap, copy, sys
from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \
QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \
QVBoxLayout, QLabel, QPlainTextEdit, \
QStringListModel, QAbstractItemModel, QFont, \
SIGNAL, QThread, Qt, QSize, QVariant, QUrl, \
QModelIndex, QAbstractTableModel, \
@ -15,8 +15,9 @@ from calibre.constants import iswindows, isosx
from calibre.gui2.dialogs.config.config_ui import Ui_Dialog
from calibre.gui2.dialogs.config.create_custom_column import CreateCustomColumn
from calibre.gui2 import choose_dir, error_dialog, config, gprefs, \
ALL_COLUMNS, NONE, info_dialog, choose_files, \
warning_dialog, ResizableDialog, question_dialog
open_url, open_local_file, \
ALL_COLUMNS, NONE, info_dialog, choose_files, \
warning_dialog, ResizableDialog, question_dialog
from calibre.utils.config import prefs
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.oeb.iterator import is_supported
@ -512,7 +513,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
def open_config_dir(self):
from calibre.utils.config import config_dir
QDesktopServices.openUrl(QUrl.fromLocalFile(config_dir))
open_local_file(config_dir)
def create_symlinks(self):
from calibre.utils.osx_symlinks import create_symlinks
@ -805,7 +806,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.stop.setEnabled(False)
def test_server(self):
QDesktopServices.openUrl(QUrl('http://127.0.0.1:'+str(self.port.value())))
open_url(QUrl('http://127.0.0.1:'+str(self.port.value())))
def compact(self, toggled):
d = CheckIntegrity(self.db, self)

View File

@ -3,13 +3,13 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import time, os
from PyQt4.Qt import SIGNAL, QUrl, QDesktopServices, QAbstractListModel, Qt, \
from PyQt4.Qt import SIGNAL, QUrl, QAbstractListModel, Qt, \
QVariant, QInputDialog
from calibre.web.feeds.recipes import compile_recipe
from calibre.web.feeds.news import AutomaticNewsRecipe
from calibre.gui2.dialogs.user_profiles_ui import Ui_Dialog
from calibre.gui2 import error_dialog, question_dialog, \
from calibre.gui2 import error_dialog, question_dialog, open_url, \
choose_files, ResizableDialog, NONE
from calibre.gui2.widgets import PythonHighlighter
from calibre.ptempfile import PersistentTemporaryFile
@ -135,7 +135,7 @@ class UserProfiles(ResizableDialog, Ui_Dialog):
url.addQueryItem('subject', subject)
url.addQueryItem('body', body)
url.addQueryItem('attachment', pt.name)
QDesktopServices.openUrl(url)
open_url(url)
def current_changed(self, current, previous):

View File

@ -20,7 +20,8 @@ from calibre.utils.config import tweaks, prefs
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
REGEXP_MATCH, CoverCache
from calibre.library.cli import parse_series_string
from calibre import strftime, isbytestring, prepare_string_for_xml
from calibre.constants import filesystem_encoding
@ -149,21 +150,22 @@ class BooksModel(QAbstractTableModel): # {{{
self.build_data_convertors()
self.reset()
self.database_changed.emit(db)
if self.cover_cache is not None:
self.cover_cache.stop()
self.cover_cache = CoverCache(db)
self.cover_cache.start()
def refresh_cover(event, ids):
if event == 'cover' and self.cover_cache is not None:
self.cover_cache.refresh(ids)
db.add_listener(refresh_cover)
def refresh_ids(self, ids, current_row=-1):
rows = self.db.refresh_ids(ids)
if rows:
self.refresh_rows(rows, current_row=current_row)
def refresh_cover_cache(self, ids):
if self.cover_cache:
self.cover_cache.refresh(ids)
def refresh_rows(self, rows, current_row=-1):
for row in rows:
if self.cover_cache:
id = self.db.id(row)
self.cover_cache.refresh([id])
if row == current_row:
self.new_bookdisplay_data.emit(
self.get_book_display_info(row))
@ -326,7 +328,7 @@ class BooksModel(QAbstractTableModel): # {{{
def set_cache(self, idx):
l, r = 0, self.count()-1
if self.cover_cache:
if self.cover_cache is not None:
l = max(l, idx-self.buffer_size)
r = min(r, idx+self.buffer_size)
k = min(r-idx, idx-l)
@ -494,11 +496,9 @@ class BooksModel(QAbstractTableModel): # {{{
data = None
try:
id = self.db.id(row_number)
if self.cover_cache:
if self.cover_cache is not None:
img = self.cover_cache.cover(id)
if img:
if img.isNull():
img = self.default_image
if not img.isNull():
return img
if not data:
data = self.db.cover(row_number)

View File

@ -12,9 +12,9 @@ __docformat__ = 'restructuredtext en'
import collections, os, sys, textwrap, time
from Queue import Queue, Empty
from threading import Thread
from PyQt4.Qt import Qt, SIGNAL, QObject, QUrl, QTimer, \
from PyQt4.Qt import Qt, SIGNAL, QObject, QTimer, \
QPixmap, QMenu, QIcon, pyqtSignal, \
QDialog, QDesktopServices, \
QDialog, \
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
QMessageBox, QHelpEvent
@ -23,7 +23,7 @@ from calibre.constants import __version__, __appname__, isosx
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import prefs, dynamic
from calibre.utils.ipc.server import Server
from calibre.gui2 import error_dialog, GetMetadata, \
from calibre.gui2 import error_dialog, GetMetadata, open_local_file, \
gprefs, max_available_height, config, info_dialog
from calibre.gui2.cover_flow import CoverFlowMixin
from calibre.gui2.widgets import ProgressIndicator
@ -38,7 +38,6 @@ from calibre.gui2.dialogs.config import ConfigDialog
from calibre.gui2.dialogs.book_info import BookInfo
from calibre.library.database2 import LibraryDatabase2
from calibre.library.caches import CoverCache
from calibre.gui2.init import ToolbarMixin, LibraryViewMixin, LayoutMixin
from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
@ -138,6 +137,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
self.restriction_in_effect = False
self.progress_indicator = ProgressIndicator(self)
self.progress_indicator.pos = (0, 20)
self.verbose = opts.verbose
self.get_metadata = GetMetadata()
self.upload_memory = {}
@ -230,9 +230,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
if self.system_tray_icon.isVisible() and opts.start_in_tray:
self.hide_windows()
self.cover_cache = CoverCache(self.library_path)
self.cover_cache.start()
self.library_view.model().cover_cache = self.cover_cache
self.library_view.model().count_changed_signal.connect \
(self.location_view.count_changed)
if not gprefs.get('quick_start_guide_added', False):
@ -575,7 +572,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
pt = PersistentTemporaryFile('_donate.htm')
pt.write(HTML.encode('utf-8'))
pt.close()
QDesktopServices.openUrl(QUrl.fromLocalFile(pt.name))
open_local_file(pt.name)
def confirm_quit(self):
@ -606,9 +603,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
while self.spare_servers:
self.spare_servers.pop().close()
self.device_manager.keep_going = False
self.cover_cache.stop()
cc = self.library_view.model().cover_cache
if cc is not None:
cc.stop()
self.hide_windows()
self.cover_cache.terminate()
self.emailer.stop()
try:
try:

View File

@ -3,13 +3,13 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import traceback
from PyQt4.Qt import QThread, pyqtSignal, QDesktopServices, QUrl, Qt
from PyQt4.Qt import QThread, pyqtSignal, Qt, QUrl
import mechanize
from calibre.constants import __appname__, __version__, iswindows, isosx
from calibre import browser
from calibre.utils.config import prefs
from calibre.gui2 import config, dynamic, question_dialog
from calibre.gui2 import config, dynamic, question_dialog, open_url
URL = 'http://status.calibre-ebook.com/latest'
@ -64,7 +64,7 @@ class UpdateMixin(object):
'ge?')%(__appname__, version)):
url = 'http://calibre-ebook.com/download_'+\
('windows' if iswindows else 'osx' if isosx else 'linux')
QDesktopServices.openUrl(QUrl(url))
open_url(QUrl(url))
dynamic.set('update to version %s'%version, False)

View File

@ -6,7 +6,7 @@ from functools import partial
from threading import Thread
from PyQt4.Qt import QApplication, Qt, QIcon, QTimer, SIGNAL, QByteArray, \
QDesktopServices, QDoubleSpinBox, QLabel, QTextBrowser, \
QDoubleSpinBox, QLabel, QTextBrowser, \
QPainter, QBrush, QColor, QStandardItemModel, QPalette, \
QStandardItem, QUrl, QRegExpValidator, QRegExp, QLineEdit, \
QToolButton, QMenu, QInputDialog, QAction, QKeySequence
@ -17,7 +17,7 @@ from calibre.gui2.viewer.bookmarkmanager import BookmarkManager
from calibre.gui2.widgets import ProgressIndicator
from calibre.gui2.main_window import MainWindow
from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \
info_dialog, error_dialog
info_dialog, error_dialog, open_url
from calibre.ebooks.oeb.iterator import EbookIterator
from calibre.ebooks import DRMError
from calibre.constants import islinux, isfreebsd
@ -472,7 +472,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
elif frag:
self.view.scroll_to(frag)
else:
QDesktopServices.openUrl(url)
open_url(url)
def load_started(self):
self.open_progress_indicator(_('Loading flow...'))

View File

@ -38,12 +38,16 @@ class ProgressIndicator(QWidget):
self.status.setWordWrap(True)
self.status.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
self.setVisible(False)
self.pos = None
def start(self, msg=''):
view = self.parent()
pwidth, pheight = view.size().width(), view.size().height()
self.resize(pwidth, min(pheight, 250))
self.move(0, (pheight-self.size().height())/2.)
if self.pos is None:
self.move(0, (pheight-self.size().height())/2.)
else:
self.move(self.pos[0], self.pos[1])
self.pi.resize(self.pi.sizeHint())
self.pi.move(int((self.size().width()-self.pi.size().width())/2.), 0)
self.status.resize(self.size().width(), self.size().height()-self.pi.size().height()-10)

View File

@ -6,11 +6,13 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import collections, glob, os, re, itertools, functools
import re, itertools, functools
from itertools import repeat
from datetime import timedelta
from threading import Thread, RLock
from Queue import Queue, Empty
from PyQt4.Qt import QThread, QReadWriteLock, QImage, Qt
from PyQt4.Qt import QImage, Qt
from calibre.utils.config import tweaks
from calibre.utils.date import parse_date, now, UNDEFINED_DATE
@ -19,120 +21,73 @@ from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import title_sort
from calibre import fit_image
class CoverCache(QThread):
class CoverCache(Thread):
def __init__(self, library_path, parent=None):
QThread.__init__(self, parent)
self.library_path = library_path
self.id_map = None
self.id_map_lock = QReadWriteLock()
self.load_queue = collections.deque()
self.load_queue_lock = QReadWriteLock(QReadWriteLock.Recursive)
self.cache = {}
self.cache_lock = QReadWriteLock()
self.id_map_stale = True
def __init__(self, db):
Thread.__init__(self)
self.daemon = True
self.db = db
self.load_queue = Queue()
self.keep_running = True
def build_id_map(self):
self.id_map_lock.lockForWrite()
self.id_map = {}
for f in glob.glob(os.path.join(self.library_path, '*', '* (*)', 'cover.jpg')):
c = os.path.basename(os.path.dirname(f))
try:
id = int(re.search(r'\((\d+)\)', c[c.rindex('('):]).group(1))
self.id_map[id] = f
except:
continue
self.id_map_lock.unlock()
self.id_map_stale = False
def set_cache(self, ids):
self.cache_lock.lockForWrite()
already_loaded = set([])
for id in self.cache.keys():
if id in ids:
already_loaded.add(id)
else:
self.cache.pop(id)
self.cache_lock.unlock()
ids = [i for i in ids if i not in already_loaded]
self.load_queue_lock.lockForWrite()
self.load_queue = collections.deque(ids)
self.load_queue_lock.unlock()
def run(self):
while self.keep_running:
if self.id_map is None or self.id_map_stale:
self.build_id_map()
while True: # Load images from the load queue
self.load_queue_lock.lockForWrite()
try:
id = self.load_queue.popleft()
except IndexError:
break
finally:
self.load_queue_lock.unlock()
self.cache_lock.lockForRead()
need = True
if id in self.cache.keys():
need = False
self.cache_lock.unlock()
if not need:
continue
path = None
self.id_map_lock.lockForRead()
if id in self.id_map.keys():
path = self.id_map[id]
else:
self.id_map_stale = True
self.id_map_lock.unlock()
if path and os.access(path, os.R_OK):
try:
img = QImage()
data = open(path, 'rb').read()
img.loadFromData(data)
if img.isNull():
continue
scaled, nwidth, nheight = fit_image(img.width(),
img.height(), 600, 800)
if scaled:
img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio,
Qt.SmoothTransformation)
except:
continue
self.cache_lock.lockForWrite()
self.cache[id] = img
self.cache_lock.unlock()
self.sleep(1)
self.cache = {}
self.lock = RLock()
self.null_image = QImage()
def stop(self):
self.keep_running = False
def cover(self, id):
val = None
if self.cache_lock.tryLockForRead(50):
val = self.cache.get(id, None)
self.cache_lock.unlock()
return val
def _image_for_id(self, id_):
img = self.db.cover(id_, index_is_id=True, as_image=True)
if img is None:
img = QImage()
if not img.isNull():
scaled, nwidth, nheight = fit_image(img.width(),
img.height(), 600, 800)
if scaled:
img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio,
Qt.SmoothTransformation)
return img
def run(self):
while self.keep_running:
try:
id_ = self.load_queue.get(True, 1)
except Empty:
continue
try:
img = self._image_for_id(id_)
except:
import traceback
traceback.print_exc()
continue
with self.lock:
self.cache[id_] = img
def set_cache(self, ids):
with self.lock:
already_loaded = set([])
for id in self.cache.keys():
if id in ids:
already_loaded.add(id)
else:
self.cache.pop(id)
for id_ in set(ids) - already_loaded:
self.load_queue.put(id_)
def cover(self, id_):
with self.lock:
return self.cache.get(id_, self.null_image)
def clear_cache(self):
self.cache_lock.lockForWrite()
self.cache = {}
self.cache_lock.unlock()
with self.lock:
self.cache = {}
def refresh(self, ids):
self.cache_lock.lockForWrite()
for id in ids:
self.cache.pop(id, None)
self.cache_lock.unlock()
self.load_queue_lock.lockForWrite()
for id in ids:
self.load_queue.appendleft(id)
self.load_queue_lock.unlock()
with self.lock:
for id_ in ids:
self.cache.pop(id_, None)
self.load_queue.put(id_)
### Global utility function for get_match here and in gui2/library.py
CONTAINS_MATCH = 0

View File

@ -8,9 +8,14 @@ __docformat__ = 'restructuredtext en'
import re
from calibre.constants import preferred_encoding
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString, \
CData, Comment, Declaration, ProcessingInstruction
from calibre import prepare_string_for_xml
# Hackish - ignoring sentences ending or beginning in numbers to avoid
# confusion with decimal points.
lost_cr_pat = re.compile('([a-z])([\.\?!])([A-Z])')
def comments_to_html(comments):
'''
Convert random comment text to normalized, xml-legal block of <p>s
@ -41,36 +46,25 @@ def comments_to_html(comments):
if '<' not in comments:
comments = prepare_string_for_xml(comments)
comments = comments.replace(u'\n', u'<br />')
return u'<p>%s</p>'%comments
# Hackish - ignoring sentences ending or beginning in numbers to avoid
# confusion with decimal points.
parts = [u'<p class="description">%s</p>'%x.replace(u'\n', u'<br />')
for x in comments.split('\n\n')]
return '\n'.join(parts)
# Explode lost CRs to \n\n
for lost_cr in re.finditer('([a-z])([\.\?!])([A-Z])', comments):
for lost_cr in lost_cr_pat.finditer(comments):
comments = comments.replace(lost_cr.group(),
'%s%s\n\n%s' % (lost_cr.group(1),
lost_cr.group(2),
lost_cr.group(3)))
comments = comments.replace(u'\r', u'')
# Convert \n\n to <p>s
if re.search('\n\n', comments):
soup = BeautifulSoup()
split_ps = comments.split(u'\n\n')
tsc = 0
for p in split_ps:
pTag = Tag(soup,'p')
pTag.insert(0,p)
soup.insert(tsc,pTag)
tsc += 1
comments = soup.renderContents(None)
comments = comments.replace(u'\n\n', u'<p>')
# Convert solo returns to <br />
comments = re.sub('[\r\n]','<br />', comments)
comments = comments.replace(u'\n', '<br />')
# Convert two hyphens to emdash
comments = re.sub('--', '&mdash;', comments)
comments = comments.replace('--', '&mdash;')
soup = BeautifulSoup(comments)
result = BeautifulSoup()
rtc = 0
@ -85,35 +79,52 @@ def comments_to_html(comments):
ptc = 0
pTag.insert(ptc,prepare_string_for_xml(token))
ptc += 1
elif token.name in ['br','b','i','em']:
elif type(token) in (CData, Comment, Declaration,
ProcessingInstruction):
continue
elif token.name in ['br', 'b', 'i', 'em', 'strong', 'span', 'font', 'a',
'hr']:
if not open_pTag:
pTag = Tag(result,'p')
open_pTag = True
ptc = 0
pTag.insert(ptc, token)
ptc += 1
else:
if open_pTag:
result.insert(rtc, pTag)
rtc += 1
open_pTag = False
ptc = 0
# Clean up NavigableStrings for xml
sub_tokens = list(token.contents)
for sub_token in sub_tokens:
if type(sub_token) is NavigableString:
sub_token.replaceWith(prepare_string_for_xml(sub_token))
result.insert(rtc, token)
rtc += 1
if open_pTag:
result.insert(rtc, pTag)
paras = result.findAll('p')
for p in paras:
for p in result.findAll('p'):
p['class'] = 'description'
for t in result.findAll(text=True):
t.replaceWith(prepare_string_for_xml(unicode(t)))
return result.renderContents(encoding=None)
def test():
for pat, val in [
('lineone\n\nlinetwo',
'<p class="description">lineone</p>\n<p class="description">linetwo</p>'),
('a <b>b&c</b>\nf', '<p class="description">a <b>b&amp;c;</b><br />f</p>'),
('a <?xml asd> b\n\ncd', '<p class="description">a b</p><p class="description">cd</p>'),
]:
print
print 'Testing: %r'%pat
cval = comments_to_html(pat)
print 'Value: %r'%cval
if comments_to_html(pat) != val:
print 'FAILED'
break
if __name__ == '__main__':
test()

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
'''
The database used to store ebook metadata
'''
import os, sys, shutil, cStringIO, glob,functools, traceback
import os, sys, shutil, cStringIO, glob, time, functools, traceback
from itertools import repeat
from math import floor
@ -440,12 +440,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if os.access(path, os.R_OK):
if as_path:
return path
f = open(path, 'rb')
try:
f = open(path, 'rb')
except (IOError, OSError):
time.sleep(0.2)
f = open(path, 'rb')
if as_image:
img = QImage()
img.loadFromData(f.read())
f.close()
return img
return f if as_file else f.read()
ans = f if as_file else f.read()
if ans is not f:
f.close()
return ans
def get_metadata(self, idx, index_is_id=False, get_cover=False):
'''
@ -492,12 +500,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
return os.access(path, os.R_OK)
def remove_cover(self, id):
def remove_cover(self, id, notify=True):
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
if os.path.exists(path):
os.remove(path)
try:
os.remove(path)
except (IOError, OSError):
time.sleep(0.2)
os.remove(path)
if notify:
self.notify('cover', [id])
def set_cover(self, id, data):
def set_cover(self, id, data, notify=True):
'''
Set the cover for this book.
@ -509,7 +523,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
else:
if callable(getattr(data, 'read', None)):
data = data.read()
save_cover_data_to(data, path)
try:
save_cover_data_to(data, path)
except (IOError, OSError):
time.sleep(0.2)
save_cover_data_to(data, path)
if notify:
self.notify('cover', [id])
def book_on_device(self, id):
if callable(self.book_on_device_func):

View File

@ -14,6 +14,7 @@ from calibre.ebooks.metadata import MetaInformation, string_to_authors, \
from calibre.utils.ipc.job import ParallelJob
from calibre.utils.ipc.server import Server
from calibre.ptempfile import PersistentTemporaryFile
from calibre import prints
podofo, podofo_err = plugins['podofo']
@ -117,12 +118,18 @@ def set_metadata(stream, mi):
job.update()
server.close()
if job.result is not None:
if job.failed:
prints(job.details)
elif job.result is not None:
stream.seek(0)
stream.truncate()
stream.write(job.result)
stream.flush()
stream.seek(0)
try:
os.remove(pt.name)
except:
pass

View File

@ -55,8 +55,7 @@ podofo_PDFDoc_load(podofo_PDFDoc *self, PyObject *args, PyObject *kwargs) {
} else return NULL;
Py_INCREF(Py_None);
return Py_None;
Py_RETURN_NONE;
}
static PyObject *
@ -73,8 +72,7 @@ podofo_PDFDoc_open(podofo_PDFDoc *self, PyObject *args, PyObject *kwargs) {
} else return NULL;
Py_INCREF(Py_None);
return Py_None;
Py_RETURN_NONE;
}
static PyObject *

View File

@ -0,0 +1,26 @@
#define USING_SHARED_PODOFO
#include <podofo.h>
#include <iostream>
using namespace PoDoFo;
using namespace std;
int main(int argc, char **argv) {
if (argc < 2) return 1;
char *fname = argv[1];
PdfMemDocument doc(fname);
PdfInfo* info = doc.GetInfo();
cout << endl;
cout << "is encrypted: " << doc.GetEncrypted() << endl;
PdfString old_title = info->GetTitle();
cout << "is hex: " << old_title.IsHex() << endl;
PdfString new_title(reinterpret_cast<const pdf_utf16be*>("\0z\0z\0z"), 3);
cout << "is new unicode: " << new_title.IsUnicode() << endl;
info->SetTitle(new_title);
doc.Write("/t/x.pdf");
cout << "Output written to: " << "/t/x.pdf" << endl;
return 0;
}

View File

@ -8,7 +8,7 @@ import copy
from lxml import html, etree
from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \
STRONG, EM, BR, SPAN, A, HR, UL, LI, H2, H3, IMG, P as PT, \
STRONG, BR, SPAN, A, HR, UL, LI, H2, H3, IMG, P as PT, \
TABLE, TD, TR
from calibre import preferred_encoding, strftime, isbytestring