Sync to trunk

This commit is contained in:
John Schember 2009-02-11 20:25:07 -05:00
commit 32d9faff6a
35 changed files with 688 additions and 28875 deletions

View File

@ -81,7 +81,8 @@ def freeze():
'PyQt4.QtScript.so', 'PyQt4.QtSql.so', 'PyQt4.QtTest.so', 'qt',
'glib', 'gobject']
packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg']
packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg',
'dateutil']
includes += ['calibre.web.feeds.recipes.'+r for r in recipe_modules]

View File

@ -342,6 +342,7 @@ def main():
'calibre.ebooks.lrf.any.*', 'calibre.ebooks.lrf.feeds.*',
'keyword', 'codeop', 'pydoc', 'readline',
'BeautifulSoup', 'calibre.ebooks.lrf.fonts.prs500.*',
'dateutil',
],
'packages' : ['PIL', 'Authorization', 'lxml'],
'excludes' : ['IPython'],

View File

@ -179,7 +179,8 @@ def main(args=sys.argv):
'calibre.ebooks.lrf.fonts.prs500.*',
'PyQt4.QtWebKit', 'PyQt4.QtNetwork',
],
'packages' : ['PIL', 'lxml', 'cherrypy'],
'packages' : ['PIL', 'lxml', 'cherrypy',
'dateutil'],
'excludes' : ["Tkconstants", "Tkinter", "tcl",
"_imagingtk", "ImageTk", "FixTk"
],

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
__version__ = '0.4.135'
__version__ = '0.4.136'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
'''
Various run time constants.

View File

@ -186,7 +186,10 @@ class BookList(_BookList):
node = self.document.createElement(self.prefix + "text")
mime = MIME_MAP[name.rpartition('.')[-1].lower()]
cid = self.max_id()+1
sourceid = str(self[0].sourceid) if len(self) else "1"
try:
sourceid = str(self[0].sourceid) if len(self) else '1'
except:
sourceid = '1'
attrs = {
"title" : info["title"],
'titleSorter' : sortable_title(info['title']),

View File

@ -248,15 +248,20 @@ class PRS505(Device):
time.sleep(3)
self.open_osx()
if self._card_prefix is not None:
cachep = os.path.join(self._card_prefix, self.CACHE_XML)
if not os.path.exists(cachep):
os.makedirs(os.path.dirname(cachep), mode=0777)
f = open(cachep, 'wb')
f.write(u'''<?xml version="1.0" encoding="UTF-8"?>
try:
cachep = os.path.join(self._card_prefix, self.CACHE_XML)
if not os.path.exists(cachep):
os.makedirs(os.path.dirname(cachep), mode=0777)
f = open(cachep, 'wb')
f.write(u'''<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns="http://www.kinoma.com/FskCache/1">
</cache>
'''.encode('utf8'))
f.close()
f.close()
except:
self._card_prefix = None
import traceback
traceback.print_exc()
def set_progress_reporter(self, pr):
self.report_progress = pr

View File

@ -74,7 +74,9 @@ def check_links(opf_path, pretty_print):
html_files = []
for item in opf.itermanifest():
if 'html' in item.get('media-type', '').lower():
f = item.get('href').split('/')[-1].decode('utf-8')
f = item.get('href').split('/')[-1]
if isinstance(f, str):
f = f.decode('utf-8')
html_files.append(os.path.abspath(content(f)))
for path in html_files:

View File

@ -330,7 +330,8 @@ class PreProcessor(object):
sanitize_head),
# Convert all entities, since lxml doesn't handle them well
(re.compile(r'&(\S+?);'), convert_entities),
# Remove the <![if/endif tags inserted by everybody's darling, MS Word
(re.compile(r'(?i)<{0,1}!\[(end){0,1}if[^>]*>'), lambda match: ''),
]
# Fix pdftohtml markup

View File

@ -192,7 +192,8 @@ class MetaInformation(object):
for attr in ('author_sort', 'title_sort', 'comments', 'category',
'publisher', 'series', 'series_index', 'rating',
'isbn', 'tags', 'cover_data', 'application_id', 'guide',
'manifest', 'spine', 'toc', 'cover', 'language', 'book_producer'):
'manifest', 'spine', 'toc', 'cover', 'language',
'book_producer', 'timestamp'):
if hasattr(mi, attr):
setattr(ans, attr, getattr(mi, attr))
@ -217,7 +218,7 @@ class MetaInformation(object):
for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher',
'series', 'series_index', 'rating', 'isbn', 'language',
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
'book_producer',
'book_producer', 'timestamp'
):
setattr(self, x, getattr(mi, x, None))
@ -235,7 +236,8 @@ class MetaInformation(object):
for attr in ('author_sort', 'title_sort', 'comments', 'category',
'publisher', 'series', 'series_index', 'rating',
'isbn', 'application_id', 'manifest', 'spine', 'toc',
'cover', 'language', 'guide', 'book_producer'):
'cover', 'language', 'guide', 'book_producer',
'timestamp'):
if hasattr(mi, attr):
val = getattr(mi, attr)
if val is not None:
@ -276,6 +278,8 @@ class MetaInformation(object):
ans += u'Series : '+unicode(self.series) + ' #%s\n'%self.format_series_index()
if self.language:
ans += u'Language : ' + unicode(self.language) + u'\n'
if self.timestamp is not None:
ans += u'Timestamp : ' + self.timestamp.isoformat(' ')
return ans.strip()
def to_html(self):
@ -289,12 +293,12 @@ class MetaInformation(object):
if self.series:
ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())]
ans += [(_('Language'), unicode(self.language))]
if self.timestamp is not None:
ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))]
for i, x in enumerate(ans):
ans[i] = u'<tr><td><b>%s</b></td><td>%s</td></tr>'%x
return u'<table>%s</table>'%u'\n'.join(ans)
def __str__(self):
return self.__unicode__().encode('utf-8')

View File

@ -43,8 +43,9 @@ def cover_from_isbn(isbn, timeout=5.):
src = browser.open('http://www.librarything.com/isbn/'+isbn).read().decode('utf-8', 'replace')
except Exception, err:
if isinstance(getattr(err, 'args', [None])[0], socket.timeout):
raise LibraryThingError(_('LibraryThing.com timed out. Try again later.'))
raise
err = LibraryThingError(_('LibraryThing.com timed out. Try again later.'))
raise err
else:
s = BeautifulSoup(src)
url = s.find('td', attrs={'class':'left'})
if url is None:

