Pull from trunk

This commit is contained in:
Kovid Goyal 2009-03-28 18:13:09 -07:00
commit b9d9df5f20
21 changed files with 951 additions and 573 deletions

View File

@ -24,7 +24,7 @@ class File(object):
path = path[:-1]
self.path = path
self.name = os.path.basename(path)
class PRS505(Device):
VENDOR_ID = 0x054c #: SONY Vendor Id
@ -33,17 +33,17 @@ class PRS505(Device):
PRODUCT_NAME = 'PRS-505'
VENDOR_NAME = 'SONY'
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml'
MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
OSX_NAME = 'Sony PRS-505'
CARD_PATH_PREFIX = __appname__
FDI_TEMPLATE = \
'''
<device>
@ -75,11 +75,11 @@ class PRS505(Device):
</match>
</device>
'''.replace('%(app)s', __appname__)
def __init__(self, log_packets=False):
self._main_prefix = self._card_prefix = None
@classmethod
def get_fdi(cls):
return cls.FDI_TEMPLATE%dict(
@ -90,7 +90,7 @@ class PRS505(Device):
main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
)
@classmethod
def is_device(cls, device_id):
device_id = device_id.upper()
@ -104,7 +104,7 @@ class PRS505(Device):
'PID_'+pid in device_id:
return True
return False
@classmethod
def get_osx_mountpoints(cls, raw=None):
if raw is None:
@ -112,7 +112,7 @@ class PRS505(Device):
if not os.access(ioreg, os.X_OK):
ioreg = 'ioreg'
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
stdout=subprocess.PIPE).stdout.read()
stdout=subprocess.PIPE).communicate()[0]
lines = raw.splitlines()
names = {}
for i, line in enumerate(lines):
@ -130,9 +130,9 @@ class PRS505(Device):
break
return names
def open_osx(self):
mount = subprocess.Popen('mount', shell=True,
mount = subprocess.Popen('mount', shell=True,
stdout=subprocess.PIPE).stdout.read()
names = self.get_osx_mountpoints()
dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+'
@ -144,12 +144,12 @@ class PRS505(Device):
if card_pat is not None:
card_pat = dev_pat%card_pat
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
def open_windows(self):
time.sleep(6)
drives = []
wmi = __import__('wmi', globals(), locals(), [], -1)
wmi = __import__('wmi', globals(), locals(), [], -1)
c = wmi.WMI()
for drive in c.Win32_DiskDrive():
if self.__class__.is_device(str(drive.PNPDeviceID)):
@ -162,22 +162,22 @@ class PRS505(Device):
drives.append((drive.Index, prefix))
except IndexError:
continue
if not drives:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
drives.sort(cmp=lambda a, b: cmp(a[0], b[0]))
self._main_prefix = drives[0][1]
if len(drives) > 1:
self._card_prefix = drives[1][1]
def open_linux(self):
import dbus
bus = dbus.SystemBus()
bus = dbus.SystemBus()
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
def conditional_mount(dev, main_mem=True):
mmo = bus.get_object("org.freedesktop.Hal", dev)
label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
@ -186,11 +186,11 @@ class PRS505(Device):
fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
if is_mounted:
return str(mount_point)
mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
dbus_interface='org.freedesktop.Hal.Device.Volume')
return os.path.normpath('/media/'+label)+'/'
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
if not mm:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
@ -201,21 +201,21 @@ class PRS505(Device):
break
except dbus.exceptions.DBusException:
continue
if not self._main_prefix:
raise DeviceError('Could not open device for reading. Try a reboot.')
self._card_prefix = None
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
keys = []
for card in cards:
keys.append(int('UC_SD' in bus.get_object("org.freedesktop.Hal", card).GetPropertyString('info.parent', dbus_interface='org.freedesktop.Hal.Device')))
cards = zip(cards, keys)
cards.sort(cmp=lambda x, y: cmp(x[1], y[1]))
cards = [i[0] for i in cards]
for dev in cards:
try:
self._card_prefix = conditional_mount(dev, False)+os.sep
@ -224,8 +224,8 @@ class PRS505(Device):
import traceback
print traceback
continue
def open(self):
time.sleep(5)
self._main_prefix = self._card_prefix = None
@ -262,16 +262,16 @@ class PRS505(Device):
self._card_prefix = None
import traceback
traceback.print_exc()
def set_progress_reporter(self, pr):
self.report_progress = pr
def get_device_information(self, end_session=True):
return (self.__class__.__name__, '', '', '')
def card_prefix(self, end_session=True):
return self._card_prefix
@classmethod
def _windows_space(cls, prefix):
if prefix is None:
@ -288,7 +288,7 @@ class PRS505(Device):
else: raise
mult = sectors_per_cluster * bytes_per_sector
return total_clusters * mult, free_clusters * mult
def total_space(self, end_session=True):
msz = csz = 0
if not iswindows:
@ -301,9 +301,9 @@ class PRS505(Device):
else:
msz = self._windows_space(self._main_prefix)[0]
csz = self._windows_space(self._card_prefix)[0]
return (msz, 0, csz)
def free_space(self, end_session=True):
msz = csz = 0
if not iswindows:
@ -316,9 +316,9 @@ class PRS505(Device):
else:
msz = self._windows_space(self._main_prefix)[1]
csz = self._windows_space(self._card_prefix)[1]
return (msz, 0, csz)
def books(self, oncard=False, end_session=True):
if oncard and self._card_prefix is None:
return []
@ -331,7 +331,7 @@ class PRS505(Device):
if os.path.exists(path):
os.unlink(path)
return bl
def munge_path(self, path):
if path.startswith('/') and not (path.startswith(self._main_prefix) or \
(self._card_prefix and path.startswith(self._card_prefix))):
@ -339,12 +339,12 @@ class PRS505(Device):
elif path.startswith('card:'):
path = path.replace('card:', self._card_prefix[:-1])
return path
def mkdir(self, path, end_session=True):
""" Make directory """
path = self.munge_path(path)
os.mkdir(path)
def list(self, path, recurse=False, end_session=True, munge=True):
if munge:
path = self.munge_path(path)
@ -356,12 +356,12 @@ class PRS505(Device):
if recurse and _file.is_dir:
dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False)
return dirs
def get_file(self, path, outfile, end_session=True):
path = self.munge_path(path)
src = open(path, 'rb')
shutil.copyfileobj(src, outfile, 10*1024*1024)
def put_file(self, infile, path, replace_file=False, end_session=True):
path = self.munge_path(path)
if os.path.isdir(path):
@ -372,25 +372,25 @@ class PRS505(Device):
shutil.copyfileobj(infile, dest, 10*1024*1024)
dest.flush()
dest.close()
def rm(self, path, end_session=True):
path = self.munge_path(path)
os.unlink(path)
def touch(self, path, end_session=True):
path = self.munge_path(path)
if not os.path.exists(path):
open(path, 'w').close()
if not os.path.isdir(path):
os.utime(path, None)
def upload_books(self, files, names, on_card=False, end_session=True,
def upload_books(self, files, names, on_card=False, end_session=True,
metadata=None):
if on_card and not self._card_prefix:
raise ValueError(_('The reader has no storage card connected.'))
path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \
else os.path.join(self._main_prefix, 'database', 'media', 'books')
def get_size(obj):
if hasattr(obj, 'seek'):
obj.seek(0, 2)
@ -398,27 +398,27 @@ class PRS505(Device):
obj.seek(0)
return size
return os.path.getsize(obj)
sizes = map(get_size, files)
size = sum(sizes)
space = self.free_space()
mspace = space[0]
cspace = space[2]
if on_card and size > cspace - 1024*1024:
if on_card and size > cspace - 1024*1024:
raise FreeSpaceError("There is insufficient free space "+\
"on the storage card")
if not on_card and size > mspace - 2*1024*1024:
if not on_card and size > mspace - 2*1024*1024:
raise FreeSpaceError("There is insufficient free space " +\
"in main memory")
paths, ctimes = [], []
names = iter(names)
for infile in files:
close = False
if not hasattr(infile, 'read'):
infile, close = open(infile, 'rb'), True
infile.seek(0)
infile.seek(0)
name = names.next()
paths.append(os.path.join(path, name))
if not os.path.exists(os.path.dirname(paths[-1])):
@ -428,7 +428,7 @@ class PRS505(Device):
infile.close()
ctimes.append(os.path.getctime(paths[-1]))
return zip(paths, sizes, ctimes, cycle([on_card]))
@classmethod
def add_books_to_metadata(cls, locations, metadata, booklists):
metadata = iter(metadata)
@ -441,12 +441,12 @@ class PRS505(Device):
name = name.replace('//', '/')
booklists[on_card].add_book(info, name, *location[1:-1])
fix_ids(*booklists)
def delete_books(self, paths, end_session=True):
for path in paths:
if os.path.exists(path):
os.unlink(path)
@classmethod
def remove_books_from_metadata(cls, paths, booklists):
for path in paths:
@ -454,7 +454,7 @@ class PRS505(Device):
if hasattr(bl, 'remove_book'):
bl.remove_book(path)
fix_ids(*booklists)
def sync_booklists(self, booklists, end_session=True):
fix_ids(*booklists)
if not os.path.exists(self._main_prefix):
@ -468,9 +468,9 @@ class PRS505(Device):
f = open(self._card_prefix + self.__class__.CACHE_XML, 'wb')
booklists[1].write(f)
f.close()
def main(args=sys.argv):
return 0

View File

@ -190,7 +190,7 @@ class Device(_Device):
self._main_prefix = drives.get('main')
self._card_prefix = drives.get('card')
if not self._main_prefix:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__)
@ -200,7 +200,7 @@ class Device(_Device):
if not os.access(ioreg, os.X_OK):
ioreg = 'ioreg'
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
stdout=subprocess.PIPE).stdout.read()
stdout=subprocess.PIPE).communicate()[0]
lines = raw.splitlines()
names = {}

