mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Pull from trunk
This commit is contained in:
commit
b9d9df5f20
@ -24,7 +24,7 @@ class File(object):
|
|||||||
path = path[:-1]
|
path = path[:-1]
|
||||||
self.path = path
|
self.path = path
|
||||||
self.name = os.path.basename(path)
|
self.name = os.path.basename(path)
|
||||||
|
|
||||||
|
|
||||||
class PRS505(Device):
|
class PRS505(Device):
|
||||||
VENDOR_ID = 0x054c #: SONY Vendor Id
|
VENDOR_ID = 0x054c #: SONY Vendor Id
|
||||||
@ -33,17 +33,17 @@ class PRS505(Device):
|
|||||||
PRODUCT_NAME = 'PRS-505'
|
PRODUCT_NAME = 'PRS-505'
|
||||||
VENDOR_NAME = 'SONY'
|
VENDOR_NAME = 'SONY'
|
||||||
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
|
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
|
||||||
|
|
||||||
MEDIA_XML = 'database/cache/media.xml'
|
MEDIA_XML = 'database/cache/media.xml'
|
||||||
CACHE_XML = 'Sony Reader/database/cache.xml'
|
CACHE_XML = 'Sony Reader/database/cache.xml'
|
||||||
|
|
||||||
MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory'
|
MAIN_MEMORY_VOLUME_LABEL = 'Sony Reader Main Memory'
|
||||||
STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
|
STORAGE_CARD_VOLUME_LABEL = 'Sony Reader Storage Card'
|
||||||
|
|
||||||
OSX_NAME = 'Sony PRS-505'
|
OSX_NAME = 'Sony PRS-505'
|
||||||
|
|
||||||
CARD_PATH_PREFIX = __appname__
|
CARD_PATH_PREFIX = __appname__
|
||||||
|
|
||||||
FDI_TEMPLATE = \
|
FDI_TEMPLATE = \
|
||||||
'''
|
'''
|
||||||
<device>
|
<device>
|
||||||
@ -75,11 +75,11 @@ class PRS505(Device):
|
|||||||
</match>
|
</match>
|
||||||
</device>
|
</device>
|
||||||
'''.replace('%(app)s', __appname__)
|
'''.replace('%(app)s', __appname__)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, log_packets=False):
|
def __init__(self, log_packets=False):
|
||||||
self._main_prefix = self._card_prefix = None
|
self._main_prefix = self._card_prefix = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fdi(cls):
|
def get_fdi(cls):
|
||||||
return cls.FDI_TEMPLATE%dict(
|
return cls.FDI_TEMPLATE%dict(
|
||||||
@ -90,7 +90,7 @@ class PRS505(Device):
|
|||||||
main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
|
main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
|
||||||
storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
|
storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_device(cls, device_id):
|
def is_device(cls, device_id):
|
||||||
device_id = device_id.upper()
|
device_id = device_id.upper()
|
||||||
@ -104,7 +104,7 @@ class PRS505(Device):
|
|||||||
'PID_'+pid in device_id:
|
'PID_'+pid in device_id:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_osx_mountpoints(cls, raw=None):
|
def get_osx_mountpoints(cls, raw=None):
|
||||||
if raw is None:
|
if raw is None:
|
||||||
@ -112,7 +112,7 @@ class PRS505(Device):
|
|||||||
if not os.access(ioreg, os.X_OK):
|
if not os.access(ioreg, os.X_OK):
|
||||||
ioreg = 'ioreg'
|
ioreg = 'ioreg'
|
||||||
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
|
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
|
||||||
stdout=subprocess.PIPE).stdout.read()
|
stdout=subprocess.PIPE).communicate()[0]
|
||||||
lines = raw.splitlines()
|
lines = raw.splitlines()
|
||||||
names = {}
|
names = {}
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
@ -130,9 +130,9 @@ class PRS505(Device):
|
|||||||
break
|
break
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
def open_osx(self):
|
def open_osx(self):
|
||||||
mount = subprocess.Popen('mount', shell=True,
|
mount = subprocess.Popen('mount', shell=True,
|
||||||
stdout=subprocess.PIPE).stdout.read()
|
stdout=subprocess.PIPE).stdout.read()
|
||||||
names = self.get_osx_mountpoints()
|
names = self.get_osx_mountpoints()
|
||||||
dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+'
|
dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+'
|
||||||
@ -144,12 +144,12 @@ class PRS505(Device):
|
|||||||
if card_pat is not None:
|
if card_pat is not None:
|
||||||
card_pat = dev_pat%card_pat
|
card_pat = dev_pat%card_pat
|
||||||
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
|
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
|
||||||
|
|
||||||
|
|
||||||
def open_windows(self):
|
def open_windows(self):
|
||||||
time.sleep(6)
|
time.sleep(6)
|
||||||
drives = []
|
drives = []
|
||||||
wmi = __import__('wmi', globals(), locals(), [], -1)
|
wmi = __import__('wmi', globals(), locals(), [], -1)
|
||||||
c = wmi.WMI()
|
c = wmi.WMI()
|
||||||
for drive in c.Win32_DiskDrive():
|
for drive in c.Win32_DiskDrive():
|
||||||
if self.__class__.is_device(str(drive.PNPDeviceID)):
|
if self.__class__.is_device(str(drive.PNPDeviceID)):
|
||||||
@ -162,22 +162,22 @@ class PRS505(Device):
|
|||||||
drives.append((drive.Index, prefix))
|
drives.append((drive.Index, prefix))
|
||||||
except IndexError:
|
except IndexError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
if not drives:
|
if not drives:
|
||||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
|
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]))
|
drives.sort(cmp=lambda a, b: cmp(a[0], b[0]))
|
||||||
self._main_prefix = drives[0][1]
|
self._main_prefix = drives[0][1]
|
||||||
if len(drives) > 1:
|
if len(drives) > 1:
|
||||||
self._card_prefix = drives[1][1]
|
self._card_prefix = drives[1][1]
|
||||||
|
|
||||||
|
|
||||||
def open_linux(self):
|
def open_linux(self):
|
||||||
import dbus
|
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")
|
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
|
||||||
|
|
||||||
def conditional_mount(dev, main_mem=True):
|
def conditional_mount(dev, main_mem=True):
|
||||||
mmo = bus.get_object("org.freedesktop.Hal", dev)
|
mmo = bus.get_object("org.freedesktop.Hal", dev)
|
||||||
label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
|
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')
|
fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
|
||||||
if is_mounted:
|
if is_mounted:
|
||||||
return str(mount_point)
|
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')
|
dbus_interface='org.freedesktop.Hal.Device.Volume')
|
||||||
return os.path.normpath('/media/'+label)+'/'
|
return os.path.normpath('/media/'+label)+'/'
|
||||||
|
|
||||||
|
|
||||||
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
|
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
|
||||||
if not mm:
|
if not mm:
|
||||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
|
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
|
||||||
@ -201,21 +201,21 @@ class PRS505(Device):
|
|||||||
break
|
break
|
||||||
except dbus.exceptions.DBusException:
|
except dbus.exceptions.DBusException:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
if not self._main_prefix:
|
if not self._main_prefix:
|
||||||
raise DeviceError('Could not open device for reading. Try a reboot.')
|
raise DeviceError('Could not open device for reading. Try a reboot.')
|
||||||
|
|
||||||
self._card_prefix = None
|
self._card_prefix = None
|
||||||
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
|
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
|
||||||
keys = []
|
keys = []
|
||||||
for card in cards:
|
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')))
|
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 = zip(cards, keys)
|
||||||
cards.sort(cmp=lambda x, y: cmp(x[1], y[1]))
|
cards.sort(cmp=lambda x, y: cmp(x[1], y[1]))
|
||||||
cards = [i[0] for i in cards]
|
cards = [i[0] for i in cards]
|
||||||
|
|
||||||
for dev in cards:
|
for dev in cards:
|
||||||
try:
|
try:
|
||||||
self._card_prefix = conditional_mount(dev, False)+os.sep
|
self._card_prefix = conditional_mount(dev, False)+os.sep
|
||||||
@ -224,8 +224,8 @@ class PRS505(Device):
|
|||||||
import traceback
|
import traceback
|
||||||
print traceback
|
print traceback
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
self._main_prefix = self._card_prefix = None
|
self._main_prefix = self._card_prefix = None
|
||||||
@ -262,16 +262,16 @@ class PRS505(Device):
|
|||||||
self._card_prefix = None
|
self._card_prefix = None
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
def set_progress_reporter(self, pr):
|
def set_progress_reporter(self, pr):
|
||||||
self.report_progress = pr
|
self.report_progress = pr
|
||||||
|
|
||||||
def get_device_information(self, end_session=True):
|
def get_device_information(self, end_session=True):
|
||||||
return (self.__class__.__name__, '', '', '')
|
return (self.__class__.__name__, '', '', '')
|
||||||
|
|
||||||
def card_prefix(self, end_session=True):
|
def card_prefix(self, end_session=True):
|
||||||
return self._card_prefix
|
return self._card_prefix
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _windows_space(cls, prefix):
|
def _windows_space(cls, prefix):
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
@ -288,7 +288,7 @@ class PRS505(Device):
|
|||||||
else: raise
|
else: raise
|
||||||
mult = sectors_per_cluster * bytes_per_sector
|
mult = sectors_per_cluster * bytes_per_sector
|
||||||
return total_clusters * mult, free_clusters * mult
|
return total_clusters * mult, free_clusters * mult
|
||||||
|
|
||||||
def total_space(self, end_session=True):
|
def total_space(self, end_session=True):
|
||||||
msz = csz = 0
|
msz = csz = 0
|
||||||
if not iswindows:
|
if not iswindows:
|
||||||
@ -301,9 +301,9 @@ class PRS505(Device):
|
|||||||
else:
|
else:
|
||||||
msz = self._windows_space(self._main_prefix)[0]
|
msz = self._windows_space(self._main_prefix)[0]
|
||||||
csz = self._windows_space(self._card_prefix)[0]
|
csz = self._windows_space(self._card_prefix)[0]
|
||||||
|
|
||||||
return (msz, 0, csz)
|
return (msz, 0, csz)
|
||||||
|
|
||||||
def free_space(self, end_session=True):
|
def free_space(self, end_session=True):
|
||||||
msz = csz = 0
|
msz = csz = 0
|
||||||
if not iswindows:
|
if not iswindows:
|
||||||
@ -316,9 +316,9 @@ class PRS505(Device):
|
|||||||
else:
|
else:
|
||||||
msz = self._windows_space(self._main_prefix)[1]
|
msz = self._windows_space(self._main_prefix)[1]
|
||||||
csz = self._windows_space(self._card_prefix)[1]
|
csz = self._windows_space(self._card_prefix)[1]
|
||||||
|
|
||||||
return (msz, 0, csz)
|
return (msz, 0, csz)
|
||||||
|
|
||||||
def books(self, oncard=False, end_session=True):
|
def books(self, oncard=False, end_session=True):
|
||||||
if oncard and self._card_prefix is None:
|
if oncard and self._card_prefix is None:
|
||||||
return []
|
return []
|
||||||
@ -331,7 +331,7 @@ class PRS505(Device):
|
|||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
return bl
|
return bl
|
||||||
|
|
||||||
def munge_path(self, path):
|
def munge_path(self, path):
|
||||||
if path.startswith('/') and not (path.startswith(self._main_prefix) or \
|
if path.startswith('/') and not (path.startswith(self._main_prefix) or \
|
||||||
(self._card_prefix and path.startswith(self._card_prefix))):
|
(self._card_prefix and path.startswith(self._card_prefix))):
|
||||||
@ -339,12 +339,12 @@ class PRS505(Device):
|
|||||||
elif path.startswith('card:'):
|
elif path.startswith('card:'):
|
||||||
path = path.replace('card:', self._card_prefix[:-1])
|
path = path.replace('card:', self._card_prefix[:-1])
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def mkdir(self, path, end_session=True):
|
def mkdir(self, path, end_session=True):
|
||||||
""" Make directory """
|
""" Make directory """
|
||||||
path = self.munge_path(path)
|
path = self.munge_path(path)
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
|
|
||||||
def list(self, path, recurse=False, end_session=True, munge=True):
|
def list(self, path, recurse=False, end_session=True, munge=True):
|
||||||
if munge:
|
if munge:
|
||||||
path = self.munge_path(path)
|
path = self.munge_path(path)
|
||||||
@ -356,12 +356,12 @@ class PRS505(Device):
|
|||||||
if recurse and _file.is_dir:
|
if recurse and _file.is_dir:
|
||||||
dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False)
|
dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False)
|
||||||
return dirs
|
return dirs
|
||||||
|
|
||||||
def get_file(self, path, outfile, end_session=True):
|
def get_file(self, path, outfile, end_session=True):
|
||||||
path = self.munge_path(path)
|
path = self.munge_path(path)
|
||||||
src = open(path, 'rb')
|
src = open(path, 'rb')
|
||||||
shutil.copyfileobj(src, outfile, 10*1024*1024)
|
shutil.copyfileobj(src, outfile, 10*1024*1024)
|
||||||
|
|
||||||
def put_file(self, infile, path, replace_file=False, end_session=True):
|
def put_file(self, infile, path, replace_file=False, end_session=True):
|
||||||
path = self.munge_path(path)
|
path = self.munge_path(path)
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
@ -372,25 +372,25 @@ class PRS505(Device):
|
|||||||
shutil.copyfileobj(infile, dest, 10*1024*1024)
|
shutil.copyfileobj(infile, dest, 10*1024*1024)
|
||||||
dest.flush()
|
dest.flush()
|
||||||
dest.close()
|
dest.close()
|
||||||
|
|
||||||
def rm(self, path, end_session=True):
|
def rm(self, path, end_session=True):
|
||||||
path = self.munge_path(path)
|
path = self.munge_path(path)
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
|
|
||||||
def touch(self, path, end_session=True):
|
def touch(self, path, end_session=True):
|
||||||
path = self.munge_path(path)
|
path = self.munge_path(path)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
open(path, 'w').close()
|
open(path, 'w').close()
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
os.utime(path, None)
|
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):
|
metadata=None):
|
||||||
if on_card and not self._card_prefix:
|
if on_card and not self._card_prefix:
|
||||||
raise ValueError(_('The reader has no storage card connected.'))
|
raise ValueError(_('The reader has no storage card connected.'))
|
||||||
path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \
|
path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \
|
||||||
else os.path.join(self._main_prefix, 'database', 'media', 'books')
|
else os.path.join(self._main_prefix, 'database', 'media', 'books')
|
||||||
|
|
||||||
def get_size(obj):
|
def get_size(obj):
|
||||||
if hasattr(obj, 'seek'):
|
if hasattr(obj, 'seek'):
|
||||||
obj.seek(0, 2)
|
obj.seek(0, 2)
|
||||||
@ -398,27 +398,27 @@ class PRS505(Device):
|
|||||||
obj.seek(0)
|
obj.seek(0)
|
||||||
return size
|
return size
|
||||||
return os.path.getsize(obj)
|
return os.path.getsize(obj)
|
||||||
|
|
||||||
sizes = map(get_size, files)
|
sizes = map(get_size, files)
|
||||||
size = sum(sizes)
|
size = sum(sizes)
|
||||||
space = self.free_space()
|
space = self.free_space()
|
||||||
mspace = space[0]
|
mspace = space[0]
|
||||||
cspace = space[2]
|
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 "+\
|
raise FreeSpaceError("There is insufficient free space "+\
|
||||||
"on the storage card")
|
"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 " +\
|
raise FreeSpaceError("There is insufficient free space " +\
|
||||||
"in main memory")
|
"in main memory")
|
||||||
|
|
||||||
paths, ctimes = [], []
|
paths, ctimes = [], []
|
||||||
|
|
||||||
names = iter(names)
|
names = iter(names)
|
||||||
for infile in files:
|
for infile in files:
|
||||||
close = False
|
close = False
|
||||||
if not hasattr(infile, 'read'):
|
if not hasattr(infile, 'read'):
|
||||||
infile, close = open(infile, 'rb'), True
|
infile, close = open(infile, 'rb'), True
|
||||||
infile.seek(0)
|
infile.seek(0)
|
||||||
name = names.next()
|
name = names.next()
|
||||||
paths.append(os.path.join(path, name))
|
paths.append(os.path.join(path, name))
|
||||||
if not os.path.exists(os.path.dirname(paths[-1])):
|
if not os.path.exists(os.path.dirname(paths[-1])):
|
||||||
@ -428,7 +428,7 @@ class PRS505(Device):
|
|||||||
infile.close()
|
infile.close()
|
||||||
ctimes.append(os.path.getctime(paths[-1]))
|
ctimes.append(os.path.getctime(paths[-1]))
|
||||||
return zip(paths, sizes, ctimes, cycle([on_card]))
|
return zip(paths, sizes, ctimes, cycle([on_card]))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_books_to_metadata(cls, locations, metadata, booklists):
|
def add_books_to_metadata(cls, locations, metadata, booklists):
|
||||||
metadata = iter(metadata)
|
metadata = iter(metadata)
|
||||||
@ -441,12 +441,12 @@ class PRS505(Device):
|
|||||||
name = name.replace('//', '/')
|
name = name.replace('//', '/')
|
||||||
booklists[on_card].add_book(info, name, *location[1:-1])
|
booklists[on_card].add_book(info, name, *location[1:-1])
|
||||||
fix_ids(*booklists)
|
fix_ids(*booklists)
|
||||||
|
|
||||||
def delete_books(self, paths, end_session=True):
|
def delete_books(self, paths, end_session=True):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_books_from_metadata(cls, paths, booklists):
|
def remove_books_from_metadata(cls, paths, booklists):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
@ -454,7 +454,7 @@ class PRS505(Device):
|
|||||||
if hasattr(bl, 'remove_book'):
|
if hasattr(bl, 'remove_book'):
|
||||||
bl.remove_book(path)
|
bl.remove_book(path)
|
||||||
fix_ids(*booklists)
|
fix_ids(*booklists)
|
||||||
|
|
||||||
def sync_booklists(self, booklists, end_session=True):
|
def sync_booklists(self, booklists, end_session=True):
|
||||||
fix_ids(*booklists)
|
fix_ids(*booklists)
|
||||||
if not os.path.exists(self._main_prefix):
|
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')
|
f = open(self._card_prefix + self.__class__.CACHE_XML, 'wb')
|
||||||
booklists[1].write(f)
|
booklists[1].write(f)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args=sys.argv):
|
||||||
return 0
|
return 0
|
||||||
|
@ -190,7 +190,7 @@ class Device(_Device):
|
|||||||
|
|
||||||
self._main_prefix = drives.get('main')
|
self._main_prefix = drives.get('main')
|
||||||
self._card_prefix = drives.get('card')
|
self._card_prefix = drives.get('card')
|
||||||
|
|
||||||
if not self._main_prefix:
|
if not self._main_prefix:
|
||||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__)
|
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):
|
if not os.access(ioreg, os.X_OK):
|
||||||
ioreg = 'ioreg'
|
ioreg = 'ioreg'
|
||||||
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
|
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
|
||||||
stdout=subprocess.PIPE).stdout.read()
|
stdout=subprocess.PIPE).communicate()[0]
|
||||||
lines = raw.splitlines()
|
lines = raw.splitlines()
|
||||||
names = {}
|
names = {}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ lxml based OPF parser.
|
|||||||
|
|
||||||
import sys, unittest, functools, os, mimetypes, uuid, glob, cStringIO
|
import sys, unittest, functools, os, mimetypes, uuid, glob, cStringIO
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
from urlparse import urlparse, urldefrag
|
from urlparse import urlparse
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
@ -258,6 +258,11 @@ class Manifest(ResourceCollection):
|
|||||||
if i.id == id:
|
if i.id == id:
|
||||||
return i.path
|
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 Spine(ResourceCollection):
|
||||||
|
|
||||||
class Item(Resource):
|
class Item(Resource):
|
||||||
@ -487,7 +492,10 @@ class OPF(object):
|
|||||||
|
|
||||||
if toc is None: return
|
if toc is None: return
|
||||||
self.toc = TOC(base_path=self.base_dir)
|
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)
|
path = self.manifest.path_for_id(toc)
|
||||||
if path:
|
if path:
|
||||||
self.toc.read_ncx_toc(path)
|
self.toc.read_ncx_toc(path)
|
||||||
|
@ -79,7 +79,7 @@ class FormatState(object):
|
|||||||
class MobiMLizer(object):
|
class MobiMLizer(object):
|
||||||
def __init__(self, ignore_tables=False):
|
def __init__(self, ignore_tables=False):
|
||||||
self.ignore_tables = ignore_tables
|
self.ignore_tables = ignore_tables
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config(cls, cfg):
|
def config(cls, cfg):
|
||||||
group = cfg.add_group('mobiml', _('Mobipocket markup options.'))
|
group = cfg.add_group('mobiml', _('Mobipocket markup options.'))
|
||||||
@ -92,7 +92,7 @@ class MobiMLizer(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, opts):
|
def generate(cls, opts):
|
||||||
return cls(ignore_tables=opts.ignore_tables)
|
return cls(ignore_tables=opts.ignore_tables)
|
||||||
|
|
||||||
def __call__(self, oeb, context):
|
def __call__(self, oeb, context):
|
||||||
oeb.logger.info('Converting XHTML to Mobipocket markup...')
|
oeb.logger.info('Converting XHTML to Mobipocket markup...')
|
||||||
self.oeb = oeb
|
self.oeb = oeb
|
||||||
@ -111,10 +111,10 @@ class MobiMLizer(object):
|
|||||||
del oeb.guide['cover']
|
del oeb.guide['cover']
|
||||||
item = oeb.manifest.hrefs[href]
|
item = oeb.manifest.hrefs[href]
|
||||||
if item.spine_position is not None:
|
if item.spine_position is not None:
|
||||||
oeb.spine.remove(item)
|
oeb.spine.remove(item)
|
||||||
if item.media_type in OEB_DOCS:
|
if item.media_type in OEB_DOCS:
|
||||||
self.oeb.manifest.remove(item)
|
self.oeb.manifest.remove(item)
|
||||||
|
|
||||||
def mobimlize_spine(self):
|
def mobimlize_spine(self):
|
||||||
for item in self.oeb.spine:
|
for item in self.oeb.spine:
|
||||||
stylizer = Stylizer(item.data, item.href, self.oeb, self.profile)
|
stylizer = Stylizer(item.data, item.href, self.oeb, self.profile)
|
||||||
@ -147,7 +147,7 @@ class MobiMLizer(object):
|
|||||||
if line:
|
if line:
|
||||||
result.append(line)
|
result.append(line)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def mobimlize_content(self, tag, text, bstate, istates):
|
def mobimlize_content(self, tag, text, bstate, istates):
|
||||||
if text or tag != 'br':
|
if text or tag != 'br':
|
||||||
bstate.content = True
|
bstate.content = True
|
||||||
@ -252,7 +252,7 @@ class MobiMLizer(object):
|
|||||||
last.tail = (last.tail or '') + item
|
last.tail = (last.tail or '') + item
|
||||||
else:
|
else:
|
||||||
inline.append(item)
|
inline.append(item)
|
||||||
|
|
||||||
def mobimlize_elem(self, elem, stylizer, bstate, istates):
|
def mobimlize_elem(self, elem, stylizer, bstate, istates):
|
||||||
if not isinstance(elem.tag, basestring) \
|
if not isinstance(elem.tag, basestring) \
|
||||||
or namespace(elem.tag) != XHTML_NS:
|
or namespace(elem.tag) != XHTML_NS:
|
||||||
|
@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
Read data from .mobi files
|
Read data from .mobi files
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import struct, os, cStringIO, re, functools
|
import struct, os, cStringIO, re, functools, datetime
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
@ -53,6 +53,12 @@ class EXTHHeader(object):
|
|||||||
self.cover_offset = co
|
self.cover_offset = co
|
||||||
elif id == 202:
|
elif id == 202:
|
||||||
self.thumbnail_offset, = struct.unpack('>L', content)
|
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')):
|
elif id == 503 and (not title or title == _('Unknown')):
|
||||||
title = content
|
title = content
|
||||||
#else:
|
#else:
|
||||||
@ -75,8 +81,14 @@ class EXTHHeader(object):
|
|||||||
if not self.mi.tags:
|
if not self.mi.tags:
|
||||||
self.mi.tags = []
|
self.mi.tags = []
|
||||||
self.mi.tags.append(content.decode(codec, 'ignore'))
|
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:
|
#else:
|
||||||
# print 'unhandled metadata record', id, repr(content), codec
|
# print 'unhandled metadata record', id, repr(content)
|
||||||
|
|
||||||
|
|
||||||
class BookHeader(object):
|
class BookHeader(object):
|
||||||
@ -327,8 +339,8 @@ class MobiReader(object):
|
|||||||
mobi_version = self.book_header.mobi_version
|
mobi_version = self.book_header.mobi_version
|
||||||
for i, tag in enumerate(root.iter(etree.Element)):
|
for i, tag in enumerate(root.iter(etree.Element)):
|
||||||
if tag.tag in ('country-region', 'place', 'placetype', 'placename',
|
if tag.tag in ('country-region', 'place', 'placetype', 'placename',
|
||||||
'state', 'city', 'street', 'address'):
|
'state', 'city', 'street', 'address', 'content'):
|
||||||
tag.tag = 'span'
|
tag.tag = 'div' if tag.tag == 'content' else 'span'
|
||||||
for key in tag.attrib.keys():
|
for key in tag.attrib.keys():
|
||||||
tag.attrib.pop(key)
|
tag.attrib.pop(key)
|
||||||
continue
|
continue
|
||||||
|
@ -211,12 +211,14 @@ class Serializer(object):
|
|||||||
|
|
||||||
def serialize_item(self, item):
|
def serialize_item(self, item):
|
||||||
buffer = self.buffer
|
buffer = self.buffer
|
||||||
|
#buffer.write('<mbp:section>')
|
||||||
if not item.linear:
|
if not item.linear:
|
||||||
self.breaks.append(buffer.tell() - 1)
|
self.breaks.append(buffer.tell() - 1)
|
||||||
self.id_offsets[item.href] = buffer.tell()
|
self.id_offsets[item.href] = buffer.tell()
|
||||||
for elem in item.data.find(XHTML('body')):
|
for elem in item.data.find(XHTML('body')):
|
||||||
self.serialize_elem(elem, item)
|
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):
|
def serialize_elem(self, elem, item, nsrmap=NSRMAP):
|
||||||
buffer = self.buffer
|
buffer = self.buffer
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__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 binascii import hexlify, unhexlify
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \
|
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.constants import islinux, iswindows
|
||||||
from calibre.gui2.dialogs.config_ui import Ui_Dialog
|
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, \
|
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \
|
||||||
ALL_COLUMNS, NONE, info_dialog, choose_files
|
ALL_COLUMNS, NONE, info_dialog, choose_files
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
@ -134,6 +135,33 @@ class CategoryModel(QStringListModel):
|
|||||||
return self.icons[index.row()]
|
return self.icons[index.row()]
|
||||||
return QStringListModel.data(self, index, role)
|
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):
|
class EmailAccounts(QAbstractTableModel):
|
||||||
|
|
||||||
@ -395,6 +423,8 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.connect(self.email_make_default, SIGNAL('clicked(bool)'),
|
self.connect(self.email_make_default, SIGNAL('clicked(bool)'),
|
||||||
lambda c: self._email_accounts.make_default(self.email_view.currentIndex()))
|
lambda c: self._email_accounts.make_default(self.email_view.currentIndex()))
|
||||||
self.email_view.resizeColumnsToContents()
|
self.email_view.resizeColumnsToContents()
|
||||||
|
self.connect(self.test_email_button, SIGNAL('clicked(bool)'),
|
||||||
|
self.test_email)
|
||||||
|
|
||||||
def add_email_account(self, checked):
|
def add_email_account(self, checked):
|
||||||
index = self._email_accounts.add()
|
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')
|
conf.set('encryption', 'TLS' if self.relay_tls.isChecked() else 'SSL')
|
||||||
return True
|
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):
|
def add_plugin(self):
|
||||||
path = unicode(self.plugin_path.text())
|
path = unicode(self.plugin_path.text())
|
||||||
if path and os.access(path, os.R_OK) and path.lower().endswith('.zip'):
|
if path and os.access(path, os.R_OK) and path.lower().endswith('.zip'):
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -6,13 +6,13 @@ from PyQt4.QtGui import QDialog
|
|||||||
from calibre.gui2.dialogs.search_ui import Ui_Dialog
|
from calibre.gui2.dialogs.search_ui import Ui_Dialog
|
||||||
from calibre.gui2 import qstring_to_unicode
|
from calibre.gui2 import qstring_to_unicode
|
||||||
|
|
||||||
|
|
||||||
class SearchDialog(QDialog, Ui_Dialog):
|
class SearchDialog(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
QDialog.__init__(self, *args)
|
QDialog.__init__(self, *args)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def tokens(self, raw):
|
def tokens(self, raw):
|
||||||
phrases = re.findall(r'\s+".*?"\s+', raw)
|
phrases = re.findall(r'\s+".*?"\s+', raw)
|
||||||
for f in phrases:
|
for f in phrases:
|
||||||
@ -20,7 +20,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
return [t.strip() for t in phrases + raw.split()]
|
return [t.strip() for t in phrases + raw.split()]
|
||||||
|
|
||||||
def search_string(self):
|
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))
|
all, any, none = map(self.tokens, (all, any, none))
|
||||||
phrase = phrase.strip()
|
phrase = phrase.strip()
|
||||||
all = ' and '.join(all)
|
all = ' and '.join(all)
|
||||||
@ -32,11 +33,11 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
if all:
|
if all:
|
||||||
ans += (' and ' if ans else '') + all
|
ans += (' and ' if ans else '') + all
|
||||||
if none:
|
if none:
|
||||||
ans += (' and not ' if ans else '') + none
|
ans += (' and not ' if ans else 'not ') + none
|
||||||
if any:
|
if any:
|
||||||
ans += (' or ' if ans else '') + any
|
ans += (' or ' if ans else '') + any
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def token(self):
|
def token(self):
|
||||||
txt = qstring_to_unicode(self.text.text()).strip()
|
txt = qstring_to_unicode(self.text.text()).strip()
|
||||||
if txt:
|
if txt:
|
||||||
@ -46,4 +47,4 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
if re.search(r'\s', tok):
|
if re.search(r'\s', tok):
|
||||||
tok = '"%s"'%tok
|
tok = '"%s"'%tok
|
||||||
return tok
|
return tok
|
||||||
|
|
||||||
|
103
src/calibre/gui2/dialogs/test_email.ui
Normal file
103
src/calibre/gui2/dialogs/test_email.ui
Normal 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>&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>
|
BIN
src/calibre/gui2/images/news/krstarica.png
Normal file
BIN
src/calibre/gui2/images/news/krstarica.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 632 B |
BIN
src/calibre/gui2/images/news/krstarica_en.png
Normal file
BIN
src/calibre/gui2/images/news/krstarica_en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 632 B |
BIN
src/calibre/gui2/images/news/tanjug.png
Normal file
BIN
src/calibre/gui2/images/news/tanjug.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 827 B |
@ -93,7 +93,7 @@ class DateDelegate(QStyledItemDelegate):
|
|||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
def createEditor(self, parent, option, index):
|
||||||
qde = QStyledItemDelegate.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.setMinimumDate(QDate(101,1,1))
|
||||||
qde.setCalendarPopup(True)
|
qde.setCalendarPopup(True)
|
||||||
return qde
|
return qde
|
||||||
@ -637,7 +637,8 @@ class BooksView(TableView):
|
|||||||
|
|
||||||
def columns_sorted(self, rating_col, timestamp_col):
|
def columns_sorted(self, rating_col, timestamp_col):
|
||||||
for i in range(self.model().columnCount(None)):
|
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())
|
self.setItemDelegateForColumn(i, self.itemDelegate())
|
||||||
if rating_col > -1:
|
if rating_col > -1:
|
||||||
self.setItemDelegateForColumn(rating_col, self.rating_delegate)
|
self.setItemDelegateForColumn(rating_col, self.rating_delegate)
|
||||||
@ -708,7 +709,7 @@ class BooksView(TableView):
|
|||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._model.close()
|
self._model.close()
|
||||||
|
|
||||||
def set_editable(self, editable):
|
def set_editable(self, editable):
|
||||||
self._model.set_editable(editable)
|
self._model.set_editable(editable)
|
||||||
|
|
||||||
@ -1001,10 +1002,10 @@ class DeviceBooksModel(BooksModel):
|
|||||||
self.sort(col, self.sorted_on[1])
|
self.sort(col, self.sorted_on[1])
|
||||||
done = True
|
done = True
|
||||||
return done
|
return done
|
||||||
|
|
||||||
def set_editable(self, editable):
|
def set_editable(self, editable):
|
||||||
self.editable = editable
|
self.editable = editable
|
||||||
|
|
||||||
|
|
||||||
class SearchBox(QLineEdit):
|
class SearchBox(QLineEdit):
|
||||||
|
|
||||||
|
@ -226,7 +226,11 @@ class ResultCache(SearchQueryParser):
|
|||||||
Returns a list of affected rows or None if the rows are filtered.
|
Returns a list of affected rows or None if the rows are filtered.
|
||||||
'''
|
'''
|
||||||
for id in ids:
|
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:
|
try:
|
||||||
return map(self.row, ids)
|
return map(self.row, ids)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -269,7 +273,7 @@ class ResultCache(SearchQueryParser):
|
|||||||
ans = cmp(self._data[x][9], self._data[y][9])
|
ans = cmp(self._data[x][9], self._data[y][9])
|
||||||
if ans != 0: return ans
|
if ans != 0: return ans
|
||||||
return cmp(self._data[x][10], self._data[y][10])
|
return cmp(self._data[x][10], self._data[y][10])
|
||||||
|
|
||||||
def cmp(self, loc, x, y, str=True, subsort=False):
|
def cmp(self, loc, x, y, str=True, subsort=False):
|
||||||
try:
|
try:
|
||||||
ans = cmp(self._data[x][loc].lower(), self._data[y][loc].lower()) if str else\
|
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:
|
if subsort and ans == 0:
|
||||||
return cmp(self._data[x][11].lower(), self._data[y][11].lower())
|
return cmp(self._data[x][11].lower(), self._data[y][11].lower())
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def sort(self, field, ascending, subsort=False):
|
def sort(self, field, ascending, subsort=False):
|
||||||
field = field.lower().strip()
|
field = field.lower().strip()
|
||||||
if field in ('author', 'tag', 'comment'):
|
if field in ('author', 'tag', 'comment'):
|
||||||
@ -733,7 +737,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
self.refresh_ids([id])
|
self.refresh_ids([id])
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
def delete_book(self, id, notify=True):
|
def delete_book(self, id, notify=True):
|
||||||
'''
|
'''
|
||||||
Removes book from the result cache and the underlying database.
|
Removes book from the result cache and the underlying database.
|
||||||
@ -751,7 +755,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
self.data.books_deleted([id])
|
self.data.books_deleted([id])
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('delete', [id])
|
self.notify('delete', [id])
|
||||||
|
|
||||||
def remove_format(self, index, format, index_is_id=False, notify=True):
|
def remove_format(self, index, format, index_is_id=False, notify=True):
|
||||||
id = index if index_is_id else self.id(index)
|
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))
|
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))
|
(id, aid))
|
||||||
except IntegrityError: # Sometimes books specify the same author twice in their metadata
|
except IntegrityError: # Sometimes books specify the same author twice in their metadata
|
||||||
pass
|
pass
|
||||||
ss = authors_to_sort_string(authors)
|
ss = authors_to_sort_string(authors)
|
||||||
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
||||||
(ss, id))
|
(ss, id))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, FIELD_MAP['authors'],
|
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)
|
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)
|
self.set_path(id, True)
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
@ -1159,7 +1163,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
else:
|
else:
|
||||||
path = path_or_stream
|
path = path_or_stream
|
||||||
return run_plugins_on_import(path, format)
|
return run_plugins_on_import(path, format)
|
||||||
|
|
||||||
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True):
|
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True):
|
||||||
'''
|
'''
|
||||||
Add a book to the database. The result cache is not updated.
|
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')
|
aus = aus.decode(preferred_encoding, 'replace')
|
||||||
title = mi.title if isinstance(mi.title, unicode) else \
|
title = mi.title if isinstance(mi.title, unicode) else \
|
||||||
mi.title.decode(preferred_encoding, 'replace')
|
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))
|
(title, None, series_index, aus))
|
||||||
id = obj.lastrowid
|
id = obj.lastrowid
|
||||||
self.data.books_added([id], self.conn)
|
self.data.books_added([id], self.conn)
|
||||||
@ -1568,3 +1572,4 @@ books_series_link feeds
|
|||||||
|
|
||||||
return duplicates
|
return duplicates
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,15 +45,17 @@ def create_mail(from_, to, subject, text=None, attachment_data=None,
|
|||||||
|
|
||||||
return outer.as_string()
|
return outer.as_string()
|
||||||
|
|
||||||
def get_mx(host):
|
def get_mx(host, verbose=0):
|
||||||
import dns.resolver
|
import dns.resolver
|
||||||
|
if verbose:
|
||||||
|
print 'Find mail exchanger for', host
|
||||||
answers = list(dns.resolver.query(host, 'MX'))
|
answers = list(dns.resolver.query(host, 'MX'))
|
||||||
answers.sort(cmp=lambda x, y: cmp(int(x.preference), int(y.preference)))
|
answers.sort(cmp=lambda x, y: cmp(int(x.preference), int(y.preference)))
|
||||||
return [str(x.exchange) for x in answers]
|
return [str(x.exchange) for x in answers]
|
||||||
|
|
||||||
def sendmail_direct(from_, to, msg, timeout, localhost, verbose):
|
def sendmail_direct(from_, to, msg, timeout, localhost, verbose):
|
||||||
import smtplib
|
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
|
timeout=None # Non blocking sockets sometimes don't work
|
||||||
s = smtplib.SMTP(timeout=timeout, local_hostname=localhost)
|
s = smtplib.SMTP(timeout=timeout, local_hostname=localhost)
|
||||||
s.set_debuglevel(verbose)
|
s.set_debuglevel(verbose)
|
||||||
|
@ -37,6 +37,7 @@ recipe_modules = ['recipe_' + r for r in (
|
|||||||
'new_york_review_of_books_no_sub', 'politico', 'adventuregamers',
|
'new_york_review_of_books_no_sub', 'politico', 'adventuregamers',
|
||||||
'mondedurable', 'instapaper', 'dnevnik_cro', 'vecernji_list',
|
'mondedurable', 'instapaper', 'dnevnik_cro', 'vecernji_list',
|
||||||
'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs',
|
'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs',
|
||||||
|
'krstarica', 'krstarica_en', 'tanjug',
|
||||||
)]
|
)]
|
||||||
|
|
||||||
import re, imp, inspect, time, os
|
import re, imp, inspect, time, os
|
||||||
|
65
src/calibre/web/feeds/recipes/recipe_krstarica.py
Normal file
65
src/calibre/web/feeds/recipes/recipe_krstarica.py
Normal 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
|
57
src/calibre/web/feeds/recipes/recipe_krstarica_en.py
Normal file
57
src/calibre/web/feeds/recipes/recipe_krstarica_en.py
Normal 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
|
43
src/calibre/web/feeds/recipes/recipe_tanjug.py
Normal file
43
src/calibre/web/feeds/recipes/recipe_tanjug.py
Normal 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
|
@ -128,7 +128,7 @@ def get_extra_content(site, sfeeds_ids, ctx):
|
|||||||
def get_posts_tags(object_list, sfeeds_obj, user_id, tag_name):
|
def get_posts_tags(object_list, sfeeds_obj, user_id, tag_name):
|
||||||
""" Adds a qtags property in every post object in a page.
|
""" 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 = {}
|
tagd = {}
|
||||||
user_obj = None
|
user_obj = None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user