View File

@ -31,8 +31,14 @@ def metadata_from_formats(formats):
mi = MetaInformation(None, None)
formats.sort(cmp=lambda x,y: cmp(METADATA_PRIORITIES[path_to_ext(x)],
METADATA_PRIORITIES[path_to_ext(y)]))
for path in formats:
ext = path_to_ext(path)
extensions = list(map(path_to_ext, formats))
if 'opf' in extensions:
opf = formats[extensions.index('opf')]
mi2 = opf_metadata(opf)
if mi2 is not None and mi2.title:
return mi2
for path, ext in zip(formats, extensions):
stream = open(path, 'rb')
try:
mi.smart_update(get_metadata(stream, stream_type=ext, use_libprs_metadata=True))

View File

@ -10,7 +10,7 @@
<dc:creator opf:role="aut" py:for="i, author in enumerate(mi.authors)" py:attrs="{'opf:file-as':mi.author_sort} if mi.author_sort and i == 0 else {}">${author}</dc:creator>
<dc:contributor opf:role="bkp" py:with="attrs={'opf:file-as':__appname__}" py:attrs="attrs">${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]</dc:contributor>
<dc:identifier opf:scheme="${__appname__}" id="${__appname__}_id">${mi.application_id}</dc:identifier>
<dc:date py:if="getattr(mi, 'timestamp', None) is not None">${mi.timestamp.isoformat()}</dc:date>
<dc:language>${mi.language if mi.language else 'UND'}</dc:language>
<dc:type py:if="mi.category">${mi.category}</dc:type>
<dc:description py:if="mi.comments">${mi.comments}</dc:description>

View File

@ -12,6 +12,7 @@ from urllib import unquote
from urlparse import urlparse
from lxml import etree
from dateutil import parser
from calibre.ebooks.chardet import xml_to_unicode
from calibre import relpath
@ -436,6 +437,7 @@ class OPF(object):
series = MetadataField('series', is_dc=False)
series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1)
rating = MetadataField('rating', is_dc=False, formatter=int)
timestamp = MetadataField('date', formatter=parser.parse)
def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True):

View File

@ -308,8 +308,11 @@ class MobiReader(object):
if 'filepos-id' in attrib:
attrib['id'] = attrib.pop('filepos-id')
if 'filepos' in attrib:
filepos = int(attrib.pop('filepos'))
attrib['href'] = "#filepos%d" % filepos
filepos = attrib.pop('filepos')
try:
attrib['href'] = "#filepos%d" % int(filepos)
except:
attrib['href'] = filepos
if tag.tag == 'img':
recindex = None
for attr in self.IMAGE_ATTRS:

View File

@ -1,7 +1,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" The GUI """
import sys, os, re, StringIO, traceback
import sys, os, re, StringIO, traceback, time
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
QByteArray, QLocale, QUrl, QTranslator, QCoreApplication, \
QModelIndex
@ -14,6 +14,9 @@ from calibre import __author__, islinux, iswindows, isosx
from calibre.startup import get_lang
from calibre.utils.config import Config, ConfigProxy, dynamic
import calibre.resources as resources
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
from calibre.ebooks.metadata import MetaInformation
NONE = QVariant() #: Null value to return from the data function of item models
@ -148,7 +151,41 @@ class Dispatcher(QObject):
def dispatch(self, args, kwargs):
self.func(*args, **kwargs)
class GetMetadata(QObject):
'''
Convenience class to ensure that metadata readers are used only in the
GUI thread. Must be instantiated in the GUI thread.
'''
def __init__(self):
QObject.__init__(self)
self.connect(self, SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
self._get_metadata, Qt.QueuedConnection)
self.connect(self, SIGNAL('idispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
self._from_formats, Qt.QueuedConnection)
def __call__(self, id, *args, **kwargs):
self.emit(SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
id, args, kwargs)
def from_formats(self, id, *args, **kwargs):
self.emit(SIGNAL('idispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
id, args, kwargs)
def _from_formats(self, id, args, kwargs):
try:
mi = metadata_from_formats(*args, **kwargs)
except:
mi = MetaInformation('', [_('Unknown')])
self.emit(SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), id, mi)
def _get_metadata(self, id, args, kwargs):
try:
mi = get_metadata(*args, **kwargs)
except:
mi = MetaInformation('', [_('Unknown')])
self.emit(SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), id, mi)
class TableView(QTableView):
def __init__(self, parent):

224
src/calibre/gui2/add.py Normal file
View File

