Merge from trunk

This commit is contained in:
Charles Haley 2010-10-09 09:00:53 +01:00
commit 5330c07458
38 changed files with 19309 additions and 13601 deletions

View File

@ -4,6 +4,85 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
- version: 0.7.23
date: 2010-10-08
new features:
- title: "Drag and drop to Tag Browser. You can use this to conveniently add tags, set series/publisher etc for a group of books"
- title: "Allow switching of library even when a device is connected"
- title: "Support for the PD Novel running Kobo"
- title: "Move check library integrity from preferences to drop down menu accessed by clicking arrow next to calibre icon"
- title: "Nicer, non-blocking update available notification"
- title: "E-book viewer: If you choose to remeber last used window size, the state of the Table of Contents view is also remembered"
tickets: [7082]
- title: "Allow moving as well as copying of books to another library"
- title: "Apple devices: Add support for plugboards"
- title: "Allow DJVU to be sent to the DR1000"
bug fixes:
- title: "Searching: Fix search expression parser to allow use of escaped double quotes in the search expression"
- title: "When saving cover images don't re-encode the image data unless absolutely neccessary. This prevents information loss due to JPEG re-compression"
- title: "Fix regression that broke setting of metadata for some MOBI/AZW/PRC files"
- title: "Fix regression in last release that could cause download of metadata for multiple files to only download the metadata for a few of them"
tickets: [7071]
- title: "MOBI Output: More tweaking of the margin handling to yield results closer to the input document."
- title: "Device drivers: Fix regression that could cause geenration of invalid metadata.calibre cache files"
- title: "Fix saving to disk with ISBN in filename"
tickets: [7090]
- title: "Fix another regression in the ISBNdb.com metadata download plugin"
- title: "Fix dragging to not interfere with multi-selection. Also dont allow drag and drop from the library to itself"
- title: "CHM input: handle another class of broken CHM files"
tickets: [7058]
new recipes:
- title: "Communications of the Association for Computing Machinery"
author: jonmisurda
- title: "Anand Tech"
author: "Oliver Niesner"
- title: "gsp.ro"
author: "bucsie"
- title: "Il Fatto Quotidiano"
author: "egilh"
- title: "Serbian Literature blog and Rusia Hoy"
author: "Darko Miletic"
- title: "Medscape"
author: "Tony Stegall"
improved recipes:
- The Age
- Australian
- Wiki news
- Times Online
- New Yorker
- Guardian
- Sueddeutsche
- HNA
- Revista Muy Interesante
- version: 0.7.22 - version: 0.7.22
date: 2010-10-03 date: 2010-10-03

View File

@ -13,6 +13,8 @@
font-size: 1.25em; font-size: 1.25em;
border: 1px solid black; border: 1px solid black;
text-color: black; text-color: black;
text-decoration: none;
margin-right: 0.5em;
background-color: #ddd; background-color: #ddd;
border-top: 1px solid ThreeDLightShadow; border-top: 1px solid ThreeDLightShadow;
border-right: 1px solid ButtonShadow; border-right: 1px solid ButtonShadow;
@ -70,6 +72,7 @@ div.navigation {
padding-right: 0em; padding-right: 0em;
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
text-decoration: none;
} }
#logo { #logo {

View File

@ -38,7 +38,7 @@ class Guardian(BasicNewsRecipe):
dict(name='div', attrs={'id':["article-toolbox","subscribe-feeds",]}), dict(name='div', attrs={'id':["article-toolbox","subscribe-feeds",]}),
dict(name='ul', attrs={'class':["pagination"]}), dict(name='ul', attrs={'class':["pagination"]}),
dict(name='ul', attrs={'id':["content-actions"]}), dict(name='ul', attrs={'id':["content-actions"]}),
dict(name='img'), #dict(name='img'),
] ]
use_embedded_content = False use_embedded_content = False

View File

