Merge from trunk

This commit is contained in:
Charles Haley 2011-03-03 09:00:38 +00:00
commit 525005f065
26 changed files with 420 additions and 109 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,8 +1,8 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__author__ = 'Todd Chapman' __author__ = 'Todd Chapman'
__copyright__ = 'Todd Chapman' __copyright__ = 'Todd Chapman'
__version__ = 'v0.1' __version__ = 'v0.2'
__date__ = '26 February 2011' __date__ = '2 March 2011'
''' '''
http://www.buffalonews.com/RSS/ http://www.buffalonews.com/RSS/
@ -12,12 +12,16 @@ from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1298680852(BasicNewsRecipe): class AdvancedUserRecipe1298680852(BasicNewsRecipe):
title = u'Buffalo News' title = u'Buffalo News'
__author__ = 'ChappyOnIce'
language = 'en'
oldest_article = 2 oldest_article = 2
language = 'en'
__author__ = 'ChappyOnIce'
max_articles_per_feed = 20 max_articles_per_feed = 20
encoding = 'utf-8' encoding = 'utf-8'
masthead_url = 'http://www.buffalonews.com/buffalonews/skins/buffalonews/images/masthead/the_buffalo_news_logo.png'
remove_javascript = True remove_javascript = True
extra_css = 'body {text-align: justify;}\n \
p {text-indent: 20px;}'
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'class':['main-content-left']}) dict(name='div', attrs={'class':['main-content-left']})
] ]
@ -28,9 +32,7 @@ class AdvancedUserRecipe1298680852(BasicNewsRecipe):
] ]
remove_tags_after = dict(name='div', attrs={'class':['body storyContent']}) remove_tags_after = dict(name='div', attrs={'class':['body storyContent']})
conversion_options = {
'base_font_size' : 14,
}
feeds = [(u'City of Buffalo', u'http://www.buffalonews.com/city/communities/buffalo/?widget=rssfeed&view=feed&contentId=77944'), feeds = [(u'City of Buffalo', u'http://www.buffalonews.com/city/communities/buffalo/?widget=rssfeed&view=feed&contentId=77944'),
(u'Southern Erie County', u'http://www.buffalonews.com/city/communities/southern-erie/?widget=rssfeed&view=feed&contentId=77944'), (u'Southern Erie County', u'http://www.buffalonews.com/city/communities/southern-erie/?widget=rssfeed&view=feed&contentId=77944'),
(u'Eastern Erie County', u'http://www.buffalonews.com/city/communities/eastern-erie/?widget=rssfeed&view=feed&contentId=77944'), (u'Eastern Erie County', u'http://www.buffalonews.com/city/communities/eastern-erie/?widget=rssfeed&view=feed&contentId=77944'),

View File

@ -1,35 +1,44 @@
#!/usr/bin/env python #!/usr/bin/env python
__license__ = 'GPL 3' __license__ = 'GPL 3'
__copyright__ = 'zotzot' __copyright__ = 'zotzo'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class CreditSlips(BasicNewsRecipe): class CreditSlips(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = 'zotzot'
language = 'en' language = 'en'
version = 1 __author__ = 'zotzot'
version = 2
title = u'Credit Slips.org' title = u'Credit Slips.org'
publisher = u'Bankr-L' publisher = u'Bankr-L'
category = u'Economic blog' category = u'Economic blog'
description = u'All things about credit.' description = u'A discussion on credit and bankruptcy'
cover_url = 'http://bit.ly/hyZSTr' cover_url = 'http://bit.ly/eAKNCB'
oldest_article = 50 oldest_article = 15
max_articles_per_feed = 100 max_articles_per_feed = 100
use_embedded_content = True use_embedded_content = True
no_stylesheets = True
remove_javascript = True
conversion_options = {
'comments': description,
'tags': category,
'language': 'en',
'publisher': publisher,
}
feeds = [ feeds = [
(u'Credit Slips', u'http://www.creditslips.org/creditslips/atom.xml') (u'Credit Slips', u'http://www.creditslips.org/creditslips/atom.xml')
] ]
conversion_options = {
'comments': description, extra_css = '''
'tags': category, .author {font-family:Helvetica,sans-serif; font-weight:normal;font-size:small;}
'language': 'en', h1 {font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
'publisher': publisher p {font-family:Helvetica,Arial,sans-serif;font-size:small;}
} body {font-family:Helvetica,Arial,sans-serif;font-size:small;}
extra_css = ''' '''
body{font-family:verdana,arial,helvetica,geneva,sans-serif;}
img {float: left; margin-right: 0.5em;} def populate_article_metadata(self, article, soup, first):
''' h2 = soup.find('h2')
h2.replaceWith(h2.prettify() + '<p><em>Posted by ' + article.author + '</em></p>')

View File

@ -0,0 +1,21 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1299061355(BasicNewsRecipe):
title = u'Post Today'
language = 'th'
__author__ = "Chotechai P."
oldest_article = 7
max_articles_per_feed = 100
cover_url = 'http://upload.wikimedia.org/wikipedia/th/2/2e/Posttoday_Logo.png'
feeds = [(u'Breaking News', u'http://www.posttoday.com/rss/src/breakingnews.xml'), (u'\u0e02\u0e48\u0e32\u0e27', u'http://www.posttoday.com/rss/src/news.xml'), (u'\u0e27\u0e34\u0e40\u0e04\u0e23\u0e32\u0e30\u0e2b\u0e4c', u'http://www.posttoday.com/rss/src/analyse.xml'), (u'\u0e40\u0e21\u0e32\u0e17\u0e4c\u0e01\u0e31\u0e19\u0e43\u0e2b\u0e49 z', u'http://www.posttoday.com/rss/src/mouth.xml'), (u'\u0e44\u0e17\u0e22\u0e42\u0e0b\u0e44\u0e0b\u0e15\u0e35\u0e49', u'http://www.posttoday.com/rss/src/thaisociety.xml'), (u'\u0e44\u0e25\u0e1f\u0e4c\u0e2a\u0e44\u0e15\u0e25\u0e4c', u'http://www.posttoday.com/rss/src/lifestyle.xml'), (u'\u0e0a\u0e35\u0e49\u0e0a\u0e48\u0e2d\u0e07\u0e23\u0e27\u0e22', u'http://www.posttoday.com/rss/src/moneyguide.xml'), (u'\u0e1a\u0e49\u0e32\u0e19-\u0e04\u0e2d\u0e19\u0e42\u0e14', u'http://www.posttoday.com/rss/src/homecondo.xml'), (u'\u0e22\u0e32\u0e19\u0e22\u0e19\u0e15\u0e4c', u'http://www.posttoday.com/rss/src/motor.xml'), (u'\u0e14\u0e34\u0e08\u0e34\u0e15\u0e2d\u0e25\u0e44\u0e25\u0e1f\u0e4c', u'http://www.posttoday.com/rss/src/digitallife.xml'), (u'\u0e01\u0e35\u0e2c\u0e32', u'http://www.posttoday.com/rss/src/sport.xml'), (u'\u0e23\u0e2d\u0e1a\u0e42\u0e25\u0e01', u'http://www.posttoday.com/rss/src/world.xml'), (u'\u0e01\u0e34\u0e19-\u0e40\u0e17\u0e35\u0e48\u0e22\u0e27', u'http://www.posttoday.com/rss/src/eattravel.xml'), (u'Mind & Soul', u'http://www.posttoday.com/rss/src/mindsoul.xml'), (u'\u0e1a\u0e25\u0e47\u0e2d\u0e01 \u0e1a\u0e01.', u'http://www.posttoday.com/rss/src/blogs.xml')]
keep_only_tags = []
keep_only_tags.append(dict(name = 'div', attrs = {'class' :
'articleContents'}))
remove_tags = []
remove_tags.append(dict(name = 'label'))
remove_tags.append(dict(name = 'span'))
remove_tags.append(dict(name = 'div', attrs = {'class' :
'socialBookmark'}))
remove_tags.append(dict(name = 'div', attrs = {'class' :
'misc'}))

View File

@ -0,0 +1,49 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1286819935(BasicNewsRecipe):
title = u'RBC.ru'
__author__ = 'A. Chewi'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
conversion_options = {'linearize_tables' : True}
remove_attributes = ['style']
language = 'ru'
timefmt = ' [%a, %d %b, %Y]'
keep_only_tags = [dict(name='h2', attrs={}),
dict(name='div', attrs={'class': 'box _ga1_on_'}),
dict(name='h1', attrs={'class': 'news_section'}),
dict(name='div', attrs={'class': 'news_body dotted_border_bottom'}),
dict(name='table', attrs={'class': 'newsBody'}),
dict(name='h2', attrs={'class': 'black'})]
feeds = [(u'Главные новости', u'http://static.feed.rbc.ru/rbc/internal/rss.rbc.ru/rbc.ru/mainnews.rss'),
(u'Политика', u'http://static.feed.rbc.ru/rbc/internal/rss.rbc.ru/rbc.ru/politics.rss'),
(u'Экономика', u'http://static.feed.rbc.ru/rbc/internal/rss.rbc.ru/rbc.ru/economics.rss'),
(u'Общество', u'http://static.feed.rbc.ru/rbc/internal/rss.rbc.ru/rbc.ru/society.rss'),
(u'Происшествия', u'http://static.feed.rbc.ru/rbc/internal/rss.rbc.ru/rbc.ru/incidents.rss'),
(u'Финансовые новости Quote.rbc.ru', u'http://static.feed.rbc.ru/rbc/internal/rss.rbc.ru/quote.ru/mainnews.rss')]
remove_tags = [dict(name='div', attrs={'class': "video-frame"}),
dict(name='div', attrs={'class': "photo-container videoContainer videoSWFLinks videoPreviewSlideContainer notes"}),
dict(name='div', attrs={'class': "notes"}),
dict(name='div', attrs={'class': "publinks"}),
dict(name='a', attrs={'class': "print"}),
dict(name='div', attrs={'class': "photo-report_new notes newslider"}),
dict(name='div', attrs={'class': "videoContainer"}),
dict(name='div', attrs={'class': "videoPreviewSlideContainer"}),
dict(name='a', attrs={'class': "videoPreviewContainer"}),
dict(name='a', attrs={'class': "red"}),]
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup
def print_version(self, url):
return url + '?print=true'

View File

@ -0,0 +1,17 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1299054026(BasicNewsRecipe):
title = u'Thai Post Daily'
__author__ = 'Chotechai P.'
oldest_article = 7
max_articles_per_feed = 100
cover_url = 'http://upload.wikimedia.org/wikipedia/th/1/10/ThaiPost_Logo.png'
feeds = [(u'\u0e02\u0e48\u0e32\u0e27\u0e2b\u0e19\u0e49\u0e32\u0e2b\u0e19\u0e36\u0e48\u0e07', u'http://thaipost.net/taxonomy/term/1/all/feed'), (u'\u0e1a\u0e17\u0e1a\u0e23\u0e23\u0e13\u0e32\u0e18\u0e34\u0e01\u0e32\u0e23', u'http://thaipost.net/taxonomy/term/11/all/feed'), (u'\u0e40\u0e1b\u0e25\u0e27 \u0e2a\u0e35\u0e40\u0e07\u0e34\u0e19', u'http://thaipost.net/taxonomy/term/2/all/feed'), (u'\u0e2a\u0e20\u0e32\u0e1b\u0e23\u0e30\u0e0a\u0e32\u0e0a\u0e19', u'http://thaipost.net/taxonomy/term/3/all/feed'), (u'\u0e16\u0e39\u0e01\u0e17\u0e38\u0e01\u0e02\u0e49\u0e2d', u'http://thaipost.net/taxonomy/term/4/all/feed'), (u'\u0e01\u0e32\u0e23\u0e40\u0e21\u0e37\u0e2d\u0e07', u'http://thaipost.net/taxonomy/term/5/all/feed'), (u'\u0e17\u0e48\u0e32\u0e19\u0e02\u0e38\u0e19\u0e19\u0e49\u0e2d\u0e22', u'http://thaipost.net/taxonomy/term/12/all/feed'), (u'\u0e1a\u0e17\u0e04\u0e27\u0e32\u0e21\u0e1e\u0e34\u0e40\u0e28\u0e29', u'http://thaipost.net/taxonomy/term/66/all/feed'), (u'\u0e23\u0e32\u0e22\u0e07\u0e32\u0e19\u0e1e\u0e34\u0e40\u0e28\u0e29', u'http://thaipost.net/taxonomy/term/67/all/feed'), (u'\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01\u0e2b\u0e19\u0e49\u0e32 4', u'http://thaipost.net/taxonomy/term/13/all/feed'), (u'\u0e40\u0e2a\u0e35\u0e22\u0e1a\u0e0b\u0e36\u0e48\u0e07\u0e2b\u0e19\u0e49\u0e32', u'http://thaipost.net/taxonomy/term/64/all/feed'), (u'\u0e04\u0e31\u0e19\u0e1b\u0e32\u0e01\u0e2d\u0e22\u0e32\u0e01\u0e40\u0e25\u0e48\u0e32', u'http://thaipost.net/taxonomy/term/65/all/feed'), (u'\u0e40\u0e28\u0e23\u0e29\u0e10\u0e01\u0e34\u0e08', u'http://thaipost.net/taxonomy/term/6/all/feed'), (u'\u0e01\u0e23\u0e30\u0e08\u0e01\u0e44\u0e23\u0e49\u0e40\u0e07\u0e32', u'http://thaipost.net/taxonomy/term/14/all/feed'), (u'\u0e01\u0e23\u0e30\u0e08\u0e01\u0e2b\u0e31\u0e01\u0e21\u0e38\u0e21', u'http://thaipost.net/taxonomy/term/71/all/feed'), (u'\u0e04\u0e34\u0e14\u0e40\u0e2b\u0e19\u0e37\u0e2d\u0e01\u0e23\u0e30\u0e41\u0e2a', u'http://thaipost.net/taxonomy/term/69/all/feed'), (u'\u0e23\u0e32\u0e22\u0e07\u0e32\u0e19', u'http://thaipost.net/taxonomy/term/68/all/feed'), (u'\u0e2d\u0e34\u0e42\u0e04\u0e42\u0e1f\u0e01\u0e31\u0e2a', u'http://thaipost.net/taxonomy/term/10/all/feed'), (u'\u0e01\u0e32\u0e23\u0e28\u0e36\u0e01\u0e29\u0e32-\u0e2a\u0e32\u0e18\u0e32\u0e23\u0e13\u0e2a\u0e38\u0e02', u'http://thaipost.net/taxonomy/term/7/all/feed'), (u'\u0e15\u0e48\u0e32\u0e07\u0e1b\u0e23\u0e30\u0e40\u0e17\u0e28', u'http://thaipost.net/taxonomy/term/8/all/feed'), (u'\u0e01\u0e35\u0e2c\u0e32', u'http://thaipost.net/taxonomy/term/9/all/feed')]
def print_version(self, url):
return url.replace(url, 'http://www.thaipost.net/print/' + url [32:])
remove_tags = []
remove_tags.append(dict(name = 'div', attrs = {'class' : 'print-logo'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'print-site_name'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'print-breadcrumb'}))

View File

@ -272,6 +272,7 @@ class NEXTBOOK(USBMS):
VENDOR_NAME = 'NEXT2' VENDOR_NAME = 'NEXT2'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '1.0.14' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '1.0.14'
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
THUMBNAIL_HEIGHT = 120
''' '''
def upload_cover(self, path, filename, metadata, filepath): def upload_cover(self, path, filename, metadata, filepath):

View File

@ -20,9 +20,26 @@ from calibre.ebooks import BOOK_EXTENSIONS
from calibre.utils.filenames import ascii_filename from calibre.utils.filenames import ascii_filename
from calibre.constants import preferred_encoding, filesystem_encoding from calibre.constants import preferred_encoding, filesystem_encoding
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2 import config from calibre.gui2 import config, question_dialog
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
def get_filters():
return [
(_('Books'), BOOK_EXTENSIONS),
(_('EPUB Books'), ['epub']),
(_('LRF Books'), ['lrf']),
(_('HTML Books'), ['htm', 'html', 'xhtm', 'xhtml']),
(_('LIT Books'), ['lit']),
(_('MOBI Books'), ['mobi', 'prc', 'azw']),
(_('Topaz books'), ['tpz','azw1']),
(_('Text books'), ['txt', 'rtf']),
(_('PDF Books'), ['pdf']),
(_('SNB Books'), ['snb']),
(_('Comics'), ['cbz', 'cbr', 'cbc']),
(_('Archives'), ['zip', 'rar']),
]
class AddAction(InterfaceAction): class AddAction(InterfaceAction):
name = 'Add Books' name = 'Add Books'
@ -47,6 +64,10 @@ class AddAction(InterfaceAction):
self.add_menu.addAction(_('Add Empty book. (Book entry with no ' self.add_menu.addAction(_('Add Empty book. (Book entry with no '
'formats)'), self.add_empty, _('Shift+Ctrl+E')) 'formats)'), self.add_empty, _('Shift+Ctrl+E'))
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn) self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
self.add_menu.addSeparator()
self.add_menu.addAction(_('Add files to selected book records'),
self.add_formats, _('Shift+A'))
self.qaction.setMenu(self.add_menu) self.qaction.setMenu(self.add_menu)
self.qaction.triggered.connect(self.add_books) self.qaction.triggered.connect(self.add_books)
@ -55,6 +76,39 @@ class AddAction(InterfaceAction):
for action in list(self.add_menu.actions())[1:]: for action in list(self.add_menu.actions())[1:]:
action.setEnabled(enabled) action.setEnabled(enabled)
def add_formats(self, *args):
if self.gui.stack.currentIndex() != 0:
return
view = self.gui.library_view
rows = view.selectionModel().selectedRows()
if not rows:
return
ids = [view.model().id(r) for r in rows]
if len(ids) > 1 and not question_dialog(self.gui,
_('Are you sure'),
_('Are you sure you want to add the same'
' files to all %d books? If the format'
'already exists for a book, it will be replaced.')%len(ids)):
return
books = choose_files(self.gui, 'add formats dialog dir',
_('Select book files'), filters=get_filters())
if not books:
return
db = view.model().db
for id_ in ids:
for fpath in books:
fmt = os.path.splitext(fpath)[1][1:].upper()
if fmt:
db.add_format_with_hooks(id_, fmt, fpath, index_is_id=True,
notify=True)
current_idx = self.gui.library_view.currentIndex()
if current_idx.isValid():
view.model().current_changed(current_idx, current_idx)
def add_recursive(self, single): def add_recursive(self, single):
root = choose_dir(self.gui, 'recursive book import root dir dialog', root = choose_dir(self.gui, 'recursive book import root dir dialog',
'Select root folder') 'Select root folder')
@ -207,27 +261,14 @@ class AddAction(InterfaceAction):
''' '''
Add books from the local filesystem to either the library or the device. Add books from the local filesystem to either the library or the device.
''' '''
filters = [ filters = get_filters()
(_('Books'), BOOK_EXTENSIONS),
(_('EPUB Books'), ['epub']),
(_('LRF Books'), ['lrf']),
(_('HTML Books'), ['htm', 'html', 'xhtm', 'xhtml']),
(_('LIT Books'), ['lit']),
(_('MOBI Books'), ['mobi', 'prc', 'azw']),
(_('Topaz books'), ['tpz','azw1']),
(_('Text books'), ['txt', 'rtf']),
(_('PDF Books'), ['pdf']),
(_('SNB Books'), ['snb']),
(_('Comics'), ['cbz', 'cbr', 'cbc']),
(_('Archives'), ['zip', 'rar']),
]
to_device = self.gui.stack.currentIndex() != 0 to_device = self.gui.stack.currentIndex() != 0
if to_device: if to_device:
fmts = self.gui.device_manager.device.settings().format_map fmts = self.gui.device_manager.device.settings().format_map
filters = [(_('Supported books'), fmts)] filters = [(_('Supported books'), fmts)]
books = choose_files(self.gui, 'add books dialog dir', 'Select books', books = choose_files(self.gui, 'add books dialog dir',
filters=filters) _('Select books'), filters=filters)
if not books: if not books:
return return
self._add_books(books, to_device) self._add_books(books, to_device)

View File

@ -355,6 +355,7 @@ class ChooseLibraryAction(InterfaceAction):
print print
print 'before:', self.before_mem print 'before:', self.before_mem
print 'after:', memory()/1024**2 print 'after:', memory()/1024**2
print
self.dbref = self.before_mem = None self.dbref = self.before_mem = None

View File

@ -341,7 +341,7 @@ from the value in the box</string>
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>990000</number> <number>99000000</number>
</property> </property>
<property name="value"> <property name="value">
<number>1</number> <number>1</number>

View File

@ -419,7 +419,7 @@ If the box is colored green, then text matches the individual author's sort stri
<string>Book </string> <string>Book </string>
</property> </property>
<property name="maximum"> <property name="maximum">
<double>9999.989999999999782</double> <double>99999999.989999994635582</double>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -351,7 +351,7 @@ class SeriesIndexEdit(QDoubleSpinBox):
QDoubleSpinBox.__init__(self, parent) QDoubleSpinBox.__init__(self, parent)
self.dialog = parent self.dialog = parent
self.db = self.original_series_name = None self.db = self.original_series_name = None
self.setMaximum(1000000) self.setMaximum(10000000)
self.series_edit = series_edit self.series_edit = series_edit
series_edit.currentIndexChanged.connect(self.enable) series_edit.currentIndexChanged.connect(self.enable)
series_edit.editTextChanged.connect(self.enable) series_edit.editTextChanged.connect(self.enable)

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import textwrap import textwrap
from PyQt4.Qt import QWidget, pyqtSignal, QCheckBox, QAbstractSpinBox, \ from PyQt4.Qt import QWidget, pyqtSignal, QCheckBox, QAbstractSpinBox, \
QLineEdit, QComboBox, QVariant QLineEdit, QComboBox, QVariant, Qt
from calibre.customize.ui import preferences_plugins from calibre.customize.ui import preferences_plugins
from calibre.utils.config import ConfigProxy from calibre.utils.config import ConfigProxy
@ -82,6 +82,8 @@ class ConfigWidgetInterface(object):
class Setting(object): class Setting(object):
CHOICES_SEARCH_FLAGS = Qt.MatchExactly | Qt.MatchCaseSensitive
def __init__(self, name, config_obj, widget, gui_name=None, def __init__(self, name, config_obj, widget, gui_name=None,
empty_string_is_None=True, choices=None, restart_required=False): empty_string_is_None=True, choices=None, restart_required=False):
self.name, self.gui_name = name, gui_name self.name, self.gui_name = name, gui_name
@ -168,7 +170,8 @@ class Setting(object):
elif self.datatype == 'string': elif self.datatype == 'string':
self.gui_obj.setText(val if val else '') self.gui_obj.setText(val if val else '')
elif self.datatype == 'choice': elif self.datatype == 'choice':
idx = self.gui_obj.findData(QVariant(val)) idx = self.gui_obj.findData(QVariant(val), role=Qt.UserRole,
flags=self.CHOICES_SEARCH_FLAGS)
if idx == -1: if idx == -1:
idx = 0 idx = 0
self.gui_obj.setCurrentIndex(idx) self.gui_obj.setCurrentIndex(idx)

View File

@ -9,7 +9,7 @@ import re
from PyQt4.Qt import Qt, QVariant, QListWidgetItem from PyQt4.Qt import Qt, QVariant, QListWidgetItem
from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
from calibre.gui2.preferences.behavior_ui import Ui_Form from calibre.gui2.preferences.behavior_ui import Ui_Form
from calibre.gui2 import config, info_dialog, dynamic from calibre.gui2 import config, info_dialog, dynamic
from calibre.utils.config import prefs from calibre.utils.config import prefs
@ -20,6 +20,10 @@ from calibre.ebooks.oeb.iterator import is_supported
from calibre.constants import iswindows from calibre.constants import iswindows
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
class OutputFormatSetting(Setting):
CHOICES_SEARCH_FLAGS = Qt.MatchFixedString
class ConfigWidget(ConfigWidgetBase, Ui_Form): class ConfigWidget(ConfigWidgetBase, Ui_Form):
def genesis(self, gui): def genesis(self, gui):
@ -43,7 +47,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
output_formats = list(sorted(available_output_formats())) output_formats = list(sorted(available_output_formats()))
output_formats.remove('oeb') output_formats.remove('oeb')
choices = [(x.upper(), x) for x in output_formats] choices = [(x.upper(), x) for x in output_formats]
r('output_format', prefs, choices=choices) r('output_format', prefs, choices=choices, setting=OutputFormatSetting)
restrictions = sorted(saved_searches().names(), key=sort_key) restrictions = sorted(saved_searches().names(), key=sort_key)
choices = [('', '')] + [(x, x) for x in restrictions] choices = [('', '')] + [(x, x) for x in restrictions]

View File

@ -38,6 +38,9 @@
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<widget class="QToolButton" name="column_up"> <widget class="QToolButton" name="column_up">
<property name="toolTip">
<string>Move column up</string>
</property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
@ -45,6 +48,12 @@
<iconset resource="../../../../resources/images.qrc"> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset> <normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset>
</property> </property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -72,6 +81,12 @@
<iconset resource="../../../../resources/images.qrc"> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/minus.png</normaloff>:/images/minus.png</iconset> <normaloff>:/images/minus.png</normaloff>:/images/minus.png</iconset>
</property> </property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -99,6 +114,12 @@
<iconset resource="../../../../resources/images.qrc"> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/plus.png</normaloff>:/images/plus.png</iconset> <normaloff>:/images/plus.png</normaloff>:/images/plus.png</iconset>
</property> </property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -126,6 +147,12 @@
<iconset resource="../../../../resources/images.qrc"> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset> <normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
</property> </property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -143,6 +170,9 @@
</item> </item>
<item> <item>
<widget class="QToolButton" name="column_down"> <widget class="QToolButton" name="column_down">
<property name="toolTip">
<string>Move column down</string>
</property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
@ -150,6 +180,12 @@
<iconset resource="../../../../resources/images.qrc"> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset> <normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset>
</property> </property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -6,7 +6,6 @@ __copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
import re import re
from functools import partial from functools import partial
from PyQt4.QtCore import SIGNAL
from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant
from calibre.gui2.preferences.create_custom_column_ui import Ui_QCreateCustomColumn from calibre.gui2.preferences.create_custom_column_ui import Ui_QCreateCustomColumn
@ -48,6 +47,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
Ui_QCreateCustomColumn.__init__(self) Ui_QCreateCustomColumn.__init__(self)
self.setupUi(self) self.setupUi(self)
self.setWindowTitle(_('Create a custom column'))
self.heading_label.setText(_('Create a custom column'))
# Remove help icon on title bar # Remove help icon on title bar
icon = self.windowIcon() icon = self.windowIcon()
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
@ -55,8 +56,18 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
self.simple_error = partial(error_dialog, self, show=True, self.simple_error = partial(error_dialog, self, show=True,
show_copy_button=False) show_copy_button=False)
self.connect(self.button_box, SIGNAL("accepted()"), self.accept) self.button_box.accepted.connect(self.accept)
self.connect(self.button_box, SIGNAL("rejected()"), self.reject) self.button_box.rejected.connect(self.reject)
self.shortcuts.linkActivated.connect(self.shortcut_activated)
text = '<p>'+_('Quick create:')
for col, name in [('isbn', _('ISBN')), ('formats', _('Formats')),
('last_modified', _('Modified Date')), ('yesno', _('Yes/No')),
('tags', _('Tags')), ('series', _('Series')), ('rating',
_('Rating'))]:
text += ' <a href="col:%s">%s</a>,'%(col, name)
text = text[:-1]
self.shortcuts.setText(text)
self.parent = parent self.parent = parent
self.editing_col = editing self.editing_col = editing
self.standard_colheads = standard_colheads self.standard_colheads = standard_colheads
@ -69,6 +80,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
self.datatype_changed() self.datatype_changed()
self.exec_() self.exec_()
return return
self.setWindowTitle(_('Edit a custom column'))
self.heading_label.setText(_('Edit a custom column'))
self.shortcuts.setVisible(False)
idx = parent.opt_columns.currentRow() idx = parent.opt_columns.currentRow()
if idx < 0: if idx < 0:
self.simple_error(_('No column selected'), self.simple_error(_('No column selected'),
@ -99,6 +113,32 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
self.datatype_changed() self.datatype_changed()
self.exec_() self.exec_()
def shortcut_activated(self, url):
which = unicode(url).split(':')[-1]
self.column_type_box.setCurrentIndex({
'yesno': 9,
'tags' : 1,
'series': 3,
'rating': 8,
}.get(which, 10))
self.column_name_box.setText(which)
self.column_heading_box.setText({
'isbn':'ISBN',
'formats':_('Formats'),
'yesno':_('Yes/No'),
'tags': _('My Tags'),
'series': _('My Series'),
'rating': _('My Rating'),
'last_modified':_('Modified Date')}[which])
if self.composite_box.isVisible():
self.composite_box.setText(
{
'isbn': '{identifiers:select(isbn)}',
'formats': '{formats}',
'last_modified':'''{last_modified:'format_date($, "%d %m, %Y")'}'''
}[which])
def datatype_changed(self, *args): def datatype_changed(self, *args):
try: try:
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype'] col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']

View File

@ -9,8 +9,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>528</width> <width>603</width>
<height>212</height> <height>344</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -19,19 +19,20 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowTitle"> <property name="windowIcon">
<string>Create or edit custom columns</string> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/column.png</normaloff>:/images/column.png</iconset>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0,0,0,0,0,0,0,0"> <layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0,0,0,0,0,0,0,0,0,0,0">
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum> <enum>QLayout::SetDefaultConstraint</enum>
</property> </property>
<property name="margin"> <property name="margin">
<number>5</number> <number>5</number>
</property> </property>
<item row="2" column="0"> <item row="5" column="0">
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="margin"> <property name="margin">
<number>0</number> <number>0</number>
@ -238,7 +239,7 @@ four values, the first of them being the empty value.</string>
</item> </item>
</layout> </layout>
</item> </item>
<item row="11" column="0"> <item row="14" column="0">
<widget class="QDialogButtonBox" name="button_box"> <widget class="QDialogButtonBox" name="button_box">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -252,7 +253,7 @@ four values, the first of them being the empty value.</string>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="heading_label">
<property name="font"> <property name="font">
<font> <font>
<weight>75</weight> <weight>75</weight>
@ -260,7 +261,31 @@ four values, the first of them being the empty value.</string>
</font> </font>
</property> </property>
<property name="text"> <property name="text">
<string>Create or edit custom columns</string> <string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="shortcuts">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -276,6 +301,8 @@ four values, the first of them being the empty value.</string>
<tabstop>composite_box</tabstop> <tabstop>composite_box</tabstop>
<tabstop>button_box</tabstop> <tabstop>button_box</tabstop>
</tabstops> </tabstops>
<resources/> <resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections/> <connections/>
</ui> </ui>

View File

@ -572,6 +572,15 @@ class TagTreeItem(object): # {{{
else: else:
self.tooltip = '' self.tooltip = ''
def break_cycles(self):
for x in self.children:
try:
x.break_cycles()
except:
pass
self.parent = self.icon_state_map = self.bold_font = self.tag = \
self.icon = self.children = None
def __str__(self): def __str__(self):
if self.type == self.ROOT: if self.type == self.ROOT:
return 'ROOT' return 'ROOT'
@ -780,6 +789,7 @@ class TagsModel(QAbstractItemModel): # {{{
self.refresh(data=data) self.refresh(data=data)
def break_cycles(self): def break_cycles(self):
self.root_item.break_cycles()
self.db = self.root_item = None self.db = self.root_item = None
def mimeTypes(self): def mimeTypes(self):

View File

@ -51,7 +51,7 @@ class Device(object):
@classmethod @classmethod
def set_output_format(cls): def set_output_format(cls):
if cls.output_format: if cls.output_format:
prefs.set('output_format', cls.output_format) prefs.set('output_format', cls.output_format.lower())
@classmethod @classmethod
def commit(cls): def commit(cls):

View File

@ -122,7 +122,6 @@ EQUALS_MATCH = 1
REGEXP_MATCH = 2 REGEXP_MATCH = 2
def _match(query, value, matchkind): def _match(query, value, matchkind):
if query.startswith('..'): if query.startswith('..'):
print 'here', query
query = query[1:] query = query[1:]
prefix_match_ok = False prefix_match_ok = False
else: else:
@ -578,7 +577,7 @@ class ResultCache(SearchQueryParser): # {{{
# special case: colon-separated fields such as identifiers. isbn # special case: colon-separated fields such as identifiers. isbn
# is a special case within the case # is a special case within the case
if fm['is_csp']: if fm.get('is_csp', False):
if location == 'identifiers' and original_location == 'isbn': if location == 'identifiers' and original_location == 'isbn':
return self.get_keypair_matches('identifiers', return self.get_keypair_matches('identifiers',
'=isbn:'+query, candidates) '=isbn:'+query, candidates)

View File

@ -833,6 +833,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.pubdate = row[fm['pubdate']] mi.pubdate = row[fm['pubdate']]
mi.uuid = row[fm['uuid']] mi.uuid = row[fm['uuid']]
mi.title_sort = row[fm['sort']] mi.title_sort = row[fm['sort']]
mi.metadata_last_modified = row[fm['last_modified']]
formats = row[fm['formats']] formats = row[fm['formats']]
if not formats: if not formats:
formats = None formats = None
@ -1273,7 +1274,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for category in tb_cats.keys(): for category in tb_cats.keys():
cat = tb_cats[category] cat = tb_cats[category]
if not cat['is_category'] or cat['kind'] in ['user', 'search'] \ if not cat['is_category'] or cat['kind'] in ['user', 'search'] \
or category in ['news', 'formats'] or cat['is_csp']: or category in ['news', 'formats'] or cat.get('is_csp',
False):
continue continue
# Get the ids for the item values # Get the ids for the item values
if not cat['is_custom']: if not cat['is_custom']:

View File

@ -459,7 +459,8 @@ class FieldMetadata(dict):
return l return l
def add_custom_field(self, label, table, column, datatype, colnum, name, def add_custom_field(self, label, table, column, datatype, colnum, name,
display, is_editable, is_multiple, is_category, is_csp): display, is_editable, is_multiple, is_category,
is_csp=False):
key = self.custom_field_prefix + label key = self.custom_field_prefix + label
if key in self._tb_cats: if key in self._tb_cats:
raise ValueError('Duplicate custom field [%s]'%(label)) raise ValueError('Duplicate custom field [%s]'%(label))

View File

@ -350,7 +350,7 @@ Why doesn't |app| have a column for foo?
|app| is designed to have columns for the most frequently and widely used fields. In addition, you can add any columns you like. Columns can be added via :guilabel:`Preferences->Interface->Add your own columns`. |app| is designed to have columns for the most frequently and widely used fields. In addition, you can add any columns you like. Columns can be added via :guilabel:`Preferences->Interface->Add your own columns`.
Watch the tutorial `UI Power tips <http://calibre-ebook.com/demo#tutorials>`_ to learn how to create your own columns. Watch the tutorial `UI Power tips <http://calibre-ebook.com/demo#tutorials>`_ to learn how to create your own columns.
You can also create "virtual columns" that contain combinations of the metadata from other columns. In the add column dialog choose the option "Column from other columns" and in the template enter the other column names. For example to create a virtual column containing formats or ISBN, enter ``{formats}`` for formats or ``{isbn}`` for ISBN. For more details, see :ref:`templatelangcalibre`. You can also create "virtual columns" that contain combinations of the metadata from other columns. In the add column dialog use the :guilabel:`Quick create` links to easily create columns to show the book ISBN, formats or the time the book was last modified. For more details, see :ref:`templatelangcalibre`.
Can I have a column showing the formats or the ISBN? Can I have a column showing the formats or the ISBN?

View File

@ -497,6 +497,8 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes
- Edit the metadata of the currently selected field in the book list. - Edit the metadata of the currently selected field in the book list.
* - :kbd:`A` * - :kbd:`A`
- Add Books - Add Books
* - :kbd:`Shift+A`
- Add Formats to the selected books
* - :kbd:`C` * - :kbd:`C`
- Convert selected Books - Convert selected Books
* - :kbd:`D` * - :kbd:`D`

View File

@ -30,7 +30,7 @@ You can use all the various metadata fields available in calibre in a template,
In addition to the column based fields, you also can use:: In addition to the column based fields, you also can use::
{formats} - A list of formats available in the calibre library for a book {formats} - A list of formats available in the calibre library for a book
{isbn} - The ISBN number of the book {identifiers:select(isbn)} - The ISBN number of the book
If a particular book does not have a particular piece of metadata, the field in the template is automatically removed for that book. Consider, for example:: If a particular book does not have a particular piece of metadata, the field in the template is automatically removed for that book. Consider, for example::
@ -95,7 +95,7 @@ Advanced features
Using templates in custom columns Using templates in custom columns
---------------------------------- ----------------------------------
There are sometimes cases where you want to display metadata that |app| does not normally display, or to display data in a way different from how |app| normally does. For example, you might want to display the ISBN, a field that |app| does not display. You can use custom columns for this by creating a column with the type 'column built from other columns' (hereafter called composite columns), and entering a template. Result: |app| will display a column showing the result of evaluating that template. To display the ISBN, create the column and enter ``{isbn}`` into the template box. To display a column containing the values of two series custom columns separated by a comma, use ``{#series1:||,}{#series2}``. There are sometimes cases where you want to display metadata that |app| does not normally display, or to display data in a way different from how |app| normally does. For example, you might want to display the ISBN, a field that |app| does not display. You can use custom columns for this by creating a column with the type 'column built from other columns' (hereafter called composite columns), and entering a template. Result: |app| will display a column showing the result of evaluating that template. To display the ISBN, create the column and enter ``{identifiers:select(isbn)}`` into the template box. To display a column containing the values of two series custom columns separated by a comma, use ``{#series1:||,}{#series2}``.
Composite columns can use any template option, including formatting. Composite columns can use any template option, including formatting.
@ -122,10 +122,10 @@ The functions available are:
* ``count(separator)`` -- interprets the value as a list of items separated by `separator`, returning the number of items in the list. Most lists use a comma as the separator, but authors uses an ampersand. Examples: `{tags:count(,)}`, `{authors:count(&)}` * ``count(separator)`` -- interprets the value as a list of items separated by `separator`, returning the number of items in the list. Most lists use a comma as the separator, but authors uses an ampersand. Examples: `{tags:count(,)}`, `{authors:count(&)}`
* ``ifempty(text)`` -- if the field is not empty, return the value of the field. Otherwise return `text`. * ``ifempty(text)`` -- if the field is not empty, return the value of the field. Otherwise return `text`.
* ``list_item(index, separator)`` -- interpret the value as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function. * ``list_item(index, separator)`` -- interpret the value as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function.
* ``lookup(pattern, field, pattern, field, ..., else_field)`` -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later).
* ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions. * ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions.
* ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed. * ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed.
* ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want. * ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want.
* ``lookup(pattern, field, pattern, field, ..., else_field)`` -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later).
* ``select(key)`` -- interpret the field as a comma-separated list of items, with the items being of the form "id:value". Find the pair with the id equal to key, and return the corresponding value. This function is particularly useful for extracting a value such as an isbn from the set of identifiers for a book. * ``select(key)`` -- interpret the field as a comma-separated list of items, with the items being of the form "id:value". Find the pair with the id equal to key, and return the corresponding value. This function is particularly useful for extracting a value such as an isbn from the set of identifiers for a book.
* ``test(text if not empty, text if empty)`` -- return `text if not empty` if the field is not empty, otherwise return `text if empty`. * ``test(text if not empty, text if empty)`` -- return `text if not empty` if the field is not empty, otherwise return `text if empty`.

View File

@ -5,53 +5,99 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import gc '''
Measure memory usage of the current process.
## {{{ http://code.activestate.com/recipes/286222/ (r1) The key function is memory() which returns the current memory usage in bytes.
import os You can pass a number to memory and it will be subtracted from the returned
value.
'''
_proc_status = '/proc/%d/status' % os.getpid() import gc, os
_scale = {'kB': 1024.0, 'mB': 1024.0*1024.0, from calibre.constants import iswindows, islinux
'KB': 1024.0, 'MB': 1024.0*1024.0}
def _VmB(VmKey): if islinux:
'''Private. ## {{{ http://code.activestate.com/recipes/286222/ (r1)
'''
global _proc_status, _scale _proc_status = '/proc/%d/status' % os.getpid()
# get pseudo file /proc/<pid>/status
try: _scale = {'kB': 1024.0, 'mB': 1024.0*1024.0,
t = open(_proc_status) 'KB': 1024.0, 'MB': 1024.0*1024.0}
v = t.read()
t.close() def _VmB(VmKey):
except: '''Private.
return 0.0 # non-Linux? '''
# get VmKey line e.g. 'VmRSS: 9999 kB\n ...' global _proc_status, _scale
i = v.index(VmKey) # get pseudo file /proc/<pid>/status
v = v[i:].split(None, 3) # whitespace try:
if len(v) < 3: t = open(_proc_status)
return 0.0 # invalid format? v = t.read()
# convert Vm value to bytes t.close()
return float(v[1]) * _scale[v[2]] except:
return 0.0 # non-Linux?
# get VmKey line e.g. 'VmRSS: 9999 kB\n ...'
i = v.index(VmKey)
v = v[i:].split(None, 3) # whitespace
if len(v) < 3:
return 0.0 # invalid format?
# convert Vm value to bytes
return float(v[1]) * _scale[v[2]]
def memory(since=0.0): def linux_memory(since=0.0):
'''Return memory usage in bytes. '''Return memory usage in bytes.
''' '''
return _VmB('VmSize:') - since return _VmB('VmSize:') - since
def resident(since=0.0): def resident(since=0.0):
'''Return resident memory usage in bytes. '''Return resident memory usage in bytes.
''' '''
return _VmB('VmRSS:') - since return _VmB('VmRSS:') - since
def stacksize(since=0.0): def stacksize(since=0.0):
'''Return stack size in bytes. '''Return stack size in bytes.
''' '''
return _VmB('VmStk:') - since return _VmB('VmStk:') - since
## end of http://code.activestate.com/recipes/286222/ }}} ## end of http://code.activestate.com/recipes/286222/ }}}
memory = linux_memory
elif iswindows:
import win32process
import win32con
import win32api
# See http://msdn.microsoft.com/en-us/library/ms684877.aspx
# for details on the info returned by get_meminfo
def get_handle(pid):
return win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, 0,
pid)
def listprocesses(self):
for process in win32process.EnumProcesses():
try:
han = get_handle(process)
procmeminfo = meminfo(han)
procmemusage = procmeminfo["WorkingSetSize"]
yield process, procmemusage
except:
pass
def get_meminfo(pid):
han = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, 0,
pid)
return meminfo(han)
def meminfo(handle):
return win32process.GetProcessMemoryInfo(handle)
def win_memory(since=0.0):
info = meminfo(get_handle(os.getpid()))
return info['WorkingSetSize'] - since
memory = win_memory
def gc_histogram(): def gc_histogram():