@ -0,0 +1,224 @@
'''
UI for adding books to the database
'''
import os
from PyQt4.Qt import QThread, SIGNAL, QMutex, QWaitCondition, Qt
from calibre.gui2.dialogs.progress import ProgressDialog
from calibre.constants import preferred_encoding
from calibre.gui2.widgets import WarningDialog
class Add(QThread):
def __init__(self):
QThread.__init__(self)
self._lock = QMutex()
self._waiting = QWaitCondition()
def is_canceled(self):
if self.pd.canceled:
self.canceled = True
return self.canceled
def wait_for_condition(self):
self._lock.lock()
self._waiting.wait(self._lock)
self._lock.unlock()
def wake_up(self):
self._waiting.wakeAll()
class AddFiles(Add):
def __init__(self, paths, default_thumbnail, get_metadata, db=None):
Add.__init__(self)
self.paths = paths
self.get_metadata = get_metadata
self.default_thumbnail = default_thumbnail
self.db = db
self.formats, self.metadata, self.names, self.infos = [], [], [], []
self.duplicates = []
self.number_of_books_added = 0
self.connect(self.get_metadata,
SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'),
self.metadata_delivered)
def metadata_delivered(self, id, mi):
if self.is_canceled():
self.reading.wakeAll()
return
if not mi.title:
mi.title = os.path.splitext(self.names[id])[0]
mi.title = mi.title if isinstance(mi.title, unicode) else \
mi.title.decode(preferred_encoding, 'replace')
self.metadata.append(mi)
self.infos.append({'title':mi.title,
'authors':', '.join(mi.authors),
'cover':self.default_thumbnail, 'tags':[]})
if self.db is not None:
duplicates, num = self.db.add_books(self.paths[id:id+1],
self.formats[id:id+1], [mi],
add_duplicates=False)
self.number_of_books_added += num
if duplicates:
if not self.duplicates:
self.duplicates = [[], [], [], []]
for i in range(4):
self.duplicates[i] += duplicates[i]
self.emit(SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'),
mi.title, id)
self.wake_up()
def create_progress_dialog(self, title, msg, parent):
self._parent = parent
self.pd = ProgressDialog(title, msg, -1, len(self.paths)-1, parent)
self.connect(self, SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'),
self.update_progress_dialog)
self.pd.setModal(True)
self.pd.show()
self.connect(self, SIGNAL('finished()'), self.pd.hide)
return self.pd
def update_progress_dialog(self, title, count):
self.pd.set_value(count)
if self.db is not None:
self.pd.set_msg(_('Added %s to library')%title)
else:
self.pd.set_msg(_('Read metadata from ')+title)
def run(self):
self.canceled = False
for c, book in enumerate(self.paths):
if self.pd.canceled:
self.canceled = True
break
format = os.path.splitext(book)[1]
format = format[1:] if format else None
stream = open(book, 'rb')
self.formats.append(format)
self.names.append(os.path.basename(book))
self.get_metadata(c, stream, stream_type=format,
use_libprs_metadata=True)
self.wait_for_condition()
def process_duplicates(self):
if self.duplicates:
files = _('<p>Books with the same title as the following already '
'exist in the database. Add them anyway?<ul>')
for mi in self.duplicates[2]:
files += '<li>'+mi.title+'</li>\n'
d = WarningDialog (_('Duplicates found!'),
_('Duplicates found!'),
files+'</ul></p>', parent=self._parent)
if d.exec_() == d.Accepted:
num = self.db.add_books(*self.duplicates,
**dict(add_duplicates=True))[1]
self.number_of_books_added += num
class AddRecursive(Add):
def __init__(self, path, db, get_metadata, single_book_per_directory, parent):
self.path = path
self.db = db
self.get_metadata = get_metadata
self.single_book_per_directory = single_book_per_directory
self.duplicates, self.books, self.metadata = [], [], []
self.number_of_books_added = 0
self.canceled = False
Add.__init__(self)
self.connect(self.get_metadata,
SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'),
self.metadata_delivered, Qt.QueuedConnection)
self.connect(self, SIGNAL('searching_done()'), self.searching_done,
Qt.QueuedConnection)
self._parent = parent
self.pd = ProgressDialog(_('Adding books recursively...'),
_('Searching for books in all sub-directories...'),
0, 0, parent)
self.connect(self, SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'),
self.update_progress_dialog)
self.connect(self, SIGNAL('update(PyQt_PyObject)'), self.pd.set_msg,
Qt.QueuedConnection)
self.connect(self, SIGNAL('pupdate(PyQt_PyObject)'), self.pd.set_value,
Qt.QueuedConnection)
self.pd.setModal(True)
self.pd.show()
self.connect(self, SIGNAL('finished()'), self.pd.hide)
def update_progress_dialog(self, title, count):
self.pd.set_value(count)
if title:
self.pd.set_msg(_('Read metadata from ')+title)
def metadata_delivered(self, id, mi):
if self.is_canceled():
self.reading.wakeAll()
return
self.emit(SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'),
mi.title, id)
self.metadata.append((mi if mi.title else None, self.books[id]))
if len(self.metadata) >= len(self.books):
self.metadata = [x for x in self.metadata if x[0] is not None]
self.pd.set_min(-1)
self.pd.set_max(len(self.metadata)-1)
self.pd.set_value(-1)
self.pd.set_msg(_('Adding books to database...'))
self.wake_up()
def searching_done(self):
self.pd.set_min(-1)
self.pd.set_max(len(self.books)-1)
self.pd.set_value(-1)
self.pd.set_msg(_('Reading metadata...'))
def run(self):
root = os.path.abspath(self.path)
for dirpath in os.walk(root):
if self.is_canceled():
return
self.emit(SIGNAL('update(PyQt_PyObject)'),
_('Searching in')+' '+dirpath[0])
self.books += list(self.db.find_books_in_directory(dirpath[0],
self.single_book_per_directory))
self.books = [formats for formats in self.books if formats]
# Reset progress bar
self.emit(SIGNAL('searching_done()'))
for c, formats in enumerate(self.books):
self.get_metadata.from_formats(c, formats)
self.wait_for_condition()
# Add books to database
for c, x in enumerate(self.metadata):
mi, formats = x
if self.is_canceled():
break
if self.db.has_book(mi):
self.duplicates.append((mi, formats))
else:
self.db.import_book(mi, formats, notify=False)
self.number_of_books_added += 1
self.emit(SIGNAL('pupdate(PyQt_PyObject)'), c)
def process_duplicates(self):
if self.duplicates:
files = _('<p>Books with the same title as the following already '
'exist in the database. Add them anyway?<ul>')
for mi in self.duplicates:
files += '<li>'+mi[0].title+'</li>\n'
d = WarningDialog (_('Duplicates found!'),
_('Duplicates found!'),
files+'</ul></p>', parent=self._parent)
if d.exec_() == d.Accepted:
for mi, formats in self.duplicates:
self.db.import_book(mi, formats, notify=False)
self.number_of_books_added += 1

View File

@ -5,7 +5,7 @@ __docformat__ = 'restructuredtext en'
from calibre.gui2 import dynamic
from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog
from PyQt4.Qt import QDialog, SIGNAL
from PyQt4.Qt import QDialog, SIGNAL, Qt
def _config_name(name):
return name + '_again'
@ -19,6 +19,7 @@ class Dialog(QDialog, Ui_Dialog):
self.msg.setText(msg)
self.name = name
self.connect(self.again, SIGNAL('stateChanged(int)'), self.toggle)
self.buttonBox.setFocus(Qt.OtherFocusReason)
def toggle(self, x):

View File

