mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
c9f27ed007
@ -179,17 +179,17 @@ class MPRecipe(BasicNewsRecipe):
|
|||||||
def get_dtlocal(self):
|
def get_dtlocal(self):
|
||||||
dt_utc = datetime.datetime.utcnow()
|
dt_utc = datetime.datetime.utcnow()
|
||||||
if __Region__ == 'Hong Kong':
|
if __Region__ == 'Hong Kong':
|
||||||
# convert UTC to local hk time - at HKT 4.30am, all news are available
|
# convert UTC to local hk time - at HKT 5.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(8.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(8.0/24) - datetime.timedelta(5.5/24)
|
||||||
# dt_local = dt_utc.astimezone(pytz.timezone('Asia/Hong_Kong')) - datetime.timedelta(4.5/24)
|
# dt_local = dt_utc.astimezone(pytz.timezone('Asia/Hong_Kong')) - datetime.timedelta(5.5/24)
|
||||||
elif __Region__ == 'Vancouver':
|
elif __Region__ == 'Vancouver':
|
||||||
# convert UTC to local Vancouver time - at PST time 4.30am, all news are available
|
# convert UTC to local Vancouver time - at PST time 5.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(-8.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(-8.0/24) - datetime.timedelta(5.5/24)
|
||||||
#dt_local = dt_utc.astimezone(pytz.timezone('America/Vancouver')) - datetime.timedelta(4.5/24)
|
#dt_local = dt_utc.astimezone(pytz.timezone('America/Vancouver')) - datetime.timedelta(5.5/24)
|
||||||
elif __Region__ == 'Toronto':
|
elif __Region__ == 'Toronto':
|
||||||
# convert UTC to local Toronto time - at EST time 4.30am, all news are available
|
# convert UTC to local Toronto time - at EST time 8.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(-5.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(-5.0/24) - datetime.timedelta(8.5/24)
|
||||||
#dt_local = dt_utc.astimezone(pytz.timezone('America/Toronto')) - datetime.timedelta(4.5/24)
|
#dt_local = dt_utc.astimezone(pytz.timezone('America/Toronto')) - datetime.timedelta(8.5/24)
|
||||||
return dt_local
|
return dt_local
|
||||||
|
|
||||||
def get_fetchdate(self):
|
def get_fetchdate(self):
|
||||||
|
@ -179,17 +179,17 @@ class MPRecipe(BasicNewsRecipe):
|
|||||||
def get_dtlocal(self):
|
def get_dtlocal(self):
|
||||||
dt_utc = datetime.datetime.utcnow()
|
dt_utc = datetime.datetime.utcnow()
|
||||||
if __Region__ == 'Hong Kong':
|
if __Region__ == 'Hong Kong':
|
||||||
# convert UTC to local hk time - at HKT 4.30am, all news are available
|
# convert UTC to local hk time - at HKT 5.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(8.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(8.0/24) - datetime.timedelta(5.5/24)
|
||||||
# dt_local = dt_utc.astimezone(pytz.timezone('Asia/Hong_Kong')) - datetime.timedelta(4.5/24)
|
# dt_local = dt_utc.astimezone(pytz.timezone('Asia/Hong_Kong')) - datetime.timedelta(5.5/24)
|
||||||
elif __Region__ == 'Vancouver':
|
elif __Region__ == 'Vancouver':
|
||||||
# convert UTC to local Vancouver time - at PST time 4.30am, all news are available
|
# convert UTC to local Vancouver time - at PST time 5.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(-8.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(-8.0/24) - datetime.timedelta(5.5/24)
|
||||||
#dt_local = dt_utc.astimezone(pytz.timezone('America/Vancouver')) - datetime.timedelta(4.5/24)
|
#dt_local = dt_utc.astimezone(pytz.timezone('America/Vancouver')) - datetime.timedelta(5.5/24)
|
||||||
elif __Region__ == 'Toronto':
|
elif __Region__ == 'Toronto':
|
||||||
# convert UTC to local Toronto time - at EST time 4.30am, all news are available
|
# convert UTC to local Toronto time - at EST time 8.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(-5.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(-5.0/24) - datetime.timedelta(8.5/24)
|
||||||
#dt_local = dt_utc.astimezone(pytz.timezone('America/Toronto')) - datetime.timedelta(4.5/24)
|
#dt_local = dt_utc.astimezone(pytz.timezone('America/Toronto')) - datetime.timedelta(8.5/24)
|
||||||
return dt_local
|
return dt_local
|
||||||
|
|
||||||
def get_fetchdate(self):
|
def get_fetchdate(self):
|
||||||
|
@ -179,17 +179,17 @@ class MPRecipe(BasicNewsRecipe):
|
|||||||
def get_dtlocal(self):
|
def get_dtlocal(self):
|
||||||
dt_utc = datetime.datetime.utcnow()
|
dt_utc = datetime.datetime.utcnow()
|
||||||
if __Region__ == 'Hong Kong':
|
if __Region__ == 'Hong Kong':
|
||||||
# convert UTC to local hk time - at HKT 4.30am, all news are available
|
# convert UTC to local hk time - at HKT 5.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(8.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(8.0/24) - datetime.timedelta(5.5/24)
|
||||||
# dt_local = dt_utc.astimezone(pytz.timezone('Asia/Hong_Kong')) - datetime.timedelta(4.5/24)
|
# dt_local = dt_utc.astimezone(pytz.timezone('Asia/Hong_Kong')) - datetime.timedelta(5.5/24)
|
||||||
elif __Region__ == 'Vancouver':
|
elif __Region__ == 'Vancouver':
|
||||||
# convert UTC to local Vancouver time - at PST time 4.30am, all news are available
|
# convert UTC to local Vancouver time - at PST time 5.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(-8.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(-8.0/24) - datetime.timedelta(5.5/24)
|
||||||
#dt_local = dt_utc.astimezone(pytz.timezone('America/Vancouver')) - datetime.timedelta(4.5/24)
|
#dt_local = dt_utc.astimezone(pytz.timezone('America/Vancouver')) - datetime.timedelta(5.5/24)
|
||||||
elif __Region__ == 'Toronto':
|
elif __Region__ == 'Toronto':
|
||||||
# convert UTC to local Toronto time - at EST time 4.30am, all news are available
|
# convert UTC to local Toronto time - at EST time 8.30am, all news are available
|
||||||
dt_local = dt_utc + datetime.timedelta(-5.0/24) - datetime.timedelta(4.5/24)
|
dt_local = dt_utc + datetime.timedelta(-5.0/24) - datetime.timedelta(8.5/24)
|
||||||
#dt_local = dt_utc.astimezone(pytz.timezone('America/Toronto')) - datetime.timedelta(4.5/24)
|
#dt_local = dt_utc.astimezone(pytz.timezone('America/Toronto')) - datetime.timedelta(8.5/24)
|
||||||
return dt_local
|
return dt_local
|
||||||
|
|
||||||
def get_fetchdate(self):
|
def get_fetchdate(self):
|
||||||
|
@ -106,10 +106,12 @@ def sanitize_file_name(name, substitute='_', as_unicode=False):
|
|||||||
name = name.encode(filesystem_encoding, 'ignore')
|
name = name.encode(filesystem_encoding, 'ignore')
|
||||||
one = _filename_sanitize.sub(substitute, name)
|
one = _filename_sanitize.sub(substitute, name)
|
||||||
one = re.sub(r'\s', ' ', one).strip()
|
one = re.sub(r'\s', ' ', one).strip()
|
||||||
one = re.sub(r'^\.+$', '_', one)
|
bname, ext = os.path.splitext(one)
|
||||||
|
one = re.sub(r'^\.+$', '_', bname)
|
||||||
if as_unicode:
|
if as_unicode:
|
||||||
one = one.decode(filesystem_encoding)
|
one = one.decode(filesystem_encoding)
|
||||||
one = one.replace('..', substitute)
|
one = one.replace('..', substitute)
|
||||||
|
one += ext
|
||||||
# Windows doesn't like path components that end with a period
|
# Windows doesn't like path components that end with a period
|
||||||
if one and one[-1] in ('.', ' '):
|
if one and one[-1] in ('.', ' '):
|
||||||
one = one[:-1]+'_'
|
one = one[:-1]+'_'
|
||||||
@ -132,8 +134,10 @@ def sanitize_file_name_unicode(name, substitute='_'):
|
|||||||
name]
|
name]
|
||||||
one = u''.join(chars)
|
one = u''.join(chars)
|
||||||
one = re.sub(r'\s', ' ', one).strip()
|
one = re.sub(r'\s', ' ', one).strip()
|
||||||
one = re.sub(r'^\.+$', '_', one)
|
bname, ext = os.path.splitext(one)
|
||||||
|
one = re.sub(r'^\.+$', '_', bname)
|
||||||
one = one.replace('..', substitute)
|
one = one.replace('..', substitute)
|
||||||
|
one += ext
|
||||||
# Windows doesn't like path components that end with a period or space
|
# Windows doesn't like path components that end with a period or space
|
||||||
if one and one[-1] in ('.', ' '):
|
if one and one[-1] in ('.', ' '):
|
||||||
one = one[:-1]+'_'
|
one = one[:-1]+'_'
|
||||||
|
@ -64,14 +64,24 @@ int do_mount(const char *dev, const char *mp) {
|
|||||||
snprintf(options, 1000, "rw,noexec,nosuid,sync,nodev");
|
snprintf(options, 1000, "rw,noexec,nosuid,sync,nodev");
|
||||||
snprintf(uids, 100, "%d", getuid());
|
snprintf(uids, 100, "%d", getuid());
|
||||||
snprintf(gids, 100, "%d", getgid());
|
snprintf(gids, 100, "%d", getgid());
|
||||||
|
#else
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
snprintf(options, 1000, "rw,noexec,nosuid,sync,-u=%d,-g=%d",getuid(),getgid());
|
||||||
#else
|
#else
|
||||||
snprintf(options, 1000, "rw,noexec,nosuid,sync,nodev,quiet,shortname=mixed,uid=%d,gid=%d,umask=077,fmask=0177,dmask=0077,utf8,iocharset=iso8859-1", getuid(), getgid());
|
snprintf(options, 1000, "rw,noexec,nosuid,sync,nodev,quiet,shortname=mixed,uid=%d,gid=%d,umask=077,fmask=0177,dmask=0077,utf8,iocharset=iso8859-1", getuid(), getgid());
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
ensure_root();
|
ensure_root();
|
||||||
|
|
||||||
#ifdef __NetBSD__
|
#ifdef __NetBSD__
|
||||||
execlp("mount_msdos", "mount_msdos", "-u", uids, "-g", gids, "-o", options, dev, mp, NULL);
|
execlp("mount_msdos", "mount_msdos", "-u", uids, "-g", gids, "-o", options, dev, mp, NULL);
|
||||||
|
#else
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
execlp("mount", "mount", "-t", "msdosfs", "-o", options, dev, mp, NULL);
|
||||||
#else
|
#else
|
||||||
execlp("mount", "mount", "-t", "auto", "-o", options, dev, mp, NULL);
|
execlp("mount", "mount", "-t", "auto", "-o", options, dev, mp, NULL);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
errsv = errno;
|
errsv = errno;
|
||||||
fprintf(stderr, "Failed to mount with error: %s\n", strerror(errsv));
|
fprintf(stderr, "Failed to mount with error: %s\n", strerror(errsv));
|
||||||
@ -91,8 +101,12 @@ int call_eject(const char *dev, const char *mp) {
|
|||||||
ensure_root();
|
ensure_root();
|
||||||
#ifdef __NetBSD__
|
#ifdef __NetBSD__
|
||||||
execlp("eject", "eject", dev, NULL);
|
execlp("eject", "eject", dev, NULL);
|
||||||
|
#else
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
execlp("umount", "umount", dev, NULL);
|
||||||
#else
|
#else
|
||||||
execlp("eject", "eject", "-s", dev, NULL);
|
execlp("eject", "eject", "-s", dev, NULL);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
/* execlp failed */
|
/* execlp failed */
|
||||||
errsv = errno;
|
errsv = errno;
|
||||||
@ -121,7 +135,11 @@ int call_umount(const char *dev, const char *mp) {
|
|||||||
|
|
||||||
if (pid == 0) { /* Child process */
|
if (pid == 0) { /* Child process */
|
||||||
ensure_root();
|
ensure_root();
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
execlp("umount", "umount", mp, NULL);
|
||||||
|
#else
|
||||||
execlp("umount", "umount", "-l", mp, NULL);
|
execlp("umount", "umount", "-l", mp, NULL);
|
||||||
|
#endif
|
||||||
/* execlp failed */
|
/* execlp failed */
|
||||||
errsv = errno;
|
errsv = errno;
|
||||||
fprintf(stderr, "Failed to umount with error: %s\n", strerror(errsv));
|
fprintf(stderr, "Failed to umount with error: %s\n", strerror(errsv));
|
||||||
|
@ -17,7 +17,7 @@ from itertools import repeat
|
|||||||
from calibre.devices.interface import DevicePlugin
|
from calibre.devices.interface import DevicePlugin
|
||||||
from calibre.devices.errors import DeviceError, FreeSpaceError
|
from calibre.devices.errors import DeviceError, FreeSpaceError
|
||||||
from calibre.devices.usbms.deviceconfig import DeviceConfig
|
from calibre.devices.usbms.deviceconfig import DeviceConfig
|
||||||
from calibre.constants import iswindows, islinux, isosx, plugins
|
from calibre.constants import iswindows, islinux, isosx, isfreebsd, plugins
|
||||||
from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to
|
from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
@ -701,7 +701,152 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
self._card_a_prefix = self._card_b_prefix
|
self._card_a_prefix = self._card_b_prefix
|
||||||
self._card_b_prefix = None
|
self._card_b_prefix = None
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
#
|
||||||
|
# open for FreeBSD
|
||||||
|
# find the device node or nodes that match the S/N we already have from the scanner
|
||||||
|
# and attempt to mount each one
|
||||||
|
# 1. get list of disk devices from sysctl
|
||||||
|
# 2. compare that list with the one from camcontrol
|
||||||
|
# 3. and see if it has a matching s/n
|
||||||
|
# 6. find any partitions/slices associated with each node
|
||||||
|
# 7. attempt to mount, using calibre-mount-helper, each one
|
||||||
|
# 8. when finished, we have a list of mount points and associated device nodes
|
||||||
|
#
|
||||||
|
def open_freebsd(self):
|
||||||
|
|
||||||
|
# this gives us access to the S/N, etc. of the reader that the scanner has found
|
||||||
|
# and the match routines for some of that data, like s/n, vendor ID, etc.
|
||||||
|
d=self.detected_device
|
||||||
|
|
||||||
|
if not d.serial:
|
||||||
|
raise DeviceError("Device has no S/N. Can't continue")
|
||||||
|
return False
|
||||||
|
|
||||||
|
devs={}
|
||||||
|
di=0
|
||||||
|
ndevs=4 # number of possible devices per reader (main, carda, cardb, launcher)
|
||||||
|
|
||||||
|
#get list of disk devices
|
||||||
|
p=subprocess.Popen(["sysctl", "kern.disks"], stdout=subprocess.PIPE)
|
||||||
|
kdsks=subprocess.Popen(["sed", "s/kern.disks: //"], stdin=p.stdout, stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
p.stdout.close()
|
||||||
|
#print kdsks
|
||||||
|
for dvc in kdsks.split():
|
||||||
|
# for each one that's also in the list of cam devices ...
|
||||||
|
p=subprocess.Popen(["camcontrol", "devlist"], stdout=subprocess.PIPE)
|
||||||
|
devmatch=subprocess.Popen(["grep", dvc], stdin=p.stdout, stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
p.stdout.close()
|
||||||
|
if devmatch:
|
||||||
|
#print "Checking ", devmatch
|
||||||
|
# ... see if we can get a S/N from the actual device node
|
||||||
|
sn=subprocess.Popen(["camcontrol", "inquiry", dvc, "-S"], stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
sn=sn[0:-1] # drop the trailing newline
|
||||||
|
#print "S/N = ", sn
|
||||||
|
if sn and d.match_serial(sn):
|
||||||
|
# we have a matching s/n, record this device node
|
||||||
|
#print "match found: ", dvc
|
||||||
|
devs[di]=dvc
|
||||||
|
di += 1
|
||||||
|
|
||||||
|
# sort the list of devices
|
||||||
|
for i in range(1,ndevs+1):
|
||||||
|
for j in reversed(range(1,i)):
|
||||||
|
if devs[j-1] > devs[j]:
|
||||||
|
x=devs[j-1]
|
||||||
|
devs[j-1]=devs[j]
|
||||||
|
devs[j]=x
|
||||||
|
#print devs
|
||||||
|
|
||||||
|
# now we need to see if any of these have slices/partitions
|
||||||
|
mtd=0
|
||||||
|
label="READER" # could use something more unique, like S/N or productID...
|
||||||
|
cmd = '/usr/local/bin/calibre-mount-helper'
|
||||||
|
cmd = [cmd, 'mount']
|
||||||
|
for i in range(0,ndevs):
|
||||||
|
cmd2="ls /dev/"+devs[i]+"*"
|
||||||
|
p=subprocess.Popen(cmd2, shell=True, stdout=subprocess.PIPE)
|
||||||
|
devs[i]=subprocess.Popen(["cut", "-d", "/", "-f" "3"], stdin=p.stdout, stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
p.stdout.close()
|
||||||
|
|
||||||
|
# try all the nodes to see what we can mount
|
||||||
|
for dev in devs[i].split():
|
||||||
|
mp='/media/'+label+'-'+dev
|
||||||
|
#print "trying ", dev, "on", mp
|
||||||
|
try:
|
||||||
|
p = subprocess.Popen(cmd + ["/dev/"+dev, mp])
|
||||||
|
except OSError:
|
||||||
|
raise DeviceError(_('Could not find mount helper: %s.')%cmd[0])
|
||||||
|
while p.poll() is None:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
if p.returncode == 0:
|
||||||
|
#print " mounted", dev
|
||||||
|
if i == 0:
|
||||||
|
self._main_prefix = mp
|
||||||
|
self._main_dev = "/dev/"+dev
|
||||||
|
#print "main = ", self._main_dev, self._main_prefix
|
||||||
|
if i == 1:
|
||||||
|
self._card_a_prefix = mp
|
||||||
|
self._card_a_dev = "/dev/"+dev
|
||||||
|
#print "card a = ", self._card_a_dev, self._card_a_prefix
|
||||||
|
if i == 2:
|
||||||
|
self._card_b_prefix = mp
|
||||||
|
self._card_b_dev = "/dev/"+dev
|
||||||
|
#print "card b = ", self._card_b_dev, self._card_b_prefix
|
||||||
|
|
||||||
|
mtd += 1
|
||||||
|
break
|
||||||
|
|
||||||
|
if mtd > 0:
|
||||||
|
return True
|
||||||
|
else :
|
||||||
|
return False
|
||||||
|
#
|
||||||
|
# ------------------------------------------------------
|
||||||
|
#
|
||||||
|
# this one is pretty simple:
|
||||||
|
# just umount each of the previously
|
||||||
|
# mounted filesystems, using the mount helper
|
||||||
|
#
|
||||||
|
def eject_freebsd(self):
|
||||||
|
cmd = '/usr/local/bin/calibre-mount-helper'
|
||||||
|
cmd = [cmd, 'eject']
|
||||||
|
|
||||||
|
if self._main_prefix:
|
||||||
|
#print "umount main:", cmd, self._main_dev, self._main_prefix
|
||||||
|
try:
|
||||||
|
p = subprocess.Popen(cmd + [self._main_dev, self._main_prefix])
|
||||||
|
except OSError:
|
||||||
|
raise DeviceError(
|
||||||
|
_('Could not find mount helper: %s.')%cmd[0])
|
||||||
|
while p.poll() is None:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
if self._card_a_prefix:
|
||||||
|
#print "umount card a:", cmd, self._card_a_dev, self._card_a_prefix
|
||||||
|
try:
|
||||||
|
p = subprocess.Popen(cmd + [self._card_a_dev, self._card_a_prefix])
|
||||||
|
except OSError:
|
||||||
|
raise DeviceError(
|
||||||
|
_('Could not find mount helper: %s.')%cmd[0])
|
||||||
|
while p.poll() is None:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
if self._card_b_prefix:
|
||||||
|
#print "umount card b:", cmd, self._card_b_dev, self._card_b_prefix
|
||||||
|
try:
|
||||||
|
p = subprocess.Popen(cmd + [self._card_b_dev, self._card_b_prefix])
|
||||||
|
except OSError:
|
||||||
|
raise DeviceError(
|
||||||
|
_('Could not find mount helper: %s.')%cmd[0])
|
||||||
|
while p.poll() is None:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
self._main_prefix = None
|
||||||
|
self._card_a_prefix = None
|
||||||
|
self._card_b_prefix = None
|
||||||
|
# ------------------------------------------------------
|
||||||
|
|
||||||
def open(self, library_uuid):
|
def open(self, library_uuid):
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
@ -712,6 +857,14 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
except DeviceError:
|
except DeviceError:
|
||||||
time.sleep(7)
|
time.sleep(7)
|
||||||
self.open_linux()
|
self.open_linux()
|
||||||
|
if isfreebsd:
|
||||||
|
self._main_dev = self._card_a_dev = self._card_b_dev = None
|
||||||
|
try:
|
||||||
|
self.open_freebsd()
|
||||||
|
except DeviceError:
|
||||||
|
subprocess.Popen(["camcontrol", "rescan", "all"])
|
||||||
|
time.sleep(2)
|
||||||
|
self.open_freebsd()
|
||||||
if iswindows:
|
if iswindows:
|
||||||
try:
|
try:
|
||||||
self.open_windows()
|
self.open_windows()
|
||||||
@ -800,6 +953,11 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
self.eject_linux()
|
self.eject_linux()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
if isfreebsd:
|
||||||
|
try:
|
||||||
|
self.eject_freebsd()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
if iswindows:
|
if iswindows:
|
||||||
try:
|
try:
|
||||||
self.eject_windows()
|
self.eject_windows()
|
||||||
|
@ -54,7 +54,7 @@ cpalmdoc_decompress(PyObject *self, PyObject *args) {
|
|||||||
// Map chars to bytes
|
// Map chars to bytes
|
||||||
for (j = 0; j < input_len; j++)
|
for (j = 0; j < input_len; j++)
|
||||||
input[j] = (_input[j] < 0) ? _input[j]+256 : _input[j];
|
input[j] = (_input[j] < 0) ? _input[j]+256 : _input[j];
|
||||||
output = (char *)PyMem_Malloc(sizeof(char)*(MAX(BUFFER, 5*input_len)));
|
output = (char *)PyMem_Malloc(sizeof(char)*(MAX(BUFFER, 8*input_len)));
|
||||||
if (output == NULL) return PyErr_NoMemory();
|
if (output == NULL) return PyErr_NoMemory();
|
||||||
|
|
||||||
while (i < input_len) {
|
while (i < input_len) {
|
||||||
|
@ -957,7 +957,10 @@ def get_metadata(stream):
|
|||||||
return get_metadata(stream)
|
return get_metadata(stream)
|
||||||
from calibre.utils.logging import Log
|
from calibre.utils.logging import Log
|
||||||
log = Log()
|
log = Log()
|
||||||
|
try:
|
||||||
mi = MetaInformation(os.path.basename(stream.name), [_('Unknown')])
|
mi = MetaInformation(os.path.basename(stream.name), [_('Unknown')])
|
||||||
|
except:
|
||||||
|
mi = MetaInformation(_('Unknown'), [_('Unknown')])
|
||||||
mh = MetadataHeader(stream, log)
|
mh = MetadataHeader(stream, log)
|
||||||
if mh.title and mh.title != _('Unknown'):
|
if mh.title and mh.title != _('Unknown'):
|
||||||
mi.title = mh.title
|
mi.title = mh.title
|
||||||
|
@ -38,3 +38,6 @@ class ShowQuickviewAction(InterfaceAction):
|
|||||||
Quickview(self.gui, self.gui.library_view, index)
|
Quickview(self.gui, self.gui.library_view, index)
|
||||||
self.current_instance.show()
|
self.current_instance.show()
|
||||||
|
|
||||||
|
def library_changed(self, db):
|
||||||
|
if self.current_instance and not self.current_instance.is_closed:
|
||||||
|
self.current_instance.set_database(db)
|
||||||
|
@ -18,16 +18,29 @@ class TableItem(QTableWidgetItem):
|
|||||||
A QTableWidgetItem that sorts on a separate string and uses ICU rules
|
A QTableWidgetItem that sorts on a separate string and uses ICU rules
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, val, sort):
|
def __init__(self, val, sort, idx=0):
|
||||||
self.sort = sort
|
self.sort = sort
|
||||||
|
self.sort_idx = idx
|
||||||
QTableWidgetItem.__init__(self, val)
|
QTableWidgetItem.__init__(self, val)
|
||||||
self.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable)
|
self.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable)
|
||||||
|
|
||||||
def __ge__(self, other):
|
def __ge__(self, other):
|
||||||
return sort_key(self.sort) >= sort_key(other.sort)
|
l = sort_key(self.sort)
|
||||||
|
r = sort_key(other.sort)
|
||||||
|
if l > r:
|
||||||
|
return 1
|
||||||
|
if l == r:
|
||||||
|
return self.sort_idx >= other.sort_idx
|
||||||
|
return 0
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return sort_key(self.sort) < sort_key(other.sort)
|
l = sort_key(self.sort)
|
||||||
|
r = sort_key(other.sort)
|
||||||
|
if l < r:
|
||||||
|
return 1
|
||||||
|
if l == r:
|
||||||
|
return self.sort_idx < other.sort_idx
|
||||||
|
return 0
|
||||||
|
|
||||||
class Quickview(QDialog, Ui_Quickview):
|
class Quickview(QDialog, Ui_Quickview):
|
||||||
|
|
||||||
@ -95,6 +108,15 @@ class Quickview(QDialog, Ui_Quickview):
|
|||||||
self.search_button.clicked.connect(self.do_search)
|
self.search_button.clicked.connect(self.do_search)
|
||||||
view.model().new_bookdisplay_data.connect(self.book_was_changed)
|
view.model().new_bookdisplay_data.connect(self.book_was_changed)
|
||||||
|
|
||||||
|
def set_database(self, db):
|
||||||
|
self.db = db
|
||||||
|
self.items.blockSignals(True)
|
||||||
|
self.books_table.blockSignals(True)
|
||||||
|
self.items.clear()
|
||||||
|
self.books_table.setRowCount(0)
|
||||||
|
self.books_table.blockSignals(False)
|
||||||
|
self.items.blockSignals(False)
|
||||||
|
|
||||||
# search button
|
# search button
|
||||||
def do_search(self):
|
def do_search(self):
|
||||||
if self.last_search is not None:
|
if self.last_search is not None:
|
||||||
@ -185,7 +207,7 @@ class Quickview(QDialog, Ui_Quickview):
|
|||||||
series = mi.format_field('series')[1]
|
series = mi.format_field('series')[1]
|
||||||
if series is None:
|
if series is None:
|
||||||
series = ''
|
series = ''
|
||||||
a = TableItem(series, series)
|
a = TableItem(series, mi.series, mi.series_index)
|
||||||
a.setToolTip(tt)
|
a.setToolTip(tt)
|
||||||
self.books_table.setItem(row, 2, a)
|
self.books_table.setItem(row, 2, a)
|
||||||
self.books_table.setRowHeight(row, self.books_table_row_height)
|
self.books_table.setRowHeight(row, self.books_table_row_height)
|
||||||
|
@ -57,19 +57,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
|
||||||
<spacer>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0" colspan="2">
|
<item row="3" column="0" colspan="2">
|
||||||
<layout class="QHBoxLayout">
|
<layout class="QHBoxLayout">
|
||||||
<item>
|
<item>
|
||||||
|
@ -1092,11 +1092,12 @@ class IdentifiersEdit(QLineEdit): # {{{
|
|||||||
for x in parts:
|
for x in parts:
|
||||||
c = x.split(':')
|
c = x.split(':')
|
||||||
if len(c) > 1:
|
if len(c) > 1:
|
||||||
if c[0] == 'isbn':
|
itype = c[0].lower()
|
||||||
|
if itype == 'isbn':
|
||||||
v = check_isbn(c[1])
|
v = check_isbn(c[1])
|
||||||
if v is not None:
|
if v is not None:
|
||||||
c[1] = v
|
c[1] = v
|
||||||
ans[c[0]] = c[1]
|
ans[itype] = c[1]
|
||||||
return ans
|
return ans
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
if not val:
|
if not val:
|
||||||
@ -1112,7 +1113,7 @@ class IdentifiersEdit(QLineEdit): # {{{
|
|||||||
if v is not None:
|
if v is not None:
|
||||||
val[k] = v
|
val[k] = v
|
||||||
ids = sorted(val.iteritems(), key=keygen)
|
ids = sorted(val.iteritems(), key=keygen)
|
||||||
txt = ', '.join(['%s:%s'%(k, v) for k, v in ids])
|
txt = ', '.join(['%s:%s'%(k.lower(), v) for k, v in ids])
|
||||||
self.setText(txt.strip())
|
self.setText(txt.strip())
|
||||||
self.setCursorPosition(0)
|
self.setCursorPosition(0)
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
@ -357,7 +357,6 @@ class Preferences(QMainWindow):
|
|||||||
bytearray(self.saveGeometry()))
|
bytearray(self.saveGeometry()))
|
||||||
if self.committed:
|
if self.committed:
|
||||||
self.gui.must_restart_before_config = self.must_restart
|
self.gui.must_restart_before_config = self.must_restart
|
||||||
self.gui.tags_view.set_new_model() # in case columns changed
|
|
||||||
self.gui.tags_view.recount()
|
self.gui.tags_view.recount()
|
||||||
self.gui.create_device_menu()
|
self.gui.create_device_menu()
|
||||||
self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
|
self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
|
||||||
|
@ -173,7 +173,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
def refresh_gui(self, gui):
|
def refresh_gui(self, gui):
|
||||||
gui.set_highlight_only_button_icon()
|
gui.set_highlight_only_button_icon()
|
||||||
if self.muc_changed:
|
if self.muc_changed:
|
||||||
gui.tags_view.set_new_model()
|
gui.tags_view.recount()
|
||||||
gui.search.search_as_you_type(config['search_as_you_type'])
|
gui.search.search_as_you_type(config['search_as_you_type'])
|
||||||
gui.search.do_search()
|
gui.search.do_search()
|
||||||
|
|
||||||
|
@ -514,7 +514,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
for category in self.category_nodes:
|
for category in self.category_nodes:
|
||||||
process_one_node(category, state_map.get(category.py_name, {}))
|
process_one_node(category, state_map.get(category.category_key, {}))
|
||||||
|
|
||||||
# Drag'n Drop {{{
|
# Drag'n Drop {{{
|
||||||
def mimeTypes(self):
|
def mimeTypes(self):
|
||||||
@ -851,7 +851,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
|
|
||||||
def index_for_category(self, name):
|
def index_for_category(self, name):
|
||||||
for row, category in enumerate(self.category_nodes):
|
for row, category in enumerate(self.category_nodes):
|
||||||
if category.py_name == name:
|
if category.category_key == name:
|
||||||
return self.index(row, 0, QModelIndex())
|
return self.index(row, 0, QModelIndex())
|
||||||
|
|
||||||
def columnCount(self, parent):
|
def columnCount(self, parent):
|
||||||
|
@ -129,10 +129,10 @@ class TagsView(QTreeView): # {{{
|
|||||||
expanded_categories = []
|
expanded_categories = []
|
||||||
for row, category in enumerate(self._model.category_nodes):
|
for row, category in enumerate(self._model.category_nodes):
|
||||||
if self.isExpanded(self._model.index(row, 0, QModelIndex())):
|
if self.isExpanded(self._model.index(row, 0, QModelIndex())):
|
||||||
expanded_categories.append(category.py_name)
|
expanded_categories.append(category.category_key)
|
||||||
states = [c.tag.state for c in category.child_tags()]
|
states = [c.tag.state for c in category.child_tags()]
|
||||||
names = [(c.tag.name, c.tag.category) for c in category.child_tags()]
|
names = [(c.tag.name, c.tag.category) for c in category.child_tags()]
|
||||||
state_map[category.py_name] = dict(izip(names, states))
|
state_map[category.category_key] = dict(izip(names, states))
|
||||||
return expanded_categories, state_map
|
return expanded_categories, state_map
|
||||||
|
|
||||||
def reread_collapse_parameters(self):
|
def reread_collapse_parameters(self):
|
||||||
@ -571,9 +571,11 @@ class TagsView(QTreeView): # {{{
|
|||||||
def show_item_at_index(self, idx, box=False,
|
def show_item_at_index(self, idx, box=False,
|
||||||
position=QTreeView.PositionAtCenter):
|
position=QTreeView.PositionAtCenter):
|
||||||
if idx.isValid() and idx.data(Qt.UserRole).toPyObject() is not self._model.root_item:
|
if idx.isValid() and idx.data(Qt.UserRole).toPyObject() is not self._model.root_item:
|
||||||
|
self.expand(self._model.parent(idx)) # Needed otherwise Qt sometimes segfaults if the
|
||||||
|
# node is buried in a collapsed, off
|
||||||
|
# screen hierarchy
|
||||||
self.setCurrentIndex(idx)
|
self.setCurrentIndex(idx)
|
||||||
self.scrollTo(idx, position)
|
self.scrollTo(idx, position)
|
||||||
self.setCurrentIndex(idx)
|
|
||||||
if box:
|
if box:
|
||||||
self._model.set_boxed(idx)
|
self._model.set_boxed(idx)
|
||||||
|
|
||||||
|
@ -1249,6 +1249,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
ret = tempfile.SpooledTemporaryFile(max_size=SPOOL_SIZE)
|
ret = tempfile.SpooledTemporaryFile(max_size=SPOOL_SIZE)
|
||||||
shutil.copyfileobj(f, ret)
|
shutil.copyfileobj(f, ret)
|
||||||
ret.seek(0)
|
ret.seek(0)
|
||||||
|
# Various bits of code try to use the name as the default
|
||||||
|
# title when reading metadata, so set it
|
||||||
|
ret.name = f.name
|
||||||
else:
|
else:
|
||||||
ret = f.read()
|
ret = f.read()
|
||||||
return ret
|
return ret
|
||||||
@ -1446,7 +1449,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
raise ValueError('sort ' + sort + ' not a valid value')
|
raise ValueError('sort ' + sort + ' not a valid value')
|
||||||
|
|
||||||
self.books_list_filter.change([] if not ids else ids)
|
self.books_list_filter.change([] if not ids else ids)
|
||||||
id_filter = None if not ids else frozenset(ids)
|
id_filter = None if ids is None else frozenset(ids)
|
||||||
|
|
||||||
tb_cats = self.field_metadata
|
tb_cats = self.field_metadata
|
||||||
tcategories = {}
|
tcategories = {}
|
||||||
@ -1524,7 +1527,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
rating_dex = self.FIELD_MAP['rating']
|
rating_dex = self.FIELD_MAP['rating']
|
||||||
tag_class = LibraryDatabase2.TCat_Tag
|
tag_class = LibraryDatabase2.TCat_Tag
|
||||||
for book in self.data.iterall():
|
for book in self.data.iterall():
|
||||||
if id_filter and book[id_dex] not in id_filter:
|
if id_filter is not None and book[id_dex] not in id_filter:
|
||||||
continue
|
continue
|
||||||
rating = book[rating_dex]
|
rating = book[rating_dex]
|
||||||
# We kept track of all possible category field_map positions above
|
# We kept track of all possible category field_map positions above
|
||||||
|
@ -558,11 +558,16 @@ Most readers do not support this. You should complain to the manufacturer about
|
|||||||
|
|
||||||
Another alternative is to create a catalog in ebook form containing a listing of all the books in your calibre library, with their metadata. Click the arrow next to the convert button to access the catalog creation tool. And before you ask, no you cannot have the catalog "link directly to" books on your reader.
|
Another alternative is to create a catalog in ebook form containing a listing of all the books in your calibre library, with their metadata. Click the arrow next to the convert button to access the catalog creation tool. And before you ask, no you cannot have the catalog "link directly to" books on your reader.
|
||||||
|
|
||||||
|
How do I get |app| to use my HTTP proxy?
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
By default, |app| uses whatever proxy settings are set in your OS. Sometimes these are incorrect, for example, on windows if you don't use Internet Explorer then the proxy settings may not be up to date. You can tell |app| to use a particular proxy server by setting the http_proxy environment variable. The format of the variable is: http://username:password@servername you should ask your network admin to give you the correct value for this variable. Note that |app| only supports HTTP proxies not SOCKS proxies. You can see the current proxies used by |app| in Preferences->Miscellaneous.
|
||||||
|
|
||||||
I want some feature added to |app|. What can I do?
|
I want some feature added to |app|. What can I do?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
You have two choices:
|
You have two choices:
|
||||||
1. Create a patch by hacking on |app| and send it to me for review and inclusion. See `Development <http://calibre-ebook.com/get-involved>`_.
|
1. Create a patch by hacking on |app| and send it to me for review and inclusion. See `Development <http://calibre-ebook.com/get-involved>`_.
|
||||||
2. `Open a ticket <http://calibre-ebook.com/bugs>`_ (you have to register and login first). Remember that |app| development is done by volunteers, so if you get no response to your feature request, it means no one feels like implementing it.
|
2. `Open a bug requesting the feature <http://calibre-ebook.com/bugs>`_ . Remember that |app| development is done by volunteers, so if you get no response to your feature request, it means no one feels like implementing it.
|
||||||
|
|
||||||
Why doesn't |app| have an automatic update?
|
Why doesn't |app| have an automatic update?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Loading…
x
Reference in New Issue
Block a user