Sync to trunk
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.4.128'
|
||||
__version__ = '0.4.129'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
'''
|
||||
Various run time constants.
|
||||
|
@ -146,36 +146,7 @@ class PRS505(Device):
|
||||
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
|
||||
|
||||
|
||||
def open_windows_nowmi(self):
|
||||
from calibre import plugins
|
||||
winutil = plugins['winutil'][0]
|
||||
volumes = winutil.get_mounted_volumes_for_usb_device(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
main = None
|
||||
for device_id in volumes.keys():
|
||||
if 'PRS-505/UC&' in device_id:
|
||||
main = volumes[device_id]+':\\'
|
||||
if not main:
|
||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
|
||||
self._main_prefix = main
|
||||
card = self._card_prefix = None
|
||||
win32api = __import__('win32api')
|
||||
for device_id in volumes.keys():
|
||||
if 'PRS-505/UC:' in device_id:
|
||||
card = volumes[device_id]+':\\'
|
||||
try:
|
||||
win32api.GetVolumeInformation(card)
|
||||
self._card_prefix = card
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
|
||||
def open_windows(self):
|
||||
try:
|
||||
self.open_windows_nowmi()
|
||||
return
|
||||
except:
|
||||
pass
|
||||
drives = []
|
||||
wmi = __import__('wmi', globals(), locals(), [], -1)
|
||||
c = wmi.WMI()
|
||||
|
@ -40,6 +40,7 @@ def convert(opts, recipe_arg, notification=None):
|
||||
c.smart_update(recipe_opts, opts)
|
||||
opts = recipe_opts
|
||||
opts.chapter_mark = 'none'
|
||||
opts.dont_split_on_page_breaks = True
|
||||
opf = glob.glob(os.path.join(tdir, '*.opf'))
|
||||
if not opf:
|
||||
raise Exception('Downloading of recipe: %s failed'%recipe_arg)
|
||||
|
@ -129,6 +129,8 @@ class HTMLProcessor(Processor, Rationalizer):
|
||||
for script in list(self.body.xpath('descendant::script')):
|
||||
script.getparent().remove(script)
|
||||
|
||||
self.fix_markup()
|
||||
|
||||
def convert_image(self, img):
|
||||
rpath = img.get('src', '')
|
||||
path = os.path.join(os.path.dirname(self.save_path()), *rpath.split('/'))
|
||||
@ -146,6 +148,17 @@ class HTMLProcessor(Processor, Rationalizer):
|
||||
self.resource_map[key] = rpath+'_calibre_converted.jpg'
|
||||
img.set('src', rpath+'_calibre_converted.jpg')
|
||||
|
||||
def fix_markup(self):
|
||||
'''
|
||||
Perform various markup transforms to get the output to render correctly
|
||||
in the quirky ADE.
|
||||
'''
|
||||
# Replace <br> that are children of <body> with <p> </p>
|
||||
if hasattr(self.body, 'xpath'):
|
||||
for br in self.body.xpath('./br'):
|
||||
br.tag = 'p'
|
||||
br.text = u'\u00a0'
|
||||
|
||||
def save(self):
|
||||
for meta in list(self.root.xpath('//meta')):
|
||||
meta.getparent().remove(meta)
|
||||
|
@ -10,11 +10,6 @@ Based on ideas from comiclrf created by FangornUK.
|
||||
import os, sys, shutil, traceback, textwrap
|
||||
from uuid import uuid4
|
||||
|
||||
try:
|
||||
from reportlab.pdfgen import canvas
|
||||
_reportlab = True
|
||||
except:
|
||||
_reportlab = False
|
||||
|
||||
|
||||
|
||||
@ -397,8 +392,7 @@ def create_lrf(pages, profile, opts, thumbnail=None):
|
||||
def create_pdf(pages, profile, opts, thumbnail=None):
|
||||
width, height = PROFILES[profile]
|
||||
|
||||
if not _reportlab:
|
||||
raise RuntimeError('Failed to load reportlab')
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
pdf = canvas.Canvas(filename=opts.output, pagesize=(width,height+15))
|
||||
pdf.setAuthor(opts.author)
|
||||
|
48
src/calibre/gui2/dialogs/progress.py
Normal file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
''''''
|
||||
|
||||
from PyQt4.Qt import QDialog, SIGNAL, Qt
|
||||
|
||||
from calibre.gui2.dialogs.progress_ui import Ui_Dialog
|
||||
|
||||
class ProgressDialog(QDialog, Ui_Dialog):
|
||||
|
||||
def __init__(self, title, msg='', min=0, max=99, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
self.setWindowTitle(title)
|
||||
self.title.setText(title)
|
||||
self.message.setText(msg)
|
||||
self.setWindowModality(Qt.ApplicationModal)
|
||||
self.set_min(min)
|
||||
self.set_max(max)
|
||||
self.canceled = False
|
||||
|
||||
self.connect(self.button_box, SIGNAL('rejected()'), self._canceled)
|
||||
|
||||
def set_msg(self, msg=''):
|
||||
self.message.setText(msg)
|
||||
|
||||
def set_value(self, val):
|
||||
self.bar.setValue(val)
|
||||
|
||||
def set_min(self, min):
|
||||
self.bar.setMinimum(min)
|
||||
|
||||
def set_max(self, max):
|
||||
self.bar.setMaximum(max)
|
||||
|
||||
def _canceled(self, *args):
|
||||
self.canceled = True
|
||||
self.button_box.setDisabled(True)
|
||||
self.title.setText(_('Aborting...'))
|
||||
|
||||
def keyPressEvent(self, ev):
|
||||
if ev.key() == Qt.Key_Escape:
|
||||
self._canceled()
|
||||
else:
|
||||
QDialog.keyPressEvent(self, ev)
|
72
src/calibre/gui2/dialogs/progress.ui
Normal file
@ -0,0 +1,72 @@
|
||||
<ui version="4.0" >
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>712</width>
|
||||
<height>308</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="windowIcon" >
|
||||
<iconset resource="../images.qrc" >
|
||||
<normaloff>:/images/jobs.svg</normaloff>:/images/jobs.svg</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="title" >
|
||||
<property name="font" >
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QProgressBar" name="bar" >
|
||||
<property name="value" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="message" >
|
||||
<property name="text" >
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" >
|
||||
<widget class="QDialogButtonBox" name="button_box" >
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Abort</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../images.qrc" />
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
BIN
src/calibre/gui2/images/news/ambito.png
Normal file
After Width: | Height: | Size: 508 B |
BIN
src/calibre/gui2/images/news/elargentino.png
Normal file
After Width: | Height: | Size: 805 B |
BIN
src/calibre/gui2/images/news/ftd.png
Normal file
After Width: | Height: | Size: 383 B |
BIN
src/calibre/gui2/images/news/heise.png
Normal file
After Width: | Height: | Size: 636 B |
BIN
src/calibre/gui2/images/news/security_watch.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
src/calibre/gui2/images/news/sueddeutsche.png
Normal file
After Width: | Height: | Size: 901 B |
BIN
src/calibre/gui2/images/news/zdnet.png
Normal file
After Width: | Height: | Size: 592 B |
@ -198,13 +198,18 @@ class BooksModel(QAbstractTableModel):
|
||||
''' Return list indices of all cells in index.row()'''
|
||||
return [ self.index(index.row(), c) for c in range(self.columnCount(None))]
|
||||
|
||||
def save_to_disk(self, rows, path, single_dir=False, single_format=None):
|
||||
def save_to_disk(self, rows, path, single_dir=False, single_format=None,
|
||||
callback=None):
|
||||
rows = [row.row() for row in rows]
|
||||
if single_format is None:
|
||||
return self.db.export_to_dir(path, rows, self.sorted_on[0] == 'authors',
|
||||
single_dir=single_dir)
|
||||
return self.db.export_to_dir(path, rows,
|
||||
self.sorted_on[0] == 'authors',
|
||||
single_dir=single_dir,
|
||||
callback=callback)
|
||||
else:
|
||||
return self.db.export_single_format_to_dir(path, rows, single_format)
|
||||
return self.db.export_single_format_to_dir(path, rows,
|
||||
single_format,
|
||||
callback=callback)
|
||||
|
||||
|
||||
def delete_books(self, indices):
|
||||
|
@ -28,6 +28,7 @@ from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
||||
from calibre.library.database import LibraryDatabase
|
||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||
from calibre.gui2.update import CheckForUpdates
|
||||
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||
from calibre.gui2.main_window import MainWindow, option_parser as _option_parser
|
||||
from calibre.gui2.main_ui import Ui_MainWindow
|
||||
from calibre.gui2.device import DeviceManager
|
||||
@ -308,17 +309,6 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.library_path = dir
|
||||
db = LibraryDatabase2(self.library_path)
|
||||
self.library_view.set_database(db)
|
||||
if self.olddb is not None:
|
||||
pd = QProgressDialog('', '', 0, 100, self)
|
||||
pd.setWindowModality(Qt.ApplicationModal)
|
||||
pd.setCancelButton(None)
|
||||
pd.setWindowTitle(_('Migrating database'))
|
||||
pd.show()
|
||||
number_of_books = db.migrate_old(self.olddb, pd)
|
||||
self.olddb.close()
|
||||
if number_of_books == 0:
|
||||
os.remove(self.olddb.dbpath)
|
||||
self.olddb = None
|
||||
prefs['library_path'] = self.library_path
|
||||
self.library_view.sortByColumn(*dynamic.get('sort_column', ('timestamp', Qt.DescendingOrder)))
|
||||
if not self.library_view.restore_column_widths():
|
||||
@ -339,6 +329,8 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'),
|
||||
self.tags_view.model().reinit)
|
||||
self.connect(self.library_view.model(), SIGNAL('count_changed(int)'), self.location_view.count_changed)
|
||||
self.connect(self.library_view.model(), SIGNAL('count_changed(int)'),
|
||||
self.tags_view.recount)
|
||||
self.library_view.model().count_changed()
|
||||
########################### Cover Flow ################################
|
||||
self.cover_flow = None
|
||||
@ -598,29 +590,26 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
root = choose_dir(self, 'recursive book import root dir dialog', 'Select root folder')
|
||||
if not root:
|
||||
return
|
||||
progress = QProgressDialog('', '&'+_('Stop'),
|
||||
0, 0, self)
|
||||
progress.setWindowModality(Qt.ApplicationModal)
|
||||
progress.setWindowTitle(_('Adding books recursively...'))
|
||||
progress = ProgressDialog(_('Adding books recursively...'),
|
||||
min=0, max=0, parent=self)
|
||||
progress.show()
|
||||
def callback(msg):
|
||||
if msg != '.':
|
||||
progress.setLabelText((_('Added ')+msg) if msg else _('Searching...'))
|
||||
stop = progress.wasCanceled()
|
||||
progress.set_msg((_('Added ')+msg) if msg else _('Searching...'))
|
||||
QApplication.processEvents()
|
||||
QApplication.sendPostedEvents()
|
||||
QApplication.flush()
|
||||
return stop
|
||||
return progress.canceled
|
||||
try:
|
||||
duplicates = self.library_view.model().db.recursive_import(root, single, callback=callback)
|
||||
finally:
|
||||
progress.hide()
|
||||
progress.close()
|
||||
if duplicates:
|
||||
files = _('<p>Books with the same title as the following already exist in the database. Add them anyway?<ul>')
|
||||
for mi, formats in duplicates:
|
||||
files += '<li>'+mi.title+'</li>\n'
|
||||
d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'), files+'</ul></p>', self)
|
||||
d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'),
|
||||
files+'</ul></p>', self)
|
||||
if d.exec_() == QDialog.Accepted:
|
||||
for mi, formats in duplicates:
|
||||
self.library_view.model().db.import_book(mi, formats )
|
||||
@ -686,15 +675,13 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
return
|
||||
# Get format and metadata information
|
||||
formats, metadata, names, infos = [], [], [], []
|
||||
progress = QProgressDialog(_('Reading metadata...'), _('Stop'), 0, len(paths), self)
|
||||
progress.setWindowTitle(_('Adding books...'))
|
||||
progress.setWindowModality(Qt.ApplicationModal)
|
||||
progress.setLabelText(_('Reading metadata...'))
|
||||
progress = ProgressDialog(_('Adding books...'), _('Reading metadata...'),
|
||||
min=0, max=len(paths), parent=self)
|
||||
progress.show()
|
||||
try:
|
||||
for c, book in enumerate(paths):
|
||||
progress.setValue(c)
|
||||
if progress.wasCanceled():
|
||||
progress.set_value(c)
|
||||
if progress.canceled:
|
||||
return
|
||||
format = os.path.splitext(book)[1]
|
||||
format = format[1:] if format else None
|
||||
@ -713,15 +700,14 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
infos.append({'title':mi.title, 'authors':', '.join(mi.authors),
|
||||
'cover':self.default_thumbnail, 'tags':[]})
|
||||
title = mi.title if isinstance(mi.title, unicode) else mi.title.decode(preferred_encoding, 'replace')
|
||||
progress.setLabelText(_('Read metadata from ')+title)
|
||||
progress.set_msg(_('Read metadata from ')+title)
|
||||
|
||||
if not to_device:
|
||||
progress.setLabelText(_('Adding books to database...'))
|
||||
progress.set_msg(_('Adding books to database...'))
|
||||
model = self.library_view.model()
|
||||
|
||||
paths = list(paths)
|
||||
duplicates, number_added = model.add_books(paths, formats, metadata)
|
||||
progress.cancel()
|
||||
if duplicates:
|
||||
files = _('<p>Books with the same title as the following already exist in the database. Add them anyway?<ul>')
|
||||
for mi in duplicates[2]:
|
||||
@ -734,9 +720,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
else:
|
||||
self.upload_books(paths, list(map(sanitize_file_name, names)), infos, on_card=on_card)
|
||||
finally:
|
||||
progress.setValue(progress.maximum())
|
||||
progress.hide()
|
||||
progress.close()
|
||||
|
||||
def upload_books(self, files, names, metadata, on_card=False, memory=None):
|
||||
'''
|
||||
@ -979,18 +963,37 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.save_to_disk(checked, True)
|
||||
|
||||
def save_to_disk(self, checked, single_dir=False, single_format=None):
|
||||
|
||||
rows = self.current_view().selectionModel().selectedRows()
|
||||
if not rows or len(rows) == 0:
|
||||
d = error_dialog(self, _('Cannot save to disk'), _('No books selected'))
|
||||
d.exec_()
|
||||
return
|
||||
|
||||
progress = ProgressDialog(_('Saving to disk...'), min=0, max=len(rows),
|
||||
parent=self)
|
||||
|
||||
def callback(count, msg):
|
||||
progress.set_value(count)
|
||||
progress.set_msg(_('Saved')+' '+msg)
|
||||
QApplication.processEvents()
|
||||
QApplication.sendPostedEvents()
|
||||
QApplication.flush()
|
||||
return not progress.canceled
|
||||
|
||||
dir = choose_dir(self, 'save to disk dialog', _('Choose destination directory'))
|
||||
if not dir:
|
||||
return
|
||||
|
||||
progress.show()
|
||||
QApplication.processEvents()
|
||||
QApplication.sendPostedEvents()
|
||||
QApplication.flush()
|
||||
try:
|
||||
if self.current_view() == self.library_view:
|
||||
failures = self.current_view().model().save_to_disk(rows, dir,
|
||||
single_dir=single_dir, single_format=single_format)
|
||||
single_dir=single_dir, callback=callback,
|
||||
single_format=single_format)
|
||||
if failures and single_format is not None:
|
||||
msg = _('<p>Could not save the following books to disk, because the %s format is not available for them:<ul>')%single_format.upper()
|
||||
for f in failures:
|
||||
@ -1001,6 +1004,8 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
else:
|
||||
paths = self.current_view().model().paths(rows)
|
||||
self.device_manager.save_books(Dispatcher(self.books_saved), paths, dir)
|
||||
finally:
|
||||
progress.hide()
|
||||
|
||||
def books_saved(self, job):
|
||||
if job.exception is not None:
|
||||
@ -1115,6 +1120,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
os.remove(f.name)
|
||||
except:
|
||||
pass
|
||||
self.tags_view.recount()
|
||||
if self.current_view() is self.library_view:
|
||||
current = self.library_view.currentIndex()
|
||||
self.library_view.model().current_changed(current, QModelIndex())
|
||||
@ -1375,39 +1381,14 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
|
||||
def initialize_database(self):
|
||||
self.library_path = prefs['library_path']
|
||||
self.olddb = None
|
||||
if self.library_path is None: # Need to migrate to new database layout
|
||||
QMessageBox.information(self, 'Database format changed',
|
||||
'''\
|
||||
<p>calibre's book storage format has changed. Instead of storing book files in a database, the
|
||||
files are now stored in a folder on your filesystem. You will now be asked to choose the folder
|
||||
in which you want to store your books files. Any existing books will be automatically migrated.
|
||||
''')
|
||||
self.database_path = prefs['database_path']
|
||||
if not os.access(os.path.dirname(self.database_path), os.W_OK):
|
||||
error_dialog(self, _('Database does not exist'),
|
||||
_('The directory in which the database should be: %s no longer exists. Please choose a new database location.')%self.database_path).exec_()
|
||||
self.database_path = choose_dir(self, 'database path dialog',
|
||||
_('Choose new location for database'))
|
||||
if not self.database_path:
|
||||
self.database_path = os.path.expanduser('~').decode(sys.getfilesystemencoding())
|
||||
if not os.path.exists(self.database_path):
|
||||
os.makedirs(self.database_path)
|
||||
self.database_path = os.path.join(self.database_path, 'library1.db')
|
||||
prefs['database_path'] = self.database_path
|
||||
home = os.path.dirname(self.database_path)
|
||||
if not os.path.exists(home):
|
||||
home = os.getcwd()
|
||||
dir = unicode(QFileDialog.getExistingDirectory(self,
|
||||
_('Choose a location for your ebook library.'), home))
|
||||
_('Choose a location for your ebook library.'), os.getcwd()))
|
||||
if not dir:
|
||||
dir = os.path.dirname(self.database_path)
|
||||
dir = os.path.expanduser('~/Library')
|
||||
self.library_path = os.path.abspath(dir)
|
||||
try:
|
||||
self.olddb = LibraryDatabase(self.database_path)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.olddb = None
|
||||
if not os.path.exists(self.library_path):
|
||||
os.makedirs(self.library_path)
|
||||
|
||||
|
||||
def read_settings(self):
|
||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
Browsing book collection by tags.
|
||||
'''
|
||||
from PyQt4.Qt import QAbstractItemModel, Qt, QVariant, QTreeView, QModelIndex, \
|
||||
QFont, SIGNAL, QSize, QColor, QIcon
|
||||
QFont, SIGNAL, QSize, QColor, QIcon, QPoint
|
||||
from calibre.gui2 import config
|
||||
NONE = QVariant()
|
||||
|
||||
@ -37,6 +37,14 @@ class TagsView(QTreeView):
|
||||
self.emit(SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'),
|
||||
self._model.tokens(), self.match_all.isChecked())
|
||||
|
||||
def recount(self, *args):
|
||||
ci = self.currentIndex()
|
||||
if not ci.isValid():
|
||||
ci = self.indexAt(QPoint(10, 10))
|
||||
self.model().refresh()
|
||||
if ci.isValid():
|
||||
self.scrollTo(ci, QTreeView.PositionAtTop)
|
||||
|
||||
class TagsModel(QAbstractItemModel):
|
||||
|
||||
categories = [_('Authors'), _('Series'), _('Formats'), _('Publishers'), _('News'), _('Tags')]
|
||||
|
@ -1390,10 +1390,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
return [i[0] for i in self.conn.get('SELECT id FROM books')]
|
||||
|
||||
def export_to_dir(self, dir, indices, byauthor=False, single_dir=False,
|
||||
index_is_id=False):
|
||||
index_is_id=False, callback=None):
|
||||
if not os.path.exists(dir):
|
||||
raise IOError('Target directory does not exist: '+dir)
|
||||
by_author = {}
|
||||
count = 0
|
||||
for index in indices:
|
||||
id = index if index_is_id else self.id(index)
|
||||
au = self.conn.get('SELECT author_sort FROM books WHERE id=?',
|
||||
@ -1403,8 +1404,6 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
if not au:
|
||||
au = _('Unknown')
|
||||
au = au.split(',')[0]
|
||||
else:
|
||||
au = au.replace(',', ';')
|
||||
if not by_author.has_key(au):
|
||||
by_author[au] = []
|
||||
by_author[au].append(index)
|
||||
@ -1456,6 +1455,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
print 'Error setting metadata for book:', mi.title
|
||||
traceback.print_exc()
|
||||
f.close()
|
||||
count += 1
|
||||
if callable(callback):
|
||||
if not callback(count, mi.title):
|
||||
return
|
||||
|
||||
|
||||
|
||||
def import_book(self, mi, formats):
|
||||
@ -1569,12 +1573,13 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
|
||||
return duplicates
|
||||
|
||||
def export_single_format_to_dir(self, dir, indices, format, index_is_id=False):
|
||||
def export_single_format_to_dir(self, dir, indices, format,
|
||||
index_is_id=False, callback=None):
|
||||
dir = os.path.abspath(dir)
|
||||
if not index_is_id:
|
||||
indices = map(self.id, indices)
|
||||
failures = []
|
||||
for id in indices:
|
||||
for count, id in enumerate(indices):
|
||||
try:
|
||||
data = self.format(id, format, index_is_id=True)
|
||||
if not data:
|
||||
@ -1599,6 +1604,9 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
except:
|
||||
pass
|
||||
f.close()
|
||||
if callable(callback):
|
||||
if not callback(count, title):
|
||||
break
|
||||
return failures
|
||||
|
||||
|
||||
|
@ -38,7 +38,6 @@ class cmd_commit(_cmd_commit):
|
||||
print attributes['summary']
|
||||
return attributes['summary']
|
||||
|
||||
|
||||
def expand_bug(self, msg, nick, config, bug_tracker, type='trac'):
|
||||
prefix = '%s_%s_'%(type, nick)
|
||||
username = config.get_user_option(prefix+'username')
|
||||
|
5367
src/calibre/translations/hu.po
Normal file
@ -22,7 +22,7 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'time_magazine', 'endgadget', 'fudzilla', 'nspm_int', 'nspm', 'pescanik',
|
||||
'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet',
|
||||
'joelonsoftware', 'telepolis', 'common_dreams', 'nin', 'tomshardware_de',
|
||||
'pagina12', 'infobae',
|
||||
'pagina12', 'infobae', 'ambito', 'elargentino', 'sueddeutsche',
|
||||
)]
|
||||
|
||||
import re, imp, inspect, time, os
|
||||
|
44
src/calibre/web/feeds/recipes/recipe_ambito.py
Normal file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
ambito.com
|
||||
'''
|
||||
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Ambito(BasicNewsRecipe):
|
||||
title = 'Ambito.com'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Informacion Libre las 24 horas'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'iso--8859-1'
|
||||
cover_url = 'http://www.ambito.com/img/logo_.jpg'
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment' , description
|
||||
, '--category' , 'news, Argentina'
|
||||
, '--publisher' , title
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Principales Noticias', u'http://www.ambito.com/rss/noticiasp.asp' )
|
||||
,(u'Economia' , u'http://www.ambito.com/rss/noticias.asp?S=Econom%EDa' )
|
||||
,(u'Politica' , u'http://www.ambito.com/rss/noticias.asp?S=Pol%EDtica' )
|
||||
,(u'Informacion General' , u'http://www.ambito.com/rss/noticias.asp?S=Informaci%F3n%20General')
|
||||
,(u'Agro' , u'http://www.ambito.com/rss/noticias.asp?S=Agro' )
|
||||
,(u'Internacionales' , u'http://www.ambito.com/rss/noticias.asp?S=Internacionales' )
|
||||
,(u'Deportes' , u'http://www.ambito.com/rss/noticias.asp?S=Deportes' )
|
||||
,(u'Espectaculos' , u'http://www.ambito.com/rss/noticias.asp?S=Espect%E1culos' )
|
||||
,(u'Tecnologia' , u'http://www.ambito.com/rss/noticias.asp?S=Tecnologia' )
|
||||
,(u'Salud' , u'http://www.ambito.com/rss/noticias.asp?S=Salud' )
|
||||
,(u'Ambito Nacional' , u'http://www.ambito.com/rss/noticias.asp?S=Ambito%20Nacional' )
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('http://www.ambito.com/noticia.asp?','http://www.ambito.com/noticias/imprimir.asp?')
|
@ -48,4 +48,3 @@ class Clarin(BasicNewsRecipe):
|
||||
rest = artl.partition('-0')[-1]
|
||||
lmain = rest.partition('.')[0]
|
||||
return 'http://www.servicios.clarin.com/notas/jsp/clarin/v9/notas/imprimir.jsp?pagid=' + lmain
|
||||
|
||||
|
55
src/calibre/web/feeds/recipes/recipe_elargentino.py
Normal file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
elargentino.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class ElArgentino(BasicNewsRecipe):
|
||||
title = 'ElArgentino.com'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Informacion Libre las 24 horas'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf8'
|
||||
cover_url = 'http://www.elargentino.com/TemplateWeb/MediosFooter/tapa_elargentino.png'
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment' , description
|
||||
, '--category' , 'news, Argentina'
|
||||
, '--publisher' , 'ElArgentino.com'
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'id':'noprint' })
|
||||
,dict(name='div', attrs={'class':'encabezadoImprimir'})
|
||||
,dict(name='a' , attrs={'target':'_blank' })
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Portada' , u'http://www.elargentino.com/Highlights.aspx?Content-Type=text/xml&ChannelDesc=Home' )
|
||||
,(u'Pais' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=112&Content-Type=text/xml&ChannelDesc=Pa%C3%ADs' )
|
||||
,(u'Economia' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=107&Content-Type=text/xml&ChannelDesc=Econom%C3%ADa' )
|
||||
,(u'Mundo' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=113&Content-Type=text/xml&ChannelDesc=Mundo' )
|
||||
,(u'Tecnologia' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=118&Content-Type=text/xml&ChannelDesc=Tecnolog%C3%ADa' )
|
||||
,(u'Espectaculos', u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=114&Content-Type=text/xml&ChannelDesc=Espect%C3%A1culos')
|
||||
,(u'Deportes' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=106&Content-Type=text/xml&ChannelDesc=Deportes' )
|
||||
,(u'Sociedad' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=109&Content-Type=text/xml&ChannelDesc=Sociedad' )
|
||||
,(u'Entrevistas' , u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=115&Content-Type=text/xml&ChannelDesc=Entrevistas' )
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
main, sep, article_part = url.partition('/nota-')
|
||||
article_id, rsep, rrest = article_part.partition('-')
|
||||
return u'http://www.elargentino.com/Impresion.aspx?Id=' + article_id
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
|
||||
soup.head.insert(0,mtag)
|
||||
soup.prettify()
|
||||
return soup
|
@ -4,7 +4,7 @@ class SecurityWatch(BasicNewsRecipe):
|
||||
title = u'securitywatch'
|
||||
description = 'security news'
|
||||
timefmt = ' [%d %b %Y]'
|
||||
__author__ = 'Oliver'
|
||||
__author__ = 'Oliver Niesner'
|
||||
no_stylesheets = True
|
||||
oldest_article = 14
|
||||
max_articles_per_feed = 100
|
||||
|
62
src/calibre/web/feeds/recipes/recipe_sueddeutsche.py
Normal file
@ -0,0 +1,62 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
'''
|
||||
Fetch sueddeutsche.
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
|
||||
class Sueddeutsche(BasicNewsRecipe):
|
||||
|
||||
title = u'Sueddeutsche'
|
||||
description = 'News from Germany'
|
||||
__author__ = 'Oliver Niesner'
|
||||
use_embedded_content = False
|
||||
timefmt = ' [%d %b %Y]'
|
||||
max_articles_per_feed = 40
|
||||
no_stylesheets = True
|
||||
encoding = 'latin1'
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'artikelBox navigatorBox'})]
|
||||
#dict(name='table', attrs={'class':'bgf2f2f2 absatz print100'})]
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'bannerSuperBanner'}),
|
||||
dict(name='div', attrs={'class':'bannerSky'}),
|
||||
dict(name='div', attrs={'class':'footerLinks'}),
|
||||
dict(name='div', attrs={'class':'seitenanfang'}),
|
||||
dict(name='td', attrs={'class':'mar5'}),
|
||||
dict(name='table', attrs={'class':'pageAktiv'}),
|
||||
dict(name='table', attrs={'class':'xartable'}),
|
||||
dict(name='table', attrs={'class':'wpnavi'}),
|
||||
dict(name='table', attrs={'class':'bgcontent absatz'}),
|
||||
dict(name='table', attrs={'class':'footer'}),
|
||||
dict(name='table', attrs={'class':'artikelBox'}),
|
||||
dict(name='table', attrs={'class':'kommentare'}),
|
||||
dict(name='table', attrs={'class':'pageBoxBot'}),
|
||||
dict(name='div', attrs={'class':'artikelBox navigatorBox'}),
|
||||
dict(name='div', attrs={'class':'similar-article-box'}),
|
||||
dict(name='div', attrs={'class':'videoBigHack'}),
|
||||
dict(name='td', attrs={'class':'artikelDruckenRight'}),
|
||||
dict(name='span', attrs={'class':'hidePrint'}),
|
||||
dict(id='headerLBox'),
|
||||
dict(id='rechteSpalte'),
|
||||
dict(id='newsticker-list-small'),
|
||||
dict(id='ntop5'),
|
||||
dict(id='ntop5send'),
|
||||
dict(id='ntop5commented'),
|
||||
dict(id='nnav-bgheader'),
|
||||
dict(id='nnav-headerteaser'),
|
||||
dict(id='nnav-head'),
|
||||
dict(id='nnav-top'),
|
||||
dict(id='nnav-logodiv'),
|
||||
dict(id='nnav-logo'),
|
||||
dict(id='nnav-oly'),
|
||||
dict(id='readcomment')]
|
||||
|
||||
feeds = [ (u'Sueddeutsche', u'http://www.sueddeutsche.de/app/service/rss/alles/rss.xml') ]
|
||||
|
||||
def postprocess_html(self, soup, first_fetch):
|
||||
for t in soup.findAll(['table', 'tr', 'td']):
|
||||
t.name = 'div'
|
||||
return soup
|
@ -395,7 +395,11 @@ class RecursiveFetcher(object, LoggingInterface):
|
||||
if self.download_stylesheets:
|
||||
self.process_stylesheets(soup, newbaseurl)
|
||||
|
||||
res = os.path.join(linkdiskpath, basename(iurl))
|
||||
_fname = basename(iurl)
|
||||
if not isinstance(_fname, unicode):
|
||||
_fname.decode('latin1', 'replace')
|
||||
_fname.encode('ascii', 'replace').replace('%', '')
|
||||
res = os.path.join(linkdiskpath, _fname)
|
||||
self.downloaded_paths.append(res)
|
||||
self.filemap[nurl] = res
|
||||
if recursion_level < self.max_recursions:
|
||||
|