mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
134bbcdbd0
@ -4,6 +4,52 @@
|
||||
# for important features/bug fixes.
|
||||
# Also, each release can have new and improved recipes.
|
||||
|
||||
- version: 0.7.22
|
||||
date: 2010-10-03
|
||||
|
||||
new features:
|
||||
- title: "Drag and drop books from your calibre library"
|
||||
type: major
|
||||
description: >
|
||||
"You can now drag and drop books from your calibre library. You can drag them to the desktop or to a file explorer, to copy them to your computer. You can drag them to the
|
||||
device icon in calibre to send them to the device. You can also drag and drop books from the device view in calibre to the calibre library icon or the operating
|
||||
system to copy them from the device."
|
||||
|
||||
- title: "There were many minor bug fixes for various bugs caused by the major changes in 0.7.21. So if you have updated to 0.7.21, it is highly recommended you update to 0.7.22"
|
||||
|
||||
- title: "Driver for the VelocityMicro ebook reader device"
|
||||
|
||||
- title: "Add a tweak to control how articles in titles are processed during sorting"
|
||||
|
||||
- title: "Add a new format type 'device_db' to plugboards to control the metadata displayed in book lists on SONY devices."
|
||||
|
||||
bug fixes:
|
||||
- title: "Fix ISBN not being read from filenames in 0.7.21"
|
||||
tickets: [7054]
|
||||
|
||||
- title: "Fix instant Search for text not found causes unhandled exception when conversion jobs are running"
|
||||
tickets: [7043]
|
||||
|
||||
- title: "Fix removing a publisher causes an error in 0.7.21"
|
||||
tickets: [7046]
|
||||
|
||||
- title: "MOBI Output: Fix some images being distorted in 0.7.21"
|
||||
tickets: [7049]
|
||||
|
||||
- title: "Fix regression that broke bulk conversion of books without covers in 0.7.21"
|
||||
|
||||
- title: "Fix regression that broke add and set_metadata commands in calibredb in 0.7.21"
|
||||
|
||||
- title: "Workaround for Qt bug in file open dialogs in linux that causes multiple file selection to ignore files with two or more spaces in the file name"
|
||||
|
||||
- title: "Conversion pipeline: Fix regression in 0.7.21 that broke conversion of LIT/EPUB documents that specified no title in their OPF files"
|
||||
|
||||
- title: "Fix regression that broke iPad driver in 0.7.21"
|
||||
|
||||
improved recipes:
|
||||
- Washington Post
|
||||
|
||||
|
||||
- version: 0.7.21
|
||||
date: 2010-10-01
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
from calibre.web.feeds.news import re
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
from BeautifulSoup import Tag
|
||||
|
||||
@ -10,26 +11,31 @@ class RevistaMuyInteresante(BasicNewsRecipe):
|
||||
language = 'es'
|
||||
|
||||
no_stylesheets = True
|
||||
remove_attributes = ['style', 'font']
|
||||
remove_javascript = True
|
||||
|
||||
extra_css = ' .txt_articulo{ font-family: sans-serif; font-size: medium; text-align: justify } .contentheading{font-family: serif; font-size: large; font-weight: bold; color: #000000; text-align: center}'
|
||||
|
||||
#then we add our own style(s) like this:
|
||||
extra_css = '''
|
||||
.contentheading{font-weight: bold}
|
||||
p {font-size: 4px;font-family: Times New Roman;}
|
||||
'''
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
|
||||
for img_tag in soup.findAll('img'):
|
||||
parent_tag = img_tag.parent
|
||||
if parent_tag.name == 'td':
|
||||
if not parent_tag.get('class') == 'txt_articulo': break
|
||||
imagen = img_tag
|
||||
new_tag = Tag(soup,'p')
|
||||
img_tag.replaceWith(new_tag)
|
||||
div = soup.find(attrs={'class':'article_category'})
|
||||
div.insert(0,imagen)
|
||||
imagen = img_tag
|
||||
new_tag = Tag(soup,'p')
|
||||
img_tag.replaceWith(new_tag)
|
||||
div = soup.find(attrs={'class':'article_category'})
|
||||
div.insert(0,imagen)
|
||||
break
|
||||
return soup
|
||||
|
||||
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<td class="contentheading" width="100%">.*?</td>', re.DOTALL|re.IGNORECASE), lambda match: '<td class="contentheading">' + match.group().replace('<td class="contentheading" width="100%">','').strip().replace('</td>','').strip() + '</td>'),
|
||||
|
||||
]
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':['article']}),dict(name='td', attrs={'class':['txt_articulo']})]
|
||||
|
||||
remove_tags = [
|
||||
@ -37,6 +43,7 @@ class RevistaMuyInteresante(BasicNewsRecipe):
|
||||
,dict(name='div', attrs={'id':['comment']})
|
||||
,dict(name='td', attrs={'class':['buttonheading']})
|
||||
,dict(name='div', attrs={'class':['tags_articles']})
|
||||
,dict(name='table', attrs={'class':['pagenav']})
|
||||
]
|
||||
|
||||
remove_tags_after = dict(name='div', attrs={'class':'tags_articles'})
|
||||
@ -71,8 +78,33 @@ class RevistaMuyInteresante(BasicNewsRecipe):
|
||||
for title, url in [
|
||||
('Historia',
|
||||
'http://www.muyinteresante.es/historia-articulos'),
|
||||
('Ciencia',
|
||||
'http://www.muyinteresante.es/ciencia-articulos'),
|
||||
('Naturaleza',
|
||||
'http://www.muyinteresante.es/naturaleza-articulos'),
|
||||
('Tecnología',
|
||||
'http://www.muyinteresante.es/tecnologia-articulos'),
|
||||
('Salud',
|
||||
'http://www.muyinteresante.es/salud-articulos'),
|
||||
('Más Muy',
|
||||
'http://www.muyinteresante.es/muy'),
|
||||
('Innova - Automoción',
|
||||
'http://www.muyinteresante.es/articulos-innovacion-autos'),
|
||||
('Innova - Salud',
|
||||
'http://www.muyinteresante.es/articulos-innovacion-salud'),
|
||||
('Innova - Medio Ambiente',
|
||||
'http://www.muyinteresante.es/articulos-innovacion-medio-ambiente'),
|
||||
('Innova - Alimentación',
|
||||
'http://www.muyinteresante.es/articulos-innovacion-alimentacion'),
|
||||
('Innova - Sociedad',
|
||||
'http://www.muyinteresante.es/articulos-innovacion-sociedad'),
|
||||
('Innova - Tecnología',
|
||||
'http://www.muyinteresante.es/articulos-innovacion-tecnologia'),
|
||||
('Innova - Ocio',
|
||||
'http://www.muyinteresante.es/articulos-innovacion-ocio'),
|
||||
]:
|
||||
articles = self.nz_parse_section(url)
|
||||
if articles:
|
||||
feeds.append((title, articles))
|
||||
return feeds
|
||||
|
||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.7.21'
|
||||
__version__ = '0.7.22'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
import re
|
||||
|
@ -460,7 +460,8 @@ from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK
|
||||
from calibre.devices.edge.driver import EDGE
|
||||
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, SOVOS
|
||||
from calibre.devices.sne.driver import SNE
|
||||
from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, GEMEI
|
||||
from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, \
|
||||
GEMEI, VELOCITYMICRO
|
||||
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
|
||||
from calibre.devices.kobo.driver import KOBO
|
||||
|
||||
@ -572,6 +573,7 @@ plugins += [
|
||||
PDNOVEL,
|
||||
SPECTRA,
|
||||
GEMEI,
|
||||
VELOCITYMICRO,
|
||||
ITUNES,
|
||||
]
|
||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||
|
@ -20,7 +20,7 @@ class IREXDR1000(USBMS):
|
||||
|
||||
# Ordered list of supported formats
|
||||
# Be sure these have an entry in calibre.devices.mime
|
||||
FORMATS = ['epub', 'mobi', 'prc', 'html', 'pdf', 'txt']
|
||||
FORMATS = ['epub', 'mobi', 'prc', 'html', 'pdf', 'djvu', 'txt']
|
||||
|
||||
VENDOR_ID = [0x1e6b]
|
||||
PRODUCT_ID = [0x001]
|
||||
|
@ -108,6 +108,24 @@ class PDNOVEL(USBMS):
|
||||
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
|
||||
coverfile.write(coverdata[2])
|
||||
|
||||
class VELOCITYMICRO(USBMS):
|
||||
name = 'VelocityMicro device interface'
|
||||
gui_name = 'VelocityMicro'
|
||||
description = _('Communicate with the VelocityMicro')
|
||||
author = 'Kovid Goyal'
|
||||
supported_platforms = ['windows', 'linux', 'osx']
|
||||
FORMATS = ['epub', 'pdb', 'txt', 'html', 'pdf']
|
||||
|
||||
VENDOR_ID = [0x18d1]
|
||||
PRODUCT_ID = [0xb015]
|
||||
BCD = [0x224]
|
||||
|
||||
VENDOR_NAME = 'ANDROID'
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE'
|
||||
|
||||
EBOOK_DIR_MAIN = 'eBooks'
|
||||
SUPPORTS_SUB_DIRS = False
|
||||
|
||||
class GEMEI(USBMS):
|
||||
name = 'Gemei Device Interface'
|
||||
gui_name = 'GM2000'
|
||||
|
@ -14,7 +14,6 @@ from calibre.devices.prs505 import CACHE_XML
|
||||
from calibre.devices.prs505.sony_cache import XMLCache
|
||||
from calibre import __appname__
|
||||
from calibre.devices.usbms.books import CollectionsBookList
|
||||
from calibre.utils.config import tweaks
|
||||
|
||||
class PRS505(USBMS):
|
||||
|
||||
@ -171,4 +170,4 @@ class PRS505(USBMS):
|
||||
|
||||
def set_plugboard(self, pb):
|
||||
debug_print('PRS505: use plugboard', pb)
|
||||
self.plugboard = pb
|
||||
self.plugboard = pb
|
||||
|
@ -151,7 +151,8 @@ class CHMReader(CHMFile):
|
||||
continue
|
||||
raise
|
||||
self._extracted = True
|
||||
files = os.listdir(output_dir)
|
||||
files = [x for x in os.listdir(output_dir) if
|
||||
os.path.isfile(os.path.join(output_dir, x))]
|
||||
if self.hhc_path not in files:
|
||||
for f in files:
|
||||
if f.lower() == self.hhc_path.lower():
|
||||
|
@ -31,12 +31,14 @@ class CoverManager(object):
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="100%%" height="100%%" viewBox="__viewbox__"
|
||||
preserveAspectRatio="__ar__">
|
||||
<image width="__width__" height="__height__" xlink:href="%s"/>
|
||||
</svg>
|
||||
<div>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="100%%" height="100%%" viewBox="__viewbox__"
|
||||
preserveAspectRatio="__ar__">
|
||||
<image width="__width__" height="__height__" xlink:href="%s"/>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
''')
|
||||
|
@ -425,8 +425,10 @@ class BooksView(QTableView): # {{{
|
||||
Accept a drop event and return a list of paths that can be read from
|
||||
and represent files with extensions.
|
||||
'''
|
||||
if event.mimeData().hasFormat('text/uri-list'):
|
||||
urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
|
||||
md = event.mimeData()
|
||||
if md.hasFormat('text/uri-list') and not \
|
||||
md.hasFormat('application/calibre+from_library'):
|
||||
urls = [unicode(u.toLocalFile()) for u in md.urls()]
|
||||
return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)]
|
||||
|
||||
def drag_icon(self, cover, multiple):
|
||||
@ -465,8 +467,25 @@ class BooksView(QTableView): # {{{
|
||||
ids = ' '.join(map(str, selected))
|
||||
md = QMimeData()
|
||||
md.setData('application/calibre+from_library', ids)
|
||||
md.setUrls([QUrl.fromLocalFile(db.abspath(i, index_is_id=True))
|
||||
for i in selected])
|
||||
fmt = prefs['output_format']
|
||||
|
||||
def url_for_id(i):
|
||||
ans = db.format_abspath(i, fmt, index_is_id=True)
|
||||
if ans is None:
|
||||
fmts = db.formats(i, index_is_id=True)
|
||||
if fmts:
|
||||
fmts = fmts.split(',')
|
||||
else:
|
||||
fmts = []
|
||||
for f in fmts:
|
||||
ans = db.format_abspath(i, f, index_is_id=True)
|
||||
if ans is not None:
|
||||
break
|
||||
if ans is None:
|
||||
ans = db.abspath(i, index_is_id=True)
|
||||
return QUrl.fromLocalFile(ans)
|
||||
|
||||
md.setUrls([url_for_id(i) for i in selected])
|
||||
drag = QDrag(self)
|
||||
drag.setMimeData(md)
|
||||
cover = self.drag_icon(m.cover(self.currentIndex().row()),
|
||||
|
@ -79,6 +79,8 @@ class TagsView(QTreeView): # {{{
|
||||
self.setHeaderHidden(True)
|
||||
self.setItemDelegate(TagDelegate(self))
|
||||
self.made_connections = False
|
||||
self.setAcceptDrops(True)
|
||||
self.setDropIndicatorShown(True)
|
||||
|
||||
def set_database(self, db, tag_match, sort_by):
|
||||
self.hidden_categories = config['tag_browser_hidden_categories']
|
||||
@ -104,6 +106,49 @@ class TagsView(QTreeView): # {{{
|
||||
def database_changed(self, event, ids):
|
||||
self.refresh_required.emit()
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
md = event.mimeData()
|
||||
if md.hasFormat("application/calibre+from_library"):
|
||||
event.setDropAction(Qt.CopyAction)
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
allowed = False
|
||||
idx = self.indexAt(event.pos())
|
||||
m = self.model()
|
||||
p = m.parent(idx)
|
||||
if idx.isValid() and p.isValid():
|
||||
item = m.data(p, Qt.UserRole)
|
||||
if item.type == TagTreeItem.CATEGORY and \
|
||||
item.category_key in \
|
||||
('tags', 'series', 'authors', 'rating', 'publisher'):
|
||||
allowed = True
|
||||
if allowed:
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def dropEvent(self, event):
|
||||
idx = self.indexAt(event.pos())
|
||||
m = self.model()
|
||||
p = m.parent(idx)
|
||||
if idx.isValid() and p.isValid():
|
||||
item = m.data(p, Qt.UserRole)
|
||||
if item.type == TagTreeItem.CATEGORY and \
|
||||
item.category_key in \
|
||||
('tags', 'series', 'authors', 'rating', 'publisher'):
|
||||
child = m.data(idx, Qt.UserRole)
|
||||
md = event.mimeData()
|
||||
mime = 'application/calibre+from_library'
|
||||
ids = list(map(int, str(md.data(mime)).split()))
|
||||
self.handle_drop(item, child, ids)
|
||||
event.accept()
|
||||
|
||||
def handle_drop(self, parent, child, ids):
|
||||
print 'Dropped ids:', ids
|
||||
|
||||
@property
|
||||
def match_all(self):
|
||||
return self.tag_match and self.tag_match.currentIndex() > 0
|
||||
@ -326,6 +371,8 @@ class TagTreeItem(object): # {{{
|
||||
self.children.append(child)
|
||||
|
||||
def data(self, role):
|
||||
if role == Qt.UserRole:
|
||||
return self
|
||||
if self.type == self.TAG:
|
||||
return self.tag_data(role)
|
||||
if self.type == self.CATEGORY:
|
||||
@ -544,8 +591,14 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
def headerData(self, *args):
|
||||
return NONE
|
||||
|
||||
def flags(self, *args):
|
||||
return Qt.ItemIsEnabled|Qt.ItemIsSelectable|Qt.ItemIsEditable
|
||||
def flags(self, index, *args):
|
||||
ans = Qt.ItemIsEnabled|Qt.ItemIsSelectable|Qt.ItemIsEditable
|
||||
if index.isValid() and self.parent(index).isValid():
|
||||
ans |= Qt.ItemIsDropEnabled
|
||||
return ans
|
||||
|
||||
def supportedDropActions(self):
|
||||
return Qt.CopyAction|Qt.MoveAction
|
||||
|
||||
def path_for_index(self, index):
|
||||
ans = []
|
||||
|
@ -8,7 +8,6 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, traceback, cStringIO, re
|
||||
|
||||
from calibre import prints
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.utils.config import Config, StringConfig, tweaks
|
||||
from calibre.utils.formatter import TemplateFormatter
|
||||
|
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
@ -18,8 +18,9 @@ If this module is run, it will perform a series of unit tests.
|
||||
|
||||
import sys, string, operator
|
||||
|
||||
from calibre.utils.pyparsing import CaselessKeyword, Group, Forward, CharsNotIn, Suppress, \
|
||||
OneOrMore, MatchFirst, CaselessLiteral, Optional, NoMatch, ParseException
|
||||
from calibre.utils.pyparsing import CaselessKeyword, Group, Forward, \
|
||||
CharsNotIn, Suppress, OneOrMore, MatchFirst, CaselessLiteral, \
|
||||
Optional, NoMatch, ParseException, QuotedString
|
||||
from calibre.constants import preferred_encoding
|
||||
|
||||
|
||||
@ -127,18 +128,21 @@ class SearchQueryParser(object):
|
||||
location |= l
|
||||
location = Optional(location, default='all')
|
||||
word_query = CharsNotIn(string.whitespace + '()')
|
||||
quoted_query = Suppress('"')+CharsNotIn('"')+Suppress('"')
|
||||
#quoted_query = Suppress('"')+CharsNotIn('"')+Suppress('"')
|
||||
quoted_query = QuotedString('"', escChar='\\')
|
||||
query = quoted_query | word_query
|
||||
Token = Group(location + query).setResultsName('token')
|
||||
|
||||
if test:
|
||||
print 'Testing Token parser:'
|
||||
Token.validate()
|
||||
failed = SearchQueryParser.run_tests(Token, 'token',
|
||||
(
|
||||
('tag:asd', ['tag', 'asd']),
|
||||
('ddsä', ['all', 'ddsä']),
|
||||
('"one two"', ['all', 'one two']),
|
||||
('title:"one two"', ['title', 'one two']),
|
||||
(u'ddsä', ['all', u'ddsä']),
|
||||
('"one \\"two"', ['all', 'one "two']),
|
||||
('title:"one \\"1.5\\" two"', ['title', 'one "1.5" two']),
|
||||
('title:abc"def', ['title', 'abc"def']),
|
||||
)
|
||||
)
|
||||
|
||||
@ -167,7 +171,7 @@ class SearchQueryParser(object):
|
||||
).setResultsName("or") | And)
|
||||
|
||||
if test:
|
||||
Or.validate()
|
||||
#Or.validate()
|
||||
self._tests_failed = bool(failed)
|
||||
|
||||
self._parser = Or
|
||||
@ -240,6 +244,8 @@ class SearchQueryParser(object):
|
||||
'''
|
||||
return set([])
|
||||
|
||||
# Testing {{{
|
||||
|
||||
class Tester(SearchQueryParser):
|
||||
|
||||
texts = {
|
||||
@ -599,3 +605,6 @@ def main(args=sys.argv):
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
# }}}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user