@ -113,6 +113,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
def set_cover(self):
row = self.formats.currentRow()
fmt = self.formats.item(row)
if fmt is None:
error_dialog(self, _('No format selected'),
_('No format selected')).exec_()
return
ext = fmt.ext.lower()
if fmt.path is None:
stream = self.db.format(self.row, ext, as_file=True)
@ -121,7 +125,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
try:
mi = get_metadata(stream, ext)
except:
error_dialog(self, _('Could not read metadata'),
error_dialog(self, _('Could not read metadata'),
_('Could not read metadata from %s format')%ext).exec_()
return
cdata = None

View File

@ -20,6 +20,7 @@ class ProgressDialog(QDialog, Ui_Dialog):
self.setWindowModality(Qt.ApplicationModal)
self.set_min(min)
self.set_max(max)
self.bar.setValue(min)
self.canceled = False
self.connect(self.button_box, SIGNAL('rejected()'), self._canceled)

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 354 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

View File

@ -1,3 +1,4 @@
from calibre.ebooks.metadata import authors_to_string
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, textwrap, traceback, time, re
@ -371,35 +372,24 @@ class BooksModel(QAbstractTableModel):
if not rows_are_ids:
rows = [self.db.id(row.row()) for row in rows]
for id in rows:
au = self.db.authors(id, index_is_id=True)
tags = self.db.tags(id, index_is_id=True)
if not au:
au = _('Unknown')
au = au.split(',')
if len(au) > 1:
t = ', '.join(au[:-1])
t += ' & ' + au[-1]
au = t
else:
au = ' & '.join(au)
if not tags:
tags = []
else:
tags = tags.split(',')
series = self.db.series(id, index_is_id=True)
if series is not None:
tags.append(series)
mi = {
'title' : self.db.title(id, index_is_id=True),
mi = self.db.get_metadata(id, index_is_id=True)
au = authors_to_string(mi.authors if mi.authors else [_('Unknown')])
tags = mi.tags if mi.tags else []
if mi.series is not None:
tags.append(mi.series)
info = {
'title' : mi.title,
'authors' : au,
'cover' : self.db.cover(id, index_is_id=True),
'tags' : tags,
'comments': self.db.comments(id, index_is_id=True),
'comments': mi.comments,
}
if series is not None:
mi['tag order'] = {series:self.db.books_in_series_of(id, index_is_id=True)}
if mi.series is not None:
info['tag order'] = {
mi.series:self.db.books_in_series_of(id, index_is_id=True)
}
metadata.append(mi)
metadata.append(info)
return metadata
def get_preferred_formats_from_ids(self, ids, all_formats, mode='r+b'):

View File

@ -13,7 +13,6 @@ from PyQt4.QtSvg import QSvgRenderer
from calibre import __version__, __appname__, islinux, sanitize_file_name, \
iswindows, isosx, preferred_encoding
from calibre.ptempfile import PersistentTemporaryFile
from calibre.ebooks.metadata.meta import get_metadata
from calibre.devices.errors import FreeSpaceError
from calibre.devices.interface import Device
from calibre.utils.config import prefs, dynamic
@ -23,7 +22,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
set_sidebar_directories, Dispatcher, \
SingleApplication, Application, available_height, \
max_available_height, config, info_dialog, \
available_width
available_width, GetMetadata
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.gui2.update import CheckForUpdates
@ -49,7 +48,6 @@ from calibre.ebooks import BOOK_EXTENSIONS
from calibre.library.database2 import LibraryDatabase2, CoverCache
from calibre.parallel import JobKilled
from calibre.utils.filenames import ascii_filename
from calibre.gui2.widgets import WarningDialog
from calibre.gui2.dialogs.confirm_delete import confirm
class Main(MainWindow, Ui_MainWindow):
@ -78,6 +76,7 @@ class Main(MainWindow, Ui_MainWindow):
self.setupUi(self)
self.setWindowTitle(__appname__)
self.verbose = opts.verbose
self.get_metadata = GetMetadata()
self.read_settings()
self.job_manager = JobManager()
self.jobs_dialog = JobsDialog(self, self.job_manager)
@ -391,7 +390,7 @@ class Main(MainWindow, Ui_MainWindow):
def change_output_format(self, x):
of = unicode(x).strip()
if of != prefs['output_format']:
if of not in ('LRF',):
if of not in ('LRF', 'EPUB'):
warning_dialog(self, 'Warning',
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
prefs.set('output_format', of)
@ -608,36 +607,26 @@ class Main(MainWindow, Ui_MainWindow):
################################# Add books ################################
def add_recursive(self, single):
root = choose_dir(self, 'recursive book import root dir dialog', 'Select root folder')
root = choose_dir(self, 'recursive book import root dir dialog',
'Select root folder')
if not root:
return
progress = ProgressDialog(_('Adding books recursively...'),
min=0, max=0, parent=self)
progress.show()
def callback(msg):
if msg != '.':
progress.set_msg((_('Added ')+msg) if msg else _('Searching...'))
QApplication.processEvents()
QApplication.sendPostedEvents()
QApplication.flush()
return progress.canceled
try:
duplicates = self.library_view.model().db.recursive_import(root, single, callback=callback)
finally:
progress.hide()
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)
if d.exec_() == QDialog.Accepted:
for mi, formats in duplicates:
self.library_view.model().db.import_book(mi, formats )
self.library_view.model().resort()
self.library_view.model().research()
from calibre.gui2.add import AddRecursive
self._add_recursive_thread = AddRecursive(root,
self.library_view.model().db, self.get_metadata,
single, self)
self.connect(self._add_recursive_thread, SIGNAL('finished()'),
self._recursive_files_added)
self._add_recursive_thread.start()
def _recursive_files_added(self):
self._add_recursive_thread.process_duplicates()
if self._add_recursive_thread.number_of_books_added > 0:
self.library_view.model().resort(reset=False)
self.library_view.model().research()
self.library_view.model().count_changed()
self._add_recursive_thread = None
def add_recursive_single(self, checked):
'''
Add books from the local filesystem to either the library or the device
@ -686,66 +675,41 @@ class Main(MainWindow, Ui_MainWindow):
return
to_device = self.stack.currentIndex() != 0
self._add_books(books, to_device)
if to_device:
self.status_bar.showMessage(_('Uploading books to device.'), 2000)
def _add_books(self, paths, to_device, on_card=None):
if on_card is None:
on_card = self.stack.currentIndex() == 2
if not paths:
return
# Get format and metadata information
formats, metadata, names, infos = [], [], [], []
progress = ProgressDialog(_('Adding books...'), _('Reading metadata...'),
min=0, max=len(paths), parent=self)
progress.show()
try:
for c, book in enumerate(paths):
progress.set_value(c+1)
if progress.canceled:
return
format = os.path.splitext(book)[1]
format = format[1:] if format else None
stream = open(book, 'rb')
try:
mi = get_metadata(stream, stream_type=format, use_libprs_metadata=True)
except:
mi = MetaInformation(None, None)
if not mi.title:
mi.title = os.path.splitext(os.path.basename(book))[0]
if not mi.authors:
mi.authors = [_('Unknown')]
formats.append(format)
metadata.append(mi)
names.append(os.path.basename(book))
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.set_msg(_('Read metadata from ')+title)
QApplication.processEvents()
if not to_device:
progress.set_msg(_('Adding books to database...'))
QApplication.processEvents()
model = self.library_view.model()
paths = list(paths)
duplicates, number_added = model.add_books(paths, formats, metadata)
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]:
files += '<li>'+mi.title+'</li>\n'
d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'), files+'</ul></p>', parent=self)
if d.exec_() == QDialog.Accepted:
num = model.add_books(*duplicates, **dict(add_duplicates=True))[1]
number_added += num
model.books_added(number_added)
from calibre.gui2.add import AddFiles
self._add_files_thread = AddFiles(paths, self.default_thumbnail,
self.get_metadata,
None if to_device else \
self.library_view.model().db
)
self._add_files_thread.send_to_device = to_device
self._add_files_thread.on_card = on_card
self._add_files_thread.create_progress_dialog(_('Adding books...'),
_('Reading metadata...'), self)
self.connect(self._add_files_thread, SIGNAL('finished()'),
self._files_added)
self._add_files_thread.start()
def _files_added(self):
t = self._add_files_thread
self._add_files_thread = None
if not t.canceled:
if t.send_to_device:
self.upload_books(t.paths,
list(map(sanitize_file_name, t.names)),
t.infos, on_card=t.on_card)
self.status_bar.showMessage(_('Uploading books to device.'), 2000)
else:
self.upload_books(paths, list(map(sanitize_file_name, names)), infos, on_card=on_card)
finally:
QApplication.processEvents()
progress.hide()
t.process_duplicates()
if t.number_of_books_added > 0:
self.library_view.model().books_added(t.number_of_books_added)
def upload_books(self, files, names, metadata, on_card=False, memory=None):
'''
Upload books to device.
@ -801,7 +765,10 @@ class Main(MainWindow, Ui_MainWindow):
if not rows or len(rows) == 0:
return
if self.stack.currentIndex() == 0:
if not confirm('<p>'+_('The selected books will be <b>permanently deleted</b> and the files removed from your computer. Are you sure?')+'</p>', 'library_delete_books', self):
if not confirm('<p>'+_('The selected books will be '
'<b>permanently deleted</b> and the files '
'removed from your computer. Are you sure?')
+'</p>', 'library_delete_books', self):
return
view.model().delete_books(rows)
else:
@ -1410,8 +1377,15 @@ class Main(MainWindow, Ui_MainWindow):
def initialize_database(self):
self.library_path = prefs['library_path']
if self.library_path is None: # Need to migrate to new database layout
base = os.path.expanduser('~')
if iswindows:
from calibre import plugins
from PyQt4.Qt import QDir
base = plugins['winutil'][0].special_folder_path(plugins['winutil'][0].CSIDL_PERSONAL)
if not base or not os.path.exists(base):
base = unicode(QDir.homePath()).replace('/', os.sep)
dir = unicode(QFileDialog.getExistingDirectory(self,
_('Choose a location for your ebook library.'), os.getcwd()))
_('Choose a location for your ebook library.'), base))
if not dir:
dir = os.path.expanduser('~/Library')
self.library_path = os.path.abspath(dir)
@ -1597,6 +1571,11 @@ def main(args=sys.argv):
print 'Restarting with:', e, sys.argv
os.execvp(e, sys.argv)
else:
if iswindows:
try:
main.system_tray_icon.hide()
except:
pass
return ret
return 0

