diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py
index f4256c4c14..6e21c60d1b 100644
--- a/src/calibre/devices/prs505/driver.py
+++ b/src/calibre/devices/prs505/driver.py
@@ -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 = \
'''
@@ -75,11 +75,11 @@ class PRS505(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
diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py
index 5943e2e13f..eb86cb7edd 100644
--- a/src/calibre/devices/usbms/device.py
+++ b/src/calibre/devices/usbms/device.py
@@ -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 = {}
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index a723959214..24e5dd83fc 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -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)
diff --git a/src/calibre/ebooks/mobi/mobiml.py b/src/calibre/ebooks/mobi/mobiml.py
index 850fe20429..18f53317e0 100644
--- a/src/calibre/ebooks/mobi/mobiml.py
+++ b/src/calibre/ebooks/mobi/mobiml.py
@@ -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:
diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py
index 5798276165..32e0126b12 100644
--- a/src/calibre/ebooks/mobi/reader.py
+++ b/src/calibre/ebooks/mobi/reader.py
@@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal '
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
diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py
index fdcae00e8b..39aea3fa30 100644
--- a/src/calibre/ebooks/mobi/writer.py
+++ b/src/calibre/ebooks/mobi/writer.py
@@ -211,12 +211,14 @@ class Serializer(object):
def serialize_item(self, item):
buffer = self.buffer
+ #buffer.write('')
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('')
+ #buffer.write('')
+ buffer.write('')
def serialize_elem(self, elem, item, nsrmap=NSRMAP):
buffer = self.buffer
diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py
index 209212a6be..91bc988a22 100644
--- a/src/calibre/gui2/dialogs/config.py
+++ b/src/calibre/gui2/dialogs/config.py
@@ -1,6 +1,6 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal '
-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'):
diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui
index 3f02ffc7f4..a75a6f0a8d 100644
--- a/src/calibre/gui2/dialogs/config.ui
+++ b/src/calibre/gui2/dialogs/config.ui
@@ -1,117 +1,118 @@
-
+
+
Kovid Goyal
Dialog
-
-
+
+
0
0
- 789
+ 800
557
-
+
Configuration
-
-
+
+
:/images/config.svg:/images/config.svg
-
- -
-
+
+
-
+
-
-
-
-
+
+
+
1
0
-
+
75
true
-
+
true
-
+
false
-
+
48
48
-
+
QAbstractItemView::ScrollPerItem
-
+
QAbstractItemView::ScrollPerPixel
-
+
QListView::TopToBottom
-
+
20
-
+
QListView::ListMode
-
-
-
-
+
+
+
100
0
-
+
0
-
-
+
+
-
-
+
-
-
-
+
+
16777215
70
-
+
&Location of ebooks (The ebooks are stored in folders sorted by author and metadata is stored in the file metadata.db)
-
+
true
-
+
location
-
-
+
-
-
+
-
-
-
+
+
Browse for the new database location
-
+
...
-
-
+
+
:/images/mimetypes/dir.svg:/images/mimetypes/dir.svg
@@ -121,107 +122,107 @@
-
-
-
+
+
Show notification when &new version is available
-
-
-
+
+
If you disable this setting, metadata is guessed from the filename instead. This can be configured in the Advanced section.
-
+
Read &metadata from files
-
+
true
-
-
-
-
-
-
+
+
-
+
+
Format for &single file save:
-
+
single_format
- -
-
+
-
+
- -
-
-
+
-
+
+
Default network &timeout:
-
+
timeout
- -
-
-
+
-
+
+
Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)
-
+
seconds
-
+
2
-
+
120
-
+
5
- -
-
+
-
+
- -
-
-
+
-
+
+
Choose &language (requires restart):
-
+
language
- -
-
+
-
+
-
-
+
Normal
-
-
+
High
-
-
+
Low
- -
-
-
+
-
+
+
Job &priority:
-
+
priority
@@ -229,19 +230,19 @@
-
-
-
+
+
Frequently used directories
-
-
-
-
+
+
-
+
-
-
-
+
+
true
-
+
22
22
@@ -250,13 +251,13 @@
-
-
+
-
-
+
Qt::Vertical
-
+
20
40
@@ -265,25 +266,25 @@
-
-
-
+
+
Add a directory to the frequently used directories list
-
+
...
-
-
+
+
:/images/plus.svg:/images/plus.svg
-
-
+
Qt::Vertical
-
+
20
40
@@ -292,25 +293,25 @@
-
-
-
+
+
Remove a directory from the frequently used directories list
-
+
...
-
-
+
+
:/images/list_remove.svg:/images/list_remove.svg
-
-
+
Qt::Vertical
-
+
20
40
@@ -327,111 +328,111 @@
-
-
+
+
-
-
-
+
+
Use &Roman numerals for series number
-
+
true
-
-
-
+
+
Enable system &tray icon (needs restart)
-
-
-
+
+
Show ¬ifications in system tray
-
-
-
+
+
Show cover &browser in a separate window (needs restart)
-
-
-
+
+
Automatically send downloaded &news to ebook reader
-
-
-
+
+
&Delete news from library when it is sent to reader
-
-
+
-
-
-
+
+
&Number of covers to show in browse mode (needs restart):
-
+
cover_browse
-
-
+
-
-
-
+
+
Toolbar
-
-
-
-
+
+
-
+
-
-
+
Large
-
-
+
Medium
-
-
+
Small
- -
-
-
+
-
+
+
&Button size in toolbar
-
+
toolbar_button_size
- -
-
-
+
-
+
+
Show &text in toolbar buttons
-
+
true
@@ -440,44 +441,44 @@
-
-
+
-
-
-
+
+
Select visible &columns in library view
-
+
-
-
+
-
-
-
+
+
true
-
+
QAbstractItemView::SelectRows
-
-
+
-
-
-
+
+
...
-
-
+
+
:/images/arrow-up.svg:/images/arrow-up.svg
-
-
-
+
+
Qt::Vertical
-
+
20
40
@@ -486,12 +487,12 @@
-
-
-
+
+
...
-
-
+
+
:/images/arrow-down.svg:/images/arrow-down.svg
@@ -504,17 +505,17 @@
-
-
-
+
+
Use internal &viewer for:
-
-
-
-
-
+
+
-
+
+
true
-
+
QAbstractItemView::NoSelection
@@ -535,99 +536,99 @@
-
-
- -
-
-
+
+
+
-
+
+
calibre can send your books to you (or your reader) by email
-
+
true
- -
-
+
-
+
-
-
-
+
+
Send email &from:
-
+
email_from
-
-
-
- <p>This is what will be present in the From: field of emails sent by calibre.<br> Set it to your email address
+
+
+ <p>This is what will be present in the From: field of emails sent by calibre.<br> Set it to your email address
- -
-
+
-
+
-
-
-
+
+
QAbstractItemView::SingleSelection
-
+
QAbstractItemView::SelectRows
-
-
+
-
-
-
+
+
Add an email address to which to send books
-
+
&Add email
-
-
+
+
:/images/plus.svg:/images/plus.svg
-
+
24
24
-
+
Qt::ToolButtonTextUnderIcon
-
-
-
+
+
Make &default
-
-
-
+
+
&Remove email
-
-
+
+
:/images/minus.svg:/images/minus.svg
-
+
24
24
-
+
Qt::ToolButtonTextUnderIcon
@@ -636,188 +637,208 @@
- -
-
-
-
-
-
- <p>A mail server is useful if the service you are sending mail to only accepts email from well know mail services.
-
-
- Mail &Server
-
-
-
-
-
-
- calibre can <b>optionally</b> use a server to send mail
+
-
+
+
+ <p>A mail server is useful if the service you are sending mail to only accepts email from well know mail services.
+
+
+ Mail &Server
+
+
+
-
+
+
+ calibre can <b>optionally</b> use a server to send mail
+
+
+ true
+
+
+
+ -
+
+
+ &Hostname:
+
+
+ relay_host
+
+
+
+ -
+
+
+ The hostname of your mail server. For e.g. smtp.gmail.com
+
+
+
+ -
+
+
-
+
+
+ &Port:
-
- true
+
+ relay_port
- -
-
-
- &Hostname:
+
-
+
+
+ The port your mail server listens for connections on. The default is 25
-
- relay_host
+
+ 1
-
-
- -
-
-
- The hostname of your mail server. For e.g. smtp.gmail.com
+
+ 65555
-
-
- -
-
-
-
-
-
- &Port:
-
-
- relay_port
-
-
-
- -
-
-
- The port your mail server listens for connections on. The default is 25
-
-
- 1
-
-
- 65555
-
-
- 25
-
-
-
-
-
- -
-
-
- &Username:
-
-
- relay_username
-
-
-
- -
-
-
- Your username on the mail server
-
-
-
- -
-
-
- &Password:
-
-
- relay_password
-
-
-
- -
-
-
- Your password on the mail server
-
-
- QLineEdit::Password
-
-
-
- -
-
-
- &Show
-
-
-
- -
-
-
- &Encryption:
-
-
- relay_tls
-
-
-
- -
-
-
- Use TLS encryption when connecting to the mail server. This is the most common.
-
-
- &TLS
-
-
- true
-
-
-
- -
-
-
- Use SSL encryption when connecting to the mail server.
-
-
- &SSL
+
+ 25
-
-
+
+ -
+
+
+ &Username:
+
+
+ relay_username
+
+
+
+ -
+
+
+ Your username on the mail server
+
+
+
+ -
+
+
+ &Password:
+
+
+ relay_password
+
+
+
+ -
+
+
+ Your password on the mail server
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+ &Show
+
+
+
+ -
+
+
+ &Encryption:
+
+
+ relay_tls
+
+
+
+ -
+
+
+ Use TLS encryption when connecting to the mail server. This is the most common.
+
+
+ &TLS
+
+
+ true
+
+
+
+ -
+
+
+ Use SSL encryption when connecting to the mail server.
+
+
+ &SSL
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+ -
+
-
-
-
+
+
Use Gmail
-
-
+
+
:/images/gmail_logo.png:/images/gmail_logo.png
-
+
48
48
-
+
Qt::ToolButtonTextUnderIcon
+ -
+
+
+ &Test email
+
+
+
-
-
+
+
-
-
+
-
-
+
Qt::Horizontal
-
+
40
20
@@ -826,21 +847,21 @@
-
-
-
+
+
Free unused diskspace from the database
-
+
&Compact database
-
-
+
Qt::Horizontal
-
+
40
20
@@ -851,17 +872,17 @@
-
-
-
+
+
&Metadata from file name
-
+
-
-
+
Qt::Vertical
-
+
20
40
@@ -874,96 +895,96 @@
-
-
+
+
-
-
-
+
+
calibre contains a network server that allows you to access your book collection using a browser from anywhere in the world. Any changes to the settings will only take effect after a server restart.
-
+
true
-
-
-
-
-
-
+
+
-
+
+
Server &port:
-
+
port
- -
-
-
+
-
+
+
1025
-
+
16000
-
+
8080
- -
-
-
+
-
+
+
&Username:
-
+
username
- -
-
+
-
+
- -
-
-
+
-
+
+
&Password:
-
+
password
- -
-
-
+
-
+
+
If you leave the password blank, anyone will be able to access your book collection using the web interface.
- -
-
-
+
-
+
+
&Show password
- -
-
-
+
-
+
+
The maximum size (widthxheight) for displayed covers. Larger covers are resized.
-
+
- -
-
-
+
-
+
+
Max. &cover size:
-
+
max_cover_size
@@ -971,27 +992,27 @@
-
-
+
-
-
-
+
+
&Start Server
-
-
-
+
+
St&op Server
-
-
-
+
+
Qt::Horizontal
-
+
40
20
@@ -1000,8 +1021,8 @@
-
-
-
+
+
&Test Server
@@ -1009,25 +1030,25 @@
-
-
-
+
+
Run server &automatically on startup
-
-
-
+
+
View &server logs
-
-
-
+
+
Qt::Vertical
-
+
20
40
@@ -1036,21 +1057,21 @@
-
-
-
+
+
If you want to use the content server to access your ebook collection on your iphone with Stanza, you will need to add the URL http://myhostname:8080/stanza as a new catalog in the stanza reader on your iphone. Here myhostname should be the fully qualified hostname or the IP address of this computer.
-
+
true
-
-
-
+
+
Qt::Vertical
-
+
20
40
@@ -1060,53 +1081,53 @@
-
-
+
+
-
-
-
+
+
Here you can customize the behavior of Calibre by controlling what plugins it uses.
-
+
true
-
-
-
+
+
32
32
-
+
true
-
+
true
-
-
+
-
-
-
+
+
Enable/&Disable plugin
-
-
-
+
+
&Customize plugin
-
-
-
+
+
&Remove plugin
@@ -1114,33 +1135,33 @@
-
-
-
+
+
Add new plugin
-
+
-
-
+
-
-
-
+
+
Plugin &file:
-
+
plugin_path
-
-
+
-
-
-
+
+
...
-
-
+
+
:/images/document_open.svg:/images/document_open.svg
@@ -1148,13 +1169,13 @@
-
-
+
-
-
-
+
+
Qt::Horizontal
-
+
40
20
@@ -1163,8 +1184,8 @@
-
-
-
+
+
&Add
@@ -1180,12 +1201,12 @@
- -
-
-
+
-
+
+
Qt::Horizontal
-
+
QDialogButtonBox::Cancel|QDialogButtonBox::Ok
@@ -1193,7 +1214,7 @@
-
+
@@ -1202,11 +1223,11 @@
Dialog
accept()
-
+
239
558
-
+
157
274
@@ -1218,11 +1239,11 @@
Dialog
reject()
-
+
307
558
-
+
286
274
diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py
index 403bb7f287..f41a80e620 100644
--- a/src/calibre/gui2/dialogs/search.py
+++ b/src/calibre/gui2/dialogs/search.py
@@ -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
-
+
diff --git a/src/calibre/gui2/dialogs/test_email.ui b/src/calibre/gui2/dialogs/test_email.ui
new file mode 100644
index 0000000000..f1d5568c03
--- /dev/null
+++ b/src/calibre/gui2/dialogs/test_email.ui
@@ -0,0 +1,103 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 542
+ 418
+
+
+
+ Test email settings
+
+
+
+ :/images/config.svg:/images/config.svg
+
+
+ -
+
+
+ Send test mail from %s to:
+
+
+ true
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ &Test
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/calibre/gui2/images/news/krstarica.png b/src/calibre/gui2/images/news/krstarica.png
new file mode 100644
index 0000000000..92eecfc4e0
Binary files /dev/null and b/src/calibre/gui2/images/news/krstarica.png differ
diff --git a/src/calibre/gui2/images/news/krstarica_en.png b/src/calibre/gui2/images/news/krstarica_en.png
new file mode 100644
index 0000000000..92eecfc4e0
Binary files /dev/null and b/src/calibre/gui2/images/news/krstarica_en.png differ
diff --git a/src/calibre/gui2/images/news/tanjug.png b/src/calibre/gui2/images/news/tanjug.png
new file mode 100644
index 0000000000..be1128073c
Binary files /dev/null and b/src/calibre/gui2/images/news/tanjug.png differ
diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py
index a97c667ddf..a5bda4d3ef 100644
--- a/src/calibre/gui2/library.py
+++ b/src/calibre/gui2/library.py
@@ -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):
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 28f861ae3a..fd80a722e5 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -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
+
diff --git a/src/calibre/utils/smtp.py b/src/calibre/utils/smtp.py
index 79e46c03c3..0234e27c55 100644
--- a/src/calibre/utils/smtp.py
+++ b/src/calibre/utils/smtp.py
@@ -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)
diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py
index 0cb80ec192..d407378ce5 100644
--- a/src/calibre/web/feeds/recipes/__init__.py
+++ b/src/calibre/web/feeds/recipes/__init__.py
@@ -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
diff --git a/src/calibre/web/feeds/recipes/recipe_krstarica.py b/src/calibre/web/feeds/recipes/recipe_krstarica.py
new file mode 100644
index 0000000000..fb25ae8d84
--- /dev/null
+++ b/src/calibre/web/feeds/recipes/recipe_krstarica.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+__license__ = 'GPL v3'
+__copyright__ = '2009, Darko Miletic '
+'''
+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 = ''
+ 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
diff --git a/src/calibre/web/feeds/recipes/recipe_krstarica_en.py b/src/calibre/web/feeds/recipes/recipe_krstarica_en.py
new file mode 100644
index 0000000000..e426e7d807
--- /dev/null
+++ b/src/calibre/web/feeds/recipes/recipe_krstarica_en.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+__license__ = 'GPL v3'
+__copyright__ = '2009, Darko Miletic '
+'''
+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 = ''
+ 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
diff --git a/src/calibre/web/feeds/recipes/recipe_tanjug.py b/src/calibre/web/feeds/recipes/recipe_tanjug.py
new file mode 100644
index 0000000000..9a8acfaca7
--- /dev/null
+++ b/src/calibre/web/feeds/recipes/recipe_tanjug.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+__license__ = 'GPL v3'
+__copyright__ = '2009, Darko Miletic '
+'''
+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 = ''
+ soup.head.insert(0,mtag)
+ return soup
diff --git a/src/calibre/www/apps/feedjack/fjlib.py b/src/calibre/www/apps/feedjack/fjlib.py
index e13fd5e5af..2801d59a70 100644
--- a/src/calibre/www/apps/feedjack/fjlib.py
+++ b/src/calibre/www/apps/feedjack/fjlib.py
@@ -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