@ -0,0 +1,64 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__author__ = 'Tony Stegall'
__copyright__ = '2010, Tony Stegall or Tonythebookworm on mobileread.com'
__version__ = '1'
__date__ = '01, October 2010'
__docformat__ = 'English'
from calibre.web.feeds.recipes import BasicNewsRecipe
class MedScrape(BasicNewsRecipe):
title = 'MedScape'
__author__ = 'Tony Stegall'
description = 'Nursing News'
language = 'en'
timefmt = ' [%a, %d %b, %Y]'
needs_subscription = True
masthead_url = 'http://images.medscape.com/pi/global/header/sp/bg-sp-medscape.gif'
no_stylesheets = True
remove_javascript = True
conversion_options = {'linearize_tables' : True}
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
p.authors{text-align:right; font-size:small;margin-top:0px;margin-bottom: 0px;}
p.postingdate{text-align:right; font-size:small;margin-top:0px;margin-bottom: 0px;}
h2{text-align:right; font-size:small;margin-top:0px;margin-bottom: 0px;}
p{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
remove_tags = [dict(name='div', attrs={'class':['closewindow2']}),
dict(name='div', attrs={'id': ['basicheaderlinks']})
]
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('https://profreg.medscape.com/px/getlogin.do')
br.select_form(name='LoginForm')
br['userId'] = self.username
br['password'] = self.password
br.submit()
return br
feeds = [
('MedInfo', 'http://www.medscape.com/cx/rssfeeds/2685.xml'),
]
def print_version(self,url):
#the original url is: http://www.medscape.com/viewarticle/728955?src=rss
#the print url is: http://www.medscape.com/viewarticle/728955_print
print_url = url.partition('?')[0] +'_print'
#print 'the printable version is: ',print_url
return print_url
def preprocess_html(self, soup):
for item in soup.findAll(attrs={'style':True}):
del item['style']
return soup

View File

@ -6,9 +6,9 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, re, cStringIO, base64, httplib, subprocess import os, re, cStringIO, base64, httplib, subprocess, hashlib, shutil
from subprocess import check_call from subprocess import check_call
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile, mkdtemp
from setup import Command, __version__, installer_name, __appname__ from setup import Command, __version__, installer_name, __appname__
@ -331,5 +331,19 @@ class UploadToServer(Command):
%(__version__, DOWNLOADS), shell=True) %(__version__, DOWNLOADS), shell=True)
check_call('ssh divok /etc/init.d/apache2 graceful', check_call('ssh divok /etc/init.d/apache2 graceful',
shell=True) shell=True)
tdir = mkdtemp()
for installer in installers():
if not os.path.exists(installer):
continue
with open(installer, 'rb') as f:
raw = f.read()
fingerprint = hashlib.sha512(raw).hexdigest()
fname = os.path.basename(installer+'.sha512')
with open(os.path.join(tdir, fname), 'wb') as f:
f.write(fingerprint)
check_call('scp %s/*.sha512 divok:%s/signatures/' % (tdir, DOWNLOADS),
shell=True)
shutil.rmtree(tdir)

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.22' __version__ = '0.7.23'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -259,6 +259,8 @@ class ITUNES(DriverBase):
self.report_progress(1.0, _('Updating device metadata listing...')) self.report_progress(1.0, _('Updating device metadata listing...'))
# Add new books to booklists[0] # Add new books to booklists[0]
# Charles thinks this should be
# for new_book in metadata[0]:
for new_book in locations[0]: for new_book in locations[0]:
if DEBUG: if DEBUG:
self.log.info(" adding '%s' by '%s' to booklists[0]" % self.log.info(" adding '%s' by '%s' to booklists[0]" %
@ -1208,6 +1210,10 @@ class ITUNES(DriverBase):
except: except:
self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0]))
self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title)) self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title))
import traceback
traceback.print_exc()
return thumb return thumb
if isosx: if isosx:
@ -2400,7 +2406,7 @@ class ITUNES(DriverBase):
try: try:
storage_path = os.path.split(cached_book['lib_book'].location().path) storage_path = os.path.split(cached_book['lib_book'].location().path)
if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \ if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \
not storage_path[0].startswith(self.calibre_library_path): not storage_path[0].startswith(prefs['library_path']):
title_storage_path = storage_path[0] title_storage_path = storage_path[0]
if DEBUG: if DEBUG:
self.log.info(" removing title_storage_path: %s" % title_storage_path) self.log.info(" removing title_storage_path: %s" % title_storage_path)
@ -2452,7 +2458,7 @@ class ITUNES(DriverBase):
if book: if book:
if self.iTunes_media and path.startswith(self.iTunes_media) and \ if self.iTunes_media and path.startswith(self.iTunes_media) and \
not path.startswith(self.calibre_library_path): not path.startswith(prefs['library_path']):
storage_path = os.path.split(path) storage_path = os.path.split(path)
if DEBUG: if DEBUG:
self.log.info(" removing '%s' at %s" % self.log.info(" removing '%s' at %s" %

View File

@ -417,14 +417,16 @@ class DevicePlugin(Plugin):
select a specific plugboard. This method is called immediately before select a specific plugboard. This method is called immediately before
add_books and sync_booklists. add_books and sync_booklists.
pb_func is a callable with the following signature: pb_func is a callable with the following signature::
def pb_func(device_name, format, plugboards) def pb_func(device_name, format, plugboards)
You give it the current device name (either the class name or You give it the current device name (either the class name or
DEVICE_PLUGBOARD_NAME), the format you are interested in (a 'real' DEVICE_PLUGBOARD_NAME), the format you are interested in (a 'real'
format or 'device_db'), and the plugboards (you were given those by format or 'device_db'), and the plugboards (you were given those by
set_plugboards, the same place you got this method). set_plugboards, the same place you got this method).
Return value: None or a single plugboard instance. :return: None or a single plugboard instance.
''' '''
pass pass

View File

@ -131,6 +131,7 @@ class InterfaceAction(QObject):
Called whenever the current library is changed. Called whenever the current library is changed.
:param db: The LibraryDatabase corresponding to the current library. :param db: The LibraryDatabase corresponding to the current library.
''' '''
pass pass
@ -148,6 +149,7 @@ class InterfaceAction(QObject):
long periods of time. long periods of time.
:return: False to halt the shutdown. You are responsible for telling :return: False to halt the shutdown. You are responsible for telling
the user why the shutdown was halted. the user why the shutdown was halted.
''' '''
return True return True

View File

@ -340,10 +340,10 @@ class ChooseLibraryAction(InterfaceAction):
m.start_metadata_backup() m.start_metadata_backup()
def restore_database(self): def restore_database(self):
info_dialog(self.gui, _('Recover database'), info_dialog(self.gui, _('Recover database'), '<p>'+
_( _(
'This command rebuilds your calibre database from the information ' 'This command rebuilds your calibre database from the information '
'stored by calibre in the OPF files.' + '<p>' + 'stored by calibre in the OPF files.<p>'
'This function is not currently available in the GUI. You can ' 'This function is not currently available in the GUI. You can '
'recover your database using the \'calibredb restore_database\' ' 'recover your database using the \'calibredb restore_database\' '
'command line function.' 'command line function.'

View File

@ -38,7 +38,10 @@ class CustomRecipeModel(QAbstractListModel):
return False return False
def rowCount(self, *args): def rowCount(self, *args):
return len(self.recipe_model.custom_recipe_collection) try:
return len(self.recipe_model.custom_recipe_collection)
except:
return 0
def data(self, index, role): def data(self, index, role):
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
@ -100,6 +103,8 @@ class UserProfiles(ResizableDialog, Ui_Dialog):
def break_cycles(self): def break_cycles(self):
self.recipe_model = self._model.recipe_model = None self.recipe_model = self._model.recipe_model = None
self.available_profiles = None
self.model = self._model = None
def remove_selected_items(self): def remove_selected_items(self):
indices = self.available_profiles.selectionModel().selectedRows() indices = self.available_profiles.selectionModel().selectedRows()

View File

@ -3,13 +3,14 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import traceback import traceback
from PyQt4.Qt import QThread, pyqtSignal, Qt, QUrl from PyQt4.Qt import QThread, pyqtSignal, Qt, QUrl, QDialog, QGridLayout, \
QLabel, QCheckBox, QDialogButtonBox, QIcon, QPixmap
import mechanize import mechanize
from calibre.constants import __appname__, __version__, iswindows, isosx from calibre.constants import __appname__, __version__, iswindows, isosx
from calibre import browser from calibre import browser
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.gui2 import config, dynamic, question_dialog, open_url from calibre.gui2 import config, dynamic, open_url
URL = 'http://status.calibre-ebook.com/latest' URL = 'http://status.calibre-ebook.com/latest'
@ -37,6 +38,53 @@ class CheckForUpdates(QThread):
traceback.print_exc() traceback.print_exc()
self.sleep(self.INTERVAL) self.sleep(self.INTERVAL)
class UpdateNotification(QDialog):
def __init__(self, version, parent=None):
QDialog.__init__(self, parent)
self.resize(400, 250)
self.l = QGridLayout()
self.setLayout(self.l)
self.logo = QLabel()
self.logo.setMaximumWidth(110)
self.logo.setPixmap(QPixmap(I('lt.png')).scaled(100, 100,
Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
self.label = QLabel('<p>'+
_('%s has been updated to version <b>%s</b>. '
'See the <a href="http://calibre-ebook.com/whats-new'
'">new features</a>. Visit the download pa'
'ge?')%(__appname__, version))
self.label.setOpenExternalLinks(True)
self.label.setWordWrap(True)
self.setWindowTitle(_('Update available!'))
self.setWindowIcon(QIcon(I('lt.png')))
self.l.addWidget(self.logo, 0, 0)
self.l.addWidget(self.label, 0, 1)
self.cb = QCheckBox(
_('Show this notification for future updates'), self)
self.l.addWidget(self.cb, 1, 0, 1, -1)
self.cb.setChecked(config.get('new_version_notification'))
self.cb.stateChanged.connect(self.show_future)
self.bb = QDialogButtonBox(self)
b = self.bb.addButton(_('&Get update'), self.bb.AcceptRole)
b.setDefault(True)
b.setIcon(QIcon(I('arrow-down.png')))
self.bb.addButton(self.bb.Cancel)
self.l.addWidget(self.bb, 2, 0, 1, -1)
self.bb.accepted.connect(self.accept)
self.bb.rejected.connect(self.reject)
dynamic.set('update to version %s'%version, False)
def show_future(self, *args):
config.set('new_version_notification', bool(self.cb.isChecked()))
def accept(self):
url = 'http://calibre-ebook.com/download_'+\
('windows' if iswindows else 'osx' if isosx else 'linux')
open_url(QUrl(url))
QDialog.accept(self)
class UpdateMixin(object): class UpdateMixin(object):
def __init__(self, opts): def __init__(self, opts):
@ -53,15 +101,8 @@ class UpdateMixin(object):
if config.get('new_version_notification') and \ if config.get('new_version_notification') and \
dynamic.get('update to version %s'%version, True): dynamic.get('update to version %s'%version, True):
if question_dialog(self, _('Update available'), self._update_notification__ = UpdateNotification(version,
_('%s has been updated to version %s. ' parent=self)
'See the <a href="http://calibre-ebook.com/whats-new' self._update_notification__.show()
'">new features</a>. Visit the download pa'
'ge?')%(__appname__, version)):
url = 'http://calibre-ebook.com/download_'+\
('windows' if iswindows else 'osx' if isosx else 'linux')
open_url(QUrl(url))
dynamic.set('update to version %s'%version, False)

View File

@ -958,17 +958,17 @@ def command_check_library(args, dbpath):
def restore_database_option_parser(): def restore_database_option_parser():
parser = get_parser(_( parser = get_parser(_(
''' '''\
%prog restore_database [options] %prog restore_database [options]
Restore this database from the metadata stored in OPF files in each Restore this database from the metadata stored in OPF files in each
directory of the calibre library. This is useful if your metadata.db file directory of the calibre library. This is useful if your metadata.db file
has been corrupted. has been corrupted.
WARNING: This command completely regenerates your database. You will lose WARNING: This command completely regenerates your database. You will lose
all saved searches, user categories, plugboards, stored per-book conversion all saved searches, user categories, plugboards, stored per-book conversion
settings, and custom recipes. Restored metadata will only be as accurate as settings, and custom recipes. Restored metadata will only be as accurate as
what is found in the OPF files. what is found in the OPF files.
''')) '''))
parser.add_option('-r', '--really-do-it', default=False, action='store_true', parser.add_option('-r', '--really-do-it', default=False, action='store_true',

View File

@ -121,7 +121,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS):
book['id'], fmt) book['id'], fmt)
), ),
CLASS('button')) CLASS('button'))
s.tail = u'\u202f' # &nbsp; s.tail = u''
last = s last = s
data.append(s) data.append(s)

View File

@ -62,9 +62,9 @@ If you want the search to ignore upper/lowercase differences, uncheck the `Case
You can have |app| change the case of the result (information after the replace has happened) by choosing one of the functions from the `Apply function after replace` box. The operations available are: You can have |app| change the case of the result (information after the replace has happened) by choosing one of the functions from the `Apply function after replace` box. The operations available are:
*`Lower case` -- change all the characters in the field to lower case * `Lower case` -- change all the characters in the field to lower case
*`Upper case` -- change all the characters in the field to upper case * `Upper case` -- change all the characters in the field to upper case
*`Title case` -- capitalize each word in the result. * `Title case` -- capitalize each word in the result.
The `Your test` box is provided for you to enter text to check that search/replace is doing what you want. In the majority of cases the book test boxes will be sufficient, but it is possible that there is a case you want to check that isn't shown in these boxes. Enter that case into `Your test`. The `Your test` box is provided for you to enter text to check that search/replace is doing what you want. In the majority of cases the book test boxes will be sufficient, but it is possible that there is a case you want to check that isn't shown in these boxes. Enter that case into `Your test`.

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