View File

@ -9,7 +9,7 @@ lxml based OPF parser.
import sys, unittest, functools, os, mimetypes, uuid, glob, cStringIO
from urllib import unquote
from urlparse import urlparse, urldefrag
from urlparse import urlparse
from lxml import etree
from dateutil import parser
@ -258,6 +258,11 @@ class Manifest(ResourceCollection):
if i.id == id:
return i.path
def type_for_id(self, id):
for i in self:
if i.id == id:
return i.mime_type
class Spine(ResourceCollection):
class Item(Resource):
@ -487,7 +492,10 @@ class OPF(object):
if toc is None: return
self.toc = TOC(base_path=self.base_dir)
if toc.lower() in ('ncx', 'ncxtoc'):
is_ncx = getattr(self, 'manifest', None) is not None and \
self.manifest.type_for_id(toc) is not None and \
'dtbncx' in self.manifest.type_for_id(toc)
if is_ncx or toc.lower() in ('ncx', 'ncxtoc'):
path = self.manifest.path_for_id(toc)
if path:
self.toc.read_ncx_toc(path)

View File

@ -79,7 +79,7 @@ class FormatState(object):
class MobiMLizer(object):
def __init__(self, ignore_tables=False):
self.ignore_tables = ignore_tables
@classmethod
def config(cls, cfg):
group = cfg.add_group('mobiml', _('Mobipocket markup options.'))
@ -92,7 +92,7 @@ class MobiMLizer(object):
@classmethod
def generate(cls, opts):
return cls(ignore_tables=opts.ignore_tables)
def __call__(self, oeb, context):
oeb.logger.info('Converting XHTML to Mobipocket markup...')
self.oeb = oeb
@ -111,10 +111,10 @@ class MobiMLizer(object):
del oeb.guide['cover']
item = oeb.manifest.hrefs[href]
if item.spine_position is not None:
oeb.spine.remove(item)
oeb.spine.remove(item)
if item.media_type in OEB_DOCS:
self.oeb.manifest.remove(item)
def mobimlize_spine(self):
for item in self.oeb.spine:
stylizer = Stylizer(item.data, item.href, self.oeb, self.profile)
@ -147,7 +147,7 @@ class MobiMLizer(object):
if line:
result.append(line)
return result
def mobimlize_content(self, tag, text, bstate, istates):
if text or tag != 'br':
bstate.content = True
@ -252,7 +252,7 @@ class MobiMLizer(object):
last.tail = (last.tail or '') + item
else:
inline.append(item)
def mobimlize_elem(self, elem, stylizer, bstate, istates):
if not isinstance(elem.tag, basestring) \
or namespace(elem.tag) != XHTML_NS:

View File

@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Read data from .mobi files
'''
import struct, os, cStringIO, re, functools
import struct, os, cStringIO, re, functools, datetime
try:
from PIL import Image as PILImage
@ -53,6 +53,12 @@ class EXTHHeader(object):
self.cover_offset = co
elif id == 202:
self.thumbnail_offset, = struct.unpack('>L', content)
elif id == 501:
# cdetype
pass
elif id == 502:
# last update time
pass
elif id == 503 and (not title or title == _('Unknown')):
title = content
#else:
@ -75,8 +81,14 @@ class EXTHHeader(object):
if not self.mi.tags:
self.mi.tags = []
self.mi.tags.append(content.decode(codec, 'ignore'))
elif id == 106:
try:
self.mi.publish_date = datetime.datetime.strptime(
content, '%Y-%m-%d',).date()
except:
pass
#else:
# print 'unhandled metadata record', id, repr(content), codec
# print 'unhandled metadata record', id, repr(content)
class BookHeader(object):
@ -327,8 +339,8 @@ class MobiReader(object):
mobi_version = self.book_header.mobi_version
for i, tag in enumerate(root.iter(etree.Element)):
if tag.tag in ('country-region', 'place', 'placetype', 'placename',
'state', 'city', 'street', 'address'):
tag.tag = 'span'
'state', 'city', 'street', 'address', 'content'):
tag.tag = 'div' if tag.tag == 'content' else 'span'
for key in tag.attrib.keys():
tag.attrib.pop(key)
continue

View File

@ -211,12 +211,14 @@ class Serializer(object):
def serialize_item(self, item):
buffer = self.buffer
#buffer.write('<mbp:section>')
if not item.linear:
self.breaks.append(buffer.tell() - 1)
self.id_offsets[item.href] = buffer.tell()
for elem in item.data.find(XHTML('body')):
self.serialize_elem(elem, item)
buffer.write('<mbp:pagebreak/>')
#buffer.write('</mbp:section>')
buffer.write('</mbp:pagebreak>')
def serialize_elem(self, elem, item, nsrmap=NSRMAP):
buffer = self.buffer

View File

@ -1,6 +1,6 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, re, time, textwrap
import os, re, time, textwrap, sys, cStringIO
from binascii import hexlify, unhexlify
from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \
@ -11,6 +11,7 @@ from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \
from calibre.constants import islinux, iswindows
from calibre.gui2.dialogs.config_ui import Ui_Dialog
from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \
ALL_COLUMNS, NONE, info_dialog, choose_files
from calibre.utils.config import prefs
@ -134,6 +135,33 @@ class CategoryModel(QStringListModel):
return self.icons[index.row()]
return QStringListModel.data(self, index, role)
class TestEmail(QDialog, TE_Dialog):
def __init__(self, accounts, parent):
QDialog.__init__(self, parent)
TE_Dialog.__init__(self)
self.setupUi(self)
opts = smtp_prefs().parse()
self.test_func = parent.test_email_settings
self.connect(self.test_button, SIGNAL('clicked(bool)'), self.test)
self.from_.setText(unicode(self.from_.text())%opts.from_)
if accounts:
self.to.setText(list(accounts.keys())[0])
if opts.relay_host:
self.label.setText(_('Using: %s:%s@%s:%s and %s encryption')%
(opts.relay_username, unhexlify(opts.relay_password),
opts.relay_host, opts.relay_port, opts.encryption))
def test(self):
self.log.setPlainText(_('Sending...'))
self.test_button.setEnabled(False)
try:
tb = self.test_func(unicode(self.to.text()))
if not tb:
tb = _('Mail successfully sent')
self.log.setPlainText(tb)
finally:
self.test_button.setEnabled(True)
class EmailAccounts(QAbstractTableModel):
@ -395,6 +423,8 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.connect(self.email_make_default, SIGNAL('clicked(bool)'),
lambda c: self._email_accounts.make_default(self.email_view.currentIndex()))
self.email_view.resizeColumnsToContents()
self.connect(self.test_email_button, SIGNAL('clicked(bool)'),
self.test_email)
def add_email_account(self, checked):
index = self._email_accounts.add()
@ -438,6 +468,33 @@ class ConfigDialog(QDialog, Ui_Dialog):
conf.set('encryption', 'TLS' if self.relay_tls.isChecked() else 'SSL')
return True
def test_email(self, *args):
if self.set_email_settings():
TestEmail(self._email_accounts.accounts, self).exec_()
def test_email_settings(self, to):
opts = smtp_prefs().parse()
from calibre.utils.smtp import sendmail, create_mail
buf = cStringIO.StringIO()
oout, oerr = sys.stdout, sys.stderr
sys.stdout = sys.stderr = buf
tb = None
try:
msg = create_mail(opts.from_, to, 'Test mail from calibre',
'Test mail from calibre')
sendmail(msg, from_=opts.from_, to=[to],
verbose=3, timeout=30, relay=opts.relay_host,
username=opts.relay_username,
password=unhexlify(opts.relay_password),
encryption=opts.encryption, port=opts.relay_port)
except:
import traceback
tb = traceback.format_exc()
tb += '\n\nLog:\n' + buf.getvalue()
finally:
sys.stdout, sys.stderr = oout, oerr
return tb
def add_plugin(self):
path = unicode(self.plugin_path.text())
if path and os.access(path, os.R_OK) and path.lower().endswith('.zip'):

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,13 @@ from PyQt4.QtGui import QDialog
from calibre.gui2.dialogs.search_ui import Ui_Dialog
from calibre.gui2 import qstring_to_unicode
class SearchDialog(QDialog, Ui_Dialog):
def __init__(self, *args):
QDialog.__init__(self, *args)
self.setupUi(self)
def tokens(self, raw):
phrases = re.findall(r'\s+".*?"\s+', raw)
for f in phrases:
@ -20,7 +20,8 @@ class SearchDialog(QDialog, Ui_Dialog):
return [t.strip() for t in phrases + raw.split()]
def search_string(self):
all, any, phrase, none = map(lambda x: unicode(x.text()), (self.all, self.any, self.phrase, self.none))
all, any, phrase, none = map(lambda x: unicode(x.text()),
(self.all, self.any, self.phrase, self.none))
all, any, none = map(self.tokens, (all, any, none))
phrase = phrase.strip()
all = ' and '.join(all)
@ -32,11 +33,11 @@ class SearchDialog(QDialog, Ui_Dialog):
if all:
ans += (' and ' if ans else '') + all
if none:
ans += (' and not ' if ans else '') + none
ans += (' and not ' if ans else 'not ') + none
if any:
ans += (' or ' if ans else '') + any
return ans
def token(self):
txt = qstring_to_unicode(self.text.text()).strip()
if txt:
@ -46,4 +47,4 @@ class SearchDialog(QDialog, Ui_Dialog):
if re.search(r'\s', tok):
tok = '"%s"'%tok
return tok

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>418</height>
</rect>
</property>
<property name="windowTitle">
<string>Test email settings</string>
</property>
<property name="windowIcon">
<iconset resource="../images.qrc">
<normaloff>:/images/config.svg</normaloff>:/images/config.svg</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="from_">
<property name="text">
<string>Send test mail from %s to:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="to"/>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="test_button">
<property name="text">
<string>&amp;Test</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="log"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../images.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

View File

@ -93,7 +93,7 @@ class DateDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat('MM/dd/yyyy')
qde.setDisplayFormat(unicode(qde.displayFormat()).replace('yy', 'yyyy'))
qde.setMinimumDate(QDate(101,1,1))
qde.setCalendarPopup(True)
return qde
@ -637,7 +637,8 @@ class BooksView(TableView):
def columns_sorted(self, rating_col, timestamp_col):
for i in range(self.model().columnCount(None)):
if self.itemDelegateForColumn(i) == self.rating_delegate:
if self.itemDelegateForColumn(i) in (self.rating_delegate,
self.timestamp_delegate):
self.setItemDelegateForColumn(i, self.itemDelegate())
if rating_col > -1:
self.setItemDelegateForColumn(rating_col, self.rating_delegate)
@ -708,7 +709,7 @@ class BooksView(TableView):
def close(self):
self._model.close()
def set_editable(self, editable):
self._model.set_editable(editable)
@ -1001,10 +1002,10 @@ class DeviceBooksModel(BooksModel):
self.sort(col, self.sorted_on[1])
done = True
return done
def set_editable(self, editable):
self.editable = editable
class SearchBox(QLineEdit):

View File

@ -226,7 +226,11 @@ class ResultCache(SearchQueryParser):
Returns a list of affected rows or None if the rows are filtered.
'''
for id in ids:
self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0]
try:
self._data[id] = conn.get('SELECT * from meta WHERE id=?',
(id,))[0]
except IndexError:
return None
try:
return map(self.row, ids)
except ValueError:
@ -269,7 +273,7 @@ class ResultCache(SearchQueryParser):
ans = cmp(self._data[x][9], self._data[y][9])
if ans != 0: return ans
return cmp(self._data[x][10], self._data[y][10])
def cmp(self, loc, x, y, str=True, subsort=False):
try:
ans = cmp(self._data[x][loc].lower(), self._data[y][loc].lower()) if str else\
@ -279,7 +283,7 @@ class ResultCache(SearchQueryParser):
if subsort and ans == 0:
return cmp(self._data[x][11].lower(), self._data[y][11].lower())
return ans
def sort(self, field, ascending, subsort=False):
field = field.lower().strip()
if field in ('author', 'tag', 'comment'):
@ -733,7 +737,7 @@ class LibraryDatabase2(LibraryDatabase):
self.refresh_ids([id])
if notify:
self.notify('metadata', [id])
def delete_book(self, id, notify=True):
'''
Removes book from the result cache and the underlying database.
@ -751,7 +755,7 @@ class LibraryDatabase2(LibraryDatabase):
self.data.books_deleted([id])
if notify:
self.notify('delete', [id])
def remove_format(self, index, format, index_is_id=False, notify=True):
id = index if index_is_id else self.id(index)
path = os.path.join(self.library_path, *self.path(id, index_is_id=True).split(os.sep))
@ -925,14 +929,14 @@ class LibraryDatabase2(LibraryDatabase):
(id, aid))
except IntegrityError: # Sometimes books specify the same author twice in their metadata
pass
ss = authors_to_sort_string(authors)
ss = authors_to_sort_string(authors)
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
(ss, id))
self.conn.commit()
self.data.set(id, FIELD_MAP['authors'],
','.join([a.replace(',', '|') for a in authors]),
','.join([a.replace(',', '|') for a in authors]),
row_is_id=True)
self.data.set(id, FIELD_MAP['author_sort'], ss, row_is_id=True)
self.data.set(id, FIELD_MAP['author_sort'], ss, row_is_id=True)
self.set_path(id, True)
if notify:
self.notify('metadata', [id])
@ -1159,7 +1163,7 @@ class LibraryDatabase2(LibraryDatabase):
else:
path = path_or_stream
return run_plugins_on_import(path, format)
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True):
'''
Add a book to the database. The result cache is not updated.
@ -1219,7 +1223,7 @@ class LibraryDatabase2(LibraryDatabase):
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 (?, ?, ?, ?)',
obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)',
(title, None, series_index, aus))
id = obj.lastrowid
self.data.books_added([id], self.conn)
@ -1568,3 +1572,4 @@ books_series_link feeds
return duplicates