View File

@ -8,8 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, math, re
from PyQt4.Qt import QWidget, QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer, \
QPainter, QPalette, QBrush, QFontDatabase, QDialog, \
QByteArray, QColor, QWheelEvent, QPoint, QImage, QRegion, \
QFont, QPrinter, QPrintPreviewDialog, QPrintDialog
QByteArray, QColor, QWheelEvent, QPoint, QImage, QRegion, QFont
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.utils.config import Config, StringConfig
@ -310,61 +309,6 @@ class DocumentView(QWebView):
def goto_bookmark(self, bm):
self.document.goto_bookmark(bm)
def all_content(self):
book_content = ''
if self.manager is not None:
for path in self.manager.iterator.spine:
html = open(path, 'rb').read().decode(path.encoding)
book_content += EntityDeclarationProcessor(html).processed_html
base_url = QUrl.fromLocalFile(self.manager.iterator.spine[0])
else:
book_content = self.page().mainFrame().toHtml()
base_url = QUrl.fromLocalFile(self.path())
return (book_content, base_url)
def print_preview(self):
print_view = QWebView()
book_content, base_url = self.all_content()
print_view.setHtml(book_content, base_url)
print_view.setTextSizeMultiplier(self.textSizeMultiplier())
def finished(ok):
printer = QPrinter(QPrinter.HighResolution)
printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
previewDialog = QPrintPreviewDialog(printer, self)
self.connect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), print_view.print_)
previewDialog.exec_()
self.disconnect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), print_view.print_)
self.disconnect(print_view, SIGNAL('loadFinished(bool)'), finished)
self.connect(print_view, SIGNAL('loadFinished(bool)'), finished)
def print_book(self):
print_view = QWebView()
book_content, base_url = self.all_content()
print_view.setHtml(book_content, base_url)
print_view.setTextSizeMultiplier(self.textSizeMultiplier())
def finished(ok):
printer = QPrinter(QPrinter.HighResolution)
printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
printDialog = QPrintDialog(printer, self)
printDialog.setWindowTitle(_("Print eBook"))
printDialog.exec_()
if printDialog.result() == QDialog.Accepted:
print_view.print_(printer)
self.disconnect(print_view, SIGNAL('loadFinished(bool)'), finished)
self.connect(print_view, SIGNAL('loadFinished(bool)'), finished)
def config(self, parent=None):
self.document.do_config(parent)
@ -612,4 +556,4 @@ class DocumentView(QWebView):
self.manager.scrolled(self.scroll_fraction)
return ret

