mirror of
https://github.com/kovidgoyal/calibre.git
synced 2026-06-07 06:25:26 -04:00
0.8.7 release
This commit is contained in:
@@ -19,6 +19,66 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.8.7
|
||||
date: 2011-06-24
|
||||
|
||||
new features:
|
||||
- title: "Connect to iTunes: You now need to tell iTunes to keep its own copy of every ebook. Do this in iTunes by going to Preferences->Advanced and setting the 'Copy files to iTunes Media folder when adding to library' option. To learn about why this is necessary, see: http://www.mobileread.com/forums/showthread.php?t=140260"
|
||||
type: major
|
||||
|
||||
- title: "Add a couple of date related functions to the calibre template langauge to get 'todays' date and create text based on the value of a date type field"
|
||||
|
||||
- title: "Improved reading of metadata from FB2 files, with support for reading isbns, tags, published date, etc."
|
||||
|
||||
- title: "Driver for the Imagine IMEB5"
|
||||
tickets: [800642]
|
||||
|
||||
- title: "Show the currently used network proxies in Preferences->Miscellaneous"
|
||||
|
||||
- title: "Kobo Touch driver: Show Favorites as a device collection. Various other minor fixes."
|
||||
|
||||
- title: "Content server now sends the Content-Disposition header when sending ebook files."
|
||||
|
||||
- title: "Allow search and replace on comments custom columns."
|
||||
|
||||
- title: "Add a new action 'Quick View' to show the books in your library by the author/tags/series/etc. of the currently selected book, in a separate window. You can add it to your toolbar or right click menu by going to Preferences->Toolbars."
|
||||
|
||||
- title: "Get Books: Add libri.de as a book source. Fix a bug that caused some books downloads to fail. Fixes to the Legimi and beam-ebooks.de stores"
|
||||
tickets: [799367]
|
||||
|
||||
bug fixes:
|
||||
- title: "Fix a memory leak that could result in the leaking of several MB of memory with large libraries"
|
||||
tickets: [800952]
|
||||
|
||||
- title: "Fix the read metadata from format button in the edit metadata dialog using the wrong timezone when setting published date"
|
||||
tickets: [799777]
|
||||
|
||||
- title: "Generating catalog: Fix occassional file in use errors when generating catalogs on windows"
|
||||
|
||||
- title: "Fix clicking on News in Tag Browser not working in non English locales."
|
||||
tickets: [799471]
|
||||
|
||||
- title: "HTML Input: Fix a regression in 0.8.6 that caused CSS stylesheets to be ignored"
|
||||
tickets: [799171]
|
||||
|
||||
- title: "Fix a regression that caused restore database to stop working on some windows sytems"
|
||||
|
||||
- title: "EPUB Output: Convert <br> tags with text in them into <divs> as ADE cannot handle them."
|
||||
tickets: [794427]
|
||||
|
||||
improved recipes:
|
||||
- Le Temps
|
||||
- Perfil
|
||||
- Financial Times UK
|
||||
|
||||
new recipes:
|
||||
- title: "Daytona Beach Journal"
|
||||
author: BRGriff
|
||||
|
||||
- title: "El club del ebook and Frontline"
|
||||
author: Darko Miletic
|
||||
|
||||
|
||||
- version: 0.8.6
|
||||
date: 2011-06-17
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
ft.com
|
||||
www.ft.com/uk-edition
|
||||
'''
|
||||
from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class FinancialTimes(BasicNewsRecipe):
|
||||
title = u'Financial Times - UK printed edition'
|
||||
title = 'Financial Times - UK printed edition'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Financial world news'
|
||||
description = "The Financial Times (FT) is one of the world's leading business news and information organisations, recognised internationally for its authority, integrity and accuracy."
|
||||
publisher = 'The Financial Times Ltd.'
|
||||
category = 'news, finances, politics, UK, World'
|
||||
oldest_article = 2
|
||||
language = 'en_GB'
|
||||
max_articles_per_feed = 250
|
||||
@@ -17,14 +19,24 @@ class FinancialTimes(BasicNewsRecipe):
|
||||
use_embedded_content = False
|
||||
needs_subscription = True
|
||||
encoding = 'utf8'
|
||||
simultaneous_downloads= 1
|
||||
delay = 1
|
||||
publication_type = 'newspaper'
|
||||
cover_url = strftime('http://specials.ft.com/vtf_pdf/%d%m%y_FRONT1_LON.pdf')
|
||||
masthead_url = 'http://im.media.ft.com/m/img/masthead_main.jpg'
|
||||
LOGIN = 'https://registration.ft.com/registration/barrier/login'
|
||||
INDEX = 'http://www.ft.com/uk-edition'
|
||||
PREFIX = 'http://www.ft.com'
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
, 'linearize_tables' : True
|
||||
}
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
br.open(self.INDEX)
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open(self.LOGIN)
|
||||
br.select_form(name='loginForm')
|
||||
@@ -33,29 +45,34 @@ class FinancialTimes(BasicNewsRecipe):
|
||||
br.submit()
|
||||
return br
|
||||
|
||||
keep_only_tags = [ dict(name='div', attrs={'id':'cont'}) ]
|
||||
remove_tags_after = dict(name='p', attrs={'class':'copyright'})
|
||||
keep_only_tags = [dict(name='div', attrs={'class':['fullstory fullstoryHeader','fullstory fullstoryBody','ft-story-header','ft-story-body','index-detail']})]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'id':'floating-con'})
|
||||
,dict(name=['meta','iframe','base','object','embed','link'])
|
||||
,dict(attrs={'class':['storyTools','story-package','screen-copy','story-package separator','expandable-image']})
|
||||
]
|
||||
remove_attributes = ['width','height','lang']
|
||||
|
||||
extra_css = """
|
||||
body{font-family:Arial,Helvetica,sans-serif;}
|
||||
h2{font-size:large;}
|
||||
.ft-story-header{font-size:xx-small;}
|
||||
.ft-story-body{font-size:small;}
|
||||
a{color:#003399;}
|
||||
body{font-family: Georgia,Times,"Times New Roman",serif}
|
||||
h2{font-size:large}
|
||||
.ft-story-header{font-size: x-small}
|
||||
.container{font-size:x-small;}
|
||||
h3{font-size:x-small;color:#003399;}
|
||||
.copyright{font-size: x-small}
|
||||
img{margin-top: 0.8em; display: block}
|
||||
.lastUpdated{font-family: Arial,Helvetica,sans-serif; font-size: x-small}
|
||||
.byline,.ft-story-body,.ft-story-header{font-family: Arial,Helvetica,sans-serif}
|
||||
"""
|
||||
|
||||
def get_artlinks(self, elem):
|
||||
articles = []
|
||||
for item in elem.findAll('a',href=True):
|
||||
url = self.PREFIX + item['href']
|
||||
rawlink = item['href']
|
||||
if rawlink.startswith('http://'):
|
||||
url = rawlink
|
||||
else:
|
||||
url = self.PREFIX + rawlink
|
||||
title = self.tag_to_string(item)
|
||||
date = strftime(self.timefmt)
|
||||
articles.append({
|
||||
@@ -65,7 +82,7 @@ class FinancialTimes(BasicNewsRecipe):
|
||||
,'description':''
|
||||
})
|
||||
return articles
|
||||
|
||||
|
||||
def parse_index(self):
|
||||
feeds = []
|
||||
soup = self.index_to_soup(self.INDEX)
|
||||
@@ -80,11 +97,34 @@ class FinancialTimes(BasicNewsRecipe):
|
||||
strest.insert(0,st)
|
||||
for item in strest:
|
||||
ftitle = self.tag_to_string(item)
|
||||
self.report_progress(0, _('Fetching feed')+' %s...'%(ftitle))
|
||||
self.report_progress(0, _('Fetching feed')+' %s...'%(ftitle))
|
||||
feedarts = self.get_artlinks(item.parent.ul)
|
||||
feeds.append((ftitle,feedarts))
|
||||
return feeds
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
||||
|
||||
items = ['promo-box','promo-title',
|
||||
'promo-headline','promo-image',
|
||||
'promo-intro','promo-link','subhead']
|
||||
for item in items:
|
||||
for it in soup.findAll(item):
|
||||
it.name = 'div'
|
||||
it.attrs = []
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll('a'):
|
||||
limg = item.find('img')
|
||||
if item.string is not None:
|
||||
str = item.string
|
||||
item.replaceWith(str)
|
||||
else:
|
||||
if limg:
|
||||
item.name = 'div'
|
||||
item.attrs = []
|
||||
else:
|
||||
str = self.tag_to_string(item)
|
||||
item.replaceWith(str)
|
||||
for item in soup.findAll('img'):
|
||||
if not item.has_key('alt'):
|
||||
item['alt'] = 'image'
|
||||
return soup
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 6)
|
||||
numeric_version = (0, 8, 7)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ class CompositeProgressReporter(object):
|
||||
(self.global_max - self.global_min)
|
||||
self.global_reporter(global_frac, msg)
|
||||
|
||||
ARCHIVE_FMTS = ('zip', 'rar', 'oebzip')
|
||||
|
||||
class Plumber(object):
|
||||
'''
|
||||
The `Plumber` manages the conversion pipeline. An UI should call the methods
|
||||
@@ -594,7 +596,7 @@ OptionRecommendation(name='sr3_replace',
|
||||
raise ValueError('Input file must have an extension')
|
||||
input_fmt = input_fmt[1:].lower()
|
||||
self.archive_input_tdir = None
|
||||
if input_fmt in ('zip', 'rar', 'oebzip'):
|
||||
if input_fmt in ARCHIVE_FMTS:
|
||||
self.log('Processing archive...')
|
||||
tdir = PersistentTemporaryDirectory('_plumber_archive')
|
||||
self.input, input_fmt = self.unarchive(self.input, tdir)
|
||||
|
||||
@@ -248,10 +248,11 @@ def error_dialog(parent, title, msg, det_msg='', show=False,
|
||||
return d.exec_()
|
||||
return d
|
||||
|
||||
def question_dialog(parent, title, msg, det_msg='', show_copy_button=False):
|
||||
def question_dialog(parent, title, msg, det_msg='', show_copy_button=False,
|
||||
default_yes=True):
|
||||
from calibre.gui2.dialogs.message_box import MessageBox
|
||||
d = MessageBox(MessageBox.QUESTION, title, msg, det_msg, parent=parent,
|
||||
show_copy_button=show_copy_button)
|
||||
show_copy_button=show_copy_button, default_yes=default_yes)
|
||||
return d.exec_() == d.Accepted
|
||||
|
||||
def info_dialog(parent, title, msg, det_msg='', show=False,
|
||||
|
||||
@@ -252,11 +252,12 @@ class ChooseLibraryAction(InterfaceAction):
|
||||
|
||||
def delete_requested(self, name, location):
|
||||
loc = location.replace('/', os.sep)
|
||||
if not question_dialog(self.gui, _('Are you sure?'), '<p>'+
|
||||
if not question_dialog(self.gui, _('Are you sure?'),
|
||||
_('<h1 style="color:red">WARNING</h1>')+
|
||||
_('<b style="color: red">All files</b> (not just ebooks) '
|
||||
'from <br><br><b>%s</b><br><br> will be '
|
||||
'<b>permanently deleted</b>. Are you sure?') % loc,
|
||||
show_copy_button=False):
|
||||
show_copy_button=False, default_yes=False):
|
||||
return
|
||||
exists = self.gui.library_view.model().db.exists_at(loc)
|
||||
if exists:
|
||||
|
||||
@@ -11,8 +11,8 @@ import sys, cPickle, shutil, importlib
|
||||
from PyQt4.Qt import QString, SIGNAL, QAbstractListModel, Qt, QVariant, QFont
|
||||
|
||||
from calibre.gui2 import ResizableDialog, NONE
|
||||
from calibre.ebooks.conversion.config import GuiRecommendations, save_specifics, \
|
||||
load_specifics
|
||||
from calibre.ebooks.conversion.config import (GuiRecommendations, save_specifics,
|
||||
load_specifics)
|
||||
from calibre.gui2.convert.single_ui import Ui_Dialog
|
||||
from calibre.gui2.convert.metadata import MetadataWidget
|
||||
from calibre.gui2.convert.look_and_feel import LookAndFeelWidget
|
||||
@@ -24,7 +24,8 @@ from calibre.gui2.convert.toc import TOCWidget
|
||||
from calibre.gui2.convert.debug import DebugWidget
|
||||
|
||||
|
||||
from calibre.ebooks.conversion.plumber import Plumber, supported_input_formats
|
||||
from calibre.ebooks.conversion.plumber import (Plumber,
|
||||
supported_input_formats, ARCHIVE_FMTS)
|
||||
from calibre.ebooks.conversion.config import delete_specifics
|
||||
from calibre.customize.ui import available_output_formats
|
||||
from calibre.customize.conversion import OptionRecommendation
|
||||
@@ -158,7 +159,10 @@ class Config(ResizableDialog, Ui_Dialog):
|
||||
output_path = 'dummy.'+output_format
|
||||
log = Log()
|
||||
log.outputs = []
|
||||
self.plumber = Plumber('dummy.'+input_format, output_path, log)
|
||||
input_file = 'dummy.'+input_format
|
||||
if input_format in ARCHIVE_FMTS:
|
||||
input_file = 'dummy.html'
|
||||
self.plumber = Plumber(input_file, output_path, log)
|
||||
|
||||
def widget_factory(cls):
|
||||
return cls(self.stack, self.plumber.get_option_by_name,
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<item row="4" column="0" colspan="4">
|
||||
<widget class="QRadioButton" name="existing_library">
|
||||
<property name="text">
|
||||
<string>Use &existing library at the new location</string>
|
||||
<string>Use the previously &existing library at the new location</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -23,7 +23,7 @@ class MessageBox(QDialog, Ui_Dialog): # {{{
|
||||
det_msg='',
|
||||
q_icon=None,
|
||||
show_copy_button=True,
|
||||
parent=None):
|
||||
parent=None, default_yes=True):
|
||||
QDialog.__init__(self, parent)
|
||||
if q_icon is None:
|
||||
icon = {
|
||||
@@ -65,7 +65,9 @@ class MessageBox(QDialog, Ui_Dialog): # {{{
|
||||
self.is_question = type_ == self.QUESTION
|
||||
if self.is_question:
|
||||
self.bb.setStandardButtons(self.bb.Yes|self.bb.No)
|
||||
self.bb.button(self.bb.Yes).setDefault(True)
|
||||
self.bb.button(self.bb.Yes if default_yes else self.bb.No
|
||||
).setDefault(True)
|
||||
self.default_yes = default_yes
|
||||
else:
|
||||
self.bb.button(self.bb.Ok).setDefault(True)
|
||||
|
||||
@@ -101,7 +103,8 @@ class MessageBox(QDialog, Ui_Dialog): # {{{
|
||||
ret = QDialog.showEvent(self, ev)
|
||||
if self.is_question:
|
||||
try:
|
||||
self.bb.button(self.bb.Yes).setFocus(Qt.OtherFocusReason)
|
||||
self.bb.button(self.bb.Yes if self.default_yes else self.bb.No
|
||||
).setFocus(Qt.OtherFocusReason)
|
||||
except:
|
||||
pass# Buttons were changed
|
||||
else:
|
||||
|
||||
@@ -5,11 +5,13 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from PyQt4.Qt import (Qt, QDialog, QAbstractItemView, QTableWidgetItem,
|
||||
QListWidgetItem, QByteArray, QModelIndex, QCoreApplication)
|
||||
QListWidgetItem, QByteArray, QCoreApplication,
|
||||
QApplication)
|
||||
|
||||
from calibre.customize.ui import find_plugin
|
||||
from calibre.gui2 import gprefs
|
||||
from calibre.gui2.dialogs.quickview_ui import Ui_Quickview
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.gui2 import gprefs
|
||||
|
||||
class TableItem(QTableWidgetItem):
|
||||
'''
|
||||
@@ -55,8 +57,9 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.is_closed = False
|
||||
self.current_book_id = None
|
||||
self.current_key = None
|
||||
self.use_current_key_for_next_refresh = False
|
||||
self.last_search = None
|
||||
self.current_column = None
|
||||
self.current_item = None
|
||||
|
||||
self.items.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.items.currentTextChanged.connect(self.item_selected)
|
||||
@@ -87,16 +90,24 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
# Add the data
|
||||
self.refresh(row)
|
||||
|
||||
self.view.selectionModel().currentChanged[QModelIndex,QModelIndex].connect(self.slave)
|
||||
self.view.clicked.connect(self.slave)
|
||||
QCoreApplication.instance().aboutToQuit.connect(self.save_state)
|
||||
self.search_button.clicked.connect(self.do_search)
|
||||
view.model().new_bookdisplay_data.connect(self.book_was_changed)
|
||||
|
||||
# search button
|
||||
def do_search(self):
|
||||
if self.last_search is not None:
|
||||
self.use_current_key_for_next_refresh = True
|
||||
self.gui.search.set_search_string(self.last_search)
|
||||
|
||||
# Called when book information is changed in the library view. Make that
|
||||
# book current. This means that prev and next in edit metadata will move
|
||||
# the current book.
|
||||
def book_was_changed(self, mi):
|
||||
if self.is_closed or self.current_column is None:
|
||||
return
|
||||
self.refresh(self.view.model().index(self.db.row(mi.id), self.current_column))
|
||||
|
||||
# clicks on the items listWidget
|
||||
def item_selected(self, txt):
|
||||
self.fill_in_books_box(unicode(txt))
|
||||
@@ -104,22 +115,15 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
# Given a cell in the library view, display the information
|
||||
def refresh(self, idx):
|
||||
bv_row = idx.row()
|
||||
key = self.view.model().column_map[idx.column()]
|
||||
|
||||
self.current_column = idx.column()
|
||||
key = self.view.model().column_map[self.current_column]
|
||||
book_id = self.view.model().id(bv_row)
|
||||
|
||||
# Double-clicking on a book to show it in the library view will result
|
||||
# in a signal emitted for column 1 of the book row. Use the original
|
||||
# column for this signal.
|
||||
if self.use_current_key_for_next_refresh:
|
||||
# Only show items for categories
|
||||
if not self.db.field_metadata[key]['is_category']:
|
||||
if self.current_key is None:
|
||||
return
|
||||
key = self.current_key
|
||||
self.use_current_key_for_next_refresh = False
|
||||
else:
|
||||
# Only show items for categories
|
||||
if not self.db.field_metadata[key]['is_category']:
|
||||
if self.current_key is None:
|
||||
return
|
||||
key = self.current_key
|
||||
self.items_label.setText('{0} ({1})'.format(
|
||||
self.db.field_metadata[key]['name'], key))
|
||||
|
||||
@@ -147,6 +151,7 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.items.blockSignals(False)
|
||||
|
||||
def fill_in_books_box(self, selected_item):
|
||||
self.current_item = selected_item
|
||||
# Do a bit of fix-up on the items so that the search works.
|
||||
if selected_item.startswith('.'):
|
||||
sv = '.' + selected_item
|
||||
@@ -162,19 +167,26 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
|
||||
select_item = None
|
||||
self.books_table.setSortingEnabled(False)
|
||||
tt = ('<p>' +
|
||||
_('Double-click on a book to change the selection in the library view. '
|
||||
'Shift- or control-double-click to edit the metadata of a book')
|
||||
+ '</p>')
|
||||
for row, b in enumerate(books):
|
||||
mi = self.db.get_metadata(b, index_is_id=True, get_user_categories=False)
|
||||
a = TableItem(mi.title, mi.title_sort)
|
||||
a.setData(Qt.UserRole, b)
|
||||
a.setToolTip(tt)
|
||||
self.books_table.setItem(row, 0, a)
|
||||
if b == self.current_book_id:
|
||||
select_item = a
|
||||
a = TableItem(' & '.join(mi.authors), mi.author_sort)
|
||||
a.setToolTip(tt)
|
||||
self.books_table.setItem(row, 1, a)
|
||||
series = mi.format_field('series')[1]
|
||||
if series is None:
|
||||
series = ''
|
||||
a = TableItem(series, series)
|
||||
a.setToolTip(tt)
|
||||
self.books_table.setItem(row, 2, a)
|
||||
self.books_table.setRowHeight(row, self.books_table_row_height)
|
||||
|
||||
@@ -201,11 +213,16 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.save_state()
|
||||
|
||||
def book_doubleclicked(self, row, column):
|
||||
self.use_current_key_for_next_refresh = True
|
||||
self.view.select_rows([self.books_table.item(row, 0).data(Qt.UserRole).toInt()[0]])
|
||||
book_id = self.books_table.item(row, 0).data(Qt.UserRole).toInt()[0]
|
||||
self.view.select_rows([book_id])
|
||||
modifiers = int(QApplication.keyboardModifiers())
|
||||
if modifiers in (Qt.CTRL, Qt.SHIFT):
|
||||
em = find_plugin('Edit Metadata')
|
||||
if em is not None:
|
||||
em.actual_plugin_.edit_metadata(None)
|
||||
|
||||
# called when a book is clicked on the library view
|
||||
def slave(self, current, previous):
|
||||
def slave(self, current):
|
||||
if self.is_closed:
|
||||
return
|
||||
self.refresh(current)
|
||||
|
||||
+1408
-943
File diff suppressed because it is too large
Load Diff
+1410
-948
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1440
-961
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1497
-958
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1419
-948
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1492
-983
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1452
-981
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1472
-962
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1517
-993
File diff suppressed because it is too large
Load Diff
+1424
-950
File diff suppressed because it is too large
Load Diff
+1413
-942
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1409
-947
File diff suppressed because it is too large
Load Diff
+1419
-948
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1429
-952
File diff suppressed because it is too large
Load Diff
+1869
-1260
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1407
-945
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1409
-944
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1422
-951
File diff suppressed because it is too large
Load Diff
+1409
-947
File diff suppressed because it is too large
Load Diff
+2064
-1480
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1433
-956
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1413
-942
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1413
-945
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1426
-955
File diff suppressed because it is too large
Load Diff
+1418
-953
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1413
-942
File diff suppressed because it is too large
Load Diff
+1432
-949
File diff suppressed because it is too large
Load Diff
+1407
-942
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1413
-942
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1415
-947
File diff suppressed because it is too large
Load Diff
+1408
-943
File diff suppressed because it is too large
Load Diff
+1419
-948
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ import inspect, re, traceback
|
||||
|
||||
from calibre.utils.titlecase import titlecase
|
||||
from calibre.utils.icu import capitalize, strcmp, sort_key
|
||||
from calibre.utils.date import parse_date, format_date
|
||||
from calibre.utils.date import parse_date, format_date, now, UNDEFINED_DATE
|
||||
|
||||
|
||||
class FormatterFunctions(object):
|
||||
@@ -579,7 +579,7 @@ class BuiltinSubitems(BuiltinFormatterFunction):
|
||||
class BuiltinFormatDate(BuiltinFormatterFunction):
|
||||
name = 'format_date'
|
||||
arg_count = 2
|
||||
category = 'Get values from metadata'
|
||||
category = 'Date functions'
|
||||
__doc__ = doc = _('format_date(val, format_string) -- format the value, '
|
||||
'which must be a date, using the format_string, returning a string. '
|
||||
'The formatting codes are: '
|
||||
@@ -754,6 +754,39 @@ class BuiltinMergeLists(BuiltinFormatterFunction):
|
||||
res.append(i)
|
||||
return ', '.join(sorted(res, key=sort_key))
|
||||
|
||||
class BuiltinToday(BuiltinFormatterFunction):
|
||||
name = 'today'
|
||||
arg_count = 0
|
||||
category = 'Date functions'
|
||||
__doc__ = doc = _('today() -- '
|
||||
'return a date string for today. This value is designed for use in '
|
||||
'format_date or days_between, but can be manipulated like any '
|
||||
'other string. The date is in ISO format.')
|
||||
def evaluate(self, formatter, kwargs, mi, locals):
|
||||
return format_date(now(), 'iso')
|
||||
|
||||
class BuiltinDaysBetween(BuiltinFormatterFunction):
|
||||
name = 'days_between'
|
||||
arg_count = 2
|
||||
category = 'Date functions'
|
||||
__doc__ = doc = _('days_between(date1, date2) -- '
|
||||
'return the number of days between date1 and date2. The number is '
|
||||
'positive if date1 is greater than date2, otherwise negative. If '
|
||||
'either date1 or date2 are not dates, the function returns the '
|
||||
'empty string.')
|
||||
def evaluate(self, formatter, kwargs, mi, locals, date1, date2):
|
||||
try:
|
||||
d1 = parse_date(date1)
|
||||
if d1 == UNDEFINED_DATE:
|
||||
return ''
|
||||
d2 = parse_date(date2)
|
||||
if d2 == UNDEFINED_DATE:
|
||||
return ''
|
||||
except:
|
||||
return ''
|
||||
i = d1 - d2
|
||||
return str(i.days)
|
||||
|
||||
|
||||
builtin_add = BuiltinAdd()
|
||||
builtin_and = BuiltinAnd()
|
||||
@@ -763,6 +796,7 @@ builtin_capitalize = BuiltinCapitalize()
|
||||
builtin_cmp = BuiltinCmp()
|
||||
builtin_contains = BuiltinContains()
|
||||
builtin_count = BuiltinCount()
|
||||
builtin_days_between= BuiltinDaysBetween()
|
||||
builtin_divide = BuiltinDivide()
|
||||
builtin_eval = BuiltinEval()
|
||||
builtin_first_non_empty = BuiltinFirstNonEmpty()
|
||||
@@ -795,6 +829,7 @@ builtin_switch = BuiltinSwitch()
|
||||
builtin_template = BuiltinTemplate()
|
||||
builtin_test = BuiltinTest()
|
||||
builtin_titlecase = BuiltinTitlecase()
|
||||
builtin_today = BuiltinToday()
|
||||
builtin_uppercase = BuiltinUppercase()
|
||||
|
||||
class FormatterUserFunction(FormatterFunction):
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
from threading import Thread
|
||||
from multiprocessing.connection import arbitrary_address, Listener
|
||||
|
||||
from calibre.constants import iswindows
|
||||
|
||||
class Server(Thread):
|
||||
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.daemon = True
|
||||
|
||||
self.auth_key = os.urandom(32)
|
||||
self.address = arbitrary_address('AF_PIPE' if iswindows else 'AF_UNIX')
|
||||
if iswindows and self.address[1] == ':':
|
||||
self.address = self.address[2:]
|
||||
self.listener = Listener(address=self.address,
|
||||
authkey=self.auth_key, backlog=4)
|
||||
|
||||
self.keep_going = True
|
||||
|
||||
def stop(self):
|
||||
self.keep_going = False
|
||||
try:
|
||||
self.listener.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
while self.keep_going:
|
||||
try:
|
||||
conn = self.listener.accept()
|
||||
self.handle_client(conn)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user