View File

@ -45,15 +45,17 @@ def create_mail(from_, to, subject, text=None, attachment_data=None,
return outer.as_string()
def get_mx(host):
def get_mx(host, verbose=0):
import dns.resolver
if verbose:
print 'Find mail exchanger for', host
answers = list(dns.resolver.query(host, 'MX'))
answers.sort(cmp=lambda x, y: cmp(int(x.preference), int(y.preference)))
return [str(x.exchange) for x in answers]
def sendmail_direct(from_, to, msg, timeout, localhost, verbose):
import smtplib
hosts = get_mx(to.split('@')[-1].strip())
hosts = get_mx(to.split('@')[-1].strip(), verbose)
timeout=None # Non blocking sockets sometimes don't work
s = smtplib.SMTP(timeout=timeout, local_hostname=localhost)
s.set_debuglevel(verbose)

View File

@ -37,6 +37,7 @@ recipe_modules = ['recipe_' + r for r in (
'new_york_review_of_books_no_sub', 'politico', 'adventuregamers',
'mondedurable', 'instapaper', 'dnevnik_cro', 'vecernji_list',
'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs',
'krstarica', 'krstarica_en', 'tanjug',
)]
import re, imp, inspect, time, os

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
vesti.krstarica.com
'''
import re
from calibre.web.feeds.news import BasicNewsRecipe
class Krstarica(BasicNewsRecipe):
title = 'Krstarica - Vesti'
__author__ = 'Darko Miletic'
description = 'Dnevne vesti iz Srbije i sveta'
publisher = 'Krstarica'
category = 'news, politics, Serbia'
oldest_article = 1
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
remove_javascript = True
encoding = 'utf-8'
language = _('Serbian')
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{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 + '"\noverride_css=" p {text-indent: 0em; margin-top: 0em; margin-bottom: 0.5em}"'
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
feeds = [
(u'Vesti dana' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=aktuelno&lang=0' )
,(u'Srbija' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=scg&lang=0' )
,(u'Svet' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=svet&lang=0' )
,(u'Politika' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=politika&lang=0' )
,(u'Ekonomija' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=ekonomija&lang=0' )
,(u'Drustvo' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=drustvo&lang=0' )
,(u'Kultura' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=kultura&lang=0' )
,(u'Nauka i Tehnologija', u'http://vesti.krstarica.com/index.php?rss=1&rubrika=nauka&lang=0' )
,(u'Medicina' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=medicina&lang=0' )
,(u'Sport' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=sport&lang=0' )
,(u'Zanimljivosti' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=zanimljivosti&lang=0')
]
def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>'
soup.head.insert(0,mtag)
titletag = soup.find('h4')
if titletag:
realtag = titletag.parent.parent
realtag.extract()
for item in soup.findAll(['table','center']):
item.extract()
soup.body.insert(1,realtag)
realtag.name = 'div'
for item in soup.findAll(style=True):
del item['style']
for item in soup.findAll(align=True):
del item['align']
return soup

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
vesti.krstarica.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Krstarica_en(BasicNewsRecipe):
title = 'Krstarica - news in english'
__author__ = 'Darko Miletic'
description = 'News from Serbia and world'
publisher = 'Krstarica'
category = 'news, politics, Serbia'
oldest_article = 1
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
remove_javascript = True
encoding = 'utf-8'
language = _('English')
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\noverride_css=" p {text-indent: 0em; margin-top: 0em; margin-bottom: 0.5em}"'
feeds = [
(u'Daily news', u'http://vesti.krstarica.com/index.php?rss=1&rubrika=aktuelno&lang=1' )
,(u'Serbia' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=scg&lang=1' )
,(u'Politics' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=politika&lang=1' )
,(u'Economy' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=ekonomija&lang=1' )
,(u'Culture' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=kultura&lang=1' )
,(u'Sports' , u'http://vesti.krstarica.com/index.php?rss=1&rubrika=sport&lang=1' )
]
def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>'
soup.head.insert(0,mtag)
titletag = soup.find('h4')
if titletag:
realtag = titletag.parent.parent
realtag.extract()
for item in soup.findAll(['table','center']):
item.extract()
soup.body.insert(1,realtag)
realtag.name = 'div'
for item in soup.findAll(style=True):
del item['style']
for item in soup.findAll(align=True):
del item['align']
return soup

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
tanjug.rs
'''
import re
from calibre.web.feeds.news import BasicNewsRecipe
class Tanjug(BasicNewsRecipe):
title = 'Tanjug'
__author__ = 'Darko Miletic'
description = 'Novinska agencija TANJUG - Dnevne vesti iz Srbije i sveta'
publisher = 'Tanjug'
category = 'news, politics, Serbia'
oldest_article = 1
max_articles_per_feed = 100
use_embedded_content = True
encoding = 'utf-8'
lang = 'sr-Latn-RS'
language = _('Serbian')
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{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 + '"\noverride_css=" p {text-indent: 0em; margin-top: 0em; margin-bottom: 0.5em}"'
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
feeds = [(u'Vesti', u'http://www.tanjug.rs/StaticPages/RssTanjug.aspx')]
def preprocess_html(self, soup):
soup.html['xml:lang'] = self.lang
soup.html['lang' ] = self.lang
soup.html['dir' ] = "ltr"
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>'
soup.head.insert(0,mtag)
return soup

View File

@ -128,7 +128,7 @@ def get_extra_content(site, sfeeds_ids, ctx):
def get_posts_tags(object_list, sfeeds_obj, user_id, tag_name):
""" Adds a qtags property in every post object in a page.
Use "qtags" instead of "tags" in templates to avoid innecesary DB hits.
Use "qtags" instead of "tags" in templates to avoid unnecessary DB hits.
"""
tagd = {}
user_obj = None