View File

@ -248,8 +248,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.connect(self.action_back, SIGNAL('triggered(bool)'), self.back)
self.connect(self.action_bookmark, SIGNAL('triggered(bool)'), self.bookmark)
self.connect(self.action_forward, SIGNAL('triggered(bool)'), self.forward)
self.connect(self.action_print_preview, SIGNAL('triggered()'), self.view.print_preview)
self.connect(self.action_print, SIGNAL('triggered()'), self.view.print_book)
self.connect(self.action_preferences, SIGNAL('triggered(bool)'), lambda x: self.view.config(self))
self.connect(self.pos, SIGNAL('valueChanged(double)'), self.goto_page)
self.connect(self.vertical_scrollbar, SIGNAL('valueChanged(int)'),

View File

@ -27,8 +27,8 @@
<widget class="QWidget" name="layoutWidget" >
<layout class="QGridLayout" name="gridLayout" >
<item row="0" column="0" >
<widget class="QWebView" name="view" >
<property name="url" >
<widget class="QWebView" native="1" name="view" >
<property name="url" stdset="0" >
<url>
<string>about:blank</string>
</url>
@ -87,9 +87,6 @@
<addaction name="action_bookmark" />
<addaction name="action_reference_mode" />
<addaction name="separator" />
<addaction name="action_print_preview" />
<addaction name="action_print" />
<addaction name="separator" />
<addaction name="action_preferences" />
<addaction name="action_full_screen" />
</widget>
@ -237,24 +234,6 @@
<string>Toggle full screen</string>
</property>
</action>
<action name="action_print" >
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/document-print.svg</normaloff>:/images/document-print.svg</iconset>
</property>
<property name="text" >
<string>Print</string>
</property>
</action>
<action name="action_print_preview" >
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/document-print-preview.svg</normaloff>:/images/document-print-preview.svg</iconset>
</property>
<property name="text" >
<string>Print Preview</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -4,13 +4,10 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Backend that implements storage of ebooks in an sqlite database.
'''
import sqlite3 as sqlite
import datetime, re, os, cPickle, sre_constants
import datetime, re, cPickle, sre_constants
from zlib import compress, decompress
from calibre import sanitize_file_name
from calibre.ebooks.metadata.meta import set_metadata, metadata_from_formats
from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.web.feeds.recipes import migrate_automatic_profile_to_automatic_recipe
class Concatenate(object):
@ -1391,117 +1388,12 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
def import_book(self, mi, formats):
series_index = 1 if mi.series_index is None else mi.series_index
if not mi.authors:
mi.authors = [_('Unknown')]
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)',
(mi.title, None, series_index, aus))
id = obj.lastrowid
self.conn.commit()
self.set_metadata(id, mi)
for path in formats:
ext = os.path.splitext(path)[1][1:].lower()
stream = open(path, 'rb')
stream.seek(0, 2)
usize = stream.tell()
stream.seek(0)
data = sqlite.Binary(compress(stream.read()))
try:
self.conn.execute('INSERT INTO data(book, format, uncompressed_size, data) VALUES (?,?,?,?)',
(id, ext, usize, data))
except sqlite.IntegrityError:
self.conn.execute('UPDATE data SET uncompressed_size=?, data=? WHERE book=? AND format=?',
(usize, data, id, ext))
self.conn.commit()
def import_book_directory_multiple(self, dirpath, callback=None):
dirpath = os.path.abspath(dirpath)
duplicates = []
books = {}
for path in os.listdir(dirpath):
if callable(callback):
callback('.')
path = os.path.abspath(os.path.join(dirpath, path))
if os.path.isdir(path) or not os.access(path, os.R_OK):
continue
ext = os.path.splitext(path)[1]
if not ext:
continue
ext = ext[1:].lower()
if ext not in BOOK_EXTENSIONS:
continue
key = os.path.splitext(path)[0]
if not books.has_key(key):
books[key] = []
books[key].append(path)
for formats in books.values():
mi = metadata_from_formats(formats)
if mi.title is None:
continue
if self.has_book(mi):
duplicates.append((mi, formats))
continue
self.import_book(mi, formats)
if callable(callback):
if callback(mi.title):
break
return duplicates
def import_book_directory(self, dirpath, callback=None):
dirpath = os.path.abspath(dirpath)
formats = []
for path in os.listdir(dirpath):
if callable(callback):
callback('.')
path = os.path.abspath(os.path.join(dirpath, path))
if os.path.isdir(path) or not os.access(path, os.R_OK):
continue
ext = os.path.splitext(path)[1]
if not ext:
continue
ext = ext[1:].lower()
if ext not in BOOK_EXTENSIONS:
continue
formats.append(path)
if not formats:
return
mi = metadata_from_formats(formats)
if mi.title is None:
return
if self.has_book(mi):
return [(mi, formats)]
self.import_book(mi, formats)
if callable(callback):
callback(mi.title)
def has_id(self, id):
return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None
def recursive_import(self, root, single_book_per_directory=True, callback=None):
root = os.path.abspath(root)
duplicates = []
for dirpath in os.walk(root):
res = self.import_book_directory(dirpath[0], callback=callback) if \
single_book_per_directory else \
self.import_book_directory_multiple(dirpath[0], callback=callback)
if res is not None:
duplicates.extend(res)
if callable(callback):
if callback(''):
break
return duplicates
class SearchToken(object):

View File

@ -12,20 +12,24 @@ from itertools import repeat
from datetime import datetime
from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock
from PyQt4.QtGui import QApplication, QPixmap, QImage
from PyQt4.QtGui import QApplication, QImage
__app = None
from calibre.library import title_sort
from calibre.library.database import LibraryDatabase
from calibre.library.sqlite import connect, IntegrityError
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.ebooks.metadata import string_to_authors, authors_to_string, MetaInformation
from calibre.ebooks.metadata.meta import get_metadata, set_metadata
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
MetaInformation, authors_to_sort_string
from calibre.ebooks.metadata.meta import get_metadata, set_metadata, \
metadata_from_formats
from calibre.ebooks.metadata.opf2 import OPFCreator
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
from calibre.ptempfile import PersistentTemporaryFile
from calibre.customize.ui import run_plugins_on_import
from calibre import sanitize_file_name
from calibre.ebooks import BOOK_EXTENSIONS
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
@ -377,8 +381,10 @@ class LibraryDatabase2(LibraryDatabase):
return row[loc]
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
'publisher', 'rating', 'series', 'series_index', 'tags', 'title'):
setattr(self, prop, functools.partial(get_property, loc=FIELD_MAP['comments' if prop == 'comment' else prop]))
'publisher', 'rating', 'series', 'series_index', 'tags',
'title', 'timestamp'):
setattr(self, prop, functools.partial(get_property,
loc=FIELD_MAP['comments' if prop == 'comment' else prop]))
def initialize_database(self):
from calibre.resources import metadata_sqlite
@ -587,6 +593,7 @@ class LibraryDatabase2(LibraryDatabase):
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
mi.comments = self.comments(idx, index_is_id=index_is_id)
mi.publisher = self.publisher(idx, index_is_id=index_is_id)
mi.timestamp = self.timestamp(idx, index_is_id=index_is_id)
tags = self.tags(idx, index_is_id=index_is_id)
if tags:
mi.tags = [i.strip() for i in tags.split(',')]
@ -627,7 +634,7 @@ class LibraryDatabase2(LibraryDatabase):
if not QCoreApplication.instance():
global __app
__app = QApplication([])
p = QPixmap()
p = QImage()
if callable(getattr(data, 'read', None)):
data = data.read()
p.loadFromData(data)
@ -881,6 +888,8 @@ class LibraryDatabase2(LibraryDatabase):
self.set_isbn(id, mi.isbn, notify=False)
if mi.series_index and mi.series_index > 0:
self.set_series_index(id, mi.series_index, notify=False)
if getattr(mi, 'timestamp', None) is not None:
self.set_timestamp(id, mi.timestamp, notify=False)
self.set_path(id, True)
self.notify('metadata', [id])
@ -1142,7 +1151,7 @@ class LibraryDatabase2(LibraryDatabase):
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True):
'''
Add a book to the database. The result cache is not updated.
@param paths: List of paths to book files or file-like objects
:param:`paths` List of paths to book files or file-like objects
'''
formats, metadata, uris = iter(formats), iter(metadata), iter(uris)
duplicates = []
@ -1180,31 +1189,40 @@ class LibraryDatabase2(LibraryDatabase):
self.conn.commit()
self.data.refresh_ids(self.conn, ids) # Needed to update format list and size
if duplicates:
paths = tuple(duplicate[0] for duplicate in duplicates)
formats = tuple(duplicate[1] for duplicate in duplicates)
metadata = tuple(duplicate[2] for duplicate in duplicates)
uris = tuple(duplicate[3] for duplicate in duplicates)
paths = list(duplicate[0] for duplicate in duplicates)
formats = list(duplicate[1] for duplicate in duplicates)
metadata = list(duplicate[2] for duplicate in duplicates)
uris = list(duplicate[3] for duplicate in duplicates)
return (paths, formats, metadata, uris), len(ids)
return None, len(ids)
def import_book(self, mi, formats):
def import_book(self, mi, formats, notify=True):
series_index = 1 if mi.series_index is None else mi.series_index
if not mi.title:
mi.title = _('Unknown')
if not mi.authors:
mi.authors = ['Unknown']
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
mi.authors = [_('Unknown')]
aus = mi.author_sort if mi.author_sort else authors_to_sort_string(mi.authors)
if isinstance(aus, str):
aus = aus.decode(preferred_encoding, 'replace')
title = mi.title if isinstance(mi.title, unicode) else \
mi.title.decode(preferred_encoding, 'replace')
obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)',
(mi.title, None, series_index, aus))
(title, None, series_index, aus))
id = obj.lastrowid
self.data.books_added([id], self.conn)
self.set_path(id, True)
self.set_metadata(id, mi)
for path in formats:
ext = os.path.splitext(path)[1][1:].lower()
if ext == 'opf':
continue
stream = open(path, 'rb')
self.add_format(id, ext, stream, index_is_id=True)
self.conn.commit()
self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size
self.notify('add', [id])
if notify:
self.notify('add', [id])
def move_library_to(self, newloc, progress=None):
header = _(u'<p>Copying books to %s<br><center>')%newloc
@ -1388,6 +1406,11 @@ books_series_link feeds
f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb')
if not mi.authors:
mi.authors = [_('Unknown')]
cdata = self.cover(int(id), index_is_id=True)
if cdata is not None:
cname = sanitize_file_name(name)+'.jpg'
open(os.path.join(base, cname), 'wb').write(cdata)
mi.cover = cname
opf = OPFCreator(base, mi)
opf.render(f)
f.close()
@ -1451,6 +1474,88 @@ books_series_link feeds
if not callback(count, title):
break
return failures
def find_books_in_directory(self, dirpath, single_book_per_directory):
dirpath = os.path.abspath(dirpath)
if single_book_per_directory:
formats = []
for path in os.listdir(dirpath):
path = os.path.abspath(os.path.join(dirpath, path))
if os.path.isdir(path) or not os.access(path, os.R_OK):
continue
ext = os.path.splitext(path)[1]
if not ext:
continue
ext = ext[1:].lower()
if ext not in BOOK_EXTENSIONS and ext != 'opf':
continue
formats.append(path)
yield formats
else:
books = {}
for path in os.listdir(dirpath):
path = os.path.abspath(os.path.join(dirpath, path))
if os.path.isdir(path) or not os.access(path, os.R_OK):
continue
ext = os.path.splitext(path)[1]
if not ext:
continue
ext = ext[1:].lower()
if ext not in BOOK_EXTENSIONS:
continue
key = os.path.splitext(path)[0]
if not books.has_key(key):
books[key] = []
books[key].append(path)
for formats in books.values():
yield formats
def import_book_directory_multiple(self, dirpath, callback=None):
duplicates = []
for formats in self.find_books_in_directory(dirpath, False):
mi = metadata_from_formats(formats)
if mi.title is None:
continue
if self.has_book(mi):
duplicates.append((mi, formats))
continue
self.import_book(mi, formats)
if callable(callback):
if callback(mi.title):
break
return duplicates
def import_book_directory(self, dirpath, callback=None):
dirpath = os.path.abspath(dirpath)
formats = self.find_books_in_directory(dirpath, True)
if not formats:
return
mi = metadata_from_formats(formats)
if mi.title is None:
return
if self.has_book(mi):
return [(mi, formats)]
self.import_book(mi, formats)
if callable(callback):
callback(mi.title)
def recursive_import(self, root, single_book_per_directory=True, callback=None):
root = os.path.abspath(root)
duplicates = []
for dirpath in os.walk(root):
res = self.import_book_directory(dirpath[0], callback=callback) if \
single_book_per_directory else \
self.import_book_directory_multiple(dirpath[0], callback=callback)
if res is not None:
duplicates.extend(res)
if callable(callback):
if callback(''):
break
return duplicates

View File

@ -12,11 +12,50 @@ from sqlite3 import IntegrityError
from threading import Thread
from Queue import Queue
from threading import RLock
from datetime import tzinfo, datetime, timedelta
from calibre.library import title_sort
global_lock = RLock()
def convert_timestamp(val):
datepart, timepart = val.split(' ')
tz, mult = None, 1
x = timepart.split('+')
if len(x) > 1:
timepart, tz = x
else:
x = timepart.split('-')
if len(x) > 1:
timepart, tz = x
mult = -1
year, month, day = map(int, datepart.split("-"))
timepart_full = timepart.split(".")
hours, minutes, seconds = map(int, timepart_full[0].split(":"))
if len(timepart_full) == 2:
microseconds = int(timepart_full[1])
else:
microseconds = 0
if tz is not None:
h, m = map(int, tz.split(':'))
delta = timedelta(minutes=mult*(60*h + m))
tz = type('CustomTZ', (tzinfo,), {'utcoffset':lambda self, dt:delta,
'dst':lambda self,dt:timedelta(0)})()
val = datetime(year, month, day, hours, minutes, seconds, microseconds,
tzinfo=tz)
if tz is not None:
val = datetime(*(val.utctimetuple()[:6]))
return val
def adapt_datetime(dt):
dt = datetime(*(dt.utctimetuple()[:6]))
return dt.isoformat(' ')
sqlite.register_adapter(datetime, adapt_datetime)
sqlite.register_converter('timestamp', convert_timestamp)
class Concatenate(object):
'''String concatenation aggregator for sqlite'''
def __init__(self, sep=','):

View File

@ -0,0 +1,12 @@
{% extends "!layout.html" %}
{% block sidebarlogo %}
{{ super() }}
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="3028915" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="Donate to support calibre development" style="border:0pt" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1"/>
</form>
<hr/>
{% endblock %}

View File

@ -35,6 +35,7 @@ class Distribution(object):
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
('lxml', '2.0.5', 'lxml', 'python-lxml', 'python-lxml'),
('python-dateutil', '1.4.1', 'python-dateutil', 'python-dateutil', 'python-dateutil'),
('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-BeautifulSoup'),
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
]

View File

@ -28,6 +28,7 @@ recipe_modules = ['recipe_' + r for r in (
'la_tercera', 'el_mercurio_chile', 'la_cuarta', 'lanacion_chile', 'la_segunda',
'jb_online', 'estadao', 'o_globo', 'vijesti', 'elmundo', 'the_oz',
'honoluluadvertiser', 'starbulletin', 'exiled', 'indy_star', 'dna',
'pobjeda',
)]
import re, imp, inspect, time, os

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
pobjeda.co.me
'''
import re
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class Pobjeda(BasicNewsRecipe):
title = 'Pobjeda Online'
__author__ = 'Darko Miletic'
description = 'News from Montenegro'
publisher = 'Pobjeda a.d.'
category = 'news, politics, Montenegro'
language = _('Serbian')
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
remove_javascript = True
encoding = 'utf8'
remove_javascript = True
use_embedded_content = False
INDEX = u'http://www.pobjeda.co.me'
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{text-align: justify; font-family: serif1, serif} .article_description{font-family: serif1, serif}'
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
keep_only_tags = [dict(name='div', attrs={'class':'vijest'})]
remove_tags = [dict(name=['object','link'])]
feeds = [
(u'Politika' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=1' )
,(u'Ekonomija' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=2' )
,(u'Drustvo' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=3' )
,(u'Crna Hronika' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=4' )
,(u'Kultura' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=5' )
,(u'Hronika Podgorice' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=7' )
,(u'Feljton' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=8' )
,(u'Crna Gora' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=9' )
,(u'Svijet' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=202')
,(u'Ekonomija i Biznis', u'http://www.pobjeda.co.me/dodatak.php?rubrika=11' )
,(u'Djeciji Svijet' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=12' )
,(u'Kultura i Drustvo' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=13' )
,(u'Agora' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=133')
,(u'Ekologija' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=252')
]
def preprocess_html(self, soup):
soup.html['xml:lang'] = 'sr-Latn-ME'
soup.html['lang'] = 'sr-Latn-ME'
mtag = '<meta http-equiv="Content-Language" content="sr-Latn-ME"/>'
soup.head.insert(0,mtag)
for item in soup.findAll(style=True):
del item['style']
return soup
def get_cover_url(self):
cover_url = None
soup = self.index_to_soup(self.INDEX)
cover_item = soup.find('img',attrs={'alt':'Naslovna strana'})
if cover_item:
cover_url = self.INDEX + cover_item.parent['href']
return cover_url
def parse_index(self):
totalfeeds = []
lfeeds = self.get_feeds()
for feedobj in lfeeds:
feedtitle, feedurl = feedobj
self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
articles = []
soup = self.index_to_soup(feedurl)
for item in soup.findAll('div', attrs={'class':'vijest'}):
description = self.tag_to_string(item.h2)
atag = item.h1.find('a')
if atag:
url = self.INDEX + '/' + atag['href']
title = self.tag_to_string(atag)
date = strftime(self.timefmt)
articles.append({
'title' :title
,'date' :date
,'url' :url
,'description':description
})
totalfeeds.append((feedtitle, articles))
return totalfeeds

View File

@ -16,6 +16,7 @@ class Politika(BasicNewsRecipe):
publisher = 'Politika novine i Magazini d.o.o'
category = 'news, politics, Serbia'
oldest_article = 2
language = _('Serbian')
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False