mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Automated conversion of % format specifiers
Using ruff. Does not change any translatable strings. There are still several thousand usages of % left that ruff wont auto-convert. Get to them someday.
This commit is contained in:
parent
39f7f616bc
commit
5c7dc9613b
@ -27,7 +27,7 @@ for name, src in sources.items():
|
|||||||
try:
|
try:
|
||||||
for sz in (16, 32, 128, 256, 512, 1024):
|
for sz in (16, 32, 128, 256, 512, 1024):
|
||||||
iname = f'icon_{sz}x{sz}.png'
|
iname = f'icon_{sz}x{sz}.png'
|
||||||
iname2x = 'icon_{0}x{0}@2x.png'.format(sz // 2)
|
iname2x = f'icon_{sz // 2}x{sz // 2}@2x.png'
|
||||||
if src.endswith('.svg'):
|
if src.endswith('.svg'):
|
||||||
subprocess.check_call(['rsvg-convert', src, '-w', str(sz), '-h', str(sz), '-o', iname])
|
subprocess.check_call(['rsvg-convert', src, '-w', str(sz), '-h', str(sz), '-o', iname])
|
||||||
else:
|
else:
|
||||||
|
@ -656,7 +656,7 @@ class Build(Command):
|
|||||||
os.chdir(bdir)
|
os.chdir(bdir)
|
||||||
try:
|
try:
|
||||||
self.check_call(cmd + ['-S', os.path.dirname(sources[0])])
|
self.check_call(cmd + ['-S', os.path.dirname(sources[0])])
|
||||||
self.check_call([self.env.make] + ['-j{}'.format(cpu_count or 1)])
|
self.check_call([self.env.make] + [f'-j{cpu_count or 1}'])
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
os.rename(self.j(bdir, 'libheadless.so'), target)
|
os.rename(self.j(bdir, 'libheadless.so'), target)
|
||||||
@ -733,7 +733,7 @@ sip-file = {os.path.basename(sipf)!r}
|
|||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
if is_macos_universal_build:
|
if is_macos_universal_build:
|
||||||
env['ARCHS'] = 'x86_64 arm64'
|
env['ARCHS'] = 'x86_64 arm64'
|
||||||
self.check_call([self.env.make] + ([] if iswindows else ['-j{}'.format(os.cpu_count() or 1)]), env=env)
|
self.check_call([self.env.make] + ([] if iswindows else [f'-j{os.cpu_count() or 1}']), env=env)
|
||||||
e = 'pyd' if iswindows else 'so'
|
e = 'pyd' if iswindows else 'so'
|
||||||
m = glob.glob(f'{ext.name}/{ext.name}.*{e}')
|
m = glob.glob(f'{ext.name}/{ext.name}.*{e}')
|
||||||
if not m:
|
if not m:
|
||||||
|
@ -114,7 +114,7 @@ class Check(Command):
|
|||||||
for i, f in enumerate(dirty_files):
|
for i, f in enumerate(dirty_files):
|
||||||
self.info('\tChecking', f)
|
self.info('\tChecking', f)
|
||||||
if self.file_has_errors(f):
|
if self.file_has_errors(f):
|
||||||
self.info('{} files left to check'.format(len(dirty_files) - i - 1))
|
self.info(f'{len(dirty_files) - i - 1} files left to check')
|
||||||
try:
|
try:
|
||||||
edit_file(f)
|
edit_file(f)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -56,16 +56,16 @@ class ReadFileWithProgressReporting: # {{{
|
|||||||
eta = int((self._total - self.tell()) / bit_rate) + 1
|
eta = int((self._total - self.tell()) / bit_rate) + 1
|
||||||
eta_m, eta_s = eta / 60, eta % 60
|
eta_m, eta_s = eta / 60, eta % 60
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
' {:.1f}% {:.1f}/{:.1f}MB {:.1f} KB/sec {} minutes, {} seconds left'
|
f' {frac * 100:.1f}% {mb_pos:.1f}/{mb_tot:.1f}MB {kb_rate:.1f} KB/sec {eta_m} minutes, {eta_s} seconds left'
|
||||||
.format(frac * 100, mb_pos, mb_tot, kb_rate, eta_m, eta_s)
|
|
||||||
)
|
)
|
||||||
sys.stdout.write('\x1b[u')
|
sys.stdout.write('\x1b[u')
|
||||||
if self.tell() >= self._total:
|
if self.tell() >= self._total:
|
||||||
sys.stdout.write('\n')
|
sys.stdout.write('\n')
|
||||||
t = int(time.time() - self.start_time) + 1
|
t = int(time.time() - self.start_time) + 1
|
||||||
print(
|
print(
|
||||||
'Upload took {} minutes and {} seconds at {:.1f} KB/sec'
|
f'Upload took {t/60} minutes and {t % 60} seconds at {kb_rate:.1f} KB/sec'
|
||||||
.format(t/60, t % 60, kb_rate)
|
|
||||||
)
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
@ -388,7 +388,7 @@ class Bootstrap(Command):
|
|||||||
st = time.time()
|
st = time.time()
|
||||||
clone_cmd.insert(2, '--depth=1')
|
clone_cmd.insert(2, '--depth=1')
|
||||||
subprocess.check_call(clone_cmd, cwd=self.d(self.SRC))
|
subprocess.check_call(clone_cmd, cwd=self.d(self.SRC))
|
||||||
print('Downloaded translations in {} seconds'.format(int(time.time() - st)))
|
print(f'Downloaded translations in {int(time.time() - st)} seconds')
|
||||||
else:
|
else:
|
||||||
if os.path.exists(tdir):
|
if os.path.exists(tdir):
|
||||||
subprocess.check_call(['git', 'pull'], cwd=tdir)
|
subprocess.check_call(['git', 'pull'], cwd=tdir)
|
||||||
|
@ -460,7 +460,7 @@ def plugin_to_index(plugin, count):
|
|||||||
released = datetime(*tuple(map(int, re.split(r'\D', plugin['last_modified'])))[:6]).strftime('%e %b, %Y').lstrip()
|
released = datetime(*tuple(map(int, re.split(r'\D', plugin['last_modified'])))[:6]).strftime('%e %b, %Y').lstrip()
|
||||||
details = [
|
details = [
|
||||||
'Version: <b>{}</b>'.format(escape('.'.join(map(str, plugin['version'])))),
|
'Version: <b>{}</b>'.format(escape('.'.join(map(str, plugin['version'])))),
|
||||||
'Released: <b>{}</b>'.format(escape(released)),
|
f'Released: <b>{escape(released)}</b>',
|
||||||
'Author: {}'.format(escape(plugin['author'])),
|
'Author: {}'.format(escape(plugin['author'])),
|
||||||
'calibre: {}'.format(escape('.'.join(map(str, plugin['minimum_calibre_version'])))),
|
'calibre: {}'.format(escape('.'.join(map(str, plugin['minimum_calibre_version'])))),
|
||||||
'Platforms: {}'.format(escape(', '.join(sorted(plugin['supported_platforms']) or ['all']))),
|
'Platforms: {}'.format(escape(', '.join(sorted(plugin['supported_platforms']) or ['all']))),
|
||||||
|
@ -50,9 +50,9 @@ class Stage2(Command):
|
|||||||
platforms = 'linux64', 'linuxarm64', 'osx', 'win'
|
platforms = 'linux64', 'linuxarm64', 'osx', 'win'
|
||||||
for x in platforms:
|
for x in platforms:
|
||||||
cmd = (
|
cmd = (
|
||||||
'''{exe} -c "import subprocess; subprocess.Popen(['{exe}', './setup.py', '{x}']).wait() != 0 and'''
|
f'''{sys.executable} -c "import subprocess; subprocess.Popen(['{sys.executable}', './setup.py', '{x}']).wait() != 0 and'''
|
||||||
''' input('Build of {x} failed, press Enter to exit');"'''
|
f''' input('Build of {x} failed, press Enter to exit');"'''
|
||||||
).format(exe=sys.executable, x=x)
|
)
|
||||||
session.append('title ' + x)
|
session.append('title ' + x)
|
||||||
session.append('launch ' + cmd)
|
session.append('launch ' + cmd)
|
||||||
|
|
||||||
@ -220,8 +220,8 @@ class Manual(Command):
|
|||||||
if x and not os.path.exists(x):
|
if x and not os.path.exists(x):
|
||||||
os.symlink('.', x)
|
os.symlink('.', x)
|
||||||
self.info(
|
self.info(
|
||||||
'Built manual for {} languages in {} minutes'
|
f'Built manual for {len(jobs)} languages in {int((time.time() - st) / 60.)} minutes'
|
||||||
.format(len(jobs), int((time.time() - st) / 60.))
|
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
@ -335,6 +335,6 @@ class TagRelease(Command):
|
|||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
self.info('Tagging release')
|
self.info('Tagging release')
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
'git tag -s v{0} -m "version-{0}"'.format(__version__).split()
|
f'git tag -s v{__version__} -m "version-{__version__}"'.split()
|
||||||
)
|
)
|
||||||
subprocess.check_call(f'git push origin v{__version__}'.split())
|
subprocess.check_call(f'git push origin v{__version__}'.split())
|
||||||
|
@ -361,12 +361,11 @@ class UploadDemo(Command): # {{{
|
|||||||
|
|
||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
check_call(
|
check_call(
|
||||||
'''ebook-convert {}/demo.html /tmp/html2lrf.lrf '''
|
f'''ebook-convert {self.j(self.SRC, HTML2LRF)}/demo.html /tmp/html2lrf.lrf '''
|
||||||
'''--title='Demonstration of html2lrf' --authors='Kovid Goyal' '''
|
'''--title='Demonstration of html2lrf' --authors='Kovid Goyal' '''
|
||||||
'''--header '''
|
'''--header '''
|
||||||
'''--serif-family "/usr/share/fonts/corefonts, Times New Roman" '''
|
'''--serif-family "/usr/share/fonts/corefonts, Times New Roman" '''
|
||||||
'''--mono-family "/usr/share/fonts/corefonts, Andale Mono" '''
|
'''--mono-family "/usr/share/fonts/corefonts, Andale Mono" ''',
|
||||||
''''''.format(self.j(self.SRC, HTML2LRF)),
|
|
||||||
shell=True
|
shell=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ def main():
|
|||||||
else:
|
else:
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
raise SystemExit('Usage: win-ci.py sw|build|test')
|
raise SystemExit('Usage: win-ci.py sw|build|test')
|
||||||
raise SystemExit('{!r} is not a valid action'.format(sys.argv[-1]))
|
raise SystemExit(f'{sys.argv[-1]!r} is not a valid action')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -292,7 +292,7 @@ def get_proxy_info(proxy_scheme, proxy_string):
|
|||||||
'''
|
'''
|
||||||
from polyglot.urllib import urlparse
|
from polyglot.urllib import urlparse
|
||||||
try:
|
try:
|
||||||
proxy_url = '%s://%s'%(proxy_scheme, proxy_string)
|
proxy_url = f'{proxy_scheme}://{proxy_string}'
|
||||||
urlinfo = urlparse(proxy_url)
|
urlinfo = urlparse(proxy_url)
|
||||||
ans = {
|
ans = {
|
||||||
'scheme': urlinfo.scheme,
|
'scheme': urlinfo.scheme,
|
||||||
|
@ -146,7 +146,7 @@ def _get_cache_dir():
|
|||||||
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
try:
|
try:
|
||||||
candidate = os.path.join(winutil.special_folder_path(winutil.CSIDL_LOCAL_APPDATA), '%s-cache'%__appname__)
|
candidate = os.path.join(winutil.special_folder_path(winutil.CSIDL_LOCAL_APPDATA), f'{__appname__}-cache')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return confcache
|
return confcache
|
||||||
elif ismacos:
|
elif ismacos:
|
||||||
@ -341,7 +341,7 @@ class Plugins(collections.abc.Mapping):
|
|||||||
try:
|
try:
|
||||||
return import_module('calibre_extensions.' + name), ''
|
return import_module('calibre_extensions.' + name), ''
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
raise KeyError('No plugin named %r'%name)
|
raise KeyError(f'No plugin named {name!r}')
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return None, str(err)
|
return None, str(err)
|
||||||
|
|
||||||
|
@ -322,8 +322,7 @@ class Plugin: # {{{
|
|||||||
interface. It is called when the user does: calibre-debug -r "Plugin
|
interface. It is called when the user does: calibre-debug -r "Plugin
|
||||||
Name". Any arguments passed are present in the args variable.
|
Name". Any arguments passed are present in the args variable.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError('The %s plugin has no command line interface'
|
raise NotImplementedError(f'The {self.name} plugin has no command line interface')
|
||||||
%self.name)
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -540,7 +539,7 @@ class CatalogPlugin(Plugin): # {{{
|
|||||||
Custom fields sort after standard fields
|
Custom fields sort after standard fields
|
||||||
'''
|
'''
|
||||||
if key.startswith('#'):
|
if key.startswith('#'):
|
||||||
return '~%s' % key[1:]
|
return f'~{key[1:]}'
|
||||||
else:
|
else:
|
||||||
return key
|
return key
|
||||||
|
|
||||||
@ -575,9 +574,8 @@ class CatalogPlugin(Plugin): # {{{
|
|||||||
if requested_fields - all_fields:
|
if requested_fields - all_fields:
|
||||||
from calibre.library import current_library_name
|
from calibre.library import current_library_name
|
||||||
invalid_fields = sorted(requested_fields - all_fields)
|
invalid_fields = sorted(requested_fields - all_fields)
|
||||||
print('invalid --fields specified: %s' % ', '.join(invalid_fields))
|
print('invalid --fields specified: {}'.format(', '.join(invalid_fields)))
|
||||||
print("available fields in '%s': %s" %
|
print("available fields in '{}': {}".format(current_library_name(), ', '.join(sorted(all_fields))))
|
||||||
(current_library_name(), ', '.join(sorted(all_fields))))
|
|
||||||
raise ValueError('unable to generate catalog with specified fields')
|
raise ValueError('unable to generate catalog with specified fields')
|
||||||
|
|
||||||
fields = [x for x in of if x in all_fields]
|
fields = [x for x in of if x in all_fields]
|
||||||
|
@ -78,10 +78,9 @@ class OptionRecommendation:
|
|||||||
def validate_parameters(self):
|
def validate_parameters(self):
|
||||||
if self.option.choices and self.recommended_value not in \
|
if self.option.choices and self.recommended_value not in \
|
||||||
self.option.choices:
|
self.option.choices:
|
||||||
raise ValueError('OpRec: %s: Recommended value not in choices'%
|
raise ValueError(f'OpRec: {self.option.name}: Recommended value not in choices')
|
||||||
self.option.name)
|
|
||||||
if not (isinstance(self.recommended_value, (numbers.Number, bytes, str)) or self.recommended_value is None):
|
if not (isinstance(self.recommended_value, (numbers.Number, bytes, str)) or self.recommended_value is None):
|
||||||
raise ValueError('OpRec: %s:'%self.option.name + repr(
|
raise ValueError(f'OpRec: {self.option.name}:' + repr(
|
||||||
self.recommended_value) + ' is not a string or a number')
|
self.recommended_value) + ' is not a string or a number')
|
||||||
|
|
||||||
|
|
||||||
@ -229,7 +228,7 @@ class InputFormatPlugin(Plugin):
|
|||||||
def __call__(self, stream, options, file_ext, log,
|
def __call__(self, stream, options, file_ext, log,
|
||||||
accelerators, output_dir):
|
accelerators, output_dir):
|
||||||
try:
|
try:
|
||||||
log('InputFormatPlugin: %s running'%self.name)
|
log(f'InputFormatPlugin: {self.name} running')
|
||||||
if hasattr(stream, 'name'):
|
if hasattr(stream, 'name'):
|
||||||
log('on', stream.name)
|
log('on', stream.name)
|
||||||
except:
|
except:
|
||||||
|
@ -85,7 +85,7 @@ def disable_plugin(plugin_or_name):
|
|||||||
if plugin is None:
|
if plugin is None:
|
||||||
raise ValueError(f'No plugin named: {x} found')
|
raise ValueError(f'No plugin named: {x} found')
|
||||||
if not plugin.can_be_disabled:
|
if not plugin.can_be_disabled:
|
||||||
raise ValueError('Plugin %s cannot be disabled'%x)
|
raise ValueError(f'Plugin {x} cannot be disabled')
|
||||||
dp = config['disabled_plugins']
|
dp = config['disabled_plugins']
|
||||||
dp.add(x)
|
dp.add(x)
|
||||||
config['disabled_plugins'] = dp
|
config['disabled_plugins'] = dp
|
||||||
@ -199,7 +199,7 @@ def _run_filetype_plugins(path_to_file, ft=None, occasion='preprocess'):
|
|||||||
try:
|
try:
|
||||||
nfp = plugin.run(nfp) or nfp
|
nfp = plugin.run(nfp) or nfp
|
||||||
except:
|
except:
|
||||||
print('Running file type plugin %s failed with traceback:'%plugin.name, file=oe)
|
print(f'Running file type plugin {plugin.name} failed with traceback:', file=oe)
|
||||||
traceback.print_exc(file=oe)
|
traceback.print_exc(file=oe)
|
||||||
sys.stdout, sys.stderr = oo, oe
|
sys.stdout, sys.stderr = oo, oe
|
||||||
def x(j):
|
def x(j):
|
||||||
@ -526,10 +526,10 @@ def add_plugin(path_to_zip_file):
|
|||||||
plugin = load_plugin(path_to_zip_file)
|
plugin = load_plugin(path_to_zip_file)
|
||||||
if plugin.name in builtin_names:
|
if plugin.name in builtin_names:
|
||||||
raise NameConflict(
|
raise NameConflict(
|
||||||
'A builtin plugin with the name %r already exists' % plugin.name)
|
f'A builtin plugin with the name {plugin.name!r} already exists')
|
||||||
if plugin.name in get_system_plugins():
|
if plugin.name in get_system_plugins():
|
||||||
raise NameConflict(
|
raise NameConflict(
|
||||||
'A system plugin with the name %r already exists' % plugin.name)
|
f'A system plugin with the name {plugin.name!r} already exists')
|
||||||
plugin = initialize_plugin(plugin, path_to_zip_file, PluginInstallationType.EXTERNAL)
|
plugin = initialize_plugin(plugin, path_to_zip_file, PluginInstallationType.EXTERNAL)
|
||||||
plugins = config['plugins']
|
plugins = config['plugins']
|
||||||
zfp = os.path.join(plugin_dir, plugin.name+'.zip')
|
zfp = os.path.join(plugin_dir, plugin.name+'.zip')
|
||||||
@ -892,7 +892,7 @@ def main(args=sys.argv):
|
|||||||
name, custom = opts.customize_plugin, ''
|
name, custom = opts.customize_plugin, ''
|
||||||
plugin = find_plugin(name.strip())
|
plugin = find_plugin(name.strip())
|
||||||
if plugin is None:
|
if plugin is None:
|
||||||
print('No plugin with the name %s exists'%name)
|
print(f'No plugin with the name {name} exists')
|
||||||
return 1
|
return 1
|
||||||
customize_plugin(plugin, custom)
|
customize_plugin(plugin, custom)
|
||||||
if opts.enable_plugin is not None:
|
if opts.enable_plugin is not None:
|
||||||
|
@ -296,14 +296,14 @@ class CalibrePluginFinder:
|
|||||||
|
|
||||||
def load(self, path_to_zip_file):
|
def load(self, path_to_zip_file):
|
||||||
if not os.access(path_to_zip_file, os.R_OK):
|
if not os.access(path_to_zip_file, os.R_OK):
|
||||||
raise PluginNotFound('Cannot access %r'%path_to_zip_file)
|
raise PluginNotFound(f'Cannot access {path_to_zip_file!r}')
|
||||||
|
|
||||||
with zipfile.ZipFile(path_to_zip_file) as zf:
|
with zipfile.ZipFile(path_to_zip_file) as zf:
|
||||||
plugin_name = self._locate_code(zf, path_to_zip_file)
|
plugin_name = self._locate_code(zf, path_to_zip_file)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ans = None
|
ans = None
|
||||||
plugin_module = 'calibre_plugins.%s'%plugin_name
|
plugin_module = f'calibre_plugins.{plugin_name}'
|
||||||
m = sys.modules.get(plugin_module, None)
|
m = sys.modules.get(plugin_module, None)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
reload(m)
|
reload(m)
|
||||||
@ -315,8 +315,7 @@ class CalibrePluginFinder:
|
|||||||
obj.name != 'Trivial Plugin':
|
obj.name != 'Trivial Plugin':
|
||||||
plugin_classes.append(obj)
|
plugin_classes.append(obj)
|
||||||
if not plugin_classes:
|
if not plugin_classes:
|
||||||
raise InvalidPlugin('No plugin class found in %s:%s'%(
|
raise InvalidPlugin(f'No plugin class found in {as_unicode(path_to_zip_file)}:{plugin_name}')
|
||||||
as_unicode(path_to_zip_file), plugin_name))
|
|
||||||
if len(plugin_classes) > 1:
|
if len(plugin_classes) > 1:
|
||||||
plugin_classes.sort(key=lambda c:(getattr(c, '__module__', None) or '').count('.'))
|
plugin_classes.sort(key=lambda c:(getattr(c, '__module__', None) or '').count('.'))
|
||||||
|
|
||||||
@ -324,14 +323,12 @@ class CalibrePluginFinder:
|
|||||||
|
|
||||||
if ans.minimum_calibre_version > numeric_version:
|
if ans.minimum_calibre_version > numeric_version:
|
||||||
raise InvalidPlugin(
|
raise InvalidPlugin(
|
||||||
'The plugin at %s needs a version of calibre >= %s' %
|
'The plugin at {} needs a version of calibre >= {}'.format(as_unicode(path_to_zip_file), '.'.join(map(str,
|
||||||
(as_unicode(path_to_zip_file), '.'.join(map(str,
|
|
||||||
ans.minimum_calibre_version))))
|
ans.minimum_calibre_version))))
|
||||||
|
|
||||||
if platform not in ans.supported_platforms:
|
if platform not in ans.supported_platforms:
|
||||||
raise InvalidPlugin(
|
raise InvalidPlugin(
|
||||||
'The plugin at %s cannot be used on %s' %
|
f'The plugin at {as_unicode(path_to_zip_file)} cannot be used on {platform}')
|
||||||
(as_unicode(path_to_zip_file), platform))
|
|
||||||
|
|
||||||
return ans
|
return ans
|
||||||
except:
|
except:
|
||||||
@ -359,8 +356,7 @@ class CalibrePluginFinder:
|
|||||||
else:
|
else:
|
||||||
if self._identifier_pat.match(plugin_name) is None:
|
if self._identifier_pat.match(plugin_name) is None:
|
||||||
raise InvalidPlugin(
|
raise InvalidPlugin(
|
||||||
'The plugin at %r uses an invalid import name: %r' %
|
f'The plugin at {path_to_zip_file!r} uses an invalid import name: {plugin_name!r}')
|
||||||
(path_to_zip_file, plugin_name))
|
|
||||||
|
|
||||||
pynames = [x for x in names if x.endswith('.py')]
|
pynames = [x for x in names if x.endswith('.py')]
|
||||||
|
|
||||||
@ -394,9 +390,8 @@ class CalibrePluginFinder:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if '__init__' not in names:
|
if '__init__' not in names:
|
||||||
raise InvalidPlugin(('The plugin in %r is invalid. It does not '
|
raise InvalidPlugin(f'The plugin in {path_to_zip_file!r} is invalid. It does not '
|
||||||
'contain a top-level __init__.py file')
|
'contain a top-level __init__.py file')
|
||||||
% path_to_zip_file)
|
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self.loaded_plugins[plugin_name] = path_to_zip_file, names, tuple(all_names)
|
self.loaded_plugins[plugin_name] = path_to_zip_file, names, tuple(all_names)
|
||||||
|
@ -163,7 +163,7 @@ class DBPrefs(dict): # {{{
|
|||||||
self.__setitem__(key, val)
|
self.__setitem__(key, val)
|
||||||
|
|
||||||
def get_namespaced(self, namespace, key, default=None):
|
def get_namespaced(self, namespace, key, default=None):
|
||||||
key = 'namespaced:%s:%s'%(namespace, key)
|
key = f'namespaced:{namespace}:{key}'
|
||||||
try:
|
try:
|
||||||
return dict.__getitem__(self, key)
|
return dict.__getitem__(self, key)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -174,7 +174,7 @@ class DBPrefs(dict): # {{{
|
|||||||
raise KeyError('Colons are not allowed in keys')
|
raise KeyError('Colons are not allowed in keys')
|
||||||
if ':' in namespace:
|
if ':' in namespace:
|
||||||
raise KeyError('Colons are not allowed in the namespace')
|
raise KeyError('Colons are not allowed in the namespace')
|
||||||
key = 'namespaced:%s:%s'%(namespace, key)
|
key = f'namespaced:{namespace}:{key}'
|
||||||
self[key] = val
|
self[key] = val
|
||||||
|
|
||||||
def write_serialized(self, library_path):
|
def write_serialized(self, library_path):
|
||||||
@ -273,7 +273,7 @@ def IdentifiersConcat():
|
|||||||
'''String concatenation aggregator for the identifiers map'''
|
'''String concatenation aggregator for the identifiers map'''
|
||||||
|
|
||||||
def step(ctxt, key, val):
|
def step(ctxt, key, val):
|
||||||
ctxt.append('%s:%s'%(key, val))
|
ctxt.append(f'{key}:{val}')
|
||||||
|
|
||||||
def finalize(ctxt):
|
def finalize(ctxt):
|
||||||
try:
|
try:
|
||||||
@ -684,7 +684,7 @@ class DB:
|
|||||||
suffix = 1
|
suffix = 1
|
||||||
while icu_lower(cat + str(suffix)) in catmap:
|
while icu_lower(cat + str(suffix)) in catmap:
|
||||||
suffix += 1
|
suffix += 1
|
||||||
prints('Renaming user category %s to %s'%(cat, cat+str(suffix)))
|
prints(f'Renaming user category {cat} to {cat+str(suffix)}')
|
||||||
user_cats[cat + str(suffix)] = user_cats[cat]
|
user_cats[cat + str(suffix)] = user_cats[cat]
|
||||||
del user_cats[cat]
|
del user_cats[cat]
|
||||||
cats_changed = True
|
cats_changed = True
|
||||||
@ -700,7 +700,7 @@ class DB:
|
|||||||
for num, label in self.conn.get(
|
for num, label in self.conn.get(
|
||||||
'SELECT id,label FROM custom_columns WHERE mark_for_delete=1'):
|
'SELECT id,label FROM custom_columns WHERE mark_for_delete=1'):
|
||||||
table, lt = self.custom_table_names(num)
|
table, lt = self.custom_table_names(num)
|
||||||
self.execute('''\
|
self.execute(f'''\
|
||||||
DROP INDEX IF EXISTS {table}_idx;
|
DROP INDEX IF EXISTS {table}_idx;
|
||||||
DROP INDEX IF EXISTS {lt}_aidx;
|
DROP INDEX IF EXISTS {lt}_aidx;
|
||||||
DROP INDEX IF EXISTS {lt}_bidx;
|
DROP INDEX IF EXISTS {lt}_bidx;
|
||||||
@ -714,7 +714,7 @@ class DB:
|
|||||||
DROP VIEW IF EXISTS tag_browser_filtered_{table};
|
DROP VIEW IF EXISTS tag_browser_filtered_{table};
|
||||||
DROP TABLE IF EXISTS {table};
|
DROP TABLE IF EXISTS {table};
|
||||||
DROP TABLE IF EXISTS {lt};
|
DROP TABLE IF EXISTS {lt};
|
||||||
'''.format(table=table, lt=lt)
|
'''
|
||||||
)
|
)
|
||||||
self.prefs.set('update_all_last_mod_dates_on_start', True)
|
self.prefs.set('update_all_last_mod_dates_on_start', True)
|
||||||
self.deleted_fields.append('#'+label)
|
self.deleted_fields.append('#'+label)
|
||||||
@ -764,16 +764,15 @@ class DB:
|
|||||||
|
|
||||||
# Create Foreign Key triggers
|
# Create Foreign Key triggers
|
||||||
if data['normalized']:
|
if data['normalized']:
|
||||||
trigger = 'DELETE FROM %s WHERE book=OLD.id;'%lt
|
trigger = f'DELETE FROM {lt} WHERE book=OLD.id;'
|
||||||
else:
|
else:
|
||||||
trigger = 'DELETE FROM %s WHERE book=OLD.id;'%table
|
trigger = f'DELETE FROM {table} WHERE book=OLD.id;'
|
||||||
triggers.append(trigger)
|
triggers.append(trigger)
|
||||||
|
|
||||||
if remove:
|
if remove:
|
||||||
with self.conn:
|
with self.conn:
|
||||||
for data in remove:
|
for data in remove:
|
||||||
prints('WARNING: Custom column %r not found, removing.' %
|
prints('WARNING: Custom column {!r} not found, removing.'.format(data['label']))
|
||||||
data['label'])
|
|
||||||
self.execute('DELETE FROM custom_columns WHERE id=?',
|
self.execute('DELETE FROM custom_columns WHERE id=?',
|
||||||
(data['num'],))
|
(data['num'],))
|
||||||
|
|
||||||
@ -783,9 +782,9 @@ class DB:
|
|||||||
CREATE TEMP TRIGGER custom_books_delete_trg
|
CREATE TEMP TRIGGER custom_books_delete_trg
|
||||||
AFTER DELETE ON books
|
AFTER DELETE ON books
|
||||||
BEGIN
|
BEGIN
|
||||||
%s
|
{}
|
||||||
END;
|
END;
|
||||||
'''%(' \n'.join(triggers)))
|
'''.format(' \n'.join(triggers)))
|
||||||
|
|
||||||
# Setup data adapters
|
# Setup data adapters
|
||||||
def adapt_text(x, d):
|
def adapt_text(x, d):
|
||||||
@ -1212,7 +1211,7 @@ class DB:
|
|||||||
if re.match(r'^\w*$', label) is None or not label[0].isalpha() or label.lower() != label:
|
if re.match(r'^\w*$', label) is None or not label[0].isalpha() or label.lower() != label:
|
||||||
raise ValueError(_('The label must contain only lower case letters, digits and underscores, and start with a letter'))
|
raise ValueError(_('The label must contain only lower case letters, digits and underscores, and start with a letter'))
|
||||||
if datatype not in CUSTOM_DATA_TYPES:
|
if datatype not in CUSTOM_DATA_TYPES:
|
||||||
raise ValueError('%r is not a supported data type'%datatype)
|
raise ValueError(f'{datatype!r} is not a supported data type')
|
||||||
normalized = datatype not in ('datetime', 'comments', 'int', 'bool',
|
normalized = datatype not in ('datetime', 'comments', 'int', 'bool',
|
||||||
'float', 'composite')
|
'float', 'composite')
|
||||||
is_multiple = is_multiple and datatype in ('text', 'composite')
|
is_multiple = is_multiple and datatype in ('text', 'composite')
|
||||||
@ -1241,29 +1240,29 @@ class DB:
|
|||||||
else:
|
else:
|
||||||
s_index = ''
|
s_index = ''
|
||||||
lines = [
|
lines = [
|
||||||
'''\
|
f'''\
|
||||||
CREATE TABLE %s(
|
CREATE TABLE {table}(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
value %s NOT NULL %s,
|
value {dt} NOT NULL {collate},
|
||||||
link TEXT NOT NULL DEFAULT "",
|
link TEXT NOT NULL DEFAULT "",
|
||||||
UNIQUE(value));
|
UNIQUE(value));
|
||||||
'''%(table, dt, collate),
|
''',
|
||||||
|
|
||||||
'CREATE INDEX %s_idx ON %s (value %s);'%(table, table, collate),
|
f'CREATE INDEX {table}_idx ON {table} (value {collate});',
|
||||||
|
|
||||||
'''\
|
f'''\
|
||||||
CREATE TABLE %s(
|
CREATE TABLE {lt}(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
book INTEGER NOT NULL,
|
book INTEGER NOT NULL,
|
||||||
value INTEGER NOT NULL,
|
value INTEGER NOT NULL,
|
||||||
%s
|
{s_index}
|
||||||
UNIQUE(book, value)
|
UNIQUE(book, value)
|
||||||
);'''%(lt, s_index),
|
);''',
|
||||||
|
|
||||||
'CREATE INDEX %s_aidx ON %s (value);'%(lt,lt),
|
f'CREATE INDEX {lt}_aidx ON {lt} (value);',
|
||||||
'CREATE INDEX %s_bidx ON %s (book);'%(lt,lt),
|
f'CREATE INDEX {lt}_bidx ON {lt} (book);',
|
||||||
|
|
||||||
'''\
|
f'''\
|
||||||
CREATE TRIGGER fkc_update_{lt}_a
|
CREATE TRIGGER fkc_update_{lt}_a
|
||||||
BEFORE UPDATE OF book ON {lt}
|
BEFORE UPDATE OF book ON {lt}
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -1324,22 +1323,22 @@ class DB:
|
|||||||
value AS sort
|
value AS sort
|
||||||
FROM {table};
|
FROM {table};
|
||||||
|
|
||||||
'''.format(lt=lt, table=table),
|
''',
|
||||||
|
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
lines = [
|
lines = [
|
||||||
'''\
|
f'''\
|
||||||
CREATE TABLE %s(
|
CREATE TABLE {table}(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
book INTEGER,
|
book INTEGER,
|
||||||
value %s NOT NULL %s,
|
value {dt} NOT NULL {collate},
|
||||||
UNIQUE(book));
|
UNIQUE(book));
|
||||||
'''%(table, dt, collate),
|
''',
|
||||||
|
|
||||||
'CREATE INDEX %s_idx ON %s (book);'%(table, table),
|
f'CREATE INDEX {table}_idx ON {table} (book);',
|
||||||
|
|
||||||
'''\
|
f'''\
|
||||||
CREATE TRIGGER fkc_insert_{table}
|
CREATE TRIGGER fkc_insert_{table}
|
||||||
BEFORE INSERT ON {table}
|
BEFORE INSERT ON {table}
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -1356,7 +1355,7 @@ class DB:
|
|||||||
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
|
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
|
||||||
END;
|
END;
|
||||||
END;
|
END;
|
||||||
'''.format(table=table),
|
''',
|
||||||
]
|
]
|
||||||
script = ' \n'.join(lines)
|
script = ' \n'.join(lines)
|
||||||
self.execute(script)
|
self.execute(script)
|
||||||
@ -2396,15 +2395,14 @@ class DB:
|
|||||||
data = []
|
data = []
|
||||||
if highlight_start is not None and highlight_end is not None:
|
if highlight_start is not None and highlight_end is not None:
|
||||||
if snippet_size is not None:
|
if snippet_size is not None:
|
||||||
text = "snippet({fts_table}, 0, ?, ?, '…', {snippet_size})".format(
|
text = f"snippet({fts_table}, 0, ?, ?, '…', {max(1, min(snippet_size, 64))})"
|
||||||
fts_table=fts_table, snippet_size=max(1, min(snippet_size, 64)))
|
|
||||||
else:
|
else:
|
||||||
text = f'highlight({fts_table}, 0, ?, ?)'
|
text = f'highlight({fts_table}, 0, ?, ?)'
|
||||||
data.append(highlight_start)
|
data.append(highlight_start)
|
||||||
data.append(highlight_end)
|
data.append(highlight_end)
|
||||||
query = 'SELECT {0}.id, {0}.book, {0}.format, {0}.user_type, {0}.user, {0}.annot_data, {1} FROM {0} '
|
query = 'SELECT {0}.id, {0}.book, {0}.format, {0}.user_type, {0}.user, {0}.annot_data, {1} FROM {0} '
|
||||||
query = query.format('annotations', text)
|
query = query.format('annotations', text)
|
||||||
query += ' JOIN {fts_table} ON annotations.id = {fts_table}.rowid'.format(fts_table=fts_table)
|
query += f' JOIN {fts_table} ON annotations.id = {fts_table}.rowid'
|
||||||
query += f' WHERE {fts_table} MATCH ?'
|
query += f' WHERE {fts_table} MATCH ?'
|
||||||
data.append(fts_engine_query)
|
data.append(fts_engine_query)
|
||||||
if restrict_to_user:
|
if restrict_to_user:
|
||||||
|
@ -916,7 +916,7 @@ class Cache:
|
|||||||
try:
|
try:
|
||||||
return frozenset(self.fields[field].table.id_map.values())
|
return frozenset(self.fields[field].table.id_map.values())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ValueError('%s is not a many-one or many-many field' % field)
|
raise ValueError(f'{field} is not a many-one or many-many field')
|
||||||
|
|
||||||
@read_api
|
@read_api
|
||||||
def get_usage_count_by_id(self, field):
|
def get_usage_count_by_id(self, field):
|
||||||
@ -925,7 +925,7 @@ class Cache:
|
|||||||
try:
|
try:
|
||||||
return {k:len(v) for k, v in iteritems(self.fields[field].table.col_book_map)}
|
return {k:len(v) for k, v in iteritems(self.fields[field].table.col_book_map)}
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ValueError('%s is not a many-one or many-many field' % field)
|
raise ValueError(f'{field} is not a many-one or many-many field')
|
||||||
|
|
||||||
@read_api
|
@read_api
|
||||||
def get_id_map(self, field):
|
def get_id_map(self, field):
|
||||||
@ -937,7 +937,7 @@ class Cache:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
if field == 'title':
|
if field == 'title':
|
||||||
return self.fields[field].table.book_col_map.copy()
|
return self.fields[field].table.book_col_map.copy()
|
||||||
raise ValueError('%s is not a many-one or many-many field' % field)
|
raise ValueError(f'{field} is not a many-one or many-many field')
|
||||||
|
|
||||||
@read_api
|
@read_api
|
||||||
def get_item_name(self, field, item_id):
|
def get_item_name(self, field, item_id):
|
||||||
@ -2319,7 +2319,7 @@ class Cache:
|
|||||||
try:
|
try:
|
||||||
func = f.table.rename_item
|
func = f.table.rename_item
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ValueError('Cannot rename items for one-one fields: %s' % field)
|
raise ValueError(f'Cannot rename items for one-one fields: {field}')
|
||||||
moved_books = set()
|
moved_books = set()
|
||||||
id_map = {}
|
id_map = {}
|
||||||
for item_id, new_name in item_id_to_new_name_map.items():
|
for item_id, new_name in item_id_to_new_name_map.items():
|
||||||
@ -2705,7 +2705,7 @@ class Cache:
|
|||||||
if mi.authors:
|
if mi.authors:
|
||||||
try:
|
try:
|
||||||
quathors = mi.authors[:20] # Too many authors causes parsing of the search expression to fail
|
quathors = mi.authors[:20] # Too many authors causes parsing of the search expression to fail
|
||||||
query = ' and '.join('authors:"=%s"'%(a.replace('"', '')) for a in quathors)
|
query = ' and '.join('authors:"={}"'.format(a.replace('"', '')) for a in quathors)
|
||||||
qauthors = mi.authors[20:]
|
qauthors = mi.authors[20:]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return identical_book_ids
|
return identical_book_ids
|
||||||
|
@ -60,8 +60,7 @@ class Tag:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def string_representation(self):
|
def string_representation(self):
|
||||||
return '%s:%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state,
|
return f'{self.name}:{self.count}:{self.id}:{self.state}:{self.category}:{self.original_categories}'
|
||||||
self.category, self.original_categories)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.string_representation
|
return self.string_representation
|
||||||
|
@ -400,7 +400,7 @@ the folder related options below.
|
|||||||
try:
|
try:
|
||||||
getattr(parser.values, option.dest).append(compile_rule(rule))
|
getattr(parser.values, option.dest).append(compile_rule(rule))
|
||||||
except Exception:
|
except Exception:
|
||||||
raise OptionValueError('%r is not a valid filename pattern' % value)
|
raise OptionValueError(f'{value!r} is not a valid filename pattern')
|
||||||
|
|
||||||
g.add_option(
|
g.add_option(
|
||||||
'-1',
|
'-1',
|
||||||
|
@ -72,7 +72,7 @@ def do_add_custom_column(db, label, name, datatype, is_multiple, display):
|
|||||||
num = db.create_custom_column(
|
num = db.create_custom_column(
|
||||||
label, name, datatype, is_multiple, display=display
|
label, name, datatype, is_multiple, display=display
|
||||||
)
|
)
|
||||||
prints('Custom column created with id: %s' % num)
|
prints(f'Custom column created with id: {num}')
|
||||||
|
|
||||||
|
|
||||||
def main(opts, args, dbctx):
|
def main(opts, args, dbctx):
|
||||||
|
@ -156,7 +156,7 @@ def main(opts, args, dbctx):
|
|||||||
|
|
||||||
def fmtr(v):
|
def fmtr(v):
|
||||||
v = v or 0
|
v = v or 0
|
||||||
ans = '%.1f' % v
|
ans = f'{v:.1f}'
|
||||||
if ans.endswith('.0'):
|
if ans.endswith('.0'):
|
||||||
ans = ans[:-2]
|
ans = ans[:-2]
|
||||||
return ans
|
return ans
|
||||||
|
@ -61,7 +61,7 @@ def do_remove_custom_column(db, label, force):
|
|||||||
' Use calibredb custom_columns to get a list of labels.'
|
' Use calibredb custom_columns to get a list of labels.'
|
||||||
) % label
|
) % label
|
||||||
)
|
)
|
||||||
prints('Column %r removed.' % label)
|
prints(f'Column {label!r} removed.')
|
||||||
|
|
||||||
|
|
||||||
def main(opts, args, dbctx):
|
def main(opts, args, dbctx):
|
||||||
|
@ -89,7 +89,7 @@ class Field:
|
|||||||
if tweaks['sort_dates_using_visible_fields']:
|
if tweaks['sort_dates_using_visible_fields']:
|
||||||
fmt = None
|
fmt = None
|
||||||
if name in {'timestamp', 'pubdate', 'last_modified'}:
|
if name in {'timestamp', 'pubdate', 'last_modified'}:
|
||||||
fmt = tweaks['gui_%s_display_format' % name]
|
fmt = tweaks[f'gui_{name}_display_format']
|
||||||
elif self.metadata['is_custom']:
|
elif self.metadata['is_custom']:
|
||||||
fmt = self.metadata.get('display', {}).get('date_format', None)
|
fmt = self.metadata.get('display', {}).get('date_format', None)
|
||||||
self._sort_key = partial(clean_date_for_sort, fmt=fmt)
|
self._sort_key = partial(clean_date_for_sort, fmt=fmt)
|
||||||
@ -454,7 +454,7 @@ class OnDeviceField(OneToOneField):
|
|||||||
loc.append(_('Card A'))
|
loc.append(_('Card A'))
|
||||||
if b is not None:
|
if b is not None:
|
||||||
loc.append(_('Card B'))
|
loc.append(_('Card B'))
|
||||||
return ', '.join(loc) + ((' (%s books)'%count) if count > 1 else '')
|
return ', '.join(loc) + ((f' ({count} books)') if count > 1 else '')
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(())
|
return iter(())
|
||||||
|
@ -263,7 +263,7 @@ def composite_getter(mi, field, dbref, book_id, cache, formatter, template_cache
|
|||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return 'ERROR WHILE EVALUATING: %s' % field
|
return f'ERROR WHILE EVALUATING: {field}'
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@ -365,7 +365,7 @@ class ProxyMetadata(Metadata):
|
|||||||
try:
|
try:
|
||||||
return ga(self, '_cache')[field]
|
return ga(self, '_cache')[field]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError('Metadata object has no attribute named: %r' % field)
|
raise AttributeError(f'Metadata object has no attribute named: {field!r}')
|
||||||
|
|
||||||
def __setattr__(self, field, val, extra=None):
|
def __setattr__(self, field, val, extra=None):
|
||||||
cache = ga(self, '_cache')
|
cache = ga(self, '_cache')
|
||||||
|
@ -616,9 +616,9 @@ class LibraryDatabase:
|
|||||||
def set_custom_bulk_multiple(self, ids, add=[], remove=[], label=None, num=None, notify=False):
|
def set_custom_bulk_multiple(self, ids, add=[], remove=[], label=None, num=None, notify=False):
|
||||||
data = self.backend.custom_field_metadata(label, num)
|
data = self.backend.custom_field_metadata(label, num)
|
||||||
if not data['editable']:
|
if not data['editable']:
|
||||||
raise ValueError('Column %r is not editable'%data['label'])
|
raise ValueError('Column {!r} is not editable'.format(data['label']))
|
||||||
if data['datatype'] != 'text' or not data['is_multiple']:
|
if data['datatype'] != 'text' or not data['is_multiple']:
|
||||||
raise ValueError('Column %r is not text/multiple'%data['label'])
|
raise ValueError('Column {!r} is not text/multiple'.format(data['label']))
|
||||||
field = self.custom_field_name(label, num)
|
field = self.custom_field_name(label, num)
|
||||||
self._do_bulk_modify(field, ids, add, remove, notify)
|
self._do_bulk_modify(field, ids, add, remove, notify)
|
||||||
|
|
||||||
@ -756,7 +756,7 @@ class LibraryDatabase:
|
|||||||
if data['datatype'] == 'composite':
|
if data['datatype'] == 'composite':
|
||||||
return set()
|
return set()
|
||||||
if not data['editable']:
|
if not data['editable']:
|
||||||
raise ValueError('Column %r is not editable'%data['label'])
|
raise ValueError('Column {!r} is not editable'.format(data['label']))
|
||||||
if data['datatype'] == 'enumeration' and (
|
if data['datatype'] == 'enumeration' and (
|
||||||
val and val not in data['display']['enum_values']):
|
val and val not in data['display']['enum_values']):
|
||||||
return set()
|
return set()
|
||||||
@ -789,7 +789,7 @@ class LibraryDatabase:
|
|||||||
val and val not in data['display']['enum_values']):
|
val and val not in data['display']['enum_values']):
|
||||||
return
|
return
|
||||||
if not data['editable']:
|
if not data['editable']:
|
||||||
raise ValueError('Column %r is not editable'%data['label'])
|
raise ValueError('Column {!r} is not editable'.format(data['label']))
|
||||||
|
|
||||||
if append:
|
if append:
|
||||||
for book_id in ids:
|
for book_id in ids:
|
||||||
@ -826,7 +826,7 @@ class LibraryDatabase:
|
|||||||
self.notify('cover', [book_id])
|
self.notify('cover', [book_id])
|
||||||
|
|
||||||
def original_fmt(self, book_id, fmt):
|
def original_fmt(self, book_id, fmt):
|
||||||
nfmt = ('ORIGINAL_%s'%fmt).upper()
|
nfmt = (f'ORIGINAL_{fmt}').upper()
|
||||||
return nfmt if self.new_api.has_format(book_id, nfmt) else fmt
|
return nfmt if self.new_api.has_format(book_id, nfmt) else fmt
|
||||||
|
|
||||||
def save_original_format(self, book_id, fmt, notify=True):
|
def save_original_format(self, book_id, fmt, notify=True):
|
||||||
@ -931,7 +931,7 @@ for field in (
|
|||||||
self.notify([book_id])
|
self.notify([book_id])
|
||||||
return ret if field == 'languages' else retval
|
return ret if field == 'languages' else retval
|
||||||
return func
|
return func
|
||||||
setattr(LibraryDatabase, 'set_%s' % field.replace('!', ''), setter(field))
|
setattr(LibraryDatabase, 'set_{}'.format(field.replace('!', '')), setter(field))
|
||||||
|
|
||||||
for field in ('authors', 'tags', 'publisher'):
|
for field in ('authors', 'tags', 'publisher'):
|
||||||
def renamer(field):
|
def renamer(field):
|
||||||
@ -941,7 +941,7 @@ for field in ('authors', 'tags', 'publisher'):
|
|||||||
return id_map[old_id]
|
return id_map[old_id]
|
||||||
return func
|
return func
|
||||||
fname = field[:-1] if field in {'tags', 'authors'} else field
|
fname = field[:-1] if field in {'tags', 'authors'} else field
|
||||||
setattr(LibraryDatabase, 'rename_%s' % fname, renamer(field))
|
setattr(LibraryDatabase, f'rename_{fname}', renamer(field))
|
||||||
|
|
||||||
LibraryDatabase.update_last_modified = lambda self, book_ids, commit=False, now=None: self.new_api.update_last_modified(book_ids, now=now)
|
LibraryDatabase.update_last_modified = lambda self, book_ids, commit=False, now=None: self.new_api.update_last_modified(book_ids, now=now)
|
||||||
|
|
||||||
@ -954,7 +954,7 @@ for field in ('authors', 'tags', 'publisher', 'series'):
|
|||||||
return self.new_api.all_field_names(field)
|
return self.new_api.all_field_names(field)
|
||||||
return func
|
return func
|
||||||
name = field[:-1] if field in {'authors', 'tags'} else field
|
name = field[:-1] if field in {'authors', 'tags'} else field
|
||||||
setattr(LibraryDatabase, 'all_%s_names' % name, getter(field))
|
setattr(LibraryDatabase, f'all_{name}_names', getter(field))
|
||||||
LibraryDatabase.all_formats = lambda self: self.new_api.all_field_names('formats')
|
LibraryDatabase.all_formats = lambda self: self.new_api.all_field_names('formats')
|
||||||
LibraryDatabase.all_custom = lambda self, label=None, num=None:self.new_api.all_field_names(self.custom_field_name(label, num))
|
LibraryDatabase.all_custom = lambda self, label=None, num=None:self.new_api.all_field_names(self.custom_field_name(label, num))
|
||||||
|
|
||||||
@ -977,7 +977,7 @@ for field in ('tags', 'series', 'publishers', 'ratings', 'languages'):
|
|||||||
def func(self):
|
def func(self):
|
||||||
return [[tid, tag] for tid, tag in iteritems(self.new_api.get_id_map(fname))]
|
return [[tid, tag] for tid, tag in iteritems(self.new_api.get_id_map(fname))]
|
||||||
return func
|
return func
|
||||||
setattr(LibraryDatabase, 'get_%s_with_ids' % field, getter(field))
|
setattr(LibraryDatabase, f'get_{field}_with_ids', getter(field))
|
||||||
|
|
||||||
for field in ('author', 'tag', 'series'):
|
for field in ('author', 'tag', 'series'):
|
||||||
def getter(field):
|
def getter(field):
|
||||||
@ -986,7 +986,7 @@ for field in ('author', 'tag', 'series'):
|
|||||||
def func(self, item_id):
|
def func(self, item_id):
|
||||||
return self.new_api.get_item_name(field, item_id)
|
return self.new_api.get_item_name(field, item_id)
|
||||||
return func
|
return func
|
||||||
setattr(LibraryDatabase, '%s_name' % field, getter(field))
|
setattr(LibraryDatabase, f'{field}_name', getter(field))
|
||||||
|
|
||||||
for field in ('publisher', 'series', 'tag'):
|
for field in ('publisher', 'series', 'tag'):
|
||||||
def getter(field):
|
def getter(field):
|
||||||
@ -995,7 +995,7 @@ for field in ('publisher', 'series', 'tag'):
|
|||||||
def func(self, item_id):
|
def func(self, item_id):
|
||||||
self.new_api.remove_items(fname, (item_id,))
|
self.new_api.remove_items(fname, (item_id,))
|
||||||
return func
|
return func
|
||||||
setattr(LibraryDatabase, 'delete_%s_using_id' % field, getter(field))
|
setattr(LibraryDatabase, f'delete_{field}_using_id', getter(field))
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Legacy field API {{{
|
# Legacy field API {{{
|
||||||
|
@ -111,13 +111,9 @@ class Restore(Thread):
|
|||||||
'and were not fully restored:\n')
|
'and were not fully restored:\n')
|
||||||
for x in self.conflicting_custom_cols:
|
for x in self.conflicting_custom_cols:
|
||||||
ans += '\t#'+x+'\n'
|
ans += '\t#'+x+'\n'
|
||||||
ans += '\tused:\t%s, %s, %s, %s\n'%(self.custom_columns[x][1],
|
ans += f'\tused:\t{self.custom_columns[x][1]}, {self.custom_columns[x][2]}, {self.custom_columns[x][3]}, {self.custom_columns[x][5]}\n'
|
||||||
self.custom_columns[x][2],
|
|
||||||
self.custom_columns[x][3],
|
|
||||||
self.custom_columns[x][5])
|
|
||||||
for coldef in self.conflicting_custom_cols[x]:
|
for coldef in self.conflicting_custom_cols[x]:
|
||||||
ans += '\tother:\t%s, %s, %s, %s\n'%(coldef[1], coldef[2],
|
ans += f'\tother:\t{coldef[1]}, {coldef[2]}, {coldef[3]}, {coldef[5]}\n'
|
||||||
coldef[3], coldef[5])
|
|
||||||
|
|
||||||
if self.mismatched_dirs:
|
if self.mismatched_dirs:
|
||||||
ans += '\n\n'
|
ans += '\n\n'
|
||||||
|
@ -243,14 +243,14 @@ class SchemaUpgrade:
|
|||||||
def upgrade_version_8(self):
|
def upgrade_version_8(self):
|
||||||
'Add Tag Browser views'
|
'Add Tag Browser views'
|
||||||
def create_tag_browser_view(table_name, column_name):
|
def create_tag_browser_view(table_name, column_name):
|
||||||
self.db.execute('''
|
self.db.execute(f'''
|
||||||
DROP VIEW IF EXISTS tag_browser_{tn};
|
DROP VIEW IF EXISTS tag_browser_{table_name};
|
||||||
CREATE VIEW tag_browser_{tn} AS SELECT
|
CREATE VIEW tag_browser_{table_name} AS SELECT
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
(SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count
|
(SELECT COUNT(id) FROM books_{table_name}_link WHERE {column_name}={table_name}.id) count
|
||||||
FROM {tn};
|
FROM {table_name};
|
||||||
'''.format(tn=table_name, cn=column_name))
|
''')
|
||||||
|
|
||||||
for tn in ('authors', 'tags', 'publishers', 'series'):
|
for tn in ('authors', 'tags', 'publishers', 'series'):
|
||||||
cn = tn[:-1]
|
cn = tn[:-1]
|
||||||
@ -280,28 +280,28 @@ class SchemaUpgrade:
|
|||||||
def upgrade_version_10(self):
|
def upgrade_version_10(self):
|
||||||
'Add restricted Tag Browser views'
|
'Add restricted Tag Browser views'
|
||||||
def create_tag_browser_view(table_name, column_name, view_column_name):
|
def create_tag_browser_view(table_name, column_name, view_column_name):
|
||||||
script = ('''
|
script = (f'''
|
||||||
DROP VIEW IF EXISTS tag_browser_{tn};
|
DROP VIEW IF EXISTS tag_browser_{table_name};
|
||||||
CREATE VIEW tag_browser_{tn} AS SELECT
|
CREATE VIEW tag_browser_{table_name} AS SELECT
|
||||||
id,
|
id,
|
||||||
{vcn},
|
{view_column_name},
|
||||||
(SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count
|
(SELECT COUNT(id) FROM books_{table_name}_link WHERE {column_name}={table_name}.id) count
|
||||||
FROM {tn};
|
FROM {table_name};
|
||||||
DROP VIEW IF EXISTS tag_browser_filtered_{tn};
|
DROP VIEW IF EXISTS tag_browser_filtered_{table_name};
|
||||||
CREATE VIEW tag_browser_filtered_{tn} AS SELECT
|
CREATE VIEW tag_browser_filtered_{table_name} AS SELECT
|
||||||
id,
|
id,
|
||||||
{vcn},
|
{view_column_name},
|
||||||
(SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE
|
(SELECT COUNT(books_{table_name}_link.id) FROM books_{table_name}_link WHERE
|
||||||
{cn}={tn}.id AND books_list_filter(book)) count
|
{column_name}={table_name}.id AND books_list_filter(book)) count
|
||||||
FROM {tn};
|
FROM {table_name};
|
||||||
'''.format(tn=table_name, cn=column_name, vcn=view_column_name))
|
''')
|
||||||
self.db.execute(script)
|
self.db.execute(script)
|
||||||
|
|
||||||
for field in itervalues(self.field_metadata):
|
for field in itervalues(self.field_metadata):
|
||||||
if field['is_category'] and not field['is_custom'] and 'link_column' in field:
|
if field['is_category'] and not field['is_custom'] and 'link_column' in field:
|
||||||
table = self.db.get(
|
table = self.db.get(
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
||||||
('books_%s_link'%field['table'],), all=False)
|
('books_{}_link'.format(field['table']),), all=False)
|
||||||
if table is not None:
|
if table is not None:
|
||||||
create_tag_browser_view(field['table'], field['link_column'], field['column'])
|
create_tag_browser_view(field['table'], field['link_column'], field['column'])
|
||||||
|
|
||||||
@ -309,75 +309,74 @@ class SchemaUpgrade:
|
|||||||
'Add average rating to tag browser views'
|
'Add average rating to tag browser views'
|
||||||
def create_std_tag_browser_view(table_name, column_name,
|
def create_std_tag_browser_view(table_name, column_name,
|
||||||
view_column_name, sort_column_name):
|
view_column_name, sort_column_name):
|
||||||
script = ('''
|
script = (f'''
|
||||||
DROP VIEW IF EXISTS tag_browser_{tn};
|
DROP VIEW IF EXISTS tag_browser_{table_name};
|
||||||
CREATE VIEW tag_browser_{tn} AS SELECT
|
CREATE VIEW tag_browser_{table_name} AS SELECT
|
||||||
id,
|
id,
|
||||||
{vcn},
|
{view_column_name},
|
||||||
(SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count,
|
(SELECT COUNT(id) FROM books_{table_name}_link WHERE {column_name}={table_name}.id) count,
|
||||||
(SELECT AVG(ratings.rating)
|
(SELECT AVG(ratings.rating)
|
||||||
FROM books_{tn}_link AS tl, books_ratings_link AS bl, ratings
|
FROM books_{table_name}_link AS tl, books_ratings_link AS bl, ratings
|
||||||
WHERE tl.{cn}={tn}.id AND bl.book=tl.book AND
|
WHERE tl.{column_name}={table_name}.id AND bl.book=tl.book AND
|
||||||
ratings.id = bl.rating AND ratings.rating <> 0) avg_rating,
|
ratings.id = bl.rating AND ratings.rating <> 0) avg_rating,
|
||||||
{scn} AS sort
|
{sort_column_name} AS sort
|
||||||
FROM {tn};
|
FROM {table_name};
|
||||||
DROP VIEW IF EXISTS tag_browser_filtered_{tn};
|
DROP VIEW IF EXISTS tag_browser_filtered_{table_name};
|
||||||
CREATE VIEW tag_browser_filtered_{tn} AS SELECT
|
CREATE VIEW tag_browser_filtered_{table_name} AS SELECT
|
||||||
id,
|
id,
|
||||||
{vcn},
|
{view_column_name},
|
||||||
(SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE
|
(SELECT COUNT(books_{table_name}_link.id) FROM books_{table_name}_link WHERE
|
||||||
{cn}={tn}.id AND books_list_filter(book)) count,
|
{column_name}={table_name}.id AND books_list_filter(book)) count,
|
||||||
(SELECT AVG(ratings.rating)
|
(SELECT AVG(ratings.rating)
|
||||||
FROM books_{tn}_link AS tl, books_ratings_link AS bl, ratings
|
FROM books_{table_name}_link AS tl, books_ratings_link AS bl, ratings
|
||||||
WHERE tl.{cn}={tn}.id AND bl.book=tl.book AND
|
WHERE tl.{column_name}={table_name}.id AND bl.book=tl.book AND
|
||||||
ratings.id = bl.rating AND ratings.rating <> 0 AND
|
ratings.id = bl.rating AND ratings.rating <> 0 AND
|
||||||
books_list_filter(bl.book)) avg_rating,
|
books_list_filter(bl.book)) avg_rating,
|
||||||
{scn} AS sort
|
{sort_column_name} AS sort
|
||||||
FROM {tn};
|
FROM {table_name};
|
||||||
|
|
||||||
'''.format(tn=table_name, cn=column_name,
|
''')
|
||||||
vcn=view_column_name, scn=sort_column_name))
|
|
||||||
self.db.execute(script)
|
self.db.execute(script)
|
||||||
|
|
||||||
def create_cust_tag_browser_view(table_name, link_table_name):
|
def create_cust_tag_browser_view(table_name, link_table_name):
|
||||||
script = '''
|
script = f'''
|
||||||
DROP VIEW IF EXISTS tag_browser_{table};
|
DROP VIEW IF EXISTS tag_browser_{table_name};
|
||||||
CREATE VIEW tag_browser_{table} AS SELECT
|
CREATE VIEW tag_browser_{table_name} AS SELECT
|
||||||
id,
|
id,
|
||||||
value,
|
value,
|
||||||
(SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count,
|
(SELECT COUNT(id) FROM {link_table_name} WHERE value={table_name}.id) count,
|
||||||
(SELECT AVG(r.rating)
|
(SELECT AVG(r.rating)
|
||||||
FROM {lt},
|
FROM {link_table_name},
|
||||||
books_ratings_link AS bl,
|
books_ratings_link AS bl,
|
||||||
ratings AS r
|
ratings AS r
|
||||||
WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
|
WHERE {link_table_name}.value={table_name}.id AND bl.book={link_table_name}.book AND
|
||||||
r.id = bl.rating AND r.rating <> 0) avg_rating,
|
r.id = bl.rating AND r.rating <> 0) avg_rating,
|
||||||
value AS sort
|
value AS sort
|
||||||
FROM {table};
|
FROM {table_name};
|
||||||
|
|
||||||
DROP VIEW IF EXISTS tag_browser_filtered_{table};
|
DROP VIEW IF EXISTS tag_browser_filtered_{table_name};
|
||||||
CREATE VIEW tag_browser_filtered_{table} AS SELECT
|
CREATE VIEW tag_browser_filtered_{table_name} AS SELECT
|
||||||
id,
|
id,
|
||||||
value,
|
value,
|
||||||
(SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND
|
(SELECT COUNT({link_table_name}.id) FROM {link_table_name} WHERE value={table_name}.id AND
|
||||||
books_list_filter(book)) count,
|
books_list_filter(book)) count,
|
||||||
(SELECT AVG(r.rating)
|
(SELECT AVG(r.rating)
|
||||||
FROM {lt},
|
FROM {link_table_name},
|
||||||
books_ratings_link AS bl,
|
books_ratings_link AS bl,
|
||||||
ratings AS r
|
ratings AS r
|
||||||
WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
|
WHERE {link_table_name}.value={table_name}.id AND bl.book={link_table_name}.book AND
|
||||||
r.id = bl.rating AND r.rating <> 0 AND
|
r.id = bl.rating AND r.rating <> 0 AND
|
||||||
books_list_filter(bl.book)) avg_rating,
|
books_list_filter(bl.book)) avg_rating,
|
||||||
value AS sort
|
value AS sort
|
||||||
FROM {table};
|
FROM {table_name};
|
||||||
'''.format(lt=link_table_name, table=table_name)
|
'''
|
||||||
self.db.execute(script)
|
self.db.execute(script)
|
||||||
|
|
||||||
for field in itervalues(self.field_metadata):
|
for field in itervalues(self.field_metadata):
|
||||||
if field['is_category'] and not field['is_custom'] and 'link_column' in field:
|
if field['is_category'] and not field['is_custom'] and 'link_column' in field:
|
||||||
table = self.db.get(
|
table = self.db.get(
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
||||||
('books_%s_link'%field['table'],), all=False)
|
('books_{}_link'.format(field['table']),), all=False)
|
||||||
if table is not None:
|
if table is not None:
|
||||||
create_std_tag_browser_view(field['table'], field['link_column'],
|
create_std_tag_browser_view(field['table'], field['link_column'],
|
||||||
field['column'], field['category_sort'])
|
field['column'], field['category_sort'])
|
||||||
@ -389,7 +388,7 @@ class SchemaUpgrade:
|
|||||||
for (table,) in db_tables:
|
for (table,) in db_tables:
|
||||||
tables.append(table)
|
tables.append(table)
|
||||||
for table in tables:
|
for table in tables:
|
||||||
link_table = 'books_%s_link'%table
|
link_table = f'books_{table}_link'
|
||||||
if table.startswith('custom_column_') and link_table in tables:
|
if table.startswith('custom_column_') and link_table in tables:
|
||||||
create_cust_tag_browser_view(table, link_table)
|
create_cust_tag_browser_view(table, link_table)
|
||||||
|
|
||||||
@ -580,9 +579,9 @@ class SchemaUpgrade:
|
|||||||
|
|
||||||
INSERT INTO identifiers (book, val) SELECT id,isbn FROM books WHERE isbn;
|
INSERT INTO identifiers (book, val) SELECT id,isbn FROM books WHERE isbn;
|
||||||
|
|
||||||
ALTER TABLE books ADD COLUMN last_modified TIMESTAMP NOT NULL DEFAULT "%s";
|
ALTER TABLE books ADD COLUMN last_modified TIMESTAMP NOT NULL DEFAULT "{}";
|
||||||
|
|
||||||
'''%isoformat(DEFAULT_DATE, sep=' ')
|
'''.format(isoformat(DEFAULT_DATE, sep=' '))
|
||||||
# Sqlite does not support non constant default values in alter
|
# Sqlite does not support non constant default values in alter
|
||||||
# statements
|
# statements
|
||||||
self.db.execute(script)
|
self.db.execute(script)
|
||||||
|
@ -108,7 +108,7 @@ class DateSearch: # {{{
|
|||||||
self.local_today = {'_today', 'today', icu_lower(_('today'))}
|
self.local_today = {'_today', 'today', icu_lower(_('today'))}
|
||||||
self.local_yesterday = {'_yesterday', 'yesterday', icu_lower(_('yesterday'))}
|
self.local_yesterday = {'_yesterday', 'yesterday', icu_lower(_('yesterday'))}
|
||||||
self.local_thismonth = {'_thismonth', 'thismonth', icu_lower(_('thismonth'))}
|
self.local_thismonth = {'_thismonth', 'thismonth', icu_lower(_('thismonth'))}
|
||||||
self.daysago_pat = regex.compile(r'(%s|daysago|_daysago)$'%_('daysago'), flags=regex.UNICODE | regex.VERSION1)
|
self.daysago_pat = regex.compile(r'({}|daysago|_daysago)$'.format(_('daysago')), flags=regex.UNICODE | regex.VERSION1)
|
||||||
|
|
||||||
def eq(self, dbdate, query, field_count):
|
def eq(self, dbdate, query, field_count):
|
||||||
if dbdate.year == query.year:
|
if dbdate.year == query.year:
|
||||||
|
@ -72,7 +72,7 @@ class Table:
|
|||||||
self.unserialize = lambda x: x.replace('|', ',') if x else ''
|
self.unserialize = lambda x: x.replace('|', ',') if x else ''
|
||||||
self.serialize = lambda x: x.replace(',', '|')
|
self.serialize = lambda x: x.replace(',', '|')
|
||||||
self.link_table = (link_table if link_table else
|
self.link_table = (link_table if link_table else
|
||||||
'books_%s_link'%self.metadata['table'])
|
'books_{}_link'.format(self.metadata['table']))
|
||||||
if self.supports_notes and dt == 'rating': # custom ratings table
|
if self.supports_notes and dt == 'rating': # custom ratings table
|
||||||
self.supports_notes = False
|
self.supports_notes = False
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ class AddRemoveTest(BaseTest):
|
|||||||
item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two']
|
item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two']
|
||||||
cache.remove_books((1,), permanent=True)
|
cache.remove_books((1,), permanent=True)
|
||||||
for x in (fmtpath, bookpath, authorpath):
|
for x in (fmtpath, bookpath, authorpath):
|
||||||
af(os.path.exists(x), 'The file %s exists, when it should not' % x)
|
af(os.path.exists(x), f'The file {x} exists, when it should not')
|
||||||
for c in (cache, self.init_cache()):
|
for c in (cache, self.init_cache()):
|
||||||
table = c.fields['authors'].table
|
table = c.fields['authors'].table
|
||||||
self.assertNotIn(1, c.all_book_ids())
|
self.assertNotIn(1, c.all_book_ids())
|
||||||
@ -279,7 +279,7 @@ class AddRemoveTest(BaseTest):
|
|||||||
item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two']
|
item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two']
|
||||||
cache.remove_books((1,))
|
cache.remove_books((1,))
|
||||||
for x in (fmtpath, bookpath, authorpath):
|
for x in (fmtpath, bookpath, authorpath):
|
||||||
af(os.path.exists(x), 'The file %s exists, when it should not' % x)
|
af(os.path.exists(x), f'The file {x} exists, when it should not')
|
||||||
b, f = cache.list_trash_entries()
|
b, f = cache.list_trash_entries()
|
||||||
self.assertEqual(len(b), 1)
|
self.assertEqual(len(b), 1)
|
||||||
self.assertEqual(len(f), 0)
|
self.assertEqual(len(f), 0)
|
||||||
|
@ -48,7 +48,7 @@ class BaseTest(unittest.TestCase):
|
|||||||
def create_db(self, library_path):
|
def create_db(self, library_path):
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
if LibraryDatabase2.exists_at(library_path):
|
if LibraryDatabase2.exists_at(library_path):
|
||||||
raise ValueError('A library already exists at %r'%library_path)
|
raise ValueError(f'A library already exists at {library_path!r}')
|
||||||
src = os.path.join(os.path.dirname(__file__), 'metadata.db')
|
src = os.path.join(os.path.dirname(__file__), 'metadata.db')
|
||||||
dest = os.path.join(library_path, 'metadata.db')
|
dest = os.path.join(library_path, 'metadata.db')
|
||||||
shutil.copyfile(src, dest)
|
shutil.copyfile(src, dest)
|
||||||
@ -114,8 +114,8 @@ class BaseTest(unittest.TestCase):
|
|||||||
if isinstance(attr1, (tuple, list)) and 'authors' not in attr and 'languages' not in attr:
|
if isinstance(attr1, (tuple, list)) and 'authors' not in attr and 'languages' not in attr:
|
||||||
attr1, attr2 = set(attr1), set(attr2)
|
attr1, attr2 = set(attr1), set(attr2)
|
||||||
self.assertEqual(attr1, attr2,
|
self.assertEqual(attr1, attr2,
|
||||||
'%s not the same: %r != %r'%(attr, attr1, attr2))
|
f'{attr} not the same: {attr1!r} != {attr2!r}')
|
||||||
if attr.startswith('#') and attr + '_index' not in exclude:
|
if attr.startswith('#') and attr + '_index' not in exclude:
|
||||||
attr1, attr2 = mi1.get_extra(attr), mi2.get_extra(attr)
|
attr1, attr2 = mi1.get_extra(attr), mi2.get_extra(attr)
|
||||||
self.assertEqual(attr1, attr2,
|
self.assertEqual(attr1, attr2,
|
||||||
'%s {#extra} not the same: %r != %r'%(attr, attr1, attr2))
|
f'{attr} {{#extra}} not the same: {attr1!r} != {attr2!r}')
|
||||||
|
@ -32,8 +32,7 @@ class ET:
|
|||||||
legacy = self.legacy or test.init_legacy(test.cloned_library)
|
legacy = self.legacy or test.init_legacy(test.cloned_library)
|
||||||
oldres = getattr(old, self.func_name)(*self.args, **self.kwargs)
|
oldres = getattr(old, self.func_name)(*self.args, **self.kwargs)
|
||||||
newres = getattr(legacy, self.func_name)(*self.args, **self.kwargs)
|
newres = getattr(legacy, self.func_name)(*self.args, **self.kwargs)
|
||||||
test.assertEqual(oldres, newres, 'Equivalence test for {} with args: {} and kwargs: {} failed'.format(
|
test.assertEqual(oldres, newres, f'Equivalence test for {self.func_name} with args: {reprlib.repr(self.args)} and kwargs: {reprlib.repr(self.kwargs)} failed')
|
||||||
self.func_name, reprlib.repr(self.args), reprlib.repr(self.kwargs)))
|
|
||||||
self.retval = newres
|
self.retval = newres
|
||||||
return newres
|
return newres
|
||||||
|
|
||||||
|
@ -165,10 +165,10 @@ class ReadingTest(BaseTest):
|
|||||||
x = list(reversed(order))
|
x = list(reversed(order))
|
||||||
ae(order, cache.multisort([(field, True)],
|
ae(order, cache.multisort([(field, True)],
|
||||||
ids_to_sort=x),
|
ids_to_sort=x),
|
||||||
'Ascending sort of %s failed'%field)
|
f'Ascending sort of {field} failed')
|
||||||
ae(x, cache.multisort([(field, False)],
|
ae(x, cache.multisort([(field, False)],
|
||||||
ids_to_sort=order),
|
ids_to_sort=order),
|
||||||
'Descending sort of %s failed'%field)
|
f'Descending sort of {field} failed')
|
||||||
|
|
||||||
# Test sorting of is_multiple fields.
|
# Test sorting of is_multiple fields.
|
||||||
|
|
||||||
@ -337,8 +337,7 @@ class ReadingTest(BaseTest):
|
|||||||
for query, ans in iteritems(oldvals):
|
for query, ans in iteritems(oldvals):
|
||||||
nr = cache.search(query, '')
|
nr = cache.search(query, '')
|
||||||
self.assertEqual(ans, nr,
|
self.assertEqual(ans, nr,
|
||||||
'Old result: %r != New result: %r for search: %s'%(
|
f'Old result: {ans!r} != New result: {nr!r} for search: {query}')
|
||||||
ans, nr, query))
|
|
||||||
|
|
||||||
# Test searching by id, which was introduced in the new backend
|
# Test searching by id, which was introduced in the new backend
|
||||||
self.assertEqual(cache.search('id:1', ''), {1})
|
self.assertEqual(cache.search('id:1', ''), {1})
|
||||||
@ -414,13 +413,12 @@ class ReadingTest(BaseTest):
|
|||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
self.assertEqual(oval, nval,
|
self.assertEqual(oval, nval,
|
||||||
'The attribute %s for %s in category %s does not match. Old is %r, New is %r'
|
f'The attribute {attr} for {old.name} in category {category} does not match. Old is {oval!r}, New is {nval!r}')
|
||||||
%(attr, old.name, category, oval, nval))
|
|
||||||
|
|
||||||
for category in old_categories:
|
for category in old_categories:
|
||||||
old, new = old_categories[category], new_categories[category]
|
old, new = old_categories[category], new_categories[category]
|
||||||
self.assertEqual(len(old), len(new),
|
self.assertEqual(len(old), len(new),
|
||||||
'The number of items in the category %s is not the same'%category)
|
f'The number of items in the category {category} is not the same')
|
||||||
for o, n in zip(old, new):
|
for o, n in zip(old, new):
|
||||||
compare_category(category, o, n)
|
compare_category(category, o, n)
|
||||||
|
|
||||||
@ -595,7 +593,7 @@ class ReadingTest(BaseTest):
|
|||||||
test(True, {3}, 'Unknown')
|
test(True, {3}, 'Unknown')
|
||||||
c.limit = 5
|
c.limit = 5
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
test(False, set(), 'nomatch_%s' % i)
|
test(False, set(), f'nomatch_{i}')
|
||||||
test(False, {3}, 'Unknown') # cached search expired
|
test(False, {3}, 'Unknown') # cached search expired
|
||||||
test(False, {3}, '', 'unknown', num=1)
|
test(False, {3}, '', 'unknown', num=1)
|
||||||
test(True, {3}, '', 'unknown', num=1)
|
test(True, {3}, '', 'unknown', num=1)
|
||||||
@ -638,7 +636,7 @@ class ReadingTest(BaseTest):
|
|||||||
v = pmi.get_standard_metadata(field)
|
v = pmi.get_standard_metadata(field)
|
||||||
self.assertTrue(v is None or isinstance(v, dict))
|
self.assertTrue(v is None or isinstance(v, dict))
|
||||||
self.assertEqual(f(mi.get_standard_metadata(field, False)), f(v),
|
self.assertEqual(f(mi.get_standard_metadata(field, False)), f(v),
|
||||||
'get_standard_metadata() failed for field %s' % field)
|
f'get_standard_metadata() failed for field {field}')
|
||||||
for field, meta in cache.field_metadata.custom_iteritems():
|
for field, meta in cache.field_metadata.custom_iteritems():
|
||||||
if meta['datatype'] != 'composite':
|
if meta['datatype'] != 'composite':
|
||||||
self.assertEqual(f(getattr(mi, field)), f(getattr(pmi, field)),
|
self.assertEqual(f(getattr(mi, field)), f(getattr(pmi, field)),
|
||||||
|
@ -65,19 +65,16 @@ class WritingTest(BaseTest):
|
|||||||
if test.name.endswith('_index'):
|
if test.name.endswith('_index'):
|
||||||
val = float(val) if val is not None else 1.0
|
val = float(val) if val is not None else 1.0
|
||||||
self.assertEqual(sqlite_res, val,
|
self.assertEqual(sqlite_res, val,
|
||||||
'Failed setting for %s with value %r, sqlite value not the same. val: %r != sqlite_val: %r'%(
|
f'Failed setting for {test.name} with value {val!r}, sqlite value not the same. val: {val!r} != sqlite_val: {sqlite_res!r}')
|
||||||
test.name, val, val, sqlite_res))
|
|
||||||
else:
|
else:
|
||||||
test.setter(db)(1, val)
|
test.setter(db)(1, val)
|
||||||
old_cached_res = getter(1)
|
old_cached_res = getter(1)
|
||||||
self.assertEqual(old_cached_res, cached_res,
|
self.assertEqual(old_cached_res, cached_res,
|
||||||
'Failed setting for %s with value %r, cached value not the same. Old: %r != New: %r'%(
|
f'Failed setting for {test.name} with value {val!r}, cached value not the same. Old: {old_cached_res!r} != New: {cached_res!r}')
|
||||||
test.name, val, old_cached_res, cached_res))
|
|
||||||
db.refresh()
|
db.refresh()
|
||||||
old_sqlite_res = getter(1)
|
old_sqlite_res = getter(1)
|
||||||
self.assertEqual(old_sqlite_res, sqlite_res,
|
self.assertEqual(old_sqlite_res, sqlite_res,
|
||||||
'Failed setting for %s, sqlite value not the same: %r != %r'%(
|
f'Failed setting for {test.name}, sqlite value not the same: {old_sqlite_res!r} != {sqlite_res!r}')
|
||||||
test.name, old_sqlite_res, sqlite_res))
|
|
||||||
del db
|
del db
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -755,7 +752,7 @@ class WritingTest(BaseTest):
|
|||||||
self.assertEqual(ldata, {aid:d['link'] for aid, d in iteritems(c.author_data())})
|
self.assertEqual(ldata, {aid:d['link'] for aid, d in iteritems(c.author_data())})
|
||||||
self.assertEqual({3}, cache.set_link_for_authors({aid:'xxx' if aid == max(adata) else str(aid) for aid in adata}),
|
self.assertEqual({3}, cache.set_link_for_authors({aid:'xxx' if aid == max(adata) else str(aid) for aid in adata}),
|
||||||
'Setting the author link to the same value as before, incorrectly marked some books as dirty')
|
'Setting the author link to the same value as before, incorrectly marked some books as dirty')
|
||||||
sdata = {aid:'%s, changed' % aid for aid in adata}
|
sdata = {aid:f'{aid}, changed' for aid in adata}
|
||||||
self.assertEqual({1,2,3}, cache.set_sort_for_authors(sdata))
|
self.assertEqual({1,2,3}, cache.set_sort_for_authors(sdata))
|
||||||
for bid in (1, 2, 3):
|
for bid in (1, 2, 3):
|
||||||
self.assertIn(', changed', cache.field_for('author_sort', bid))
|
self.assertIn(', changed', cache.field_for('author_sort', bid))
|
||||||
|
@ -294,7 +294,7 @@ class ThumbnailCache:
|
|||||||
if not hasattr(self, 'total_size'):
|
if not hasattr(self, 'total_size'):
|
||||||
self._load_index()
|
self._load_index()
|
||||||
self._invalidate_sizes()
|
self._invalidate_sizes()
|
||||||
ts = ('%.2f' % timestamp).replace('.00', '')
|
ts = (f'{timestamp:.2f}').replace('.00', '')
|
||||||
path = '%s%s%s%s%d-%s-%d-%dx%d' % (
|
path = '%s%s%s%s%d-%s-%d-%dx%d' % (
|
||||||
self.group_id, os.sep, book_id % 100, os.sep,
|
self.group_id, os.sep, book_id % 100, os.sep,
|
||||||
book_id, ts, len(data), self.thumbnail_size[0], self.thumbnail_size[1])
|
book_id, ts, len(data), self.thumbnail_size[0], self.thumbnail_size[1])
|
||||||
|
@ -95,7 +95,7 @@ def format_is_multiple(x, sep=',', repl=None):
|
|||||||
def format_identifiers(x):
|
def format_identifiers(x):
|
||||||
if not x:
|
if not x:
|
||||||
return None
|
return None
|
||||||
return ','.join('%s:%s'%(k, v) for k, v in iteritems(x))
|
return ','.join(f'{k}:{v}' for k, v in iteritems(x))
|
||||||
|
|
||||||
|
|
||||||
class View:
|
class View:
|
||||||
@ -190,7 +190,7 @@ class View:
|
|||||||
|
|
||||||
def _get_id(self, idx, index_is_id=True):
|
def _get_id(self, idx, index_is_id=True):
|
||||||
if index_is_id and not self.cache.has_id(idx):
|
if index_is_id and not self.cache.has_id(idx):
|
||||||
raise IndexError('No book with id %s present'%idx)
|
raise IndexError(f'No book with id {idx} present')
|
||||||
return idx if index_is_id else self.index_to_id(idx)
|
return idx if index_is_id else self.index_to_id(idx)
|
||||||
|
|
||||||
def has_id(self, book_id):
|
def has_id(self, book_id):
|
||||||
@ -242,7 +242,7 @@ class View:
|
|||||||
def _get(self, field, idx, index_is_id=True, default_value=None, fmt=lambda x:x):
|
def _get(self, field, idx, index_is_id=True, default_value=None, fmt=lambda x:x):
|
||||||
id_ = idx if index_is_id else self.index_to_id(idx)
|
id_ = idx if index_is_id else self.index_to_id(idx)
|
||||||
if index_is_id and not self.cache.has_id(id_):
|
if index_is_id and not self.cache.has_id(id_):
|
||||||
raise IndexError('No book with id %s present'%idx)
|
raise IndexError(f'No book with id {idx} present')
|
||||||
return fmt(self.cache.field_for(field, id_, default_value=default_value))
|
return fmt(self.cache.field_for(field, id_, default_value=default_value))
|
||||||
|
|
||||||
def get_series_sort(self, idx, index_is_id=True, default_value=''):
|
def get_series_sort(self, idx, index_is_id=True, default_value=''):
|
||||||
|
@ -204,7 +204,7 @@ def one_one_in_books(book_id_val_map, db, field, *args):
|
|||||||
if book_id_val_map:
|
if book_id_val_map:
|
||||||
sequence = ((sqlite_datetime(v), k) for k, v in book_id_val_map.items())
|
sequence = ((sqlite_datetime(v), k) for k, v in book_id_val_map.items())
|
||||||
db.executemany(
|
db.executemany(
|
||||||
'UPDATE books SET %s=? WHERE id=?'%field.metadata['column'], sequence)
|
'UPDATE books SET {}=? WHERE id=?'.format(field.metadata['column']), sequence)
|
||||||
field.table.book_col_map.update(book_id_val_map)
|
field.table.book_col_map.update(book_id_val_map)
|
||||||
return set(book_id_val_map)
|
return set(book_id_val_map)
|
||||||
|
|
||||||
@ -229,13 +229,13 @@ def one_one_in_other(book_id_val_map, db, field, *args):
|
|||||||
book_id_val_map = {k:v for k, v in iteritems(book_id_val_map) if v != g(k, missing)}
|
book_id_val_map = {k:v for k, v in iteritems(book_id_val_map) if v != g(k, missing)}
|
||||||
deleted = tuple((k,) for k, v in iteritems(book_id_val_map) if v is None)
|
deleted = tuple((k,) for k, v in iteritems(book_id_val_map) if v is None)
|
||||||
if deleted:
|
if deleted:
|
||||||
db.executemany('DELETE FROM %s WHERE book=?'%field.metadata['table'],
|
db.executemany('DELETE FROM {} WHERE book=?'.format(field.metadata['table']),
|
||||||
deleted)
|
deleted)
|
||||||
for book_id in deleted:
|
for book_id in deleted:
|
||||||
field.table.book_col_map.pop(book_id[0], None)
|
field.table.book_col_map.pop(book_id[0], None)
|
||||||
updated = {k:v for k, v in iteritems(book_id_val_map) if v is not None}
|
updated = {k:v for k, v in iteritems(book_id_val_map) if v is not None}
|
||||||
if updated:
|
if updated:
|
||||||
db.executemany('INSERT OR REPLACE INTO %s(book,%s) VALUES (?,?)'%(
|
db.executemany('INSERT OR REPLACE INTO {}(book,{}) VALUES (?,?)'.format(
|
||||||
field.metadata['table'], field.metadata['column']),
|
field.metadata['table'], field.metadata['column']),
|
||||||
((k, sqlite_datetime(v)) for k, v in iteritems(updated)))
|
((k, sqlite_datetime(v)) for k, v in iteritems(updated)))
|
||||||
field.table.book_col_map.update(updated)
|
field.table.book_col_map.update(updated)
|
||||||
@ -260,7 +260,7 @@ def custom_series_index(book_id_val_map, db, field, *args):
|
|||||||
# sorts the same as other books with no series.
|
# sorts the same as other books with no series.
|
||||||
field.table.remove_books((book_id,), db)
|
field.table.remove_books((book_id,), db)
|
||||||
if sequence:
|
if sequence:
|
||||||
db.executemany('UPDATE %s SET %s=? WHERE book=? AND value=?'%(
|
db.executemany('UPDATE {} SET {}=? WHERE book=? AND value=?'.format(
|
||||||
field.metadata['table'], field.metadata['column']), sequence)
|
field.metadata['table'], field.metadata['column']), sequence)
|
||||||
return {s[1] for s in sequence}
|
return {s[1] for s in sequence}
|
||||||
# }}}
|
# }}}
|
||||||
@ -287,7 +287,7 @@ def get_db_id(val, db, m, table, kmap, rid_map, allow_case_change,
|
|||||||
db.execute('INSERT INTO authors(name,sort) VALUES (?,?)',
|
db.execute('INSERT INTO authors(name,sort) VALUES (?,?)',
|
||||||
(val.replace(',', '|'), aus))
|
(val.replace(',', '|'), aus))
|
||||||
else:
|
else:
|
||||||
db.execute('INSERT INTO %s(%s) VALUES (?)'%(
|
db.execute('INSERT INTO {}({}) VALUES (?)'.format(
|
||||||
m['table'], m['column']), (val,))
|
m['table'], m['column']), (val,))
|
||||||
item_id = rid_map[kval] = db.last_insert_rowid()
|
item_id = rid_map[kval] = db.last_insert_rowid()
|
||||||
table.id_map[item_id] = val
|
table.id_map[item_id] = val
|
||||||
@ -310,7 +310,7 @@ def change_case(case_changes, dirtied, db, table, m, is_authors=False):
|
|||||||
else:
|
else:
|
||||||
vals = ((val, item_id) for item_id, val in iteritems(case_changes))
|
vals = ((val, item_id) for item_id, val in iteritems(case_changes))
|
||||||
db.executemany(
|
db.executemany(
|
||||||
'UPDATE %s SET %s=? WHERE id=?'%(m['table'], m['column']), vals)
|
'UPDATE {} SET {}=? WHERE id=?'.format(m['table'], m['column']), vals)
|
||||||
for item_id, val in iteritems(case_changes):
|
for item_id, val in iteritems(case_changes):
|
||||||
table.id_map[item_id] = val
|
table.id_map[item_id] = val
|
||||||
dirtied.update(table.col_book_map[item_id])
|
dirtied.update(table.col_book_map[item_id])
|
||||||
@ -366,7 +366,7 @@ def many_one(book_id_val_map, db, field, allow_case_change, *args):
|
|||||||
|
|
||||||
# Update the db link table
|
# Update the db link table
|
||||||
if deleted:
|
if deleted:
|
||||||
db.executemany('DELETE FROM %s WHERE book=?'%table.link_table,
|
db.executemany(f'DELETE FROM {table.link_table} WHERE book=?',
|
||||||
((k,) for k in deleted))
|
((k,) for k in deleted))
|
||||||
if updated:
|
if updated:
|
||||||
sql = (
|
sql = (
|
||||||
@ -383,7 +383,7 @@ def many_one(book_id_val_map, db, field, allow_case_change, *args):
|
|||||||
if remove:
|
if remove:
|
||||||
if table.supports_notes:
|
if table.supports_notes:
|
||||||
db.clear_notes_for_category_items(table.name, remove)
|
db.clear_notes_for_category_items(table.name, remove)
|
||||||
db.executemany('DELETE FROM %s WHERE id=?'%m['table'],
|
db.executemany('DELETE FROM {} WHERE id=?'.format(m['table']),
|
||||||
((item_id,) for item_id in remove))
|
((item_id,) for item_id in remove))
|
||||||
for item_id in remove:
|
for item_id in remove:
|
||||||
del table.id_map[item_id]
|
del table.id_map[item_id]
|
||||||
@ -467,14 +467,14 @@ def many_many(book_id_val_map, db, field, allow_case_change, *args):
|
|||||||
|
|
||||||
# Update the db link table
|
# Update the db link table
|
||||||
if deleted:
|
if deleted:
|
||||||
db.executemany('DELETE FROM %s WHERE book=?'%table.link_table,
|
db.executemany(f'DELETE FROM {table.link_table} WHERE book=?',
|
||||||
((k,) for k in deleted))
|
((k,) for k in deleted))
|
||||||
if updated:
|
if updated:
|
||||||
vals = (
|
vals = (
|
||||||
(book_id, val) for book_id, vals in iteritems(updated)
|
(book_id, val) for book_id, vals in iteritems(updated)
|
||||||
for val in vals
|
for val in vals
|
||||||
)
|
)
|
||||||
db.executemany('DELETE FROM %s WHERE book=?'%table.link_table,
|
db.executemany(f'DELETE FROM {table.link_table} WHERE book=?',
|
||||||
((k,) for k in updated))
|
((k,) for k in updated))
|
||||||
db.executemany('INSERT INTO {}(book,{}) VALUES(?, ?)'.format(
|
db.executemany('INSERT INTO {}(book,{}) VALUES(?, ?)'.format(
|
||||||
table.link_table, m['link_column']), vals)
|
table.link_table, m['link_column']), vals)
|
||||||
@ -488,7 +488,7 @@ def many_many(book_id_val_map, db, field, allow_case_change, *args):
|
|||||||
if remove:
|
if remove:
|
||||||
if table.supports_notes:
|
if table.supports_notes:
|
||||||
db.clear_notes_for_category_items(table.name, remove)
|
db.clear_notes_for_category_items(table.name, remove)
|
||||||
db.executemany('DELETE FROM %s WHERE id=?'%m['table'],
|
db.executemany('DELETE FROM {} WHERE id=?'.format(m['table']),
|
||||||
((item_id,) for item_id in remove))
|
((item_id,) for item_id in remove))
|
||||||
for item_id in remove:
|
for item_id in remove:
|
||||||
del table.id_map[item_id]
|
del table.id_map[item_id]
|
||||||
|
@ -321,7 +321,7 @@ def main(args=sys.argv):
|
|||||||
elif ext in {'mobi', 'azw', 'azw3'}:
|
elif ext in {'mobi', 'azw', 'azw3'}:
|
||||||
inspect_mobi(path)
|
inspect_mobi(path)
|
||||||
else:
|
else:
|
||||||
print('Cannot dump unknown filetype: %s' % path)
|
print(f'Cannot dump unknown filetype: {path}')
|
||||||
elif len(args) >= 2 and os.path.exists(os.path.join(args[1], '__main__.py')):
|
elif len(args) >= 2 and os.path.exists(os.path.join(args[1], '__main__.py')):
|
||||||
sys.path.insert(0, args[1])
|
sys.path.insert(0, args[1])
|
||||||
run_script(os.path.join(args[1], '__main__.py'), args[2:])
|
run_script(os.path.join(args[1], '__main__.py'), args[2:])
|
||||||
|
@ -90,7 +90,7 @@ def debug(ioreg_to_tmp=False, buf=None, plugins=None,
|
|||||||
try:
|
try:
|
||||||
d.startup()
|
d.startup()
|
||||||
except:
|
except:
|
||||||
out('Startup failed for device plugin: %s'%d)
|
out(f'Startup failed for device plugin: {d}')
|
||||||
|
|
||||||
if disabled_plugins is None:
|
if disabled_plugins is None:
|
||||||
disabled_plugins = list(disabled_device_plugins())
|
disabled_plugins = list(disabled_device_plugins())
|
||||||
|
@ -208,7 +208,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
d.startup()
|
d.startup()
|
||||||
except:
|
except:
|
||||||
print('Startup failed for device plugin: %s'%d)
|
print(f'Startup failed for device plugin: {d}')
|
||||||
if d.MANAGES_DEVICE_PRESENCE:
|
if d.MANAGES_DEVICE_PRESENCE:
|
||||||
cd = d.detect_managed_devices(scanner.devices)
|
cd = d.detect_managed_devices(scanner.devices)
|
||||||
if cd is not None:
|
if cd is not None:
|
||||||
|
@ -49,7 +49,7 @@ class CYBOOK(USBMS):
|
|||||||
coverdata = coverdata[2]
|
coverdata = coverdata[2]
|
||||||
else:
|
else:
|
||||||
coverdata = None
|
coverdata = None
|
||||||
with open('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile:
|
with open(f'{os.path.join(path, filename)}_6090.t2b', 'wb') as t2bfile:
|
||||||
t2b.write_t2b(t2bfile, coverdata)
|
t2b.write_t2b(t2bfile, coverdata)
|
||||||
fsync(t2bfile)
|
fsync(t2bfile)
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ class ORIZON(CYBOOK):
|
|||||||
coverdata = coverdata[2]
|
coverdata = coverdata[2]
|
||||||
else:
|
else:
|
||||||
coverdata = None
|
coverdata = None
|
||||||
with open('%s.thn' % filepath, 'wb') as thnfile:
|
with open(f'{filepath}.thn', 'wb') as thnfile:
|
||||||
t4b.write_t4b(thnfile, coverdata)
|
t4b.write_t4b(thnfile, coverdata)
|
||||||
fsync(thnfile)
|
fsync(thnfile)
|
||||||
|
|
||||||
|
@ -28,9 +28,9 @@ class IRIVER_STORY(USBMS):
|
|||||||
VENDOR_NAME = 'IRIVER'
|
VENDOR_NAME = 'IRIVER'
|
||||||
WINDOWS_MAIN_MEM = ['STORY', 'STORY_EB05', 'STORY_WI-FI', 'STORY_EB07',
|
WINDOWS_MAIN_MEM = ['STORY', 'STORY_EB05', 'STORY_WI-FI', 'STORY_EB07',
|
||||||
'STORY_EB12']
|
'STORY_EB12']
|
||||||
WINDOWS_MAIN_MEM = re.compile(r'(%s)&'%('|'.join(WINDOWS_MAIN_MEM)))
|
WINDOWS_MAIN_MEM = re.compile(r'({})&'.format('|'.join(WINDOWS_MAIN_MEM)))
|
||||||
WINDOWS_CARD_A_MEM = ['STORY', 'STORY_SD', 'STORY_EB12_SD']
|
WINDOWS_CARD_A_MEM = ['STORY', 'STORY_SD', 'STORY_EB12_SD']
|
||||||
WINDOWS_CARD_A_MEM = re.compile(r'(%s)&'%('|'.join(WINDOWS_CARD_A_MEM)))
|
WINDOWS_CARD_A_MEM = re.compile(r'({})&'.format('|'.join(WINDOWS_CARD_A_MEM)))
|
||||||
|
|
||||||
# OSX_MAIN_MEM = 'Kindle Internal Storage Media'
|
# OSX_MAIN_MEM = 'Kindle Internal Storage Media'
|
||||||
# OSX_CARD_A_MEM = 'Kindle Card Storage Media'
|
# OSX_CARD_A_MEM = 'Kindle Card Storage Media'
|
||||||
|
@ -105,13 +105,13 @@ class APNXBuilder:
|
|||||||
|
|
||||||
# Updated header if we have a KF8 file...
|
# Updated header if we have a KF8 file...
|
||||||
if apnx_meta['format'] == 'MOBI_8':
|
if apnx_meta['format'] == 'MOBI_8':
|
||||||
content_header = '{"contentGuid":"%(guid)s","asin":"%(asin)s","cdeType":"%(cdetype)s","format":"%(format)s","fileRevisionId":"1","acr":"%(acr)s"}' % apnx_meta # noqa: E501
|
content_header = '{{"contentGuid":"{guid}","asin":"{asin}","cdeType":"{cdetype}","format":"{format}","fileRevisionId":"1","acr":"{acr}"}}'.format(**apnx_meta) # noqa: E501
|
||||||
else:
|
else:
|
||||||
# My 5.1.x Touch & 3.4 K3 seem to handle the 'extended' header fine for
|
# My 5.1.x Touch & 3.4 K3 seem to handle the 'extended' header fine for
|
||||||
# legacy mobi files, too. But, since they still handle this one too, let's
|
# legacy mobi files, too. But, since they still handle this one too, let's
|
||||||
# try not to break old devices, and keep using the simple header ;).
|
# try not to break old devices, and keep using the simple header ;).
|
||||||
content_header = '{"contentGuid":"%(guid)s","asin":"%(asin)s","cdeType":"%(cdetype)s","fileRevisionId":"1"}' % apnx_meta
|
content_header = '{{"contentGuid":"{guid}","asin":"{asin}","cdeType":"{cdetype}","fileRevisionId":"1"}}'.format(**apnx_meta)
|
||||||
page_header = '{"asin":"%(asin)s","pageMap":"' % apnx_meta
|
page_header = '{{"asin":"{asin}","pageMap":"'.format(**apnx_meta)
|
||||||
page_header += pages.page_maps + '"}'
|
page_header += pages.page_maps + '"}'
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
prints('APNX Content Header:', content_header)
|
prints('APNX Content Header:', content_header)
|
||||||
|
@ -33,7 +33,7 @@ class Bookmark: # {{{
|
|||||||
def record(self, n):
|
def record(self, n):
|
||||||
from calibre.ebooks.metadata.mobi import StreamSlicer
|
from calibre.ebooks.metadata.mobi import StreamSlicer
|
||||||
if n >= self.nrecs:
|
if n >= self.nrecs:
|
||||||
raise ValueError('non-existent record %r' % n)
|
raise ValueError(f'non-existent record {n!r}')
|
||||||
offoff = 78 + (8 * n)
|
offoff = 78 + (8 * n)
|
||||||
start, = unpack('>I', self.data[offoff + 0:offoff + 4])
|
start, = unpack('>I', self.data[offoff + 0:offoff + 4])
|
||||||
stop = None
|
stop = None
|
||||||
@ -141,7 +141,7 @@ class Bookmark: # {{{
|
|||||||
# Search looks for book title match, highlight match, and location match
|
# Search looks for book title match, highlight match, and location match
|
||||||
# Author is not matched
|
# Author is not matched
|
||||||
# This will find the first instance of a clipping only
|
# This will find the first instance of a clipping only
|
||||||
book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format)
|
book_fs = self.path.replace(f'.{self.bookmark_extension}',f'.{self.book_format}')
|
||||||
with open(book_fs,'rb') as f2:
|
with open(book_fs,'rb') as f2:
|
||||||
stream = io.BytesIO(f2.read())
|
stream = io.BytesIO(f2.read())
|
||||||
mi = get_topaz_metadata(stream)
|
mi = get_topaz_metadata(stream)
|
||||||
@ -152,7 +152,7 @@ class Bookmark: # {{{
|
|||||||
with open(my_clippings, encoding='utf-8', errors='replace') as f2:
|
with open(my_clippings, encoding='utf-8', errors='replace') as f2:
|
||||||
marker_found = 0
|
marker_found = 0
|
||||||
text = ''
|
text = ''
|
||||||
search_str1 = '%s' % (mi.title)
|
search_str1 = f'{mi.title}'
|
||||||
search_str2 = '- Highlight Loc. %d' % (displayed_location)
|
search_str2 = '- Highlight Loc. %d' % (displayed_location)
|
||||||
for line in f2:
|
for line in f2:
|
||||||
if marker_found == 0:
|
if marker_found == 0:
|
||||||
@ -271,12 +271,12 @@ class Bookmark: # {{{
|
|||||||
self.last_read_location = self.last_read - self.pdf_page_offset
|
self.last_read_location = self.last_read - self.pdf_page_offset
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print('unsupported bookmark_extension: %s' % self.bookmark_extension)
|
print(f'unsupported bookmark_extension: {self.bookmark_extension}')
|
||||||
self.user_notes = user_notes
|
self.user_notes = user_notes
|
||||||
|
|
||||||
def get_book_length(self):
|
def get_book_length(self):
|
||||||
from calibre.ebooks.metadata.mobi import StreamSlicer
|
from calibre.ebooks.metadata.mobi import StreamSlicer
|
||||||
book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format)
|
book_fs = self.path.replace(f'.{self.bookmark_extension}',f'.{self.book_format}')
|
||||||
|
|
||||||
self.book_length = 0
|
self.book_length = 0
|
||||||
if self.bookmark_extension == 'mbp':
|
if self.bookmark_extension == 'mbp':
|
||||||
@ -300,6 +300,6 @@ class Bookmark: # {{{
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
print('unsupported bookmark_extension: %s' % self.bookmark_extension)
|
print(f'unsupported bookmark_extension: {self.bookmark_extension}')
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -294,7 +294,7 @@ class KINDLE(USBMS):
|
|||||||
typ=user_notes[location]['type'],
|
typ=user_notes[location]['type'],
|
||||||
text=(user_notes[location]['text'] if
|
text=(user_notes[location]['text'] if
|
||||||
user_notes[location]['type'] == 'Note' else
|
user_notes[location]['type'] == 'Note' else
|
||||||
'<i>%s</i>' % user_notes[location]['text'])))
|
'<i>{}</i>'.format(user_notes[location]['text']))))
|
||||||
else:
|
else:
|
||||||
if bookmark.book_format == 'pdf':
|
if bookmark.book_format == 'pdf':
|
||||||
annotations.append(
|
annotations.append(
|
||||||
@ -351,7 +351,7 @@ class KINDLE(USBMS):
|
|||||||
bm.value.path, index_is_id=True)
|
bm.value.path, index_is_id=True)
|
||||||
elif bm.type == 'kindle_clippings':
|
elif bm.type == 'kindle_clippings':
|
||||||
# Find 'My Clippings' author=Kindle in database, or add
|
# Find 'My Clippings' author=Kindle in database, or add
|
||||||
last_update = 'Last modified %s' % strftime('%x %X',bm.value['timestamp'].timetuple())
|
last_update = 'Last modified {}'.format(strftime('%x %X',bm.value['timestamp'].timetuple()))
|
||||||
mc_id = list(db.data.search_getting_ids('title:"My Clippings"', '', sort_results=False))
|
mc_id = list(db.data.search_getting_ids('title:"My Clippings"', '', sort_results=False))
|
||||||
if mc_id:
|
if mc_id:
|
||||||
db.add_format_with_hooks(mc_id[0], 'TXT', bm.value['path'],
|
db.add_format_with_hooks(mc_id[0], 'TXT', bm.value['path'],
|
||||||
@ -623,7 +623,7 @@ class KINDLE2(KINDLE):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
apnx_path = '%s.apnx' % os.path.join(path, filename)
|
apnx_path = f'{os.path.join(path, filename)}.apnx'
|
||||||
apnx_builder = APNXBuilder()
|
apnx_builder = APNXBuilder()
|
||||||
# Check to see if there is an existing apnx file on Kindle we should keep.
|
# Check to see if there is an existing apnx file on Kindle we should keep.
|
||||||
if opts.extra_customization[self.OPT_APNX_OVERWRITE] or not os.path.exists(apnx_path):
|
if opts.extra_customization[self.OPT_APNX_OVERWRITE] or not os.path.exists(apnx_path):
|
||||||
@ -636,7 +636,7 @@ class KINDLE2(KINDLE):
|
|||||||
if temp in self.EXTRA_CUSTOMIZATION_CHOICES[self.OPT_APNX_METHOD]:
|
if temp in self.EXTRA_CUSTOMIZATION_CHOICES[self.OPT_APNX_METHOD]:
|
||||||
method = temp
|
method = temp
|
||||||
else:
|
else:
|
||||||
print('Invalid method choice for this book (%r), ignoring.' % temp)
|
print(f'Invalid method choice for this book ({temp!r}), ignoring.')
|
||||||
except:
|
except:
|
||||||
print('Could not retrieve override method choice, using default.')
|
print('Could not retrieve override method choice, using default.')
|
||||||
apnx_builder.write_apnx(filepath, apnx_path, method=method, page_count=custom_page_count)
|
apnx_builder.write_apnx(filepath, apnx_path, method=method, page_count=custom_page_count)
|
||||||
|
@ -106,7 +106,7 @@ class Book(Book_):
|
|||||||
if self.contentID:
|
if self.contentID:
|
||||||
fmt('Content ID', self.contentID)
|
fmt('Content ID', self.contentID)
|
||||||
if self.kobo_series:
|
if self.kobo_series:
|
||||||
fmt('Kobo Series', self.kobo_series + ' #%s'%self.kobo_series_number)
|
fmt('Kobo Series', self.kobo_series + f' #{self.kobo_series_number}')
|
||||||
if self.kobo_series_id:
|
if self.kobo_series_id:
|
||||||
fmt('Kobo Series ID', self.kobo_series_id)
|
fmt('Kobo Series ID', self.kobo_series_id)
|
||||||
if self.kobo_subtitle:
|
if self.kobo_subtitle:
|
||||||
@ -203,7 +203,7 @@ class KTCollectionsBookList(CollectionsBookList):
|
|||||||
fm = None
|
fm = None
|
||||||
attr = attr.strip()
|
attr = attr.strip()
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KTCollectionsBookList:get_collections - attr='%s'"%attr)
|
debug_print(f"KTCollectionsBookList:get_collections - attr='{attr}'")
|
||||||
|
|
||||||
# If attr is device_collections, then we cannot use
|
# If attr is device_collections, then we cannot use
|
||||||
# format_field, because we don't know the fields where the
|
# format_field, because we don't know the fields where the
|
||||||
|
@ -384,25 +384,25 @@ class KOBO(USBMS):
|
|||||||
if self.dbversion >= 33:
|
if self.dbversion >= 33:
|
||||||
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
||||||
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, IsDownloaded from content where '
|
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, IsDownloaded from content where '
|
||||||
'BookID is Null %(previews)s %(recommendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(
|
'BookID is Null {previews} {recommendations} and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) {expiry}').format(**dict(
|
||||||
expiry=' and ContentType = 6)' if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')',
|
expiry=' and ContentType = 6)' if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')',
|
||||||
previews=' and Accessibility <> 6' if not self.show_previews else '',
|
previews=' and Accessibility <> 6' if not self.show_previews else '',
|
||||||
recommendations=" and IsDownloaded in ('true', 1)" if opts.extra_customization[self.OPT_SHOW_RECOMMENDATIONS] is False else '')
|
recommendations=" and IsDownloaded in ('true', 1)" if opts.extra_customization[self.OPT_SHOW_RECOMMENDATIONS] is False else ''))
|
||||||
elif self.dbversion >= 16 and self.dbversion < 33:
|
elif self.dbversion >= 16 and self.dbversion < 33:
|
||||||
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
||||||
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, "1" as IsDownloaded from content where '
|
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility, "1" as IsDownloaded from content where '
|
||||||
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)'
|
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) {expiry}').format(**dict(expiry=' and ContentType = 6)'
|
||||||
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
|
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')'))
|
||||||
elif self.dbversion < 16 and self.dbversion >= 14:
|
elif self.dbversion < 16 and self.dbversion >= 14:
|
||||||
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
||||||
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where '
|
'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where '
|
||||||
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)'
|
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) {expiry}').format(**dict(expiry=' and ContentType = 6)'
|
||||||
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
|
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')'))
|
||||||
elif self.dbversion < 14 and self.dbversion >= 8:
|
elif self.dbversion < 14 and self.dbversion >= 8:
|
||||||
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
||||||
'ImageID, ReadStatus, ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where '
|
'ImageID, ReadStatus, ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility, "1" as IsDownloaded from content where '
|
||||||
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)'
|
'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) {expiry}').format(**dict(expiry=' and ContentType = 6)'
|
||||||
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')')
|
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')'))
|
||||||
else:
|
else:
|
||||||
query = ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
query = ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, '
|
||||||
'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as FavouritesIndex, '
|
'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as FavouritesIndex, '
|
||||||
@ -608,12 +608,12 @@ class KOBO(USBMS):
|
|||||||
self.report_progress(1.0, _('Removing books from device metadata listing...'))
|
self.report_progress(1.0, _('Removing books from device metadata listing...'))
|
||||||
|
|
||||||
def add_books_to_metadata(self, locations, metadata, booklists):
|
def add_books_to_metadata(self, locations, metadata, booklists):
|
||||||
debug_print('KoboTouch::add_books_to_metadata - start. metadata=%s' % metadata[0])
|
debug_print(f'KoboTouch::add_books_to_metadata - start. metadata={metadata[0]}')
|
||||||
metadata = iter(metadata)
|
metadata = iter(metadata)
|
||||||
for i, location in enumerate(locations):
|
for i, location in enumerate(locations):
|
||||||
self.report_progress((i+1) / float(len(locations)), _('Adding books to device metadata listing...'))
|
self.report_progress((i+1) / float(len(locations)), _('Adding books to device metadata listing...'))
|
||||||
info = next(metadata)
|
info = next(metadata)
|
||||||
debug_print('KoboTouch::add_books_to_metadata - info=%s' % info)
|
debug_print(f'KoboTouch::add_books_to_metadata - info={info}')
|
||||||
blist = 2 if location[1] == 'cardb' else 1 if location[1] == 'carda' else 0
|
blist = 2 if location[1] == 'cardb' else 1 if location[1] == 'carda' else 0
|
||||||
|
|
||||||
# Extract the correct prefix from the pathname. To do this correctly,
|
# Extract the correct prefix from the pathname. To do this correctly,
|
||||||
@ -645,7 +645,7 @@ class KOBO(USBMS):
|
|||||||
book.size = os.stat(self.normalize_path(path)).st_size
|
book.size = os.stat(self.normalize_path(path)).st_size
|
||||||
b = booklists[blist].add_book(book, replace_metadata=True)
|
b = booklists[blist].add_book(book, replace_metadata=True)
|
||||||
if b:
|
if b:
|
||||||
debug_print('KoboTouch::add_books_to_metadata - have a new book - book=%s' % book)
|
debug_print(f'KoboTouch::add_books_to_metadata - have a new book - book={book}')
|
||||||
b._new_book = True
|
b._new_book = True
|
||||||
self.report_progress(1.0, _('Adding books to device metadata listing...'))
|
self.report_progress(1.0, _('Adding books to device metadata listing...'))
|
||||||
|
|
||||||
@ -755,9 +755,9 @@ class KOBO(USBMS):
|
|||||||
' selecting "Configure this device" and then the '
|
' selecting "Configure this device" and then the '
|
||||||
' "Attempt to support newer firmware" option.'
|
' "Attempt to support newer firmware" option.'
|
||||||
' Doing so may require you to perform a Factory reset of'
|
' Doing so may require you to perform a Factory reset of'
|
||||||
' your Kobo.') + ((
|
' your Kobo.') + (
|
||||||
'\nDevice database version: %s.'
|
f'\nDevice database version: {self.dbversion}.'
|
||||||
'\nDevice firmware version: %s') % (self.dbversion, self.display_fwversion))
|
f'\nDevice firmware version: {self.display_fwversion}')
|
||||||
, UserFeedback.WARN)
|
, UserFeedback.WARN)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -807,7 +807,7 @@ class KOBO(USBMS):
|
|||||||
('card_a', 'metadata.calibre', 1),
|
('card_a', 'metadata.calibre', 1),
|
||||||
('card_b', 'metadata.calibre', 2)
|
('card_b', 'metadata.calibre', 2)
|
||||||
]:
|
]:
|
||||||
prefix = getattr(self, '_%s_prefix'%prefix)
|
prefix = getattr(self, f'_{prefix}_prefix')
|
||||||
if prefix is not None and os.path.exists(prefix):
|
if prefix is not None and os.path.exists(prefix):
|
||||||
paths[source_id] = os.path.join(prefix, *(path.split('/')))
|
paths[source_id] = os.path.join(prefix, *(path.split('/')))
|
||||||
return paths
|
return paths
|
||||||
@ -891,7 +891,7 @@ class KOBO(USBMS):
|
|||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
def update_device_database_collections(self, booklists, collections_attributes, oncard):
|
def update_device_database_collections(self, booklists, collections_attributes, oncard):
|
||||||
debug_print("Kobo:update_device_database_collections - oncard='%s'"%oncard)
|
debug_print(f"Kobo:update_device_database_collections - oncard='{oncard}'")
|
||||||
if self.modify_database_check('update_device_database_collections') is False:
|
if self.modify_database_check('update_device_database_collections') is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1678,7 +1678,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
return "'true'" if x else "'false'"
|
return "'true'" if x else "'false'"
|
||||||
|
|
||||||
def books(self, oncard=None, end_session=True):
|
def books(self, oncard=None, end_session=True):
|
||||||
debug_print("KoboTouch:books - oncard='%s'"%oncard)
|
debug_print(f"KoboTouch:books - oncard='{oncard}'")
|
||||||
self.debugging_title = self.get_debugging_title()
|
self.debugging_title = self.get_debugging_title()
|
||||||
|
|
||||||
dummy_bl = self.booklist_class(None, None, None)
|
dummy_bl = self.booklist_class(None, None, None)
|
||||||
@ -1699,11 +1699,11 @@ class KOBOTOUCH(KOBO):
|
|||||||
prefix = self._card_a_prefix if oncard == 'carda' else \
|
prefix = self._card_a_prefix if oncard == 'carda' else \
|
||||||
self._card_b_prefix if oncard == 'cardb' \
|
self._card_b_prefix if oncard == 'cardb' \
|
||||||
else self._main_prefix
|
else self._main_prefix
|
||||||
debug_print("KoboTouch:books - oncard='%s', prefix='%s'"%(oncard, prefix))
|
debug_print(f"KoboTouch:books - oncard='{oncard}', prefix='{prefix}'")
|
||||||
|
|
||||||
self.fwversion = self.get_firmware_version()
|
self.fwversion = self.get_firmware_version()
|
||||||
|
|
||||||
debug_print('Kobo device: %s' % self.gui_name)
|
debug_print(f'Kobo device: {self.gui_name}')
|
||||||
debug_print('Version of driver:', self.version, 'Has kepubs:', self.has_kepubs)
|
debug_print('Version of driver:', self.version, 'Has kepubs:', self.has_kepubs)
|
||||||
debug_print('Version of firmware:', self.fwversion, 'Has kepubs:', self.has_kepubs)
|
debug_print('Version of firmware:', self.fwversion, 'Has kepubs:', self.has_kepubs)
|
||||||
debug_print('Firmware supports cover image tree:', self.fwversion >= self.min_fwversion_images_tree)
|
debug_print('Firmware supports cover image tree:', self.fwversion >= self.min_fwversion_images_tree)
|
||||||
@ -1718,7 +1718,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print('KoboTouch:books - driver options=', self)
|
debug_print('KoboTouch:books - driver options=', self)
|
||||||
debug_print("KoboTouch:books - prefs['manage_device_metadata']=", prefs['manage_device_metadata'])
|
debug_print("KoboTouch:books - prefs['manage_device_metadata']=", prefs['manage_device_metadata'])
|
||||||
debugging_title = self.debugging_title
|
debugging_title = self.debugging_title
|
||||||
debug_print("KoboTouch:books - set_debugging_title to '%s'" % debugging_title)
|
debug_print(f"KoboTouch:books - set_debugging_title to '{debugging_title}'")
|
||||||
bl.set_debugging_title(debugging_title)
|
bl.set_debugging_title(debugging_title)
|
||||||
debug_print('KoboTouch:books - length bl=%d'%len(bl))
|
debug_print('KoboTouch:books - length bl=%d'%len(bl))
|
||||||
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
|
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
|
||||||
@ -1739,7 +1739,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
show_debug = self.is_debugging_title(title)
|
show_debug = self.is_debugging_title(title)
|
||||||
# show_debug = authors == 'L. Frank Baum'
|
# show_debug = authors == 'L. Frank Baum'
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:update_booklist - title='%s'"%title, 'ContentType=%s'%ContentType, 'isdownloaded=', isdownloaded)
|
debug_print(f"KoboTouch:update_booklist - title='{title}'", f'ContentType={ContentType}', 'isdownloaded=', isdownloaded)
|
||||||
debug_print(
|
debug_print(
|
||||||
' prefix=%s, DateCreated=%s, readstatus=%d, MimeType=%s, expired=%d, favouritesindex=%d, accessibility=%d, isdownloaded=%s'%
|
' prefix=%s, DateCreated=%s, readstatus=%d, MimeType=%s, expired=%d, favouritesindex=%d, accessibility=%d, isdownloaded=%s'%
|
||||||
(prefix, DateCreated, readstatus, MimeType, expired, favouritesindex, accessibility, isdownloaded,))
|
(prefix, DateCreated, readstatus, MimeType, expired, favouritesindex, accessibility, isdownloaded,))
|
||||||
@ -1838,19 +1838,19 @@ class KOBOTOUCH(KOBO):
|
|||||||
try:
|
try:
|
||||||
kobo_metadata.pubdate = datetime.strptime(DateCreated, '%Y-%m-%dT%H:%M:%S.%fZ')
|
kobo_metadata.pubdate = datetime.strptime(DateCreated, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||||
except:
|
except:
|
||||||
debug_print("KoboTouch:update_booklist - Cannot convert date - DateCreated='%s'"%DateCreated)
|
debug_print(f"KoboTouch:update_booklist - Cannot convert date - DateCreated='{DateCreated}'")
|
||||||
|
|
||||||
idx = bl_cache.get(lpath, None)
|
idx = bl_cache.get(lpath, None)
|
||||||
if idx is not None: # and not (accessibility == 1 and isdownloaded == 'false'):
|
if idx is not None: # and not (accessibility == 1 and isdownloaded == 'false'):
|
||||||
if show_debug:
|
if show_debug:
|
||||||
self.debug_index = idx
|
self.debug_index = idx
|
||||||
debug_print('KoboTouch:update_booklist - idx=%d'%idx)
|
debug_print('KoboTouch:update_booklist - idx=%d'%idx)
|
||||||
debug_print('KoboTouch:update_booklist - lpath=%s'%lpath)
|
debug_print(f'KoboTouch:update_booklist - lpath={lpath}')
|
||||||
debug_print('KoboTouch:update_booklist - bl[idx].device_collections=', bl[idx].device_collections)
|
debug_print('KoboTouch:update_booklist - bl[idx].device_collections=', bl[idx].device_collections)
|
||||||
debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map)
|
debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map)
|
||||||
debug_print('KoboTouch:update_booklist - bookshelves=', bookshelves)
|
debug_print('KoboTouch:update_booklist - bookshelves=', bookshelves)
|
||||||
debug_print('KoboTouch:update_booklist - kobo_collections=', kobo_collections)
|
debug_print('KoboTouch:update_booklist - kobo_collections=', kobo_collections)
|
||||||
debug_print('KoboTouch:update_booklist - series="%s"' % bl[idx].series)
|
debug_print(f'KoboTouch:update_booklist - series="{bl[idx].series}"')
|
||||||
debug_print('KoboTouch:update_booklist - the book=', bl[idx])
|
debug_print('KoboTouch:update_booklist - the book=', bl[idx])
|
||||||
debug_print('KoboTouch:update_booklist - the authors=', bl[idx].authors)
|
debug_print('KoboTouch:update_booklist - the authors=', bl[idx].authors)
|
||||||
debug_print('KoboTouch:update_booklist - application_id=', bl[idx].application_id)
|
debug_print('KoboTouch:update_booklist - application_id=', bl[idx].application_id)
|
||||||
@ -1871,7 +1871,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print('KoboTouch:update_booklist - book size=', bl[idx].size)
|
debug_print('KoboTouch:update_booklist - book size=', bl[idx].size)
|
||||||
|
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:update_booklist - ContentID='%s'"%ContentID)
|
debug_print(f"KoboTouch:update_booklist - ContentID='{ContentID}'")
|
||||||
bl[idx].contentID = ContentID
|
bl[idx].contentID = ContentID
|
||||||
bl[idx].kobo_metadata = kobo_metadata
|
bl[idx].kobo_metadata = kobo_metadata
|
||||||
bl[idx].kobo_series = series
|
bl[idx].kobo_series = series
|
||||||
@ -1897,8 +1897,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print('KoboTouch:update_booklist - updated bl[idx].device_collections=', bl[idx].device_collections)
|
debug_print('KoboTouch:update_booklist - updated bl[idx].device_collections=', bl[idx].device_collections)
|
||||||
debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map, 'changed=', changed)
|
debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map, 'changed=', changed)
|
||||||
# debug_print('KoboTouch:update_booklist - book=', bl[idx])
|
# debug_print('KoboTouch:update_booklist - book=', bl[idx])
|
||||||
debug_print('KoboTouch:update_booklist - book class=%s'%bl[idx].__class__)
|
debug_print(f'KoboTouch:update_booklist - book class={bl[idx].__class__}')
|
||||||
debug_print('KoboTouch:update_booklist - book title=%s'%bl[idx].title)
|
debug_print(f'KoboTouch:update_booklist - book title={bl[idx].title}')
|
||||||
else:
|
else:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:update_booklist - idx is none')
|
debug_print('KoboTouch:update_booklist - idx is none')
|
||||||
@ -1911,10 +1911,10 @@ class KOBOTOUCH(KOBO):
|
|||||||
title = 'FILE MISSING: ' + title
|
title = 'FILE MISSING: ' + title
|
||||||
book = self.book_class(prefix, lpath, title, authors, MimeType, DateCreated, ContentType, ImageID, size=0)
|
book = self.book_class(prefix, lpath, title, authors, MimeType, DateCreated, ContentType, ImageID, size=0)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:update_booklist - book file does not exist. ContentID="%s"'%ContentID)
|
debug_print(f'KoboTouch:update_booklist - book file does not exist. ContentID="{ContentID}"')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_print("KoboTouch:update_booklist - exception creating book: '%s'"%str(e))
|
debug_print(f"KoboTouch:update_booklist - exception creating book: '{e!s}'")
|
||||||
debug_print(' prefix: ', prefix, 'lpath: ', lpath, 'title: ', title, 'authors: ', authors,
|
debug_print(' prefix: ', prefix, 'lpath: ', lpath, 'title: ', title, 'authors: ', authors,
|
||||||
'MimeType: ', MimeType, 'DateCreated: ', DateCreated, 'ContentType: ', ContentType, 'ImageID: ', ImageID)
|
'MimeType: ', MimeType, 'DateCreated: ', DateCreated, 'ContentType: ', ContentType, 'ImageID: ', ImageID)
|
||||||
raise
|
raise
|
||||||
@ -1922,10 +1922,10 @@ class KOBOTOUCH(KOBO):
|
|||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:update_booklist - class:', book.__class__)
|
debug_print('KoboTouch:update_booklist - class:', book.__class__)
|
||||||
# debug_print(' resolution:', book.__class__.__mro__)
|
# debug_print(' resolution:', book.__class__.__mro__)
|
||||||
debug_print(" contentid: '%s'"%book.contentID)
|
debug_print(f" contentid: '{book.contentID}'")
|
||||||
debug_print(" title:'%s'"%book.title)
|
debug_print(f" title:'{book.title}'")
|
||||||
debug_print(' the book:', book)
|
debug_print(' the book:', book)
|
||||||
debug_print(" author_sort:'%s'"%book.author_sort)
|
debug_print(f" author_sort:'{book.author_sort}'")
|
||||||
debug_print(' bookshelves:', bookshelves)
|
debug_print(' bookshelves:', bookshelves)
|
||||||
debug_print(' kobo_collections:', kobo_collections)
|
debug_print(' kobo_collections:', kobo_collections)
|
||||||
|
|
||||||
@ -2021,39 +2021,35 @@ class KOBOTOUCH(KOBO):
|
|||||||
if self.supports_kobo_archive() or self.supports_overdrive():
|
if self.supports_kobo_archive() or self.supports_overdrive():
|
||||||
where_clause = (" WHERE BookID IS NULL "
|
where_clause = (" WHERE BookID IS NULL "
|
||||||
" AND ((Accessibility = -1 AND IsDownloaded in ('true', 1 )) " # Sideloaded books
|
" AND ((Accessibility = -1 AND IsDownloaded in ('true', 1 )) " # Sideloaded books
|
||||||
" OR (Accessibility IN (%(downloaded_accessibility)s) %(expiry)s) " # Purchased books
|
" OR (Accessibility IN ({downloaded_accessibility}) {expiry}) " # Purchased books
|
||||||
" %(previews)s %(recommendations)s ) " # Previews or Recommendations
|
" {previews} {recommendations} ) " # Previews or Recommendations
|
||||||
) % \
|
).format(**dict(
|
||||||
dict(
|
|
||||||
expiry='' if self.show_archived_books else "and IsDownloaded in ('true', 1)",
|
expiry='' if self.show_archived_books else "and IsDownloaded in ('true', 1)",
|
||||||
previews=" OR (Accessibility in (6) AND ___UserID <> '')" if self.show_previews else '',
|
previews=" OR (Accessibility in (6) AND ___UserID <> '')" if self.show_previews else '',
|
||||||
recommendations=" OR (Accessibility IN (-1, 4, 6) AND ___UserId = '')" if self.show_recommendations else '',
|
recommendations=" OR (Accessibility IN (-1, 4, 6) AND ___UserId = '')" if self.show_recommendations else '',
|
||||||
downloaded_accessibility='1,2,8,9' if self.supports_overdrive() else '1,2'
|
downloaded_accessibility='1,2,8,9' if self.supports_overdrive() else '1,2'
|
||||||
)
|
))
|
||||||
elif self.supports_series():
|
elif self.supports_series():
|
||||||
where_clause = (" WHERE BookID IS NULL "
|
where_clause = (" WHERE BookID IS NULL "
|
||||||
" AND ((Accessibility = -1 AND IsDownloaded IN ('true', 1)) or (Accessibility IN (1,2)) %(previews)s %(recommendations)s )"
|
" AND ((Accessibility = -1 AND IsDownloaded IN ('true', 1)) or (Accessibility IN (1,2)) {previews} {recommendations} )"
|
||||||
" AND NOT ((___ExpirationStatus=3 OR ___ExpirationStatus is Null) %(expiry)s)"
|
" AND NOT ((___ExpirationStatus=3 OR ___ExpirationStatus is Null) {expiry})"
|
||||||
) % \
|
).format(**dict(
|
||||||
dict(
|
|
||||||
expiry=' AND ContentType = 6' if self.show_archived_books else '',
|
expiry=' AND ContentType = 6' if self.show_archived_books else '',
|
||||||
previews=" or (Accessibility IN (6) AND ___UserID <> '')" if self.show_previews else '',
|
previews=" or (Accessibility IN (6) AND ___UserID <> '')" if self.show_previews else '',
|
||||||
recommendations=" or (Accessibility in (-1, 4, 6) AND ___UserId = '')" if self.show_recommendations else ''
|
recommendations=" or (Accessibility in (-1, 4, 6) AND ___UserId = '')" if self.show_recommendations else ''
|
||||||
)
|
))
|
||||||
elif self.dbversion >= 33:
|
elif self.dbversion >= 33:
|
||||||
where_clause = (' WHERE BookID IS NULL %(previews)s %(recommendations)s AND NOT'
|
where_clause = (' WHERE BookID IS NULL {previews} {recommendations} AND NOT'
|
||||||
' ((___ExpirationStatus=3 or ___ExpirationStatus IS NULL) %(expiry)s)'
|
' ((___ExpirationStatus=3 or ___ExpirationStatus IS NULL) {expiry})'
|
||||||
) % \
|
).format(**dict(
|
||||||
dict(
|
|
||||||
expiry=' AND ContentType = 6' if self.show_archived_books else '',
|
expiry=' AND ContentType = 6' if self.show_archived_books else '',
|
||||||
previews=' AND Accessibility <> 6' if not self.show_previews else '',
|
previews=' AND Accessibility <> 6' if not self.show_previews else '',
|
||||||
recommendations=" AND IsDownloaded IN ('true', 1)" if not self.show_recommendations else ''
|
recommendations=" AND IsDownloaded IN ('true', 1)" if not self.show_recommendations else ''
|
||||||
)
|
))
|
||||||
elif self.dbversion >= 16:
|
elif self.dbversion >= 16:
|
||||||
where_clause = (' WHERE BookID IS NULL '
|
where_clause = (' WHERE BookID IS NULL '
|
||||||
'AND NOT ((___ExpirationStatus=3 OR ___ExpirationStatus IS Null) %(expiry)s)'
|
'AND NOT ((___ExpirationStatus=3 OR ___ExpirationStatus IS Null) {expiry})'
|
||||||
) % \
|
).format(**dict(expiry=' and ContentType = 6' if self.show_archived_books else ''))
|
||||||
dict(expiry=' and ContentType = 6' if self.show_archived_books else '')
|
|
||||||
else:
|
else:
|
||||||
where_clause = ' WHERE BookID IS NULL'
|
where_clause = ' WHERE BookID IS NULL'
|
||||||
|
|
||||||
@ -2094,7 +2090,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
show_debug = self.is_debugging_title(row['Title'])
|
show_debug = self.is_debugging_title(row['Title'])
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:books - looping on database - row=%d' % i)
|
debug_print('KoboTouch:books - looping on database - row=%d' % i)
|
||||||
debug_print("KoboTouch:books - title='%s'"%row['Title'], 'authors=', row['Attribution'])
|
debug_print("KoboTouch:books - title='{}'".format(row['Title']), 'authors=', row['Attribution'])
|
||||||
debug_print('KoboTouch:books - row=', row)
|
debug_print('KoboTouch:books - row=', row)
|
||||||
if not hasattr(row['ContentID'], 'startswith') or row['ContentID'].lower().startswith(
|
if not hasattr(row['ContentID'], 'startswith') or row['ContentID'].lower().startswith(
|
||||||
'file:///usr/local/kobo/help/') or row['ContentID'].lower().startswith('/usr/local/kobo/help/'):
|
'file:///usr/local/kobo/help/') or row['ContentID'].lower().startswith('/usr/local/kobo/help/'):
|
||||||
@ -2103,7 +2099,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
externalId = None if row['ExternalId'] and len(row['ExternalId']) == 0 else row['ExternalId']
|
externalId = None if row['ExternalId'] and len(row['ExternalId']) == 0 else row['ExternalId']
|
||||||
path = self.path_from_contentid(row['ContentID'], row['ContentType'], row['MimeType'], oncard, externalId)
|
path = self.path_from_contentid(row['ContentID'], row['ContentType'], row['MimeType'], oncard, externalId)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:books - path='%s'"%path, " ContentID='%s'"%row['ContentID'], ' externalId=%s' % externalId)
|
debug_print(f"KoboTouch:books - path='{path}'", " ContentID='{}'".format(row['ContentID']), f' externalId={externalId}')
|
||||||
|
|
||||||
bookshelves = get_bookshelvesforbook(connection, row['ContentID'])
|
bookshelves = get_bookshelvesforbook(connection, row['ContentID'])
|
||||||
|
|
||||||
@ -2142,7 +2138,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
need_sync = True
|
need_sync = True
|
||||||
del bl[idx]
|
del bl[idx]
|
||||||
else:
|
else:
|
||||||
debug_print("KoboTouch:books - Book in mtadata.calibre, on file system but not database - bl[idx].title:'%s'"%bl[idx].title)
|
debug_print(f"KoboTouch:books - Book in mtadata.calibre, on file system but not database - bl[idx].title:'{bl[idx].title}'")
|
||||||
|
|
||||||
# print('count found in cache: %d, count of files in metadata: %d, need_sync: %s' % \
|
# print('count found in cache: %d, count of files in metadata: %d, need_sync: %s' % \
|
||||||
# (len(bl_cache), len(bl), need_sync))
|
# (len(bl_cache), len(bl), need_sync))
|
||||||
@ -2159,12 +2155,12 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print('KoboTouch:books - have done sync_booklists')
|
debug_print('KoboTouch:books - have done sync_booklists')
|
||||||
|
|
||||||
self.report_progress(1.0, _('Getting list of books on device...'))
|
self.report_progress(1.0, _('Getting list of books on device...'))
|
||||||
debug_print("KoboTouch:books - end - oncard='%s'"%oncard)
|
debug_print(f"KoboTouch:books - end - oncard='{oncard}'")
|
||||||
return bl
|
return bl
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def book_from_path(cls, prefix, lpath, title, authors, mime, date, ContentType, ImageID):
|
def book_from_path(cls, prefix, lpath, title, authors, mime, date, ContentType, ImageID):
|
||||||
debug_print('KoboTouch:book_from_path - title=%s'%title)
|
debug_print(f'KoboTouch:book_from_path - title={title}')
|
||||||
book = super().book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
|
book = super().book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
|
||||||
|
|
||||||
# Kobo Audiobooks are directories with files in them.
|
# Kobo Audiobooks are directories with files in them.
|
||||||
@ -2222,11 +2218,11 @@ class KOBOTOUCH(KOBO):
|
|||||||
fpath = path + ending
|
fpath = path + ending
|
||||||
if os.path.exists(fpath):
|
if os.path.exists(fpath):
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:imagefilename_from_imageID - have cover image fpath=%s' % (fpath))
|
debug_print(f'KoboTouch:imagefilename_from_imageID - have cover image fpath={fpath}')
|
||||||
return fpath
|
return fpath
|
||||||
|
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:imagefilename_from_imageID - no cover image found - ImageID=%s' % (ImageID))
|
debug_print(f'KoboTouch:imagefilename_from_imageID - no cover image found - ImageID={ImageID}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_extra_css(self):
|
def get_extra_css(self):
|
||||||
@ -2313,7 +2309,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_print('KoboTouch:upload_books - Exception: %s'%str(e))
|
debug_print(f'KoboTouch:upload_books - Exception: {e!s}')
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -2419,7 +2415,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
imageId = super().delete_via_sql(ContentID, ContentType)
|
imageId = super().delete_via_sql(ContentID, ContentType)
|
||||||
|
|
||||||
if self.dbversion >= 53:
|
if self.dbversion >= 53:
|
||||||
debug_print('KoboTouch:delete_via_sql: ContentID="%s"'%ContentID, 'ContentType="%s"'%ContentType)
|
debug_print(f'KoboTouch:delete_via_sql: ContentID="{ContentID}"', f'ContentType="{ContentType}"')
|
||||||
try:
|
try:
|
||||||
with closing(self.device_database_connection()) as connection:
|
with closing(self.device_database_connection()) as connection:
|
||||||
debug_print('KoboTouch:delete_via_sql: have database connection')
|
debug_print('KoboTouch:delete_via_sql: have database connection')
|
||||||
@ -2457,9 +2453,9 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print('KoboTouch:delete_via_sql: finished SQL')
|
debug_print('KoboTouch:delete_via_sql: finished SQL')
|
||||||
debug_print('KoboTouch:delete_via_sql: After SQL, no exception')
|
debug_print('KoboTouch:delete_via_sql: After SQL, no exception')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_print('KoboTouch:delete_via_sql - Database Exception: %s'%str(e))
|
debug_print(f'KoboTouch:delete_via_sql - Database Exception: {e!s}')
|
||||||
|
|
||||||
debug_print('KoboTouch:delete_via_sql: imageId="%s"'%imageId)
|
debug_print(f'KoboTouch:delete_via_sql: imageId="{imageId}"')
|
||||||
if imageId is None:
|
if imageId is None:
|
||||||
imageId = self.imageid_from_contentid(ContentID)
|
imageId = self.imageid_from_contentid(ContentID)
|
||||||
|
|
||||||
@ -2469,12 +2465,12 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print('KoboTouch:delete_images - ImageID=', ImageID)
|
debug_print('KoboTouch:delete_images - ImageID=', ImageID)
|
||||||
if ImageID is not None:
|
if ImageID is not None:
|
||||||
path = self.images_path(book_path, ImageID)
|
path = self.images_path(book_path, ImageID)
|
||||||
debug_print('KoboTouch:delete_images - path=%s' % path)
|
debug_print(f'KoboTouch:delete_images - path={path}')
|
||||||
|
|
||||||
for ending in self.cover_file_endings().keys():
|
for ending in self.cover_file_endings().keys():
|
||||||
fpath = path + ending
|
fpath = path + ending
|
||||||
fpath = self.normalize_path(fpath)
|
fpath = self.normalize_path(fpath)
|
||||||
debug_print('KoboTouch:delete_images - fpath=%s' % fpath)
|
debug_print(f'KoboTouch:delete_images - fpath={fpath}')
|
||||||
|
|
||||||
if os.path.exists(fpath):
|
if os.path.exists(fpath):
|
||||||
debug_print('KoboTouch:delete_images - Image File Exists')
|
debug_print('KoboTouch:delete_images - Image File Exists')
|
||||||
@ -2488,8 +2484,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
def contentid_from_path(self, path, ContentType):
|
def contentid_from_path(self, path, ContentType):
|
||||||
show_debug = self.is_debugging_title(path) and True
|
show_debug = self.is_debugging_title(path) and True
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:contentid_from_path - path='%s'"%path, "ContentType='%s'"%ContentType)
|
debug_print(f"KoboTouch:contentid_from_path - path='{path}'", f"ContentType='{ContentType}'")
|
||||||
debug_print("KoboTouch:contentid_from_path - self._main_prefix='%s'"%self._main_prefix, "self._card_a_prefix='%s'"%self._card_a_prefix)
|
debug_print(f"KoboTouch:contentid_from_path - self._main_prefix='{self._main_prefix}'", f"self._card_a_prefix='{self._card_a_prefix}'")
|
||||||
if ContentType == 6:
|
if ContentType == 6:
|
||||||
extension = os.path.splitext(path)[1]
|
extension = os.path.splitext(path)[1]
|
||||||
if extension == '.kobo':
|
if extension == '.kobo':
|
||||||
@ -2504,19 +2500,19 @@ class KOBOTOUCH(KOBO):
|
|||||||
ContentID = ContentID.replace(self._main_prefix, 'file:///mnt/onboard/')
|
ContentID = ContentID.replace(self._main_prefix, 'file:///mnt/onboard/')
|
||||||
|
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:contentid_from_path - 1 ContentID='%s'"%ContentID)
|
debug_print(f"KoboTouch:contentid_from_path - 1 ContentID='{ContentID}'")
|
||||||
|
|
||||||
if self._card_a_prefix is not None:
|
if self._card_a_prefix is not None:
|
||||||
ContentID = ContentID.replace(self._card_a_prefix, 'file:///mnt/sd/')
|
ContentID = ContentID.replace(self._card_a_prefix, 'file:///mnt/sd/')
|
||||||
else: # ContentType = 16
|
else: # ContentType = 16
|
||||||
debug_print("KoboTouch:contentid_from_path ContentType other than 6 - ContentType='%d'"%ContentType, "path='%s'"%path)
|
debug_print("KoboTouch:contentid_from_path ContentType other than 6 - ContentType='%d'"%ContentType, f"path='{path}'")
|
||||||
ContentID = path
|
ContentID = path
|
||||||
ContentID = ContentID.replace(self._main_prefix, 'file:///mnt/onboard/')
|
ContentID = ContentID.replace(self._main_prefix, 'file:///mnt/onboard/')
|
||||||
if self._card_a_prefix is not None:
|
if self._card_a_prefix is not None:
|
||||||
ContentID = ContentID.replace(self._card_a_prefix, 'file:///mnt/sd/')
|
ContentID = ContentID.replace(self._card_a_prefix, 'file:///mnt/sd/')
|
||||||
ContentID = ContentID.replace('\\', '/')
|
ContentID = ContentID.replace('\\', '/')
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:contentid_from_path - end - ContentID='%s'"%ContentID)
|
debug_print(f"KoboTouch:contentid_from_path - end - ContentID='{ContentID}'")
|
||||||
return ContentID
|
return ContentID
|
||||||
|
|
||||||
def get_content_type_from_path(self, path):
|
def get_content_type_from_path(self, path):
|
||||||
@ -2538,8 +2534,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
self.plugboard_func = pb_func
|
self.plugboard_func = pb_func
|
||||||
|
|
||||||
def update_device_database_collections(self, booklists, collections_attributes, oncard):
|
def update_device_database_collections(self, booklists, collections_attributes, oncard):
|
||||||
debug_print("KoboTouch:update_device_database_collections - oncard='%s'"%oncard)
|
debug_print(f"KoboTouch:update_device_database_collections - oncard='{oncard}'")
|
||||||
debug_print("KoboTouch:update_device_database_collections - device='%s'" % self)
|
debug_print(f"KoboTouch:update_device_database_collections - device='{self}'")
|
||||||
if self.modify_database_check('update_device_database_collections') is False:
|
if self.modify_database_check('update_device_database_collections') is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -2573,7 +2569,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
update_core_metadata = self.update_core_metadata
|
update_core_metadata = self.update_core_metadata
|
||||||
update_purchased_kepubs = self.update_purchased_kepubs
|
update_purchased_kepubs = self.update_purchased_kepubs
|
||||||
debugging_title = self.get_debugging_title()
|
debugging_title = self.get_debugging_title()
|
||||||
debug_print("KoboTouch:update_device_database_collections - set_debugging_title to '%s'" % debugging_title)
|
debug_print(f"KoboTouch:update_device_database_collections - set_debugging_title to '{debugging_title}'")
|
||||||
booklists.set_debugging_title(debugging_title)
|
booklists.set_debugging_title(debugging_title)
|
||||||
booklists.set_device_managed_collections(self.ignore_collections_names)
|
booklists.set_device_managed_collections(self.ignore_collections_names)
|
||||||
|
|
||||||
@ -2623,11 +2619,11 @@ class KOBOTOUCH(KOBO):
|
|||||||
# debug_print(' Title:', book.title, 'category: ', category)
|
# debug_print(' Title:', book.title, 'category: ', category)
|
||||||
show_debug = self.is_debugging_title(book.title)
|
show_debug = self.is_debugging_title(book.title)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(' Title="%s"'%book.title, 'category="%s"'%category)
|
debug_print(f' Title="{book.title}"', f'category="{category}"')
|
||||||
# debug_print(book)
|
# debug_print(book)
|
||||||
debug_print(' class=%s'%book.__class__)
|
debug_print(f' class={book.__class__}')
|
||||||
debug_print(' book.contentID="%s"'%book.contentID)
|
debug_print(f' book.contentID="{book.contentID}"')
|
||||||
debug_print(' book.application_id="%s"'%book.application_id)
|
debug_print(f' book.application_id="{book.application_id}"')
|
||||||
|
|
||||||
if book.application_id is None:
|
if book.application_id is None:
|
||||||
continue
|
continue
|
||||||
@ -2635,13 +2631,13 @@ class KOBOTOUCH(KOBO):
|
|||||||
category_added = False
|
category_added = False
|
||||||
|
|
||||||
if book.contentID is None:
|
if book.contentID is None:
|
||||||
debug_print(' Do not know ContentID - Title="%s", Authors="%s", path="%s"'%(book.title, book.author, book.path))
|
debug_print(f' Do not know ContentID - Title="{book.title}", Authors="{book.author}", path="{book.path}"')
|
||||||
extension = os.path.splitext(book.path)[1]
|
extension = os.path.splitext(book.path)[1]
|
||||||
ContentType = self.get_content_type_from_extension(extension) if extension else self.get_content_type_from_path(book.path)
|
ContentType = self.get_content_type_from_extension(extension) if extension else self.get_content_type_from_path(book.path)
|
||||||
book.contentID = self.contentid_from_path(book.path, ContentType)
|
book.contentID = self.contentid_from_path(book.path, ContentType)
|
||||||
|
|
||||||
if category in self.ignore_collections_names:
|
if category in self.ignore_collections_names:
|
||||||
debug_print(' Ignoring collection=%s' % category)
|
debug_print(f' Ignoring collection={category}')
|
||||||
category_added = True
|
category_added = True
|
||||||
elif category in self.bookshelvelist and self.supports_bookshelves:
|
elif category in self.bookshelvelist and self.supports_bookshelves:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
@ -2652,18 +2648,18 @@ class KOBOTOUCH(KOBO):
|
|||||||
self.set_bookshelf(connection, book, category)
|
self.set_bookshelf(connection, book, category)
|
||||||
category_added = True
|
category_added = True
|
||||||
elif category in readstatuslist:
|
elif category in readstatuslist:
|
||||||
debug_print("KoboTouch:update_device_database_collections - about to set_readstatus - category='%s'"%(category, ))
|
debug_print(f"KoboTouch:update_device_database_collections - about to set_readstatus - category='{category}'")
|
||||||
# Manage ReadStatus
|
# Manage ReadStatus
|
||||||
self.set_readstatus(connection, book.contentID, readstatuslist.get(category))
|
self.set_readstatus(connection, book.contentID, readstatuslist.get(category))
|
||||||
category_added = True
|
category_added = True
|
||||||
|
|
||||||
elif category == 'Shortlist' and self.dbversion >= 14:
|
elif category == 'Shortlist' and self.dbversion >= 14:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(' Have an older version shortlist - %s'%book.title)
|
debug_print(f' Have an older version shortlist - {book.title}')
|
||||||
# Manage FavouritesIndex/Shortlist
|
# Manage FavouritesIndex/Shortlist
|
||||||
if not self.supports_bookshelves:
|
if not self.supports_bookshelves:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(' and about to set it - %s'%book.title)
|
debug_print(f' and about to set it - {book.title}')
|
||||||
self.set_favouritesindex(connection, book.contentID)
|
self.set_favouritesindex(connection, book.contentID)
|
||||||
category_added = True
|
category_added = True
|
||||||
elif category in accessibilitylist:
|
elif category in accessibilitylist:
|
||||||
@ -2677,7 +2673,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
else:
|
else:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(' category not added to book.device_collections', book.device_collections)
|
debug_print(' category not added to book.device_collections', book.device_collections)
|
||||||
debug_print("KoboTouch:update_device_database_collections - end for category='%s'"%category)
|
debug_print(f"KoboTouch:update_device_database_collections - end for category='{category}'")
|
||||||
|
|
||||||
elif have_bookshelf_attributes: # No collections but have set the shelf option
|
elif have_bookshelf_attributes: # No collections but have set the shelf option
|
||||||
# Since no collections exist the ReadStatus needs to be reset to 0 (Unread)
|
# Since no collections exist the ReadStatus needs to be reset to 0 (Unread)
|
||||||
@ -2702,11 +2698,10 @@ class KOBOTOUCH(KOBO):
|
|||||||
books_in_library += 1
|
books_in_library += 1
|
||||||
show_debug = self.is_debugging_title(book.title)
|
show_debug = self.is_debugging_title(book.title)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:update_device_database_collections - book.title=%s' % book.title)
|
debug_print(f'KoboTouch:update_device_database_collections - book.title={book.title}')
|
||||||
debug_print(
|
debug_print(
|
||||||
'KoboTouch:update_device_database_collections - contentId=%s,'
|
f'KoboTouch:update_device_database_collections - contentId={book.contentID},'
|
||||||
'update_core_metadata=%s,update_purchased_kepubs=%s, book.is_sideloaded=%s' % (
|
f'update_core_metadata={update_core_metadata},update_purchased_kepubs={update_purchased_kepubs}, book.is_sideloaded={book.is_sideloaded}')
|
||||||
book.contentID, update_core_metadata, update_purchased_kepubs, book.is_sideloaded))
|
|
||||||
if update_core_metadata and (update_purchased_kepubs or book.is_sideloaded):
|
if update_core_metadata and (update_purchased_kepubs or book.is_sideloaded):
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:update_device_database_collections - calling set_core_metadata')
|
debug_print('KoboTouch:update_device_database_collections - calling set_core_metadata')
|
||||||
@ -2717,7 +2712,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
self.set_core_metadata(connection, book, series_only=True)
|
self.set_core_metadata(connection, book, series_only=True)
|
||||||
if self.manage_collections and have_bookshelf_attributes:
|
if self.manage_collections and have_bookshelf_attributes:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:update_device_database_collections - about to remove a book from shelves book.title=%s' % book.title)
|
debug_print(f'KoboTouch:update_device_database_collections - about to remove a book from shelves book.title={book.title}')
|
||||||
self.remove_book_from_device_bookshelves(connection, book)
|
self.remove_book_from_device_bookshelves(connection, book)
|
||||||
book.device_collections.extend(book.kobo_collections)
|
book.device_collections.extend(book.kobo_collections)
|
||||||
if not prefs['manage_device_metadata'] == 'manual' and delete_empty_collections:
|
if not prefs['manage_device_metadata'] == 'manual' and delete_empty_collections:
|
||||||
@ -2749,8 +2744,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
:param filepath: The full path to the ebook file
|
:param filepath: The full path to the ebook file
|
||||||
|
|
||||||
'''
|
'''
|
||||||
debug_print("KoboTouch:upload_cover - path='%s' filename='%s' "%(path, filename))
|
debug_print(f"KoboTouch:upload_cover - path='{path}' filename='{filename}' ")
|
||||||
debug_print(" filepath='%s' "%(filepath))
|
debug_print(f" filepath='{filepath}' ")
|
||||||
|
|
||||||
if not self.upload_covers:
|
if not self.upload_covers:
|
||||||
# Building thumbnails disabled
|
# Building thumbnails disabled
|
||||||
@ -2769,7 +2764,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
self.keep_cover_aspect, self.letterbox_fs_covers, self.png_covers,
|
self.keep_cover_aspect, self.letterbox_fs_covers, self.png_covers,
|
||||||
letterbox_color=self.letterbox_fs_covers_color)
|
letterbox_color=self.letterbox_fs_covers_color)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_print('KoboTouch: FAILED to upload cover=%s Exception=%s'%(filepath, str(e)))
|
debug_print(f'KoboTouch: FAILED to upload cover={filepath} Exception={e!s}')
|
||||||
|
|
||||||
def imageid_from_contentid(self, ContentID):
|
def imageid_from_contentid(self, ContentID):
|
||||||
ImageID = ContentID.replace('/', '_')
|
ImageID = ContentID.replace('/', '_')
|
||||||
@ -2793,7 +2788,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
hash1 = qhash(imageId)
|
hash1 = qhash(imageId)
|
||||||
dir1 = hash1 & (0xff * 1)
|
dir1 = hash1 & (0xff * 1)
|
||||||
dir2 = (hash1 & (0xff00 * 1)) >> 8
|
dir2 = (hash1 & (0xff00 * 1)) >> 8
|
||||||
path = os.path.join(path, '%s' % dir1, '%s' % dir2)
|
path = os.path.join(path, f'{dir1}', f'{dir2}')
|
||||||
|
|
||||||
if imageId:
|
if imageId:
|
||||||
path = os.path.join(path, imageId)
|
path = os.path.join(path, imageId)
|
||||||
@ -2864,15 +2859,15 @@ class KOBOTOUCH(KOBO):
|
|||||||
):
|
):
|
||||||
from calibre.utils.img import optimize_png
|
from calibre.utils.img import optimize_png
|
||||||
from calibre.utils.imghdr import identify
|
from calibre.utils.imghdr import identify
|
||||||
debug_print("KoboTouch:_upload_cover - filename='%s' upload_grayscale='%s' dithered_covers='%s' "%(filename, upload_grayscale, dithered_covers))
|
debug_print(f"KoboTouch:_upload_cover - filename='{filename}' upload_grayscale='{upload_grayscale}' dithered_covers='{dithered_covers}' ")
|
||||||
|
|
||||||
if not metadata.cover:
|
if not metadata.cover:
|
||||||
return
|
return
|
||||||
|
|
||||||
show_debug = self.is_debugging_title(filename)
|
show_debug = self.is_debugging_title(filename)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:_upload_cover - path='%s'"%path, "filename='%s'"%filename)
|
debug_print(f"KoboTouch:_upload_cover - path='{path}'", f"filename='{filename}'")
|
||||||
debug_print(" filepath='%s'"%filepath)
|
debug_print(f" filepath='{filepath}'")
|
||||||
cover = self.normalize_path(metadata.cover.replace('/', os.sep))
|
cover = self.normalize_path(metadata.cover.replace('/', os.sep))
|
||||||
|
|
||||||
if not os.path.exists(cover):
|
if not os.path.exists(cover):
|
||||||
@ -2895,7 +2890,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
ImageID = result[0]
|
ImageID = result[0]
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
ImageID = self.imageid_from_contentid(ContentID)
|
ImageID = self.imageid_from_contentid(ContentID)
|
||||||
debug_print("KoboTouch:_upload_cover - No rows exist in the database - generated ImageID='%s'" % ImageID)
|
debug_print(f"KoboTouch:_upload_cover - No rows exist in the database - generated ImageID='{ImageID}'")
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
@ -2907,7 +2902,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
image_dir = os.path.dirname(os.path.abspath(path))
|
image_dir = os.path.dirname(os.path.abspath(path))
|
||||||
if not os.path.exists(image_dir):
|
if not os.path.exists(image_dir):
|
||||||
debug_print("KoboTouch:_upload_cover - Image folder does not exist. Creating path='%s'" % (image_dir))
|
debug_print(f"KoboTouch:_upload_cover - Image folder does not exist. Creating path='{image_dir}'")
|
||||||
os.makedirs(image_dir)
|
os.makedirs(image_dir)
|
||||||
|
|
||||||
with open(cover, 'rb') as f:
|
with open(cover, 'rb') as f:
|
||||||
@ -2924,7 +2919,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
if self.dbversion >= min_dbversion and self.dbversion <= max_dbversion:
|
if self.dbversion >= min_dbversion and self.dbversion <= max_dbversion:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:_upload_cover - creating cover for ending='%s'"%ending) # , "library_cover_size'%s'"%library_cover_size)
|
debug_print(f"KoboTouch:_upload_cover - creating cover for ending='{ending}'") # , "library_cover_size'%s'"%library_cover_size)
|
||||||
fpath = path + ending
|
fpath = path + ending
|
||||||
fpath = self.normalize_path(fpath.replace('/', os.sep))
|
fpath = self.normalize_path(fpath.replace('/', os.sep))
|
||||||
|
|
||||||
@ -2943,9 +2938,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
resize_to, expand_to = self._calculate_kobo_cover_size(library_cover_size, kobo_size, not is_full_size, keep_cover_aspect, letterbox)
|
resize_to, expand_to = self._calculate_kobo_cover_size(library_cover_size, kobo_size, not is_full_size, keep_cover_aspect, letterbox)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(
|
debug_print(
|
||||||
'KoboTouch:_calculate_kobo_cover_size - expand_to=%s'
|
f'KoboTouch:_calculate_kobo_cover_size - expand_to={expand_to}'
|
||||||
' (vs. kobo_size=%s) & resize_to=%s, keep_cover_aspect=%s & letterbox_fs_covers=%s, png_covers=%s' % (
|
f' (vs. kobo_size={kobo_size}) & resize_to={resize_to}, keep_cover_aspect={keep_cover_aspect} & letterbox_fs_covers={letterbox_fs_covers}, png_covers={png_covers}')
|
||||||
expand_to, kobo_size, resize_to, keep_cover_aspect, letterbox_fs_covers, png_covers))
|
|
||||||
|
|
||||||
# NOTE: To speed things up, we enforce a lower
|
# NOTE: To speed things up, we enforce a lower
|
||||||
# compression level for png_covers, as the final
|
# compression level for png_covers, as the final
|
||||||
@ -2983,7 +2977,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
fsync(f)
|
fsync(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err = str(e)
|
err = str(e)
|
||||||
debug_print('KoboTouch:_upload_cover - Exception string: %s'%err)
|
debug_print(f'KoboTouch:_upload_cover - Exception string: {err}')
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def remove_book_from_device_bookshelves(self, connection, book):
|
def remove_book_from_device_bookshelves(self, connection, book):
|
||||||
@ -2993,8 +2987,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
remove_shelf_list = remove_shelf_list - set(self.ignore_collections_names)
|
remove_shelf_list = remove_shelf_list - set(self.ignore_collections_names)
|
||||||
|
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.application_id="%s"'%book.application_id)
|
debug_print(f'KoboTouch:remove_book_from_device_bookshelves - book.application_id="{book.application_id}"')
|
||||||
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.contentID="%s"'%book.contentID)
|
debug_print(f'KoboTouch:remove_book_from_device_bookshelves - book.contentID="{book.contentID}"')
|
||||||
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.device_collections=', book.device_collections)
|
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.device_collections=', book.device_collections)
|
||||||
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.current_shelves=', book.current_shelves)
|
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.current_shelves=', book.current_shelves)
|
||||||
debug_print('KoboTouch:remove_book_from_device_bookshelves - remove_shelf_list=', remove_shelf_list)
|
debug_print('KoboTouch:remove_book_from_device_bookshelves - remove_shelf_list=', remove_shelf_list)
|
||||||
@ -3009,12 +3003,12 @@ class KOBOTOUCH(KOBO):
|
|||||||
if book.device_collections:
|
if book.device_collections:
|
||||||
placeholder = '?'
|
placeholder = '?'
|
||||||
placeholders = ','.join(placeholder for unused in book.device_collections)
|
placeholders = ','.join(placeholder for unused in book.device_collections)
|
||||||
query += ' and ShelfName not in (%s)' % placeholders
|
query += f' and ShelfName not in ({placeholders})'
|
||||||
values.extend(book.device_collections)
|
values.extend(book.device_collections)
|
||||||
|
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:remove_book_from_device_bookshelves query="%s"'%query)
|
debug_print(f'KoboTouch:remove_book_from_device_bookshelves query="{query}"')
|
||||||
debug_print('KoboTouch:remove_book_from_device_bookshelves values="%s"'%values)
|
debug_print(f'KoboTouch:remove_book_from_device_bookshelves values="{values}"')
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute(query, values)
|
cursor.execute(query, values)
|
||||||
@ -3023,7 +3017,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
def set_filesize_in_device_database(self, connection, contentID, fpath):
|
def set_filesize_in_device_database(self, connection, contentID, fpath):
|
||||||
show_debug = self.is_debugging_title(fpath)
|
show_debug = self.is_debugging_title(fpath)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:set_filesize_in_device_database contentID="%s"'%contentID)
|
debug_print(f'KoboTouch:set_filesize_in_device_database contentID="{contentID}"')
|
||||||
|
|
||||||
test_query = ('SELECT ___FileSize '
|
test_query = ('SELECT ___FileSize '
|
||||||
'FROM content '
|
'FROM content '
|
||||||
@ -3136,8 +3130,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
def set_bookshelf(self, connection, book, shelfName):
|
def set_bookshelf(self, connection, book, shelfName):
|
||||||
show_debug = self.is_debugging_title(book.title)
|
show_debug = self.is_debugging_title(book.title)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:set_bookshelf book.ContentID="%s"'%book.contentID)
|
debug_print(f'KoboTouch:set_bookshelf book.ContentID="{book.contentID}"')
|
||||||
debug_print('KoboTouch:set_bookshelf book.current_shelves="%s"'%book.current_shelves)
|
debug_print(f'KoboTouch:set_bookshelf book.current_shelves="{book.current_shelves}"')
|
||||||
|
|
||||||
if shelfName in book.current_shelves:
|
if shelfName in book.current_shelves:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
@ -3175,7 +3169,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
def check_for_bookshelf(self, connection, bookshelf_name):
|
def check_for_bookshelf(self, connection, bookshelf_name):
|
||||||
show_debug = self.is_debugging_title(bookshelf_name)
|
show_debug = self.is_debugging_title(bookshelf_name)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:check_for_bookshelf bookshelf_name="%s"'%bookshelf_name)
|
debug_print(f'KoboTouch:check_for_bookshelf bookshelf_name="{bookshelf_name}"')
|
||||||
test_query = 'SELECT InternalName, Name, _IsDeleted FROM Shelf WHERE Name = ?'
|
test_query = 'SELECT InternalName, Name, _IsDeleted FROM Shelf WHERE Name = ?'
|
||||||
test_values = (bookshelf_name, )
|
test_values = (bookshelf_name, )
|
||||||
addquery = 'INSERT INTO "main"."Shelf"'
|
addquery = 'INSERT INTO "main"."Shelf"'
|
||||||
@ -3220,7 +3214,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(' Did not find a record - adding shelf "%s"' % bookshelf_name)
|
debug_print(f' Did not find a record - adding shelf "{bookshelf_name}"')
|
||||||
cursor.execute(addquery, add_values)
|
cursor.execute(addquery, add_values)
|
||||||
elif self.is_true_value(result['_IsDeleted']):
|
elif self.is_true_value(result['_IsDeleted']):
|
||||||
debug_print("KoboTouch:check_for_bookshelf - Shelf '{}' is deleted - undeleting. result['_IsDeleted']='{}'".format(
|
debug_print("KoboTouch:check_for_bookshelf - Shelf '{}' is deleted - undeleting. result['_IsDeleted']='{}'".format(
|
||||||
@ -3253,7 +3247,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
if bookshelves:
|
if bookshelves:
|
||||||
placeholder = '?'
|
placeholder = '?'
|
||||||
placeholders = ','.join(placeholder for unused in bookshelves)
|
placeholders = ','.join(placeholder for unused in bookshelves)
|
||||||
query += ' and ShelfName in (%s)' % placeholders
|
query += f' and ShelfName in ({placeholders})'
|
||||||
values.append(bookshelves)
|
values.append(bookshelves)
|
||||||
debug_print('KoboTouch:remove_from_bookshelf query=', query)
|
debug_print('KoboTouch:remove_from_bookshelf query=', query)
|
||||||
debug_print('KoboTouch:remove_from_bookshelf values=', values)
|
debug_print('KoboTouch:remove_from_bookshelf values=', values)
|
||||||
@ -3267,8 +3261,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
def set_series(self, connection, book):
|
def set_series(self, connection, book):
|
||||||
show_debug = self.is_debugging_title(book.title)
|
show_debug = self.is_debugging_title(book.title)
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:set_series book.kobo_series="%s"'%book.kobo_series)
|
debug_print(f'KoboTouch:set_series book.kobo_series="{book.kobo_series}"')
|
||||||
debug_print('KoboTouch:set_series book.series="%s"'%book.series)
|
debug_print(f'KoboTouch:set_series book.series="{book.series}"')
|
||||||
debug_print('KoboTouch:set_series book.series_index=', book.series_index)
|
debug_print('KoboTouch:set_series book.series_index=', book.series_index)
|
||||||
|
|
||||||
if book.series == book.kobo_series:
|
if book.series == book.kobo_series:
|
||||||
@ -3289,7 +3283,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
elif book.series_index is None: # This should never happen, but...
|
elif book.series_index is None: # This should never happen, but...
|
||||||
update_values = (book.series, None, book.contentID, )
|
update_values = (book.series, None, book.contentID, )
|
||||||
else:
|
else:
|
||||||
update_values = (book.series, '%g'%book.series_index, book.contentID, )
|
update_values = (book.series, f'{book.series_index:g}', book.contentID, )
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
try:
|
try:
|
||||||
@ -3320,7 +3314,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
else:
|
else:
|
||||||
new_value = new_value if len(new_value.strip()) else None
|
new_value = new_value if len(new_value.strip()) else None
|
||||||
if new_value is not None and new_value.startswith('PLUGBOARD TEMPLATE ERROR'):
|
if new_value is not None and new_value.startswith('PLUGBOARD TEMPLATE ERROR'):
|
||||||
debug_print("KoboTouch:generate_update_from_template template error - template='%s'" % template)
|
debug_print(f"KoboTouch:generate_update_from_template template error - template='{template}'")
|
||||||
debug_print('KoboTouch:generate_update_from_template - new_value=', new_value)
|
debug_print('KoboTouch:generate_update_from_template - new_value=', new_value)
|
||||||
|
|
||||||
# debug_print(
|
# debug_print(
|
||||||
@ -3366,7 +3360,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
if newmi.series is not None:
|
if newmi.series is not None:
|
||||||
new_series = newmi.series
|
new_series = newmi.series
|
||||||
try:
|
try:
|
||||||
new_series_number = '%g' % newmi.series_index
|
new_series_number = f'{newmi.series_index:g}'
|
||||||
except:
|
except:
|
||||||
new_series_number = None
|
new_series_number = None
|
||||||
else:
|
else:
|
||||||
@ -3463,7 +3457,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
else:
|
else:
|
||||||
new_subtitle = book.subtitle if len(book.subtitle.strip()) else None
|
new_subtitle = book.subtitle if len(book.subtitle.strip()) else None
|
||||||
if new_subtitle is not None and new_subtitle.startswith('PLUGBOARD TEMPLATE ERROR'):
|
if new_subtitle is not None and new_subtitle.startswith('PLUGBOARD TEMPLATE ERROR'):
|
||||||
debug_print("KoboTouch:set_core_metadata subtitle template error - self.subtitle_template='%s'" % self.subtitle_template)
|
debug_print(f"KoboTouch:set_core_metadata subtitle template error - self.subtitle_template='{self.subtitle_template}'")
|
||||||
debug_print('KoboTouch:set_core_metadata - new_subtitle=', new_subtitle)
|
debug_print('KoboTouch:set_core_metadata - new_subtitle=', new_subtitle)
|
||||||
|
|
||||||
if (new_subtitle is not None and (book.kobo_subtitle is None or book.subtitle != book.kobo_subtitle)) or \
|
if (new_subtitle is not None and (book.kobo_subtitle is None or book.subtitle != book.kobo_subtitle)) or \
|
||||||
@ -3509,9 +3503,9 @@ class KOBOTOUCH(KOBO):
|
|||||||
update_query += ', '.join([col_name + ' = ?' for col_name in set_clause])
|
update_query += ', '.join([col_name + ' = ?' for col_name in set_clause])
|
||||||
changes_found = True
|
changes_found = True
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print('KoboTouch:set_core_metadata set_clause="%s"' % set_clause)
|
debug_print(f'KoboTouch:set_core_metadata set_clause="{set_clause}"')
|
||||||
debug_print('KoboTouch:set_core_metadata update_values="%s"' % update_values)
|
debug_print(f'KoboTouch:set_core_metadata update_values="{update_values}"')
|
||||||
debug_print('KoboTouch:set_core_metadata update_values="%s"' % update_query)
|
debug_print(f'KoboTouch:set_core_metadata update_values="{update_query}"')
|
||||||
if changes_found:
|
if changes_found:
|
||||||
update_query += ' WHERE ContentID = ? AND BookID IS NULL'
|
update_query += ' WHERE ContentID = ? AND BookID IS NULL'
|
||||||
update_values.append(book.contentID)
|
update_values.append(book.contentID)
|
||||||
@ -4087,9 +4081,9 @@ class KOBOTOUCH(KOBO):
|
|||||||
' Kobo forum at MobileRead. This is at %s.'
|
' Kobo forum at MobileRead. This is at %s.'
|
||||||
) % 'https://www.mobileread.com/forums/forumdisplay.php?f=223' + '\n' +
|
) % 'https://www.mobileread.com/forums/forumdisplay.php?f=223' + '\n' +
|
||||||
(
|
(
|
||||||
'\nDevice database version: %s.'
|
f'\nDevice database version: {self.dbversion}.'
|
||||||
'\nDevice firmware version: %s'
|
f'\nDevice firmware version: {self.display_fwversion}'
|
||||||
) % (self.dbversion, self.display_fwversion),
|
),
|
||||||
UserFeedback.WARN
|
UserFeedback.WARN
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -4206,7 +4200,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
try:
|
try:
|
||||||
is_debugging = (len(self.debugging_title) > 0 and title.lower().find(self.debugging_title.lower()) >= 0) or len(title) == 0
|
is_debugging = (len(self.debugging_title) > 0 and title.lower().find(self.debugging_title.lower()) >= 0) or len(title) == 0
|
||||||
except:
|
except:
|
||||||
debug_print(("KoboTouch::is_debugging_title - Exception checking debugging title for title '{}'.").format(title))
|
debug_print(f"KoboTouch::is_debugging_title - Exception checking debugging title for title '{title}'.")
|
||||||
is_debugging = False
|
is_debugging = False
|
||||||
|
|
||||||
return is_debugging
|
return is_debugging
|
||||||
|
@ -98,7 +98,7 @@ class PDNOVEL(USBMS):
|
|||||||
def upload_cover(self, path, filename, metadata, filepath):
|
def upload_cover(self, path, filename, metadata, filepath):
|
||||||
coverdata = getattr(metadata, 'thumbnail', None)
|
coverdata = getattr(metadata, 'thumbnail', None)
|
||||||
if coverdata and coverdata[2]:
|
if coverdata and coverdata[2]:
|
||||||
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
|
with open(f'{os.path.join(path, filename)}.jpg', 'wb') as coverfile:
|
||||||
coverfile.write(coverdata[2])
|
coverfile.write(coverdata[2])
|
||||||
fsync(coverfile)
|
fsync(coverfile)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ DEFAULT_THUMBNAIL_HEIGHT = 320
|
|||||||
class MTPInvalidSendPathError(PathError):
|
class MTPInvalidSendPathError(PathError):
|
||||||
|
|
||||||
def __init__(self, folder):
|
def __init__(self, folder):
|
||||||
PathError.__init__(self, 'Trying to send to ignored folder: %s'%folder)
|
PathError.__init__(self, f'Trying to send to ignored folder: {folder}')
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
|
|
||||||
|
|
||||||
@ -405,7 +405,7 @@ class MTP_DEVICE(BASE):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
ans.append((path, e, traceback.format_exc()))
|
ans.append((path, e, traceback.format_exc()))
|
||||||
continue
|
continue
|
||||||
base = os.path.join(tdir, '%s'%f.object_id)
|
base = os.path.join(tdir, f'{f.object_id}')
|
||||||
os.mkdir(base)
|
os.mkdir(base)
|
||||||
name = f.name
|
name = f.name
|
||||||
if iswindows:
|
if iswindows:
|
||||||
@ -628,8 +628,7 @@ class MTP_DEVICE(BASE):
|
|||||||
try:
|
try:
|
||||||
self.recursive_delete(parent)
|
self.recursive_delete(parent)
|
||||||
except:
|
except:
|
||||||
prints('Failed to delete parent: %s, ignoring'%(
|
prints('Failed to delete parent: {}, ignoring'.format('/'.join(parent.full_path)))
|
||||||
'/'.join(parent.full_path)))
|
|
||||||
|
|
||||||
def delete_books(self, paths, end_session=True):
|
def delete_books(self, paths, end_session=True):
|
||||||
self.report_progress(0, _('Deleting books from device...'))
|
self.report_progress(0, _('Deleting books from device...'))
|
||||||
@ -673,7 +672,7 @@ class MTP_DEVICE(BASE):
|
|||||||
If that is not found looks for a device default and if that is not
|
If that is not found looks for a device default and if that is not
|
||||||
found uses the global default.'''
|
found uses the global default.'''
|
||||||
dd = self.current_device_defaults if self.is_mtp_device_connected else {}
|
dd = self.current_device_defaults if self.is_mtp_device_connected else {}
|
||||||
dev_settings = self.prefs.get('device-%s'%self.current_serial_num, {})
|
dev_settings = self.prefs.get(f'device-{self.current_serial_num}', {})
|
||||||
default_value = dd.get(key, self.prefs[key])
|
default_value = dd.get(key, self.prefs[key])
|
||||||
return dev_settings.get(key, default_value)
|
return dev_settings.get(key, default_value)
|
||||||
|
|
||||||
|
@ -69,8 +69,7 @@ class FileOrFolder:
|
|||||||
self.last_modified = as_utc(self.last_modified)
|
self.last_modified = as_utc(self.last_modified)
|
||||||
|
|
||||||
if self.storage_id not in fs_cache.all_storage_ids:
|
if self.storage_id not in fs_cache.all_storage_ids:
|
||||||
raise ValueError('Storage id %s not valid for %s, valid values: %s'%(self.storage_id,
|
raise ValueError(f'Storage id {self.storage_id} not valid for {entry}, valid values: {fs_cache.all_storage_ids}')
|
||||||
entry, fs_cache.all_storage_ids))
|
|
||||||
|
|
||||||
self.is_hidden = entry.get('is_hidden', False)
|
self.is_hidden = entry.get('is_hidden', False)
|
||||||
self.is_system = entry.get('is_system', False)
|
self.is_system = entry.get('is_system', False)
|
||||||
@ -92,7 +91,7 @@ class FileOrFolder:
|
|||||||
self.deleted = False
|
self.deleted = False
|
||||||
|
|
||||||
if self.is_storage:
|
if self.is_storage:
|
||||||
self.storage_prefix = 'mtp:::%s:::'%self.persistent_id
|
self.storage_prefix = f'mtp:::{self.persistent_id}:::'
|
||||||
|
|
||||||
# Ignore non ebook files and AppleDouble files
|
# Ignore non ebook files and AppleDouble files
|
||||||
self.is_ebook = (not self.is_folder and not self.is_storage and
|
self.is_ebook = (not self.is_folder and not self.is_storage and
|
||||||
@ -107,11 +106,10 @@ class FileOrFolder:
|
|||||||
path = str(self.full_path)
|
path = str(self.full_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
path = ''
|
path = ''
|
||||||
datum = 'size=%s'%(self.size)
|
datum = f'size={self.size}'
|
||||||
if self.is_folder or self.is_storage:
|
if self.is_folder or self.is_storage:
|
||||||
datum = 'children=%s'%(len(self.files) + len(self.folders))
|
datum = 'children=%s'%(len(self.files) + len(self.folders))
|
||||||
return '%s(id=%s, storage_id=%s, %s, path=%s, modified=%s)'%(name, self.object_id,
|
return f'{name}(id={self.object_id}, storage_id={self.storage_id}, {datum}, path={path}, modified={self.last_mod_string})'
|
||||||
self.storage_id, datum, path, self.last_mod_string)
|
|
||||||
|
|
||||||
__str__ = __repr__
|
__str__ = __repr__
|
||||||
__unicode__ = __repr__
|
__unicode__ = __repr__
|
||||||
@ -171,10 +169,10 @@ class FileOrFolder:
|
|||||||
|
|
||||||
def dump(self, prefix='', out=sys.stdout):
|
def dump(self, prefix='', out=sys.stdout):
|
||||||
c = '+' if self.is_folder else '-'
|
c = '+' if self.is_folder else '-'
|
||||||
data = ('%s children'%(sum(map(len, (self.files, self.folders))))
|
data = (f'{sum(map(len, (self.files, self.folders)))} children'
|
||||||
if self.is_folder else human_readable(self.size))
|
if self.is_folder else human_readable(self.size))
|
||||||
data += ' modified=%s'%self.last_mod_string
|
data += f' modified={self.last_mod_string}'
|
||||||
line = '%s%s %s [id:%s %s]'%(prefix, c, self.name, self.object_id, data)
|
line = f'{prefix}{c} {self.name} [id:{self.object_id} {data}]'
|
||||||
prints(line, file=out)
|
prints(line, file=out)
|
||||||
for c in (self.folders, self.files):
|
for c in (self.folders, self.files):
|
||||||
for e in sorted(c, key=lambda x: sort_key(x.name)):
|
for e in sorted(c, key=lambda x: sort_key(x.name)):
|
||||||
@ -290,14 +288,14 @@ class FilesystemCache:
|
|||||||
|
|
||||||
def resolve_mtp_id_path(self, path):
|
def resolve_mtp_id_path(self, path):
|
||||||
if not path.startswith('mtp:::'):
|
if not path.startswith('mtp:::'):
|
||||||
raise ValueError('%s is not a valid MTP path'%path)
|
raise ValueError(f'{path} is not a valid MTP path')
|
||||||
parts = path.split(':::', 2)
|
parts = path.split(':::', 2)
|
||||||
if len(parts) < 3:
|
if len(parts) < 3:
|
||||||
raise ValueError('%s is not a valid MTP path'%path)
|
raise ValueError(f'{path} is not a valid MTP path')
|
||||||
try:
|
try:
|
||||||
object_id = json.loads(parts[1])
|
object_id = json.loads(parts[1])
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError('%s is not a valid MTP path'%path)
|
raise ValueError(f'{path} is not a valid MTP path')
|
||||||
id_map = {}
|
id_map = {}
|
||||||
path = parts[2]
|
path = parts[2]
|
||||||
storage_name = path.partition('/')[0]
|
storage_name = path.partition('/')[0]
|
||||||
@ -308,4 +306,4 @@ class FilesystemCache:
|
|||||||
try:
|
try:
|
||||||
return id_map[object_id]
|
return id_map[object_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError('No object found with MTP path: %s'%path)
|
raise ValueError(f'No object found with MTP path: {path}')
|
||||||
|
@ -182,7 +182,7 @@ class TestDeviceInteraction(unittest.TestCase):
|
|||||||
return end_mem - start_mem
|
return end_mem - start_mem
|
||||||
|
|
||||||
def check_memory(self, once, many, msg, factor=2):
|
def check_memory(self, once, many, msg, factor=2):
|
||||||
msg += ' for once: %g for many: %g'%(once, many)
|
msg += f' for once: {once:g} for many: {many:g}'
|
||||||
if once > 0:
|
if once > 0:
|
||||||
self.assertTrue(many <= once*factor, msg=msg)
|
self.assertTrue(many <= once*factor, msg=msg)
|
||||||
else:
|
else:
|
||||||
|
@ -228,8 +228,7 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
self.dev = self.create_device(connected_device)
|
self.dev = self.create_device(connected_device)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.blacklisted_devices.add(connected_device)
|
self.blacklisted_devices.add(connected_device)
|
||||||
raise OpenFailed('Failed to open %s: Error: %s'%(
|
raise OpenFailed(f'Failed to open {connected_device}: Error: {as_unicode(e)}')
|
||||||
connected_device, as_unicode(e)))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
storage = sorted_storage(self.dev.storage_info)
|
storage = sorted_storage(self.dev.storage_info)
|
||||||
@ -259,13 +258,13 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
storage = [x for x in storage if x.get('rw', False)]
|
storage = [x for x in storage if x.get('rw', False)]
|
||||||
if not storage:
|
if not storage:
|
||||||
self.blacklisted_devices.add(connected_device)
|
self.blacklisted_devices.add(connected_device)
|
||||||
raise OpenFailed('No storage found for device %s'%(connected_device,))
|
raise OpenFailed(f'No storage found for device {connected_device}')
|
||||||
snum = self.dev.serial_number
|
snum = self.dev.serial_number
|
||||||
if snum in self.prefs.get('blacklist', []):
|
if snum in self.prefs.get('blacklist', []):
|
||||||
self.blacklisted_devices.add(connected_device)
|
self.blacklisted_devices.add(connected_device)
|
||||||
self.dev = None
|
self.dev = None
|
||||||
raise BlacklistedDevice(
|
raise BlacklistedDevice(
|
||||||
'The %s device has been blacklisted by the user'%(connected_device,))
|
f'The {connected_device} device has been blacklisted by the user')
|
||||||
self._main_id = storage[0]['id']
|
self._main_id = storage[0]['id']
|
||||||
self._carda_id = self._cardb_id = None
|
self._carda_id = self._cardb_id = None
|
||||||
if len(storage) > 1:
|
if len(storage) > 1:
|
||||||
@ -281,11 +280,11 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
@synchronous
|
@synchronous
|
||||||
def device_debug_info(self):
|
def device_debug_info(self):
|
||||||
ans = self.get_gui_name()
|
ans = self.get_gui_name()
|
||||||
ans += '\nSerial number: %s'%self.current_serial_num
|
ans += f'\nSerial number: {self.current_serial_num}'
|
||||||
ans += '\nManufacturer: %s'%self.dev.manufacturer_name
|
ans += f'\nManufacturer: {self.dev.manufacturer_name}'
|
||||||
ans += '\nModel: %s'%self.dev.model_name
|
ans += f'\nModel: {self.dev.model_name}'
|
||||||
ans += '\nids: %s'%(self.dev.ids,)
|
ans += f'\nids: {self.dev.ids}'
|
||||||
ans += '\nDevice version: %s'%self.dev.device_version
|
ans += f'\nDevice version: {self.dev.device_version}'
|
||||||
ans += '\nStorage:\n'
|
ans += '\nStorage:\n'
|
||||||
storage = sorted_storage(self.dev.storage_info)
|
storage = sorted_storage(self.dev.storage_info)
|
||||||
ans += pprint.pformat(storage)
|
ans += pprint.pformat(storage)
|
||||||
@ -306,7 +305,7 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
path = tuple(reversed(path))
|
path = tuple(reversed(path))
|
||||||
ok = not self.is_folder_ignored(self._currently_getting_sid, path)
|
ok = not self.is_folder_ignored(self._currently_getting_sid, path)
|
||||||
if not ok:
|
if not ok:
|
||||||
debug('Ignored object: %s' % '/'.join(path))
|
debug('Ignored object: {}'.format('/'.join(path)))
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -335,14 +334,10 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
all_items.extend(items), all_errs.extend(errs)
|
all_items.extend(items), all_errs.extend(errs)
|
||||||
if not all_items and all_errs:
|
if not all_items and all_errs:
|
||||||
raise DeviceError(
|
raise DeviceError(
|
||||||
'Failed to read filesystem from %s with errors: %s'
|
f'Failed to read filesystem from {self.current_friendly_name} with errors: {self.format_errorstack(all_errs)}')
|
||||||
%(self.current_friendly_name,
|
|
||||||
self.format_errorstack(all_errs)))
|
|
||||||
if all_errs:
|
if all_errs:
|
||||||
prints('There were some errors while getting the '
|
prints('There were some errors while getting the '
|
||||||
' filesystem from %s: %s'%(
|
f' filesystem from {self.current_friendly_name}: {self.format_errorstack(all_errs)}')
|
||||||
self.current_friendly_name,
|
|
||||||
self.format_errorstack(all_errs)))
|
|
||||||
self._filesystem_cache = FilesystemCache(storage, all_items)
|
self._filesystem_cache = FilesystemCache(storage, all_items)
|
||||||
debug('Filesystem metadata loaded in %g seconds (%d objects)'%(
|
debug('Filesystem metadata loaded in %g seconds (%d objects)'%(
|
||||||
time.time()-st, len(self._filesystem_cache)))
|
time.time()-st, len(self._filesystem_cache)))
|
||||||
@ -377,7 +372,7 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
@synchronous
|
@synchronous
|
||||||
def create_folder(self, parent, name):
|
def create_folder(self, parent, name):
|
||||||
if not parent.is_folder:
|
if not parent.is_folder:
|
||||||
raise ValueError('%s is not a folder'%(parent.full_path,))
|
raise ValueError(f'{parent.full_path} is not a folder')
|
||||||
e = parent.folder_named(name)
|
e = parent.folder_named(name)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
return e
|
return e
|
||||||
@ -387,21 +382,18 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
ans, errs = self.dev.create_folder(sid, pid, name)
|
ans, errs = self.dev.create_folder(sid, pid, name)
|
||||||
if ans is None:
|
if ans is None:
|
||||||
raise DeviceError(
|
raise DeviceError(
|
||||||
'Failed to create folder named %s in %s with error: %s'%
|
f'Failed to create folder named {name} in {parent.full_path} with error: {self.format_errorstack(errs)}')
|
||||||
(name, parent.full_path, self.format_errorstack(errs)))
|
|
||||||
return parent.add_child(ans)
|
return parent.add_child(ans)
|
||||||
|
|
||||||
@synchronous
|
@synchronous
|
||||||
def put_file(self, parent, name, stream, size, callback=None, replace=True):
|
def put_file(self, parent, name, stream, size, callback=None, replace=True):
|
||||||
e = parent.folder_named(name)
|
e = parent.folder_named(name)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
raise ValueError('Cannot upload file, %s already has a folder named: %s'%(
|
raise ValueError(f'Cannot upload file, {parent.full_path} already has a folder named: {e.name}')
|
||||||
parent.full_path, e.name))
|
|
||||||
e = parent.file_named(name)
|
e = parent.file_named(name)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
if not replace:
|
if not replace:
|
||||||
raise ValueError('Cannot upload file %s, it already exists'%(
|
raise ValueError(f'Cannot upload file {e.full_path}, it already exists')
|
||||||
e.full_path,))
|
|
||||||
self.delete_file_or_folder(e)
|
self.delete_file_or_folder(e)
|
||||||
sid, pid = parent.storage_id, parent.object_id
|
sid, pid = parent.storage_id, parent.object_id
|
||||||
if pid == sid:
|
if pid == sid:
|
||||||
@ -409,21 +401,19 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
|
|
||||||
ans, errs = self.dev.put_file(sid, pid, name, stream, size, callback)
|
ans, errs = self.dev.put_file(sid, pid, name, stream, size, callback)
|
||||||
if ans is None:
|
if ans is None:
|
||||||
raise DeviceError('Failed to upload file named: %s to %s: %s'
|
raise DeviceError(f'Failed to upload file named: {name} to {parent.full_path}: {self.format_errorstack(errs)}')
|
||||||
%(name, parent.full_path, self.format_errorstack(errs)))
|
|
||||||
return parent.add_child(ans)
|
return parent.add_child(ans)
|
||||||
|
|
||||||
@synchronous
|
@synchronous
|
||||||
def get_mtp_file(self, f, stream=None, callback=None):
|
def get_mtp_file(self, f, stream=None, callback=None):
|
||||||
if f.is_folder:
|
if f.is_folder:
|
||||||
raise ValueError('%s if a folder'%(f.full_path,))
|
raise ValueError(f'{f.full_path} if a folder')
|
||||||
set_name = stream is None
|
set_name = stream is None
|
||||||
if stream is None:
|
if stream is None:
|
||||||
stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat')
|
stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat')
|
||||||
ok, errs = self.dev.get_file(f.object_id, stream, callback)
|
ok, errs = self.dev.get_file(f.object_id, stream, callback)
|
||||||
if not ok:
|
if not ok:
|
||||||
raise DeviceError('Failed to get file: %s with errors: %s'%(
|
raise DeviceError(f'Failed to get file: {f.full_path} with errors: {self.format_errorstack(errs)}')
|
||||||
f.full_path, self.format_errorstack(errs)))
|
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
if set_name:
|
if set_name:
|
||||||
stream.name = f.name
|
stream.name = f.name
|
||||||
@ -476,18 +466,14 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
if obj.deleted:
|
if obj.deleted:
|
||||||
return
|
return
|
||||||
if not obj.can_delete:
|
if not obj.can_delete:
|
||||||
raise ValueError('Cannot delete %s as deletion not allowed'%
|
raise ValueError(f'Cannot delete {obj.full_path} as deletion not allowed')
|
||||||
(obj.full_path,))
|
|
||||||
if obj.is_system:
|
if obj.is_system:
|
||||||
raise ValueError('Cannot delete %s as it is a system object'%
|
raise ValueError(f'Cannot delete {obj.full_path} as it is a system object')
|
||||||
(obj.full_path,))
|
|
||||||
if obj.files or obj.folders:
|
if obj.files or obj.folders:
|
||||||
raise ValueError('Cannot delete %s as it is not empty'%
|
raise ValueError(f'Cannot delete {obj.full_path} as it is not empty')
|
||||||
(obj.full_path,))
|
|
||||||
parent = obj.parent
|
parent = obj.parent
|
||||||
ok, errs = self.dev.delete_object(obj.object_id)
|
ok, errs = self.dev.delete_object(obj.object_id)
|
||||||
if not ok:
|
if not ok:
|
||||||
raise DeviceError('Failed to delete %s with error: %s'%
|
raise DeviceError(f'Failed to delete {obj.full_path} with error: {self.format_errorstack(errs)}')
|
||||||
(obj.full_path, self.format_errorstack(errs)))
|
|
||||||
parent.remove_child(obj)
|
parent.remove_child(obj)
|
||||||
return parent
|
return parent
|
||||||
|
@ -34,7 +34,7 @@ class MTPDetect:
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
ipath = os.path.join(self.base, '{0}-*/{0}-*/interface'.format(dev.busnum))
|
ipath = os.path.join(self.base, f'{dev.busnum}-*/{dev.busnum}-*/interface')
|
||||||
for x in glob.glob(ipath):
|
for x in glob.glob(ipath):
|
||||||
raw = read(x)
|
raw = read(x)
|
||||||
if not raw or raw.strip() != b'MTP':
|
if not raw or raw.strip() != b'MTP':
|
||||||
@ -44,8 +44,8 @@ class MTPDetect:
|
|||||||
try:
|
try:
|
||||||
if raw and int(raw) == dev.devnum:
|
if raw and int(raw) == dev.devnum:
|
||||||
if debug is not None:
|
if debug is not None:
|
||||||
debug('Unknown device {} claims to be an MTP device'
|
debug(f'Unknown device {dev} claims to be an MTP device'
|
||||||
.format(dev))
|
)
|
||||||
return True
|
return True
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
continue
|
continue
|
||||||
|
@ -258,7 +258,7 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
path = tuple(reversed(path))
|
path = tuple(reversed(path))
|
||||||
ok = not self.is_folder_ignored(self._currently_getting_sid, path)
|
ok = not self.is_folder_ignored(self._currently_getting_sid, path)
|
||||||
if not ok:
|
if not ok:
|
||||||
debug('Ignored object: %s' % '/'.join(path))
|
debug('Ignored object: {}'.format('/'.join(path)))
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -330,19 +330,18 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
self.dev = self.wpd.Device(connected_device)
|
self.dev = self.wpd.Device(connected_device)
|
||||||
except self.wpd.WPDError as e:
|
except self.wpd.WPDError as e:
|
||||||
self.blacklisted_devices.add(connected_device)
|
self.blacklisted_devices.add(connected_device)
|
||||||
raise OpenFailed('Failed to open %s with error: %s'%(
|
raise OpenFailed(f'Failed to open {connected_device} with error: {as_unicode(e)}')
|
||||||
connected_device, as_unicode(e)))
|
|
||||||
devdata = self.dev.data
|
devdata = self.dev.data
|
||||||
storage = [s for s in devdata.get('storage', []) if s.get('rw', False)]
|
storage = [s for s in devdata.get('storage', []) if s.get('rw', False)]
|
||||||
if not storage:
|
if not storage:
|
||||||
self.blacklisted_devices.add(connected_device)
|
self.blacklisted_devices.add(connected_device)
|
||||||
raise OpenFailed('No storage found for device %s'%(connected_device,))
|
raise OpenFailed(f'No storage found for device {connected_device}')
|
||||||
snum = devdata.get('serial_number', None)
|
snum = devdata.get('serial_number', None)
|
||||||
if snum in self.prefs.get('blacklist', []):
|
if snum in self.prefs.get('blacklist', []):
|
||||||
self.blacklisted_devices.add(connected_device)
|
self.blacklisted_devices.add(connected_device)
|
||||||
self.dev = None
|
self.dev = None
|
||||||
raise BlacklistedDevice(
|
raise BlacklistedDevice(
|
||||||
'The %s device has been blacklisted by the user'%(connected_device,))
|
f'The {connected_device} device has been blacklisted by the user')
|
||||||
|
|
||||||
storage = sorted_storage(storage)
|
storage = sorted_storage(storage)
|
||||||
|
|
||||||
@ -435,7 +434,7 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
@same_thread
|
@same_thread
|
||||||
def get_mtp_file(self, f, stream=None, callback=None):
|
def get_mtp_file(self, f, stream=None, callback=None):
|
||||||
if f.is_folder:
|
if f.is_folder:
|
||||||
raise ValueError('%s if a folder'%(f.full_path,))
|
raise ValueError(f'{f.full_path} if a folder')
|
||||||
set_name = stream is None
|
set_name = stream is None
|
||||||
if stream is None:
|
if stream is None:
|
||||||
stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat')
|
stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat')
|
||||||
@ -446,8 +445,7 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
self.dev.get_file(f.object_id, stream, callback)
|
self.dev.get_file(f.object_id, stream, callback)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise DeviceError('Failed to fetch the file %s with error: %s'%
|
raise DeviceError(f'Failed to fetch the file {f.full_path} with error: {as_unicode(e)}')
|
||||||
(f.full_path, as_unicode(e)))
|
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
if set_name:
|
if set_name:
|
||||||
stream.name = f.name
|
stream.name = f.name
|
||||||
@ -456,7 +454,7 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
@same_thread
|
@same_thread
|
||||||
def create_folder(self, parent, name):
|
def create_folder(self, parent, name):
|
||||||
if not parent.is_folder:
|
if not parent.is_folder:
|
||||||
raise ValueError('%s is not a folder'%(parent.full_path,))
|
raise ValueError(f'{parent.full_path} is not a folder')
|
||||||
e = parent.folder_named(name)
|
e = parent.folder_named(name)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
return e
|
return e
|
||||||
@ -472,14 +470,11 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
if obj.deleted:
|
if obj.deleted:
|
||||||
return
|
return
|
||||||
if not obj.can_delete:
|
if not obj.can_delete:
|
||||||
raise ValueError('Cannot delete %s as deletion not allowed'%
|
raise ValueError(f'Cannot delete {obj.full_path} as deletion not allowed')
|
||||||
(obj.full_path,))
|
|
||||||
if obj.is_system:
|
if obj.is_system:
|
||||||
raise ValueError('Cannot delete %s as it is a system object'%
|
raise ValueError(f'Cannot delete {obj.full_path} as it is a system object')
|
||||||
(obj.full_path,))
|
|
||||||
if obj.files or obj.folders:
|
if obj.files or obj.folders:
|
||||||
raise ValueError('Cannot delete %s as it is not empty'%
|
raise ValueError(f'Cannot delete {obj.full_path} as it is not empty')
|
||||||
(obj.full_path,))
|
|
||||||
parent = obj.parent
|
parent = obj.parent
|
||||||
self.dev.delete_object(obj.object_id)
|
self.dev.delete_object(obj.object_id)
|
||||||
parent.remove_child(obj)
|
parent.remove_child(obj)
|
||||||
@ -489,13 +484,11 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
def put_file(self, parent, name, stream, size, callback=None, replace=True):
|
def put_file(self, parent, name, stream, size, callback=None, replace=True):
|
||||||
e = parent.folder_named(name)
|
e = parent.folder_named(name)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
raise ValueError('Cannot upload file, %s already has a folder named: %s'%(
|
raise ValueError(f'Cannot upload file, {parent.full_path} already has a folder named: {e.name}')
|
||||||
parent.full_path, e.name))
|
|
||||||
e = parent.file_named(name)
|
e = parent.file_named(name)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
if not replace:
|
if not replace:
|
||||||
raise ValueError('Cannot upload file %s, it already exists'%(
|
raise ValueError(f'Cannot upload file {e.full_path}, it already exists')
|
||||||
e.full_path,))
|
|
||||||
self.delete_file_or_folder(e)
|
self.delete_file_or_folder(e)
|
||||||
sid, pid = parent.storage_id, parent.object_id
|
sid, pid = parent.storage_id, parent.object_id
|
||||||
ans = self.dev.put_file(pid, name, stream, size, callback)
|
ans = self.dev.put_file(pid, name, stream, size, callback)
|
||||||
|
@ -70,7 +70,7 @@ class NOOK(USBMS):
|
|||||||
cover.save(data, 'JPEG')
|
cover.save(data, 'JPEG')
|
||||||
coverdata = data.getvalue()
|
coverdata = data.getvalue()
|
||||||
|
|
||||||
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
|
with open(f'{os.path.join(path, filename)}.jpg', 'wb') as coverfile:
|
||||||
coverfile.write(coverdata)
|
coverfile.write(coverdata)
|
||||||
fsync(coverfile)
|
fsync(coverfile)
|
||||||
|
|
||||||
|
@ -214,11 +214,11 @@ class PALADIN(USBMS):
|
|||||||
import traceback
|
import traceback
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
raise DeviceError((('The Paladin database is corrupted. '
|
raise DeviceError((('The Paladin database is corrupted. '
|
||||||
' Delete the file %s on your reader and then disconnect '
|
f' Delete the file {dbpath} on your reader and then disconnect '
|
||||||
' reconnect it. If you are using an SD card, you '
|
' reconnect it. If you are using an SD card, you '
|
||||||
' should delete the file on the card as well. Note that '
|
' should delete the file on the card as well. Note that '
|
||||||
' deleting this file will cause your reader to forget '
|
' deleting this file will cause your reader to forget '
|
||||||
' any notes/highlights, etc.')%dbpath)+' Underlying error:'
|
' any notes/highlights, etc.'))+' Underlying error:'
|
||||||
'\n'+tb)
|
'\n'+tb)
|
||||||
|
|
||||||
def get_database_min_id(self, source_id):
|
def get_database_min_id(self, source_id):
|
||||||
@ -261,11 +261,11 @@ class PALADIN(USBMS):
|
|||||||
import traceback
|
import traceback
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
raise DeviceError((('The Paladin database is corrupted. '
|
raise DeviceError((('The Paladin database is corrupted. '
|
||||||
' Delete the file %s on your reader and then disconnect '
|
f' Delete the file {dbpath} on your reader and then disconnect '
|
||||||
' reconnect it. If you are using an SD card, you '
|
' reconnect it. If you are using an SD card, you '
|
||||||
' should delete the file on the card as well. Note that '
|
' should delete the file on the card as well. Note that '
|
||||||
' deleting this file will cause your reader to forget '
|
' deleting this file will cause your reader to forget '
|
||||||
' any notes/highlights, etc.')%dbpath)+' Underlying error:'
|
' any notes/highlights, etc.'))+' Underlying error:'
|
||||||
'\n'+tb)
|
'\n'+tb)
|
||||||
|
|
||||||
# Get the books themselves, but keep track of any that are less than the minimum.
|
# Get the books themselves, but keep track of any that are less than the minimum.
|
||||||
@ -398,11 +398,11 @@ class PALADIN(USBMS):
|
|||||||
import traceback
|
import traceback
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
raise DeviceError((('The Paladin database is corrupted. '
|
raise DeviceError((('The Paladin database is corrupted. '
|
||||||
' Delete the file %s on your reader and then disconnect '
|
f' Delete the file {dbpath} on your reader and then disconnect '
|
||||||
' reconnect it. If you are using an SD card, you '
|
' reconnect it. If you are using an SD card, you '
|
||||||
' should delete the file on the card as well. Note that '
|
' should delete the file on the card as well. Note that '
|
||||||
' deleting this file will cause your reader to forget '
|
' deleting this file will cause your reader to forget '
|
||||||
' any notes/highlights, etc.')%dbpath)+' Underlying error:'
|
' any notes/highlights, etc.'))+' Underlying error:'
|
||||||
'\n'+tb)
|
'\n'+tb)
|
||||||
|
|
||||||
db_collections = {}
|
db_collections = {}
|
||||||
|
@ -170,7 +170,7 @@ class PRS505(USBMS):
|
|||||||
def filename_callback(self, fname, mi):
|
def filename_callback(self, fname, mi):
|
||||||
if getattr(mi, 'application_id', None) is not None:
|
if getattr(mi, 'application_id', None) is not None:
|
||||||
base = fname.rpartition('.')[0]
|
base = fname.rpartition('.')[0]
|
||||||
suffix = '_%s'%mi.application_id
|
suffix = f'_{mi.application_id}'
|
||||||
if not base.endswith(suffix):
|
if not base.endswith(suffix):
|
||||||
fname = base + suffix + '.' + fname.rpartition('.')[-1]
|
fname = base + suffix + '.' + fname.rpartition('.')[-1]
|
||||||
return fname
|
return fname
|
||||||
@ -183,7 +183,7 @@ class PRS505(USBMS):
|
|||||||
('card_a', CACHE_XML, CACHE_EXT, 1),
|
('card_a', CACHE_XML, CACHE_EXT, 1),
|
||||||
('card_b', CACHE_XML, CACHE_EXT, 2)
|
('card_b', CACHE_XML, CACHE_EXT, 2)
|
||||||
]:
|
]:
|
||||||
prefix = getattr(self, '_%s_prefix'%prefix)
|
prefix = getattr(self, f'_{prefix}_prefix')
|
||||||
if prefix is not None and os.path.exists(prefix):
|
if prefix is not None and os.path.exists(prefix):
|
||||||
paths[source_id] = os.path.join(prefix, *(path.split('/')))
|
paths[source_id] = os.path.join(prefix, *(path.split('/')))
|
||||||
ext_paths[source_id] = os.path.join(prefix, *(ext_path.split('/')))
|
ext_paths[source_id] = os.path.join(prefix, *(ext_path.split('/')))
|
||||||
@ -298,4 +298,4 @@ class PRS505(USBMS):
|
|||||||
cpath = os.path.join(thumbnail_dir, 'main_thumbnail.jpg')
|
cpath = os.path.join(thumbnail_dir, 'main_thumbnail.jpg')
|
||||||
with open(cpath, 'wb') as f:
|
with open(cpath, 'wb') as f:
|
||||||
f.write(metadata.thumbnail[-1])
|
f.write(metadata.thumbnail[-1])
|
||||||
debug_print('Cover uploaded to: %r'%cpath)
|
debug_print(f'Cover uploaded to: {cpath!r}')
|
||||||
|
@ -103,8 +103,8 @@ class XMLCache:
|
|||||||
for source_id, path in paths.items():
|
for source_id, path in paths.items():
|
||||||
if source_id == 0:
|
if source_id == 0:
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise DeviceError(('The SONY XML cache %r does not exist. Try'
|
raise DeviceError(f'The SONY XML cache {repr(path)!r} does not exist. Try'
|
||||||
' disconnecting and reconnecting your reader.')%repr(path))
|
' disconnecting and reconnecting your reader.')
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
raw = f.read()
|
raw = f.read()
|
||||||
else:
|
else:
|
||||||
@ -117,8 +117,8 @@ class XMLCache:
|
|||||||
xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True, verbose=DEBUG)[0]
|
xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True, verbose=DEBUG)[0]
|
||||||
)
|
)
|
||||||
if self.roots[source_id] is None:
|
if self.roots[source_id] is None:
|
||||||
raise Exception(('The SONY database at %r is corrupted. Try '
|
raise Exception(f'The SONY database at {path!r} is corrupted. Try '
|
||||||
' disconnecting and reconnecting your reader.')%path)
|
' disconnecting and reconnecting your reader.')
|
||||||
|
|
||||||
self.ext_paths, self.ext_roots = {}, {}
|
self.ext_paths, self.ext_roots = {}, {}
|
||||||
for source_id, path in ext_paths.items():
|
for source_id, path in ext_paths.items():
|
||||||
@ -265,7 +265,7 @@ class XMLCache:
|
|||||||
if title in self._playlist_to_playlist_id_map[bl_idx]:
|
if title in self._playlist_to_playlist_id_map[bl_idx]:
|
||||||
return self._playlist_to_playlist_id_map[bl_idx][title]
|
return self._playlist_to_playlist_id_map[bl_idx][title]
|
||||||
debug_print('Creating playlist:', title)
|
debug_print('Creating playlist:', title)
|
||||||
ans = root.makeelement('{%s}playlist'%self.namespaces[bl_idx],
|
ans = root.makeelement(f'{{{self.namespaces[bl_idx]}}}playlist',
|
||||||
nsmap=root.nsmap, attrib={
|
nsmap=root.nsmap, attrib={
|
||||||
'uuid' : uuid(),
|
'uuid' : uuid(),
|
||||||
'title': title,
|
'title': title,
|
||||||
@ -303,11 +303,11 @@ class XMLCache:
|
|||||||
if id_ in idmap:
|
if id_ in idmap:
|
||||||
item.set('id', idmap[id_])
|
item.set('id', idmap[id_])
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
debug_print('Remapping id %s to %s'%(id_, idmap[id_]))
|
debug_print(f'Remapping id {id_} to {idmap[id_]}')
|
||||||
|
|
||||||
def ensure_media_xml_base_ids(root):
|
def ensure_media_xml_base_ids(root):
|
||||||
for num, tag in enumerate(('library', 'watchSpecial')):
|
for num, tag in enumerate(('library', 'watchSpecial')):
|
||||||
for x in root.xpath('//*[local-name()="%s"]'%tag):
|
for x in root.xpath(f'//*[local-name()="{tag}"]'):
|
||||||
x.set('id', str(num))
|
x.set('id', str(num))
|
||||||
|
|
||||||
def rebase_ids(root, base, sourceid, pl_sourceid):
|
def rebase_ids(root, base, sourceid, pl_sourceid):
|
||||||
@ -538,7 +538,7 @@ class XMLCache:
|
|||||||
# add the ids that get_collections didn't know about.
|
# add the ids that get_collections didn't know about.
|
||||||
for id_ in ids + extra_ids:
|
for id_ in ids + extra_ids:
|
||||||
item = playlist.makeelement(
|
item = playlist.makeelement(
|
||||||
'{%s}item'%self.namespaces[bl_index],
|
f'{{{self.namespaces[bl_index]}}}item',
|
||||||
nsmap=playlist.nsmap, attrib={'id':id_})
|
nsmap=playlist.nsmap, attrib={'id':id_})
|
||||||
playlist.append(item)
|
playlist.append(item)
|
||||||
|
|
||||||
@ -569,14 +569,14 @@ class XMLCache:
|
|||||||
attrib = {
|
attrib = {
|
||||||
'page':'0', 'part':'0','pageOffset':'0','scale':'0',
|
'page':'0', 'part':'0','pageOffset':'0','scale':'0',
|
||||||
'id':str(id_), 'sourceid':'1', 'path':lpath}
|
'id':str(id_), 'sourceid':'1', 'path':lpath}
|
||||||
ans = root.makeelement('{%s}text'%namespace, attrib=attrib, nsmap=root.nsmap)
|
ans = root.makeelement(f'{{{namespace}}}text', attrib=attrib, nsmap=root.nsmap)
|
||||||
root.append(ans)
|
root.append(ans)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def create_ext_text_record(self, root, bl_id, lpath, thumbnail):
|
def create_ext_text_record(self, root, bl_id, lpath, thumbnail):
|
||||||
namespace = root.nsmap[None]
|
namespace = root.nsmap[None]
|
||||||
attrib = {'path': lpath}
|
attrib = {'path': lpath}
|
||||||
ans = root.makeelement('{%s}text'%namespace, attrib=attrib,
|
ans = root.makeelement(f'{{{namespace}}}text', attrib=attrib,
|
||||||
nsmap=root.nsmap)
|
nsmap=root.nsmap)
|
||||||
ans.tail = '\n'
|
ans.tail = '\n'
|
||||||
if len(root) > 0:
|
if len(root) > 0:
|
||||||
@ -586,7 +586,7 @@ class XMLCache:
|
|||||||
root.append(ans)
|
root.append(ans)
|
||||||
if thumbnail and thumbnail[-1]:
|
if thumbnail and thumbnail[-1]:
|
||||||
ans.text = '\n' + '\t\t'
|
ans.text = '\n' + '\t\t'
|
||||||
t = root.makeelement('{%s}thumbnail'%namespace,
|
t = root.makeelement(f'{{{namespace}}}thumbnail',
|
||||||
attrib={'width':str(thumbnail[0]), 'height':str(thumbnail[1])},
|
attrib={'width':str(thumbnail[0]), 'height':str(thumbnail[1])},
|
||||||
nsmap=root.nsmap)
|
nsmap=root.nsmap)
|
||||||
t.text = 'main_thumbnail.jpg'
|
t.text = 'main_thumbnail.jpg'
|
||||||
@ -757,7 +757,7 @@ class XMLCache:
|
|||||||
return m
|
return m
|
||||||
|
|
||||||
def book_by_lpath(self, lpath, root):
|
def book_by_lpath(self, lpath, root):
|
||||||
matches = root.xpath('//*[local-name()="text" and @path="%s"]'%lpath)
|
matches = root.xpath(f'//*[local-name()="text" and @path="{lpath}"]')
|
||||||
if matches:
|
if matches:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
@ -782,7 +782,7 @@ class XMLCache:
|
|||||||
for i in self.roots:
|
for i in self.roots:
|
||||||
for c in ('library', 'text', 'image', 'playlist', 'thumbnail',
|
for c in ('library', 'text', 'image', 'playlist', 'thumbnail',
|
||||||
'watchSpecial'):
|
'watchSpecial'):
|
||||||
matches = self.record_roots[i].xpath('//*[local-name()="%s"]'%c)
|
matches = self.record_roots[i].xpath(f'//*[local-name()="{c}"]')
|
||||||
if matches:
|
if matches:
|
||||||
e = matches[0]
|
e = matches[0]
|
||||||
self.namespaces[i] = e.nsmap[e.prefix]
|
self.namespaces[i] = e.nsmap[e.prefix]
|
||||||
|
@ -316,11 +316,11 @@ class PRST1(USBMS):
|
|||||||
import traceback
|
import traceback
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
raise DeviceError((('The SONY database is corrupted. '
|
raise DeviceError((('The SONY database is corrupted. '
|
||||||
' Delete the file %s on your reader and then disconnect '
|
f' Delete the file {dbpath} on your reader and then disconnect '
|
||||||
' reconnect it. If you are using an SD card, you '
|
' reconnect it. If you are using an SD card, you '
|
||||||
' should delete the file on the card as well. Note that '
|
' should delete the file on the card as well. Note that '
|
||||||
' deleting this file will cause your reader to forget '
|
' deleting this file will cause your reader to forget '
|
||||||
' any notes/highlights, etc.')%dbpath)+' Underlying error:'
|
' any notes/highlights, etc.'))+' Underlying error:'
|
||||||
'\n'+tb)
|
'\n'+tb)
|
||||||
|
|
||||||
def get_lastrowid(self, cursor):
|
def get_lastrowid(self, cursor):
|
||||||
@ -374,11 +374,11 @@ class PRST1(USBMS):
|
|||||||
import traceback
|
import traceback
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
raise DeviceError((('The SONY database is corrupted. '
|
raise DeviceError((('The SONY database is corrupted. '
|
||||||
' Delete the file %s on your reader and then disconnect '
|
f' Delete the file {dbpath} on your reader and then disconnect '
|
||||||
' reconnect it. If you are using an SD card, you '
|
' reconnect it. If you are using an SD card, you '
|
||||||
' should delete the file on the card as well. Note that '
|
' should delete the file on the card as well. Note that '
|
||||||
' deleting this file will cause your reader to forget '
|
' deleting this file will cause your reader to forget '
|
||||||
' any notes/highlights, etc.')%dbpath)+' Underlying error:'
|
' any notes/highlights, etc.'))+' Underlying error:'
|
||||||
'\n'+tb)
|
'\n'+tb)
|
||||||
|
|
||||||
# Get the books themselves, but keep track of any that are less than the minimum.
|
# Get the books themselves, but keep track of any that are less than the minimum.
|
||||||
@ -546,11 +546,11 @@ class PRST1(USBMS):
|
|||||||
import traceback
|
import traceback
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
raise DeviceError((('The SONY database is corrupted. '
|
raise DeviceError((('The SONY database is corrupted. '
|
||||||
' Delete the file %s on your reader and then disconnect '
|
f' Delete the file {dbpath} on your reader and then disconnect '
|
||||||
' reconnect it. If you are using an SD card, you '
|
' reconnect it. If you are using an SD card, you '
|
||||||
' should delete the file on the card as well. Note that '
|
' should delete the file on the card as well. Note that '
|
||||||
' deleting this file will cause your reader to forget '
|
' deleting this file will cause your reader to forget '
|
||||||
' any notes/highlights, etc.')%dbpath)+' Underlying error:'
|
' any notes/highlights, etc.'))+' Underlying error:'
|
||||||
'\n'+tb)
|
'\n'+tb)
|
||||||
|
|
||||||
db_collections = {}
|
db_collections = {}
|
||||||
|
@ -45,11 +45,9 @@ class USBDevice(_USBDevice):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ('USBDevice(busnum=%s, devnum=%s, '
|
return (f'USBDevice(busnum={self.busnum}, devnum={self.devnum}, '
|
||||||
'vendor_id=0x%04x, product_id=0x%04x, bcd=0x%04x, '
|
f'vendor_id=0x{self.vendor_id:04x}, product_id=0x{self.product_id:04x}, bcd=0x{self.bcd:04x}, '
|
||||||
'manufacturer=%s, product=%s, serial=%s)')%(
|
f'manufacturer={self.manufacturer}, product={self.product}, serial={self.serial})')
|
||||||
self.busnum, self.devnum, self.vendor_id, self.product_id,
|
|
||||||
self.bcd, self.manufacturer, self.product, self.serial)
|
|
||||||
|
|
||||||
__str__ = __repr__
|
__str__ = __repr__
|
||||||
__unicode__ = __repr__
|
__unicode__ = __repr__
|
||||||
|
@ -402,8 +402,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
return
|
return
|
||||||
total_elapsed = time.time() - self.debug_start_time
|
total_elapsed = time.time() - self.debug_start_time
|
||||||
elapsed = time.time() - self.debug_time
|
elapsed = time.time() - self.debug_time
|
||||||
print('SMART_DEV (%7.2f:%7.3f) %s'%(total_elapsed, elapsed,
|
print(f'SMART_DEV ({total_elapsed:7.2f}:{elapsed:7.3f}) {inspect.stack()[1][3]}', end='')
|
||||||
inspect.stack()[1][3]), end='')
|
|
||||||
for a in args:
|
for a in args:
|
||||||
try:
|
try:
|
||||||
if isinstance(a, dict):
|
if isinstance(a, dict):
|
||||||
@ -712,7 +711,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
wait_for_response=self.can_send_ok_to_sendbook)
|
wait_for_response=self.can_send_ok_to_sendbook)
|
||||||
if self.can_send_ok_to_sendbook:
|
if self.can_send_ok_to_sendbook:
|
||||||
if opcode == 'ERROR':
|
if opcode == 'ERROR':
|
||||||
raise UserFeedback(msg='Sending book %s to device failed' % lpath,
|
raise UserFeedback(msg=f'Sending book {lpath} to device failed',
|
||||||
details=result.get('message', ''),
|
details=result.get('message', ''),
|
||||||
level=UserFeedback.ERROR)
|
level=UserFeedback.ERROR)
|
||||||
return
|
return
|
||||||
@ -1493,7 +1492,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
|
|||||||
book = SDBook(self.PREFIX, lpath, other=mdata)
|
book = SDBook(self.PREFIX, lpath, other=mdata)
|
||||||
length, lpath = self._put_file(infile, lpath, book, i, len(files))
|
length, lpath = self._put_file(infile, lpath, book, i, len(files))
|
||||||
if length < 0:
|
if length < 0:
|
||||||
raise ControlError(desc='Sending book %s to device failed' % lpath)
|
raise ControlError(desc=f'Sending book {lpath} to device failed')
|
||||||
paths.append((lpath, length))
|
paths.append((lpath, length))
|
||||||
# No need to deal with covers. The client will get the thumbnails
|
# No need to deal with covers. The client will get the thumbnails
|
||||||
# in the mi structure
|
# in the mi structure
|
||||||
|
@ -256,7 +256,7 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
if dl in dlmap['readonly_drives']:
|
if dl in dlmap['readonly_drives']:
|
||||||
filtered.add(dl)
|
filtered.add(dl)
|
||||||
if debug:
|
if debug:
|
||||||
prints('Ignoring the drive %s as it is readonly' % dl)
|
prints(f'Ignoring the drive {dl} as it is readonly')
|
||||||
elif self.windows_filter_pnp_id(pnp_id):
|
elif self.windows_filter_pnp_id(pnp_id):
|
||||||
filtered.add(dl)
|
filtered.add(dl)
|
||||||
if debug:
|
if debug:
|
||||||
@ -264,7 +264,7 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
elif not drive_is_ok(dl, debug=debug):
|
elif not drive_is_ok(dl, debug=debug):
|
||||||
filtered.add(dl)
|
filtered.add(dl)
|
||||||
if debug:
|
if debug:
|
||||||
prints('Ignoring the drive %s because failed to get free space for it' % dl)
|
prints(f'Ignoring the drive {dl} because failed to get free space for it')
|
||||||
dlmap['drive_letters'] = [dl for dl in dlmap['drive_letters'] if dl not in filtered]
|
dlmap['drive_letters'] = [dl for dl in dlmap['drive_letters'] if dl not in filtered]
|
||||||
|
|
||||||
if not dlmap['drive_letters']:
|
if not dlmap['drive_letters']:
|
||||||
|
@ -57,7 +57,7 @@ class DeviceConfig:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _config(cls):
|
def _config(cls):
|
||||||
name = cls._config_base_name()
|
name = cls._config_base_name()
|
||||||
c = Config('device_drivers_%s' % name, _('settings for device drivers'))
|
c = Config(f'device_drivers_{name}', _('settings for device drivers'))
|
||||||
c.add_opt('format_map', default=cls.FORMATS,
|
c.add_opt('format_map', default=cls.FORMATS,
|
||||||
help=_('Ordered list of formats the device will accept'))
|
help=_('Ordered list of formats the device will accept'))
|
||||||
c.add_opt('use_subdirs', default=cls.SUPPORTS_SUB_DIRS_DEFAULT,
|
c.add_opt('use_subdirs', default=cls.SUPPORTS_SUB_DIRS_DEFAULT,
|
||||||
|
@ -47,7 +47,7 @@ def safe_walk(top, topdown=True, onerror=None, followlinks=False, maxdepth=128):
|
|||||||
try:
|
try:
|
||||||
name = name.decode(filesystem_encoding)
|
name = name.decode(filesystem_encoding)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
debug_print('Skipping undecodeable file: %r' % name)
|
debug_print(f'Skipping undecodeable file: {name!r}')
|
||||||
continue
|
continue
|
||||||
if isdir(join(top, name)):
|
if isdir(join(top, name)):
|
||||||
dirs.append(name)
|
dirs.append(name)
|
||||||
|
@ -60,7 +60,7 @@ def build_template_regexp(template):
|
|||||||
template = template.rpartition('/')[2]
|
template = template.rpartition('/')[2]
|
||||||
return re.compile(re.sub(r'{([^}]*)}', f, template) + r'([_\d]*$)')
|
return re.compile(re.sub(r'{([^}]*)}', f, template) + r'([_\d]*$)')
|
||||||
except:
|
except:
|
||||||
prints('Failed to parse template: %r'%template)
|
prints(f'Failed to parse template: {template!r}')
|
||||||
template = '{title} - {authors}'
|
template = '{title} - {authors}'
|
||||||
return re.compile(re.sub(r'{([^}]*)}', f, template) + r'([_\d]*$)')
|
return re.compile(re.sub(r'{([^}]*)}', f, template) + r'([_\d]*$)')
|
||||||
|
|
||||||
|
@ -69,8 +69,8 @@ class GUID(Structure):
|
|||||||
self.data1,
|
self.data1,
|
||||||
self.data2,
|
self.data2,
|
||||||
self.data3,
|
self.data3,
|
||||||
''.join(['%02x' % d for d in self.data4[:2]]),
|
''.join([f'{d:02x}' for d in self.data4[:2]]),
|
||||||
''.join(['%02x' % d for d in self.data4[2:]]),
|
''.join([f'{d:02x}' for d in self.data4[2:]]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -394,7 +394,7 @@ def cwrap(name, restype, *argtypes, **kw):
|
|||||||
lib = cfgmgr if name.startswith('CM') else setupapi
|
lib = cfgmgr if name.startswith('CM') else setupapi
|
||||||
func = prototype((name, kw.pop('lib', lib)))
|
func = prototype((name, kw.pop('lib', lib)))
|
||||||
if kw:
|
if kw:
|
||||||
raise TypeError('Unknown keyword arguments: %r' % kw)
|
raise TypeError(f'Unknown keyword arguments: {kw!r}')
|
||||||
if errcheck is not None:
|
if errcheck is not None:
|
||||||
func.errcheck = errcheck
|
func.errcheck = errcheck
|
||||||
return func
|
return func
|
||||||
@ -414,7 +414,7 @@ def bool_err_check(result, func, args):
|
|||||||
|
|
||||||
def config_err_check(result, func, args):
|
def config_err_check(result, func, args):
|
||||||
if result != CR_CODES['CR_SUCCESS']:
|
if result != CR_CODES['CR_SUCCESS']:
|
||||||
raise WinError(result, 'The cfgmgr32 function failed with err: %s' % CR_CODE_NAMES.get(result, result))
|
raise WinError(result, f'The cfgmgr32 function failed with err: {CR_CODE_NAMES.get(result, result)}')
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
@ -575,7 +575,7 @@ def get_device_id(devinst, buf=None):
|
|||||||
buf = create_unicode_buffer(devid_size.value)
|
buf = create_unicode_buffer(devid_size.value)
|
||||||
continue
|
continue
|
||||||
if ret != CR_CODES['CR_SUCCESS']:
|
if ret != CR_CODES['CR_SUCCESS']:
|
||||||
raise WinError(ret, 'The cfgmgr32 function failed with err: %s' % CR_CODE_NAMES.get(ret, ret))
|
raise WinError(ret, f'The cfgmgr32 function failed with err: {CR_CODE_NAMES.get(ret, ret)}')
|
||||||
break
|
break
|
||||||
return wstring_at(buf), buf
|
return wstring_at(buf), buf
|
||||||
|
|
||||||
@ -610,7 +610,7 @@ def convert_registry_data(raw, size, dtype):
|
|||||||
if size == 0:
|
if size == 0:
|
||||||
return 0
|
return 0
|
||||||
return cast(raw, POINTER(QWORD)).contents.value
|
return cast(raw, POINTER(QWORD)).contents.value
|
||||||
raise ValueError('Unsupported data type: %r' % dtype)
|
raise ValueError(f'Unsupported data type: {dtype!r}')
|
||||||
|
|
||||||
|
|
||||||
def get_device_registry_property(dev_list, p_devinfo, property_type=SPDRP_HARDWAREID, buf=None):
|
def get_device_registry_property(dev_list, p_devinfo, property_type=SPDRP_HARDWAREID, buf=None):
|
||||||
@ -712,9 +712,8 @@ class USBDevice(_USBDevice):
|
|||||||
def r(x):
|
def r(x):
|
||||||
if x is None:
|
if x is None:
|
||||||
return 'None'
|
return 'None'
|
||||||
return '0x%x' % x
|
return f'0x{x:x}'
|
||||||
return 'USBDevice(vendor_id={} product_id={} bcd={} devid={} devinst={})'.format(
|
return f'USBDevice(vendor_id={r(self.vendor_id)} product_id={r(self.product_id)} bcd={r(self.bcd)} devid={self.devid} devinst={self.devinst})'
|
||||||
r(self.vendor_id), r(self.product_id), r(self.bcd), self.devid, self.devinst)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_hex(x):
|
def parse_hex(x):
|
||||||
@ -976,7 +975,7 @@ def get_device_string(hub_handle, device_port, index, buf=None, lang=0x409):
|
|||||||
data = cast(buf, PUSB_DESCRIPTOR_REQUEST).contents.Data
|
data = cast(buf, PUSB_DESCRIPTOR_REQUEST).contents.Data
|
||||||
sz, dtype = data.bLength, data.bType
|
sz, dtype = data.bLength, data.bType
|
||||||
if dtype != 0x03:
|
if dtype != 0x03:
|
||||||
raise OSError(errno.EINVAL, 'Invalid datatype for string descriptor: 0x%x' % dtype)
|
raise OSError(errno.EINVAL, f'Invalid datatype for string descriptor: 0x{dtype:x}')
|
||||||
return buf, wstring_at(addressof(data.String), sz // 2).rstrip('\0')
|
return buf, wstring_at(addressof(data.String), sz // 2).rstrip('\0')
|
||||||
|
|
||||||
|
|
||||||
@ -996,7 +995,7 @@ def get_device_languages(hub_handle, device_port, buf=None):
|
|||||||
data = cast(buf, PUSB_DESCRIPTOR_REQUEST).contents.Data
|
data = cast(buf, PUSB_DESCRIPTOR_REQUEST).contents.Data
|
||||||
sz, dtype = data.bLength, data.bType
|
sz, dtype = data.bLength, data.bType
|
||||||
if dtype != 0x03:
|
if dtype != 0x03:
|
||||||
raise OSError(errno.EINVAL, 'Invalid datatype for string descriptor: 0x%x' % dtype)
|
raise OSError(errno.EINVAL, f'Invalid datatype for string descriptor: 0x{dtype:x}')
|
||||||
data = cast(data.String, POINTER(USHORT*(sz//2)))
|
data = cast(data.String, POINTER(USHORT*(sz//2)))
|
||||||
return buf, list(filter(None, data.contents))
|
return buf, list(filter(None, data.contents))
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ def escape_xpath_attr(value):
|
|||||||
if x:
|
if x:
|
||||||
q = "'" if '"' in x else '"'
|
q = "'" if '"' in x else '"'
|
||||||
ans.append(q + x + q)
|
ans.append(q + x + q)
|
||||||
return 'concat(%s)' % ', '.join(ans)
|
return 'concat({})'.format(', '.join(ans))
|
||||||
else:
|
else:
|
||||||
return "'%s'" % value
|
return f"'{value}'"
|
||||||
return '"%s"' % value
|
return f'"{value}"'
|
||||||
|
@ -55,7 +55,7 @@ class CHMReader(CHMFile):
|
|||||||
t.write(open(input, 'rb').read())
|
t.write(open(input, 'rb').read())
|
||||||
input = t.name
|
input = t.name
|
||||||
if not self.LoadCHM(input):
|
if not self.LoadCHM(input):
|
||||||
raise CHMError("Unable to open CHM file '%s'"%(input,))
|
raise CHMError(f"Unable to open CHM file '{input}'")
|
||||||
self.log = log
|
self.log = log
|
||||||
self.input_encoding = input_encoding
|
self.input_encoding = input_encoding
|
||||||
self._sourcechm = input
|
self._sourcechm = input
|
||||||
@ -188,7 +188,7 @@ class CHMReader(CHMFile):
|
|||||||
try:
|
try:
|
||||||
data = self.GetFile(path)
|
data = self.GetFile(path)
|
||||||
except:
|
except:
|
||||||
self.log.exception('Failed to extract %s from CHM, ignoring'%path)
|
self.log.exception(f'Failed to extract {path} from CHM, ignoring')
|
||||||
continue
|
continue
|
||||||
if lpath.find(';') != -1:
|
if lpath.find(';') != -1:
|
||||||
# fix file names with ";<junk>" at the end, see _reformat()
|
# fix file names with ";<junk>" at the end, see _reformat()
|
||||||
@ -203,7 +203,7 @@ class CHMReader(CHMFile):
|
|||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
if iswindows and len(lpath) > 250:
|
if iswindows and len(lpath) > 250:
|
||||||
self.log.warn('%r filename too long, skipping'%path)
|
self.log.warn(f'{path!r} filename too long, skipping')
|
||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ def decompress(stream):
|
|||||||
txt = []
|
txt = []
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
if stream.read(9) != b'!!8-Bit!!':
|
if stream.read(9) != b'!!8-Bit!!':
|
||||||
raise ValueError('File %s contains an invalid TCR header.' % stream.name)
|
raise ValueError(f'File {stream.name} contains an invalid TCR header.')
|
||||||
|
|
||||||
# Codes that the file contents are broken down into.
|
# Codes that the file contents are broken down into.
|
||||||
entries = []
|
entries = []
|
||||||
|
@ -371,8 +371,7 @@ def read_sr_patterns(path, log=None):
|
|||||||
try:
|
try:
|
||||||
re.compile(line)
|
re.compile(line)
|
||||||
except:
|
except:
|
||||||
msg = 'Invalid regular expression: %r from file: %r'%(
|
msg = f'Invalid regular expression: {line!r} from file: {path!r}'
|
||||||
line, path)
|
|
||||||
if log is not None:
|
if log is not None:
|
||||||
log.error(msg)
|
log.error(msg)
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
@ -23,7 +23,7 @@ class CHMInput(InputFormatPlugin):
|
|||||||
from calibre.ebooks.chm.reader import CHMReader
|
from calibre.ebooks.chm.reader import CHMReader
|
||||||
log.debug('Opening CHM file')
|
log.debug('Opening CHM file')
|
||||||
rdr = CHMReader(chm_path, log, input_encoding=self.opts.input_encoding)
|
rdr = CHMReader(chm_path, log, input_encoding=self.opts.input_encoding)
|
||||||
log.debug('Extracting CHM to %s' % output_dir)
|
log.debug(f'Extracting CHM to {output_dir}')
|
||||||
rdr.extract_content(output_dir, debug_dump=debug_dump)
|
rdr.extract_content(output_dir, debug_dump=debug_dump)
|
||||||
self._chm_reader = rdr
|
self._chm_reader = rdr
|
||||||
return rdr.hhc_path
|
return rdr.hhc_path
|
||||||
@ -46,8 +46,8 @@ class CHMInput(InputFormatPlugin):
|
|||||||
|
|
||||||
# closing stream so CHM can be opened by external library
|
# closing stream so CHM can be opened by external library
|
||||||
stream.close()
|
stream.close()
|
||||||
log.debug('tdir=%s' % tdir)
|
log.debug(f'tdir={tdir}')
|
||||||
log.debug('stream.name=%s' % stream.name)
|
log.debug(f'stream.name={stream.name}')
|
||||||
debug_dump = False
|
debug_dump = False
|
||||||
odi = options.debug_pipeline
|
odi = options.debug_pipeline
|
||||||
if odi:
|
if odi:
|
||||||
|
@ -99,10 +99,9 @@ class ComicInput(InputFormatPlugin):
|
|||||||
comics = []
|
comics = []
|
||||||
with CurrentDir(tdir):
|
with CurrentDir(tdir):
|
||||||
if not os.path.exists('comics.txt'):
|
if not os.path.exists('comics.txt'):
|
||||||
raise ValueError((
|
raise ValueError(
|
||||||
'%s is not a valid comic collection'
|
f'{stream.name} is not a valid comic collection'
|
||||||
' no comics.txt was found in the file')
|
' no comics.txt was found in the file')
|
||||||
%stream.name)
|
|
||||||
with open('comics.txt', 'rb') as f:
|
with open('comics.txt', 'rb') as f:
|
||||||
raw = f.read()
|
raw = f.read()
|
||||||
if raw.startswith(codecs.BOM_UTF16_BE):
|
if raw.startswith(codecs.BOM_UTF16_BE):
|
||||||
@ -125,7 +124,7 @@ class ComicInput(InputFormatPlugin):
|
|||||||
if os.access(fname, os.R_OK):
|
if os.access(fname, os.R_OK):
|
||||||
comics.append([title, fname])
|
comics.append([title, fname])
|
||||||
if not comics:
|
if not comics:
|
||||||
raise ValueError('%s has no comics'%stream.name)
|
raise ValueError(f'{stream.name} has no comics')
|
||||||
return comics
|
return comics
|
||||||
|
|
||||||
def get_pages(self, comic, tdir2):
|
def get_pages(self, comic, tdir2):
|
||||||
@ -135,12 +134,11 @@ class ComicInput(InputFormatPlugin):
|
|||||||
verbose=self.opts.verbose)
|
verbose=self.opts.verbose)
|
||||||
thumbnail = None
|
thumbnail = None
|
||||||
if not new_pages:
|
if not new_pages:
|
||||||
raise ValueError('Could not find any pages in the comic: %s'
|
raise ValueError(f'Could not find any pages in the comic: {comic}')
|
||||||
%comic)
|
|
||||||
if self.opts.no_process:
|
if self.opts.no_process:
|
||||||
n2 = []
|
n2 = []
|
||||||
for i, page in enumerate(new_pages):
|
for i, page in enumerate(new_pages):
|
||||||
n2.append(os.path.join(tdir2, '{} - {}' .format(i, os.path.basename(page))))
|
n2.append(os.path.join(tdir2, f'{i} - {os.path.basename(page)}'))
|
||||||
shutil.copyfile(page, n2[-1])
|
shutil.copyfile(page, n2[-1])
|
||||||
new_pages = n2
|
new_pages = n2
|
||||||
else:
|
else:
|
||||||
@ -152,8 +150,7 @@ class ComicInput(InputFormatPlugin):
|
|||||||
for f in failures:
|
for f in failures:
|
||||||
self.log.warning('\t', f)
|
self.log.warning('\t', f)
|
||||||
if not new_pages:
|
if not new_pages:
|
||||||
raise ValueError('Could not find any valid pages in comic: %s'
|
raise ValueError(f'Could not find any valid pages in comic: {comic}')
|
||||||
% comic)
|
|
||||||
thumbnail = os.path.join(tdir2,
|
thumbnail = os.path.join(tdir2,
|
||||||
'thumbnail.'+self.opts.output_format.lower())
|
'thumbnail.'+self.opts.output_format.lower())
|
||||||
if not os.access(thumbnail, os.R_OK):
|
if not os.access(thumbnail, os.R_OK):
|
||||||
@ -193,7 +190,7 @@ class ComicInput(InputFormatPlugin):
|
|||||||
comics.append((title, pages, wrappers))
|
comics.append((title, pages, wrappers))
|
||||||
|
|
||||||
if not comics:
|
if not comics:
|
||||||
raise ValueError('No comic pages found in %s'%stream.name)
|
raise ValueError(f'No comic pages found in {stream.name}')
|
||||||
|
|
||||||
mi = MetaInformation(os.path.basename(stream.name).rpartition('.')[0],
|
mi = MetaInformation(os.path.basename(stream.name).rpartition('.')[0],
|
||||||
[_('Unknown')])
|
[_('Unknown')])
|
||||||
@ -299,8 +296,8 @@ class ComicInput(InputFormatPlugin):
|
|||||||
|
|
||||||
pages = '\n'.join(page(i, src) for i, src in enumerate(pages))
|
pages = '\n'.join(page(i, src) for i, src in enumerate(pages))
|
||||||
base = os.path.dirname(pages[0])
|
base = os.path.dirname(pages[0])
|
||||||
wrapper = '''
|
wrapper = f'''
|
||||||
<html xmlns="{}">
|
<html xmlns="{XHTML_NS}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
@ -317,10 +314,10 @@ class ComicInput(InputFormatPlugin):
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{}
|
{pages}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
'''.format(XHTML_NS, pages)
|
'''
|
||||||
path = os.path.join(base, cdir, 'wrapper.xhtml')
|
path = os.path.join(base, cdir, 'wrapper.xhtml')
|
||||||
with open(path, 'wb') as f:
|
with open(path, 'wb') as f:
|
||||||
f.write(wrapper.encode('utf-8'))
|
f.write(wrapper.encode('utf-8'))
|
||||||
|
@ -281,7 +281,7 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
path = getattr(stream, 'name', 'stream')
|
path = getattr(stream, 'name', 'stream')
|
||||||
|
|
||||||
if opf is None:
|
if opf is None:
|
||||||
raise ValueError('%s is not a valid EPUB file (could not find opf)'%path)
|
raise ValueError(f'{path} is not a valid EPUB file (could not find opf)')
|
||||||
|
|
||||||
opf = os.path.relpath(opf, os.getcwd())
|
opf = os.path.relpath(opf, os.getcwd())
|
||||||
parts = os.path.split(opf)
|
parts = os.path.split(opf)
|
||||||
@ -369,7 +369,7 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
root = parse(raw, log=log)
|
root = parse(raw, log=log)
|
||||||
ncx = safe_xml_fromstring('<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="eng"><navMap/></ncx>')
|
ncx = safe_xml_fromstring('<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="eng"><navMap/></ncx>')
|
||||||
navmap = ncx[0]
|
navmap = ncx[0]
|
||||||
et = '{%s}type' % EPUB_NS
|
et = f'{{{EPUB_NS}}}type'
|
||||||
bn = os.path.basename(nav_path)
|
bn = os.path.basename(nav_path)
|
||||||
|
|
||||||
def add_from_li(li, parent):
|
def add_from_li(li, parent):
|
||||||
|
@ -335,7 +335,7 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
|
|
||||||
key = re.sub(r'[^a-fA-F0-9]', '', uuid)
|
key = re.sub(r'[^a-fA-F0-9]', '', uuid)
|
||||||
if len(key) < 16:
|
if len(key) < 16:
|
||||||
raise ValueError('UUID identifier %r is invalid'%uuid)
|
raise ValueError(f'UUID identifier {uuid!r} is invalid')
|
||||||
key = bytearray(from_hex_bytes((key + key)[:32]))
|
key = bytearray(from_hex_bytes((key + key)[:32]))
|
||||||
paths = []
|
paths = []
|
||||||
with CurrentDir(tdir):
|
with CurrentDir(tdir):
|
||||||
@ -362,10 +362,10 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
<enc:EncryptedData>
|
<enc:EncryptedData>
|
||||||
<enc:EncryptionMethod Algorithm="http://ns.adobe.com/pdf/enc#RC"/>
|
<enc:EncryptionMethod Algorithm="http://ns.adobe.com/pdf/enc#RC"/>
|
||||||
<enc:CipherData>
|
<enc:CipherData>
|
||||||
<enc:CipherReference URI="%s"/>
|
<enc:CipherReference URI="{}"/>
|
||||||
</enc:CipherData>
|
</enc:CipherData>
|
||||||
</enc:EncryptedData>
|
</enc:EncryptedData>
|
||||||
'''%(uri.replace('"', '\\"')))
|
'''.format(uri.replace('"', '\\"')))
|
||||||
if fonts:
|
if fonts:
|
||||||
ans = '''<encryption
|
ans = '''<encryption
|
||||||
xmlns="urn:oasis:names:tc:opendocument:xmlns:container"
|
xmlns="urn:oasis:names:tc:opendocument:xmlns:container"
|
||||||
@ -409,7 +409,7 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
frag = urlunquote(frag)
|
frag = urlunquote(frag)
|
||||||
if frag and frag_pat.match(frag) is None:
|
if frag and frag_pat.match(frag) is None:
|
||||||
self.log.warn(
|
self.log.warn(
|
||||||
'Removing fragment identifier %r from TOC as Adobe Digital Editions cannot handle it'%frag)
|
f'Removing fragment identifier {frag!r} from TOC as Adobe Digital Editions cannot handle it')
|
||||||
node.href = base
|
node.href = base
|
||||||
|
|
||||||
for x in self.oeb.spine:
|
for x in self.oeb.spine:
|
||||||
@ -540,7 +540,7 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
from calibre.ebooks.oeb.polish.toc import item_at_top
|
from calibre.ebooks.oeb.polish.toc import item_at_top
|
||||||
|
|
||||||
def frag_is_at_top(root, frag):
|
def frag_is_at_top(root, frag):
|
||||||
elem = XPath('//*[@id="%s" or @name="%s"]'%(frag, frag))(root)
|
elem = XPath(f'//*[@id="{frag}" or @name="{frag}"]')(root)
|
||||||
if elem:
|
if elem:
|
||||||
elem = elem[0]
|
elem = elem[0]
|
||||||
else:
|
else:
|
||||||
|
@ -77,7 +77,7 @@ class FB2Input(InputFormatPlugin):
|
|||||||
parser = css_parser.CSSParser(fetcher=None,
|
parser = css_parser.CSSParser(fetcher=None,
|
||||||
log=logging.getLogger('calibre.css'))
|
log=logging.getLogger('calibre.css'))
|
||||||
|
|
||||||
XHTML_CSS_NAMESPACE = '@namespace "%s";\n' % XHTML_NS
|
XHTML_CSS_NAMESPACE = f'@namespace "{XHTML_NS}";\n'
|
||||||
text = XHTML_CSS_NAMESPACE + css
|
text = XHTML_CSS_NAMESPACE + css
|
||||||
log.debug('Parsing stylesheet...')
|
log.debug('Parsing stylesheet...')
|
||||||
stylesheet = parser.parseString(text)
|
stylesheet = parser.parseString(text)
|
||||||
@ -115,7 +115,7 @@ class FB2Input(InputFormatPlugin):
|
|||||||
if not note.get('id', None):
|
if not note.get('id', None):
|
||||||
note.set('id', 'cite%d' % c)
|
note.set('id', 'cite%d' % c)
|
||||||
all_ids.add(note.get('id'))
|
all_ids.add(note.get('id'))
|
||||||
a.set('href', '#%s' % note.get('id'))
|
a.set('href', '#{}'.format(note.get('id')))
|
||||||
for x in result.xpath('//*[@link_note or @link_cite]'):
|
for x in result.xpath('//*[@link_note or @link_cite]'):
|
||||||
x.attrib.pop('link_note', None)
|
x.attrib.pop('link_note', None)
|
||||||
x.attrib.pop('link_cite', None)
|
x.attrib.pop('link_cite', None)
|
||||||
@ -148,7 +148,7 @@ class FB2Input(InputFormatPlugin):
|
|||||||
cpath = os.path.abspath('fb2_cover_calibre_mi.jpg')
|
cpath = os.path.abspath('fb2_cover_calibre_mi.jpg')
|
||||||
else:
|
else:
|
||||||
for img in doc.xpath('//f:coverpage/f:image', namespaces=NAMESPACES):
|
for img in doc.xpath('//f:coverpage/f:image', namespaces=NAMESPACES):
|
||||||
href = img.get('{%s}href'%XLINK_NS, img.get('href', None))
|
href = img.get(f'{{{XLINK_NS}}}href', img.get('href', None))
|
||||||
if href is not None:
|
if href is not None:
|
||||||
if href.startswith('#'):
|
if href.startswith('#'):
|
||||||
href = href[1:]
|
href = href[1:]
|
||||||
@ -182,8 +182,7 @@ class FB2Input(InputFormatPlugin):
|
|||||||
try:
|
try:
|
||||||
data = base64_decode(raw)
|
data = base64_decode(raw)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.log.exception('Binary data with id=%s is corrupted, ignoring'%(
|
self.log.exception('Binary data with id={} is corrupted, ignoring'.format(elem.get('id')))
|
||||||
elem.get('id')))
|
|
||||||
else:
|
else:
|
||||||
with open(fname, 'wb') as f:
|
with open(fname, 'wb') as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
@ -254,7 +254,7 @@ class HTMLInput(InputFormatPlugin):
|
|||||||
try:
|
try:
|
||||||
link_ = link_.decode('utf-8', 'error')
|
link_ = link_.decode('utf-8', 'error')
|
||||||
except:
|
except:
|
||||||
self.log.warn('Failed to decode link %r. Ignoring'%link_)
|
self.log.warn(f'Failed to decode link {link_!r}. Ignoring')
|
||||||
return None, None
|
return None, None
|
||||||
if self.root_dir_for_absolute_links and link_.startswith('/'):
|
if self.root_dir_for_absolute_links and link_.startswith('/'):
|
||||||
link_ = link_.lstrip('/')
|
link_ = link_.lstrip('/')
|
||||||
@ -262,7 +262,7 @@ class HTMLInput(InputFormatPlugin):
|
|||||||
try:
|
try:
|
||||||
l = Link(link_, base if base else os.getcwd())
|
l = Link(link_, base if base else os.getcwd())
|
||||||
except:
|
except:
|
||||||
self.log.exception('Failed to process link: %r'%link_)
|
self.log.exception(f'Failed to process link: {link_!r}')
|
||||||
return None, None
|
return None, None
|
||||||
if l.path is None:
|
if l.path is None:
|
||||||
# Not a local resource
|
# Not a local resource
|
||||||
@ -311,7 +311,7 @@ class HTMLInput(InputFormatPlugin):
|
|||||||
bhref = os.path.basename(link)
|
bhref = os.path.basename(link)
|
||||||
id, href = self.oeb.manifest.generate(id='added', href=sanitize_file_name(bhref))
|
id, href = self.oeb.manifest.generate(id='added', href=sanitize_file_name(bhref))
|
||||||
if media_type == 'text/plain':
|
if media_type == 'text/plain':
|
||||||
self.log.warn('Ignoring link to text file %r'%link_)
|
self.log.warn(f'Ignoring link to text file {link_!r}')
|
||||||
return None
|
return None
|
||||||
if media_type == self.BINARY_MIME:
|
if media_type == self.BINARY_MIME:
|
||||||
# Check for the common case, images
|
# Check for the common case, images
|
||||||
|
@ -47,7 +47,7 @@ class LITInput(InputFormatPlugin):
|
|||||||
self.log('LIT file with all text in single <pre> tag detected')
|
self.log('LIT file with all text in single <pre> tag detected')
|
||||||
html = separate_paragraphs_single_line(pre.text)
|
html = separate_paragraphs_single_line(pre.text)
|
||||||
html = convert_basic(html).replace('<html>',
|
html = convert_basic(html).replace('<html>',
|
||||||
'<html xmlns="%s">'%XHTML_NS)
|
f'<html xmlns="{XHTML_NS}">')
|
||||||
html = xml_to_unicode(html, strip_encoding_pats=True,
|
html = xml_to_unicode(html, strip_encoding_pats=True,
|
||||||
resolve_entities=True)[0]
|
resolve_entities=True)[0]
|
||||||
if opts.smarten_punctuation:
|
if opts.smarten_punctuation:
|
||||||
|
@ -39,19 +39,18 @@ class LRFInput(InputFormatPlugin):
|
|||||||
char_button_map = {}
|
char_button_map = {}
|
||||||
for x in doc.xpath('//CharButton[@refobj]'):
|
for x in doc.xpath('//CharButton[@refobj]'):
|
||||||
ro = x.get('refobj')
|
ro = x.get('refobj')
|
||||||
jump_button = doc.xpath('//*[@objid="%s"]'%ro)
|
jump_button = doc.xpath(f'//*[@objid="{ro}"]')
|
||||||
if jump_button:
|
if jump_button:
|
||||||
jump_to = jump_button[0].xpath('descendant::JumpTo[@refpage and @refobj]')
|
jump_to = jump_button[0].xpath('descendant::JumpTo[@refpage and @refobj]')
|
||||||
if jump_to:
|
if jump_to:
|
||||||
char_button_map[ro] = '%s.xhtml#%s'%(jump_to[0].get('refpage'),
|
char_button_map[ro] = '{}.xhtml#{}'.format(jump_to[0].get('refpage'),
|
||||||
jump_to[0].get('refobj'))
|
jump_to[0].get('refobj'))
|
||||||
plot_map = {}
|
plot_map = {}
|
||||||
for x in doc.xpath('//Plot[@refobj]'):
|
for x in doc.xpath('//Plot[@refobj]'):
|
||||||
ro = x.get('refobj')
|
ro = x.get('refobj')
|
||||||
image = doc.xpath('//Image[@objid="%s" and @refstream]'%ro)
|
image = doc.xpath(f'//Image[@objid="{ro}" and @refstream]')
|
||||||
if image:
|
if image:
|
||||||
imgstr = doc.xpath('//ImageStream[@objid="%s" and @file]'%
|
imgstr = doc.xpath('//ImageStream[@objid="{}" and @file]'.format(image[0].get('refstream')))
|
||||||
image[0].get('refstream'))
|
|
||||||
if imgstr:
|
if imgstr:
|
||||||
plot_map[ro] = imgstr[0].get('file')
|
plot_map[ro] = imgstr[0].get('file')
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ class LRFOutput(OutputFormatPlugin):
|
|||||||
ps['textheight'] = height
|
ps['textheight'] = height
|
||||||
book = Book(title=opts.title, author=opts.author,
|
book = Book(title=opts.title, author=opts.author,
|
||||||
bookid=uuid4().hex,
|
bookid=uuid4().hex,
|
||||||
publisher='%s %s'%(__appname__, __version__),
|
publisher=f'{__appname__} {__version__}',
|
||||||
category=_('Comic'), pagestyledefault=ps,
|
category=_('Comic'), pagestyledefault=ps,
|
||||||
booksetting=BookSetting(screenwidth=width, screenheight=height))
|
booksetting=BookSetting(screenwidth=width, screenheight=height))
|
||||||
for page in pages:
|
for page in pages:
|
||||||
|
@ -37,7 +37,7 @@ class MOBIInput(InputFormatPlugin):
|
|||||||
mr.extract_content('.', parse_cache)
|
mr.extract_content('.', parse_cache)
|
||||||
|
|
||||||
if mr.kf8_type is not None:
|
if mr.kf8_type is not None:
|
||||||
log('Found KF8 MOBI of type %r'%mr.kf8_type)
|
log(f'Found KF8 MOBI of type {mr.kf8_type!r}')
|
||||||
if mr.kf8_type == 'joint':
|
if mr.kf8_type == 'joint':
|
||||||
self.mobi_is_joint = True
|
self.mobi_is_joint = True
|
||||||
from calibre.ebooks.mobi.reader.mobi8 import Mobi8Reader
|
from calibre.ebooks.mobi.reader.mobi8 import Mobi8Reader
|
||||||
|
@ -83,7 +83,7 @@ class OEBOutput(OutputFormatPlugin):
|
|||||||
|
|
||||||
def manifest_items_with_id(id_):
|
def manifest_items_with_id(id_):
|
||||||
return root.xpath('//*[local-name() = "manifest"]/*[local-name() = "item" '
|
return root.xpath('//*[local-name() = "manifest"]/*[local-name() = "item" '
|
||||||
' and @id="%s"]'%id_)
|
f' and @id="{id_}"]')
|
||||||
|
|
||||||
if len(cov) == 1:
|
if len(cov) == 1:
|
||||||
cov = cov[0]
|
cov = cov[0]
|
||||||
|
@ -24,8 +24,7 @@ class PDBInput(InputFormatPlugin):
|
|||||||
Reader = get_reader(header.ident)
|
Reader = get_reader(header.ident)
|
||||||
|
|
||||||
if Reader is None:
|
if Reader is None:
|
||||||
raise PDBError('No reader available for format within container.\n Identity is %s. Book type is %s' %
|
raise PDBError('No reader available for format within container.\n Identity is {}. Book type is {}'.format(header.ident, IDENTITY_TO_NAME.get(header.ident, _('Unknown'))))
|
||||||
(header.ident, IDENTITY_TO_NAME.get(header.ident, _('Unknown'))))
|
|
||||||
|
|
||||||
log.debug(f'Detected ebook format as: {IDENTITY_TO_NAME[header.ident]} with identity: {header.ident}')
|
log.debug(f'Detected ebook format as: {IDENTITY_TO_NAME[header.ident]} with identity: {header.ident}')
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class PDBOutput(OutputFormatPlugin):
|
|||||||
Writer = get_writer(opts.format)
|
Writer = get_writer(opts.format)
|
||||||
|
|
||||||
if Writer is None:
|
if Writer is None:
|
||||||
raise PDBError('No writer available for format %s.' % format)
|
raise PDBError(f'No writer available for format {format}.')
|
||||||
|
|
||||||
setattr(opts, 'max_line_length', 0)
|
setattr(opts, 'max_line_length', 0)
|
||||||
setattr(opts, 'force_max_line_length', False)
|
setattr(opts, 'force_max_line_length', False)
|
||||||
|
@ -47,7 +47,7 @@ class PMLInput(InputFormatPlugin):
|
|||||||
self.log.debug('Converting PML to HTML...')
|
self.log.debug('Converting PML to HTML...')
|
||||||
hizer = PML_HTMLizer()
|
hizer = PML_HTMLizer()
|
||||||
html = hizer.parse_pml(pml_stream.read().decode(ienc), html_path)
|
html = hizer.parse_pml(pml_stream.read().decode(ienc), html_path)
|
||||||
html = '<html><head><title></title></head><body>%s</body></html>'%html
|
html = f'<html><head><title></title></head><body>{html}</body></html>'
|
||||||
html_stream.write(html.encode('utf-8', 'replace'))
|
html_stream.write(html.encode('utf-8', 'replace'))
|
||||||
|
|
||||||
if pclose:
|
if pclose:
|
||||||
@ -106,7 +106,7 @@ class PMLInput(InputFormatPlugin):
|
|||||||
html_path = os.path.join(os.getcwd(), html_name)
|
html_path = os.path.join(os.getcwd(), html_name)
|
||||||
|
|
||||||
pages.append(html_name)
|
pages.append(html_name)
|
||||||
log.debug('Processing PML item %s...' % pml)
|
log.debug(f'Processing PML item {pml}...')
|
||||||
ttoc = self.process_pml(pml, html_path)
|
ttoc = self.process_pml(pml, html_path)
|
||||||
toc += ttoc
|
toc += ttoc
|
||||||
images = self.get_images(stream, tdir, True)
|
images = self.get_images(stream, tdir, True)
|
||||||
|
@ -111,8 +111,7 @@ class RecipeInput(InputFormatPlugin):
|
|||||||
self.recipe_source = raw
|
self.recipe_source = raw
|
||||||
if recipe.requires_version > numeric_version:
|
if recipe.requires_version > numeric_version:
|
||||||
log.warn(
|
log.warn(
|
||||||
'Downloaded recipe needs calibre version at least: %s' %
|
'Downloaded recipe needs calibre version at least: {}'.format('.'.join(recipe.requires_version)))
|
||||||
('.'.join(recipe.requires_version)))
|
|
||||||
builtin = True
|
builtin = True
|
||||||
except:
|
except:
|
||||||
log.exception('Failed to compile downloaded recipe. Falling '
|
log.exception('Failed to compile downloaded recipe. Falling '
|
||||||
@ -130,8 +129,7 @@ class RecipeInput(InputFormatPlugin):
|
|||||||
log('Using downloaded builtin recipe')
|
log('Using downloaded builtin recipe')
|
||||||
|
|
||||||
if recipe is None:
|
if recipe is None:
|
||||||
raise ValueError('%r is not a valid recipe file or builtin recipe' %
|
raise ValueError(f'{recipe_or_file!r} is not a valid recipe file or builtin recipe')
|
||||||
recipe_or_file)
|
|
||||||
|
|
||||||
disabled = getattr(recipe, 'recipe_disabled', None)
|
disabled = getattr(recipe, 'recipe_disabled', None)
|
||||||
if disabled is not None:
|
if disabled is not None:
|
||||||
|
@ -164,7 +164,7 @@ class RTFInput(InputFormatPlugin):
|
|||||||
try:
|
try:
|
||||||
return self.rasterize_wmf(name)
|
return self.rasterize_wmf(name)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.exception('Failed to convert WMF image %r'%name)
|
self.log.exception(f'Failed to convert WMF image {name!r}')
|
||||||
return self.replace_wmf(name)
|
return self.replace_wmf(name)
|
||||||
|
|
||||||
def replace_wmf(self, name):
|
def replace_wmf(self, name):
|
||||||
@ -217,7 +217,7 @@ class RTFInput(InputFormatPlugin):
|
|||||||
css += '\n' +'\n'.join(color_classes)
|
css += '\n' +'\n'.join(color_classes)
|
||||||
|
|
||||||
for cls, val in iteritems(border_styles):
|
for cls, val in iteritems(border_styles):
|
||||||
css += '\n\n.%s {\n%s\n}'%(cls, val)
|
css += f'\n\n.{cls} {{\n{val}\n}}'
|
||||||
|
|
||||||
with open('styles.css', 'ab') as f:
|
with open('styles.css', 'ab') as f:
|
||||||
f.write(css.encode('utf-8'))
|
f.write(css.encode('utf-8'))
|
||||||
@ -229,16 +229,16 @@ class RTFInput(InputFormatPlugin):
|
|||||||
style = ['border-style: hidden', 'border-width: 1px',
|
style = ['border-style: hidden', 'border-width: 1px',
|
||||||
'border-color: black']
|
'border-color: black']
|
||||||
for x in ('bottom', 'top', 'left', 'right'):
|
for x in ('bottom', 'top', 'left', 'right'):
|
||||||
bs = elem.get('border-cell-%s-style'%x, None)
|
bs = elem.get(f'border-cell-{x}-style', None)
|
||||||
if bs:
|
if bs:
|
||||||
cbs = border_style_map.get(bs, 'solid')
|
cbs = border_style_map.get(bs, 'solid')
|
||||||
style.append('border-%s-style: %s'%(x, cbs))
|
style.append(f'border-{x}-style: {cbs}')
|
||||||
bw = elem.get('border-cell-%s-line-width'%x, None)
|
bw = elem.get(f'border-cell-{x}-line-width', None)
|
||||||
if bw:
|
if bw:
|
||||||
style.append('border-%s-width: %spt'%(x, bw))
|
style.append(f'border-{x}-width: {bw}pt')
|
||||||
bc = elem.get('border-cell-%s-color'%x, None)
|
bc = elem.get(f'border-cell-{x}-color', None)
|
||||||
if bc:
|
if bc:
|
||||||
style.append('border-%s-color: %s'%(x, bc))
|
style.append(f'border-{x}-color: {bc}')
|
||||||
style = ';\n'.join(style)
|
style = ';\n'.join(style)
|
||||||
if style not in border_styles:
|
if style not in border_styles:
|
||||||
border_styles.append(style)
|
border_styles.append(style)
|
||||||
|
@ -98,9 +98,9 @@ class SNBInput(InputFormatPlugin):
|
|||||||
lines = []
|
lines = []
|
||||||
for line in snbc.find('.//body'):
|
for line in snbc.find('.//body'):
|
||||||
if line.tag == 'text':
|
if line.tag == 'text':
|
||||||
lines.append('<p>%s</p>' % html_encode(line.text))
|
lines.append(f'<p>{html_encode(line.text)}</p>')
|
||||||
elif line.tag == 'img':
|
elif line.tag == 'img':
|
||||||
lines.append('<p><img src="%s" /></p>' % html_encode(line.text))
|
lines.append(f'<p><img src="{html_encode(line.text)}" /></p>')
|
||||||
with open(os.path.join(tdir, fname), 'wb') as f:
|
with open(os.path.join(tdir, fname), 'wb') as f:
|
||||||
f.write((HTML_TEMPLATE % (chapterName, '\n'.join(lines))).encode('utf-8', 'replace'))
|
f.write((HTML_TEMPLATE % (chapterName, '\n'.join(lines))).encode('utf-8', 'replace'))
|
||||||
oeb.toc.add(ch.text, fname)
|
oeb.toc.add(ch.text, fname)
|
||||||
|
@ -141,7 +141,7 @@ class SNBOutput(OutputFormatPlugin):
|
|||||||
if tocitem.href.find('#') != -1:
|
if tocitem.href.find('#') != -1:
|
||||||
item = tocitem.href.split('#')
|
item = tocitem.href.split('#')
|
||||||
if len(item) != 2:
|
if len(item) != 2:
|
||||||
log.error('Error in TOC item: %s' % tocitem)
|
log.error(f'Error in TOC item: {tocitem}')
|
||||||
else:
|
else:
|
||||||
if item[0] in outputFiles:
|
if item[0] in outputFiles:
|
||||||
outputFiles[item[0]].append((item[1], tocitem.title))
|
outputFiles[item[0]].append((item[1], tocitem.title))
|
||||||
@ -176,16 +176,16 @@ class SNBOutput(OutputFormatPlugin):
|
|||||||
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_IMAGES
|
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_IMAGES
|
||||||
if m.hrefs[item.href].media_type in OEB_DOCS:
|
if m.hrefs[item.href].media_type in OEB_DOCS:
|
||||||
if item.href not in outputFiles:
|
if item.href not in outputFiles:
|
||||||
log.debug('File %s is unused in TOC. Continue in last chapter' % item.href)
|
log.debug(f'File {item.href} is unused in TOC. Continue in last chapter')
|
||||||
mergeLast = True
|
mergeLast = True
|
||||||
else:
|
else:
|
||||||
if oldTree is not None and mergeLast:
|
if oldTree is not None and mergeLast:
|
||||||
log.debug('Output the modified chapter again: %s' % lastName)
|
log.debug(f'Output the modified chapter again: {lastName}')
|
||||||
with open(os.path.join(snbcDir, lastName), 'wb') as f:
|
with open(os.path.join(snbcDir, lastName), 'wb') as f:
|
||||||
f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8'))
|
f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8'))
|
||||||
mergeLast = False
|
mergeLast = False
|
||||||
|
|
||||||
log.debug('Converting %s to snbc...' % item.href)
|
log.debug(f'Converting {item.href} to snbc...')
|
||||||
snbwriter = SNBMLizer(log)
|
snbwriter = SNBMLizer(log)
|
||||||
snbcTrees = None
|
snbcTrees = None
|
||||||
if not mergeLast:
|
if not mergeLast:
|
||||||
@ -199,11 +199,11 @@ class SNBOutput(OutputFormatPlugin):
|
|||||||
with open(os.path.join(snbcDir, lastName), 'wb') as f:
|
with open(os.path.join(snbcDir, lastName), 'wb') as f:
|
||||||
f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8'))
|
f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8'))
|
||||||
else:
|
else:
|
||||||
log.debug('Merge %s with last TOC item...' % item.href)
|
log.debug(f'Merge {item.href} with last TOC item...')
|
||||||
snbwriter.merge_content(oldTree, oeb_book, item, [('', _('Start'))], opts)
|
snbwriter.merge_content(oldTree, oeb_book, item, [('', _('Start'))], opts)
|
||||||
|
|
||||||
# Output the last one if needed
|
# Output the last one if needed
|
||||||
log.debug('Output the last modified chapter again: %s' % lastName)
|
log.debug(f'Output the last modified chapter again: {lastName}')
|
||||||
if oldTree is not None and mergeLast:
|
if oldTree is not None and mergeLast:
|
||||||
with open(os.path.join(snbcDir, lastName), 'wb') as f:
|
with open(os.path.join(snbcDir, lastName), 'wb') as f:
|
||||||
f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8'))
|
f.write(etree.tostring(oldTree, pretty_print=True, encoding='utf-8'))
|
||||||
@ -211,7 +211,7 @@ class SNBOutput(OutputFormatPlugin):
|
|||||||
|
|
||||||
for item in m:
|
for item in m:
|
||||||
if m.hrefs[item.href].media_type in OEB_IMAGES:
|
if m.hrefs[item.href].media_type in OEB_IMAGES:
|
||||||
log.debug('Converting image: %s ...' % item.href)
|
log.debug(f'Converting image: {item.href} ...')
|
||||||
content = m.hrefs[item.href].data
|
content = m.hrefs[item.href].data
|
||||||
# Convert & Resize image
|
# Convert & Resize image
|
||||||
self.HandleImage(content, os.path.join(snbiDir, ProcessFileName(item.href)))
|
self.HandleImage(content, os.path.join(snbiDir, ProcessFileName(item.href)))
|
||||||
|
@ -198,13 +198,13 @@ class TXTInput(InputFormatPlugin):
|
|||||||
if file_ext in {'md', 'textile', 'markdown'}:
|
if file_ext in {'md', 'textile', 'markdown'}:
|
||||||
options.formatting_type = {'md': 'markdown'}.get(file_ext, file_ext)
|
options.formatting_type = {'md': 'markdown'}.get(file_ext, file_ext)
|
||||||
log.info('File extension indicates particular formatting. '
|
log.info('File extension indicates particular formatting. '
|
||||||
'Forcing formatting type to: %s'%options.formatting_type)
|
f'Forcing formatting type to: {options.formatting_type}')
|
||||||
options.paragraph_type = 'off'
|
options.paragraph_type = 'off'
|
||||||
|
|
||||||
# Get the encoding of the document.
|
# Get the encoding of the document.
|
||||||
if options.input_encoding:
|
if options.input_encoding:
|
||||||
ienc = options.input_encoding
|
ienc = options.input_encoding
|
||||||
log.debug('Using user specified input encoding of %s' % ienc)
|
log.debug(f'Using user specified input encoding of {ienc}')
|
||||||
else:
|
else:
|
||||||
det_encoding = detect(txt[:4096])
|
det_encoding = detect(txt[:4096])
|
||||||
det_encoding, confidence = det_encoding['encoding'], det_encoding['confidence']
|
det_encoding, confidence = det_encoding['encoding'], det_encoding['confidence']
|
||||||
@ -218,7 +218,7 @@ class TXTInput(InputFormatPlugin):
|
|||||||
log.debug(f'Detected input encoding as {ienc} with a confidence of {confidence * 100}%')
|
log.debug(f'Detected input encoding as {ienc} with a confidence of {confidence * 100}%')
|
||||||
if not ienc:
|
if not ienc:
|
||||||
ienc = 'utf-8'
|
ienc = 'utf-8'
|
||||||
log.debug('No input encoding specified and could not auto detect using %s' % ienc)
|
log.debug(f'No input encoding specified and could not auto detect using {ienc}')
|
||||||
# Remove BOM from start of txt as its presence can confuse markdown
|
# Remove BOM from start of txt as its presence can confuse markdown
|
||||||
import codecs
|
import codecs
|
||||||
for bom in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE, codecs.BOM_UTF8, codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
|
for bom in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE, codecs.BOM_UTF8, codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
|
||||||
@ -240,12 +240,12 @@ class TXTInput(InputFormatPlugin):
|
|||||||
log.debug('Could not reliably determine paragraph type using block')
|
log.debug('Could not reliably determine paragraph type using block')
|
||||||
options.paragraph_type = 'block'
|
options.paragraph_type = 'block'
|
||||||
else:
|
else:
|
||||||
log.debug('Auto detected paragraph type as %s' % options.paragraph_type)
|
log.debug(f'Auto detected paragraph type as {options.paragraph_type}')
|
||||||
|
|
||||||
# Detect formatting
|
# Detect formatting
|
||||||
if options.formatting_type == 'auto':
|
if options.formatting_type == 'auto':
|
||||||
options.formatting_type = detect_formatting_type(txt)
|
options.formatting_type = detect_formatting_type(txt)
|
||||||
log.debug('Auto detected formatting as %s' % options.formatting_type)
|
log.debug(f'Auto detected formatting as {options.formatting_type}')
|
||||||
|
|
||||||
if options.formatting_type == 'heuristic':
|
if options.formatting_type == 'heuristic':
|
||||||
setattr(options, 'enable_heuristics', True)
|
setattr(options, 'enable_heuristics', True)
|
||||||
|
@ -945,7 +945,7 @@ OptionRecommendation(name='search_replace',
|
|||||||
|
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
self.log('Downloading cover from %r'%url)
|
self.log(f'Downloading cover from {url!r}')
|
||||||
br = browser()
|
br = browser()
|
||||||
raw = br.open_novisit(url).read()
|
raw = br.open_novisit(url).read()
|
||||||
buf = io.BytesIO(raw)
|
buf = io.BytesIO(raw)
|
||||||
@ -999,7 +999,7 @@ OptionRecommendation(name='search_replace',
|
|||||||
setattr(self.opts, attr, x)
|
setattr(self.opts, attr, x)
|
||||||
return
|
return
|
||||||
self.log.warn(
|
self.log.warn(
|
||||||
'Profile (%s) %r is no longer available, using default'%(which, sval))
|
f'Profile ({which}) {sval!r} is no longer available, using default')
|
||||||
for x in profiles():
|
for x in profiles():
|
||||||
if x.short_name == 'default':
|
if x.short_name == 'default':
|
||||||
setattr(self.opts, attr, x)
|
setattr(self.opts, attr, x)
|
||||||
@ -1017,7 +1017,7 @@ OptionRecommendation(name='search_replace',
|
|||||||
self.log('Conversion options changed from defaults:')
|
self.log('Conversion options changed from defaults:')
|
||||||
for rec in self.changed_options:
|
for rec in self.changed_options:
|
||||||
if rec.option.name not in ('username', 'password'):
|
if rec.option.name not in ('username', 'password'):
|
||||||
self.log(' ', '%s:' % rec.option.name, repr(rec.recommended_value))
|
self.log(' ', f'{rec.option.name}:', repr(rec.recommended_value))
|
||||||
if self.opts.verbose > 1:
|
if self.opts.verbose > 1:
|
||||||
self.log.debug('Resolved conversion options')
|
self.log.debug('Resolved conversion options')
|
||||||
try:
|
try:
|
||||||
@ -1204,7 +1204,7 @@ OptionRecommendation(name='search_replace',
|
|||||||
try:
|
try:
|
||||||
fkey = list(map(float, fkey.split(',')))
|
fkey = list(map(float, fkey.split(',')))
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.error('Invalid font size key: %r ignoring'%fkey)
|
self.log.error(f'Invalid font size key: {fkey!r} ignoring')
|
||||||
fkey = self.opts.dest.fkey
|
fkey = self.opts.dest.fkey
|
||||||
|
|
||||||
from calibre.ebooks.oeb.transforms.jacket import Jacket
|
from calibre.ebooks.oeb.transforms.jacket import Jacket
|
||||||
@ -1298,7 +1298,7 @@ OptionRecommendation(name='search_replace',
|
|||||||
self.dump_oeb(self.oeb, out_dir)
|
self.dump_oeb(self.oeb, out_dir)
|
||||||
self.log('Processed HTML written to:', out_dir)
|
self.log('Processed HTML written to:', out_dir)
|
||||||
|
|
||||||
self.log.info('Creating %s...'%self.output_plugin.name)
|
self.log.info(f'Creating {self.output_plugin.name}...')
|
||||||
our = CompositeProgressReporter(0.67, 1., self.ui_reporter)
|
our = CompositeProgressReporter(0.67, 1., self.ui_reporter)
|
||||||
self.output_plugin.report_progress = our
|
self.output_plugin.report_progress = our
|
||||||
our(0., _('Running %s plugin')%self.output_plugin.name)
|
our(0., _('Running %s plugin')%self.output_plugin.name)
|
||||||
|
@ -41,7 +41,7 @@ _ligpat = re.compile('|'.join(LIGATURES))
|
|||||||
def sanitize_head(match):
|
def sanitize_head(match):
|
||||||
x = match.group(1).strip()
|
x = match.group(1).strip()
|
||||||
x = _span_pat.sub('', x)
|
x = _span_pat.sub('', x)
|
||||||
return '<head>\n%s\n</head>' % x
|
return f'<head>\n{x}\n</head>'
|
||||||
|
|
||||||
|
|
||||||
def chap_head(match):
|
def chap_head(match):
|
||||||
@ -200,12 +200,12 @@ class Dehyphenator:
|
|||||||
"((ed)?ly|'?e?s||a?(t|s)?ion(s|al(ly)?)?|ings?|er|(i)?ous|"
|
"((ed)?ly|'?e?s||a?(t|s)?ion(s|al(ly)?)?|ings?|er|(i)?ous|"
|
||||||
"(i|a)ty|(it)?ies|ive|gence|istic(ally)?|(e|a)nce|m?ents?|ism|ated|"
|
"(i|a)ty|(it)?ies|ive|gence|istic(ally)?|(e|a)nce|m?ents?|ism|ated|"
|
||||||
"(e|u)ct(ed)?|ed|(i|ed)?ness|(e|a)ncy|ble|ier|al|ex|ian)$")
|
"(e|u)ct(ed)?|ed|(i|ed)?ness|(e|a)ncy|ble|ier|al|ex|ian)$")
|
||||||
self.suffixes = re.compile(r'^%s' % self.suffix_string, re.IGNORECASE)
|
self.suffixes = re.compile(rf'^{self.suffix_string}', re.IGNORECASE)
|
||||||
self.removesuffixes = re.compile(r'%s' % self.suffix_string, re.IGNORECASE)
|
self.removesuffixes = re.compile(rf'{self.suffix_string}', re.IGNORECASE)
|
||||||
# remove prefixes if the prefix was not already the point of hyphenation
|
# remove prefixes if the prefix was not already the point of hyphenation
|
||||||
self.prefix_string = '^(dis|re|un|in|ex)'
|
self.prefix_string = '^(dis|re|un|in|ex)'
|
||||||
self.prefixes = re.compile(r'%s$' % self.prefix_string, re.IGNORECASE)
|
self.prefixes = re.compile(rf'{self.prefix_string}$', re.IGNORECASE)
|
||||||
self.removeprefix = re.compile(r'%s' % self.prefix_string, re.IGNORECASE)
|
self.removeprefix = re.compile(rf'{self.prefix_string}', re.IGNORECASE)
|
||||||
|
|
||||||
def dehyphenate(self, match):
|
def dehyphenate(self, match):
|
||||||
firsthalf = match.group('firstpart')
|
firsthalf = match.group('firstpart')
|
||||||
@ -295,10 +295,10 @@ class CSSPreProcessor:
|
|||||||
# Remove some of the broken CSS Microsoft products
|
# Remove some of the broken CSS Microsoft products
|
||||||
# create
|
# create
|
||||||
MS_PAT = re.compile(r'''
|
MS_PAT = re.compile(r'''
|
||||||
(?P<start>^|;|\{)\s* # The end of the previous rule or block start
|
(?P<start>^|;|\{{)\s* # The end of the previous rule or block start
|
||||||
(%s).+? # The invalid selectors
|
({}).+? # The invalid selectors
|
||||||
(?P<end>$|;|\}) # The end of the declaration
|
(?P<end>$|;|\}}) # The end of the declaration
|
||||||
'''%'mso-|panose-|text-underline|tab-interval',
|
'''.format('mso-|panose-|text-underline|tab-interval'),
|
||||||
re.MULTILINE|re.IGNORECASE|re.VERBOSE)
|
re.MULTILINE|re.IGNORECASE|re.VERBOSE)
|
||||||
|
|
||||||
def ms_sub(self, match):
|
def ms_sub(self, match):
|
||||||
@ -433,13 +433,13 @@ def book_designer_rules():
|
|||||||
lambda match : '<span style="page-break-after:always"> </span>'),
|
lambda match : '<span style="page-break-after:always"> </span>'),
|
||||||
# Create header tags
|
# Create header tags
|
||||||
(re.compile(r'<h2[^><]*?id=BookTitle[^><]*?(align=)*(?(1)(\w+))*[^><]*?>[^><]*?</h2>', re.IGNORECASE),
|
(re.compile(r'<h2[^><]*?id=BookTitle[^><]*?(align=)*(?(1)(\w+))*[^><]*?>[^><]*?</h2>', re.IGNORECASE),
|
||||||
lambda match : '<h1 id="BookTitle" align="%s">%s</h1>'%(match.group(2) if match.group(2) else 'center', match.group(3))),
|
lambda match : '<h1 id="BookTitle" align="{}">{}</h1>'.format(match.group(2) if match.group(2) else 'center', match.group(3))),
|
||||||
(re.compile(r'<h2[^><]*?id=BookAuthor[^><]*?(align=)*(?(1)(\w+))*[^><]*?>[^><]*?</h2>', re.IGNORECASE),
|
(re.compile(r'<h2[^><]*?id=BookAuthor[^><]*?(align=)*(?(1)(\w+))*[^><]*?>[^><]*?</h2>', re.IGNORECASE),
|
||||||
lambda match : '<h2 id="BookAuthor" align="%s">%s</h2>'%(match.group(2) if match.group(2) else 'center', match.group(3))),
|
lambda match : '<h2 id="BookAuthor" align="{}">{}</h2>'.format(match.group(2) if match.group(2) else 'center', match.group(3))),
|
||||||
(re.compile(r'<span[^><]*?id=title[^><]*?>(.*?)</span>', re.IGNORECASE|re.DOTALL),
|
(re.compile(r'<span[^><]*?id=title[^><]*?>(.*?)</span>', re.IGNORECASE|re.DOTALL),
|
||||||
lambda match : '<h2 class="title">%s</h2>'%(match.group(1),)),
|
lambda match : f'<h2 class="title">{match.group(1)}</h2>'),
|
||||||
(re.compile(r'<span[^><]*?id=subtitle[^><]*?>(.*?)</span>', re.IGNORECASE|re.DOTALL),
|
(re.compile(r'<span[^><]*?id=subtitle[^><]*?>(.*?)</span>', re.IGNORECASE|re.DOTALL),
|
||||||
lambda match : '<h3 class="subtitle">%s</h3>'%(match.group(1),)),
|
lambda match : f'<h3 class="subtitle">{match.group(1)}</h3>'),
|
||||||
]
|
]
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
@ -494,8 +494,7 @@ class HTMLPreProcessor:
|
|||||||
rules.insert(0, (search_re, replace_txt))
|
rules.insert(0, (search_re, replace_txt))
|
||||||
user_sr_rules[(search_re, replace_txt)] = search_pattern
|
user_sr_rules[(search_re, replace_txt)] = search_pattern
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error('Failed to parse %r regexp because %s' %
|
self.log.error(f'Failed to parse {search!r} regexp because {as_unicode(e)}')
|
||||||
(search, as_unicode(e)))
|
|
||||||
|
|
||||||
# search / replace using the sr?_search / sr?_replace options
|
# search / replace using the sr?_search / sr?_replace options
|
||||||
for i in range(1, 4):
|
for i in range(1, 4):
|
||||||
@ -572,9 +571,8 @@ class HTMLPreProcessor:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if rule in user_sr_rules:
|
if rule in user_sr_rules:
|
||||||
self.log.error(
|
self.log.error(
|
||||||
'User supplied search & replace rule: %s -> %s '
|
f'User supplied search & replace rule: {user_sr_rules[rule]} -> {rule[1]} '
|
||||||
'failed with error: %s, ignoring.'%(
|
f'failed with error: {e}, ignoring.')
|
||||||
user_sr_rules[rule], rule[1], e))
|
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@ -595,10 +593,10 @@ class HTMLPreProcessor:
|
|||||||
# Handle broken XHTML w/ SVG (ugh)
|
# Handle broken XHTML w/ SVG (ugh)
|
||||||
if 'svg:' in html and SVG_NS not in html:
|
if 'svg:' in html and SVG_NS not in html:
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
'<html', '<html xmlns:svg="%s"' % SVG_NS, 1)
|
'<html', f'<html xmlns:svg="{SVG_NS}"', 1)
|
||||||
if 'xlink:' in html and XLINK_NS not in html:
|
if 'xlink:' in html and XLINK_NS not in html:
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
'<html', '<html xmlns:xlink="%s"' % XLINK_NS, 1)
|
'<html', f'<html xmlns:xlink="{XLINK_NS}"', 1)
|
||||||
|
|
||||||
html = XMLDECL_RE.sub('', html)
|
html = XMLDECL_RE.sub('', html)
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ class HeuristicProcessor:
|
|||||||
]
|
]
|
||||||
|
|
||||||
for word in ITALICIZE_WORDS:
|
for word in ITALICIZE_WORDS:
|
||||||
html = re.sub(r'(?<=\s|>)' + re.escape(word) + r'(?=\s|<)', '<i>%s</i>' % word, html)
|
html = re.sub(r'(?<=\s|>)' + re.escape(word) + r'(?=\s|<)', f'<i>{word}</i>', html)
|
||||||
|
|
||||||
search_text = re.sub(r'(?s)<head[^>]*>.*?</head>', '', html)
|
search_text = re.sub(r'(?s)<head[^>]*>.*?</head>', '', html)
|
||||||
search_text = re.sub(r'<[^>]*>', '', search_text)
|
search_text = re.sub(r'<[^>]*>', '', search_text)
|
||||||
@ -183,7 +183,7 @@ class HeuristicProcessor:
|
|||||||
ital_string = str(match.group('words'))
|
ital_string = str(match.group('words'))
|
||||||
# self.log.debug("italicising "+str(match.group(0))+" with <i>"+ital_string+"</i>")
|
# self.log.debug("italicising "+str(match.group(0))+" with <i>"+ital_string+"</i>")
|
||||||
try:
|
try:
|
||||||
html = re.sub(re.escape(str(match.group(0))), '<i>%s</i>' % ital_string, html)
|
html = re.sub(re.escape(str(match.group(0))), f'<i>{ital_string}</i>', html)
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
# match.group(0) was too large to be compiled into a regex
|
# match.group(0) was too large to be compiled into a regex
|
||||||
continue
|
continue
|
||||||
@ -305,7 +305,7 @@ class HeuristicProcessor:
|
|||||||
|
|
||||||
chapter_marker = arg_ignorecase+init_lookahead+full_chapter_line+blank_lines+lp_n_lookahead_open+n_lookahead+lp_n_lookahead_close+ \
|
chapter_marker = arg_ignorecase+init_lookahead+full_chapter_line+blank_lines+lp_n_lookahead_open+n_lookahead+lp_n_lookahead_close+ \
|
||||||
lp_opt_title_open+title_line_open+title_header_open+lp_title+title_header_close+title_line_close+lp_opt_title_close
|
lp_opt_title_open+title_line_open+title_header_open+lp_title+title_header_close+title_line_close+lp_opt_title_close
|
||||||
chapdetect = re.compile(r'%s' % chapter_marker)
|
chapdetect = re.compile(rf'{chapter_marker}')
|
||||||
|
|
||||||
if analyze:
|
if analyze:
|
||||||
hits = len(chapdetect.findall(html))
|
hits = len(chapdetect.findall(html))
|
||||||
@ -383,9 +383,9 @@ class HeuristicProcessor:
|
|||||||
em_en_unwrap_regex = em_en_lookahead+line_ending+blanklines+line_opening
|
em_en_unwrap_regex = em_en_lookahead+line_ending+blanklines+line_opening
|
||||||
shy_unwrap_regex = soft_hyphen+line_ending+blanklines+line_opening
|
shy_unwrap_regex = soft_hyphen+line_ending+blanklines+line_opening
|
||||||
|
|
||||||
unwrap = re.compile('%s' % unwrap_regex, re.UNICODE)
|
unwrap = re.compile(f'{unwrap_regex}', re.UNICODE)
|
||||||
em_en_unwrap = re.compile('%s' % em_en_unwrap_regex, re.UNICODE)
|
em_en_unwrap = re.compile(f'{em_en_unwrap_regex}', re.UNICODE)
|
||||||
shy_unwrap = re.compile('%s' % shy_unwrap_regex, re.UNICODE)
|
shy_unwrap = re.compile(f'{shy_unwrap_regex}', re.UNICODE)
|
||||||
|
|
||||||
if format == 'txt':
|
if format == 'txt':
|
||||||
content = unwrap.sub(' ', content)
|
content = unwrap.sub(' ', content)
|
||||||
@ -449,7 +449,7 @@ class HeuristicProcessor:
|
|||||||
for i in range(2):
|
for i in range(2):
|
||||||
html = re.sub(r'\s*<span[^>]*>\s*(<span[^>]*>\s*</span>){0,2}\s*</span>\s*', ' ', html)
|
html = re.sub(r'\s*<span[^>]*>\s*(<span[^>]*>\s*</span>){0,2}\s*</span>\s*', ' ', html)
|
||||||
html = re.sub(
|
html = re.sub(
|
||||||
r'\s*{open}\s*({open}\s*{close}\s*){{0,2}}\s*{close}'.format(open=open_fmt_pat, close=close_fmt_pat), ' ', html)
|
rf'\s*{open_fmt_pat}\s*({open_fmt_pat}\s*{close_fmt_pat}\s*){{0,2}}\s*{close_fmt_pat}', ' ', html)
|
||||||
# delete surrounding divs from empty paragraphs
|
# delete surrounding divs from empty paragraphs
|
||||||
html = re.sub(r'<div[^>]*>\s*<p[^>]*>\s*</p>\s*</div>', '<p> </p>', html)
|
html = re.sub(r'<div[^>]*>\s*<p[^>]*>\s*</p>\s*</div>', '<p> </p>', html)
|
||||||
# Empty heading tags
|
# Empty heading tags
|
||||||
@ -560,7 +560,7 @@ class HeuristicProcessor:
|
|||||||
line_two = '(?P<line_two>'+re.sub(r'(ou|in|cha)', 'linetwo_', self.line_open)+ \
|
line_two = '(?P<line_two>'+re.sub(r'(ou|in|cha)', 'linetwo_', self.line_open)+ \
|
||||||
r'\s*(?P<line_two_content>.*?)'+re.sub(r'(ou|in|cha)', 'linetwo_', self.line_close)+')'
|
r'\s*(?P<line_two_content>.*?)'+re.sub(r'(ou|in|cha)', 'linetwo_', self.line_close)+')'
|
||||||
div_break_candidate_pattern = line+r'\s*<div[^>]*>\s*</div>\s*'+line_two
|
div_break_candidate_pattern = line+r'\s*<div[^>]*>\s*</div>\s*'+line_two
|
||||||
div_break_candidate = re.compile(r'%s' % div_break_candidate_pattern, re.IGNORECASE|re.UNICODE)
|
div_break_candidate = re.compile(rf'{div_break_candidate_pattern}', re.IGNORECASE|re.UNICODE)
|
||||||
|
|
||||||
def convert_div_softbreaks(match):
|
def convert_div_softbreaks(match):
|
||||||
init_is_paragraph = self.check_paragraph(match.group('init_content'))
|
init_is_paragraph = self.check_paragraph(match.group('init_content'))
|
||||||
@ -583,7 +583,7 @@ class HeuristicProcessor:
|
|||||||
def detect_scene_breaks(self, html):
|
def detect_scene_breaks(self, html):
|
||||||
scene_break_regex = self.line_open+'(?!('+self.common_in_text_beginnings+'|.*?'+self.common_in_text_endings+ \
|
scene_break_regex = self.line_open+'(?!('+self.common_in_text_beginnings+'|.*?'+self.common_in_text_endings+ \
|
||||||
r'<))(?P<break>((?P<break_char>((?!\s)\W))\s*(?P=break_char)?){1,10})\s*'+self.line_close
|
r'<))(?P<break>((?P<break_char>((?!\s)\W))\s*(?P=break_char)?){1,10})\s*'+self.line_close
|
||||||
scene_breaks = re.compile(r'%s' % scene_break_regex, re.IGNORECASE|re.UNICODE)
|
scene_breaks = re.compile(rf'{scene_break_regex}', re.IGNORECASE|re.UNICODE)
|
||||||
html = scene_breaks.sub(self.scene_break_open+r'\g<break></p>', html)
|
html = scene_breaks.sub(self.scene_break_open+r'\g<break></p>', html)
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
@ -762,7 +762,7 @@ def test(scale=0.25):
|
|||||||
for r, color in enumerate(sorted(default_color_themes)):
|
for r, color in enumerate(sorted(default_color_themes)):
|
||||||
for c, style in enumerate(sorted(all_styles())):
|
for c, style in enumerate(sorted(all_styles())):
|
||||||
mi.series_index = c + 1
|
mi.series_index = c + 1
|
||||||
mi.title = 'An algorithmic cover [%s]' % color
|
mi.title = f'An algorithmic cover [{color}]'
|
||||||
prefs = override_prefs(cprefs, override_color_theme=color, override_style=style)
|
prefs = override_prefs(cprefs, override_color_theme=color, override_style=style)
|
||||||
scale_cover(prefs, scale)
|
scale_cover(prefs, scale)
|
||||||
img = generate_cover(mi, prefs=prefs, as_qimage=True)
|
img = generate_cover(mi, prefs=prefs, as_qimage=True)
|
||||||
|
@ -85,7 +85,7 @@ class BZZDecoderError(Exception):
|
|||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'BZZDecoderError: %s' % (self.msg)
|
return f'BZZDecoderError: {self.msg}'
|
||||||
|
|
||||||
|
|
||||||
# This table has been designed for the ZPCoder
|
# This table has been designed for the ZPCoder
|
||||||
|
@ -39,7 +39,7 @@ inherit = Inherit()
|
|||||||
|
|
||||||
|
|
||||||
def binary_property(parent, name, XPath, get):
|
def binary_property(parent, name, XPath, get):
|
||||||
vals = XPath('./w:%s' % name)(parent)
|
vals = XPath(f'./w:{name}')(parent)
|
||||||
if not vals:
|
if not vals:
|
||||||
return inherit
|
return inherit
|
||||||
val = get(vals[0], 'w:val', 'on')
|
val = get(vals[0], 'w:val', 'on')
|
||||||
@ -108,7 +108,7 @@ border_edges = ('left', 'top', 'right', 'bottom', 'between')
|
|||||||
|
|
||||||
def read_single_border(parent, edge, XPath, get):
|
def read_single_border(parent, edge, XPath, get):
|
||||||
color = style = width = padding = None
|
color = style = width = padding = None
|
||||||
for elem in XPath('./w:%s' % edge)(parent):
|
for elem in XPath(f'./w:{edge}')(parent):
|
||||||
c = get(elem, 'w:color')
|
c = get(elem, 'w:color')
|
||||||
if c is not None:
|
if c is not None:
|
||||||
color = simple_color(c)
|
color = simple_color(c)
|
||||||
@ -145,20 +145,20 @@ def read_border(parent, dest, XPath, get, border_edges=border_edges, name='pBdr'
|
|||||||
|
|
||||||
|
|
||||||
def border_to_css(edge, style, css):
|
def border_to_css(edge, style, css):
|
||||||
bs = getattr(style, 'border_%s_style' % edge)
|
bs = getattr(style, f'border_{edge}_style')
|
||||||
bc = getattr(style, 'border_%s_color' % edge)
|
bc = getattr(style, f'border_{edge}_color')
|
||||||
bw = getattr(style, 'border_%s_width' % edge)
|
bw = getattr(style, f'border_{edge}_width')
|
||||||
if isinstance(bw, numbers.Number):
|
if isinstance(bw, numbers.Number):
|
||||||
# WebKit needs at least 1pt to render borders and 3pt to render double borders
|
# WebKit needs at least 1pt to render borders and 3pt to render double borders
|
||||||
bw = max(bw, (3 if bs == 'double' else 1))
|
bw = max(bw, (3 if bs == 'double' else 1))
|
||||||
if bs is not inherit and bs is not None:
|
if bs is not inherit and bs is not None:
|
||||||
css['border-%s-style' % edge] = bs
|
css[f'border-{edge}-style'] = bs
|
||||||
if bc is not inherit and bc is not None:
|
if bc is not inherit and bc is not None:
|
||||||
css['border-%s-color' % edge] = bc
|
css[f'border-{edge}-color'] = bc
|
||||||
if bw is not inherit and bw is not None:
|
if bw is not inherit and bw is not None:
|
||||||
if isinstance(bw, numbers.Number):
|
if isinstance(bw, numbers.Number):
|
||||||
bw = '%.3gpt' % bw
|
bw = f'{bw:.3g}pt'
|
||||||
css['border-%s-width' % edge] = bw
|
css[f'border-{edge}-width'] = bw
|
||||||
|
|
||||||
|
|
||||||
def read_indent(parent, dest, XPath, get):
|
def read_indent(parent, dest, XPath, get):
|
||||||
@ -305,12 +305,12 @@ class Frame:
|
|||||||
else:
|
else:
|
||||||
if self.h_rule != 'auto':
|
if self.h_rule != 'auto':
|
||||||
t = 'min-height' if self.h_rule == 'atLeast' else 'height'
|
t = 'min-height' if self.h_rule == 'atLeast' else 'height'
|
||||||
ans[t] = '%.3gpt' % self.h
|
ans[t] = f'{self.h:.3g}pt'
|
||||||
if self.w is not None:
|
if self.w is not None:
|
||||||
ans['width'] = '%.3gpt' % self.w
|
ans['width'] = f'{self.w:.3g}pt'
|
||||||
ans['padding-top'] = ans['padding-bottom'] = '%.3gpt' % self.v_space
|
ans['padding-top'] = ans['padding-bottom'] = f'{self.v_space:.3g}pt'
|
||||||
if self.wrap not in {None, 'none'}:
|
if self.wrap not in {None, 'none'}:
|
||||||
ans['padding-left'] = ans['padding-right'] = '%.3gpt' % self.h_space
|
ans['padding-left'] = ans['padding-right'] = f'{self.h_space:.3g}pt'
|
||||||
if self.x_align is None:
|
if self.x_align is None:
|
||||||
fl = 'left' if self.x/page.width < 0.5 else 'right'
|
fl = 'left' if self.x/page.width < 0.5 else 'right'
|
||||||
else:
|
else:
|
||||||
@ -412,12 +412,12 @@ class ParagraphStyle:
|
|||||||
c['page-break-after'] = 'avoid'
|
c['page-break-after'] = 'avoid'
|
||||||
for edge in ('left', 'top', 'right', 'bottom'):
|
for edge in ('left', 'top', 'right', 'bottom'):
|
||||||
border_to_css(edge, self, c)
|
border_to_css(edge, self, c)
|
||||||
val = getattr(self, 'padding_%s' % edge)
|
val = getattr(self, f'padding_{edge}')
|
||||||
if val is not inherit:
|
if val is not inherit:
|
||||||
c['padding-%s' % edge] = '%.3gpt' % val
|
c[f'padding-{edge}'] = f'{val:.3g}pt'
|
||||||
val = getattr(self, 'margin_%s' % edge)
|
val = getattr(self, f'margin_{edge}')
|
||||||
if val is not inherit:
|
if val is not inherit:
|
||||||
c['margin-%s' % edge] = val
|
c[f'margin-{edge}'] = val
|
||||||
|
|
||||||
if self.line_height not in {inherit, '1'}:
|
if self.line_height not in {inherit, '1'}:
|
||||||
c['line-height'] = self.line_height
|
c['line-height'] = self.line_height
|
||||||
@ -426,7 +426,7 @@ class ParagraphStyle:
|
|||||||
val = getattr(self, x)
|
val = getattr(self, x)
|
||||||
if val is not inherit:
|
if val is not inherit:
|
||||||
if x == 'font_size':
|
if x == 'font_size':
|
||||||
val = '%.3gpt' % val
|
val = f'{val:.3g}pt'
|
||||||
c[x.replace('_', '-')] = val
|
c[x.replace('_', '-')] = val
|
||||||
ta = self.text_align
|
ta = self.text_align
|
||||||
if ta is not inherit:
|
if ta is not inherit:
|
||||||
@ -465,11 +465,11 @@ class ParagraphStyle:
|
|||||||
|
|
||||||
def apply_between_border(self):
|
def apply_between_border(self):
|
||||||
for prop in ('width', 'color', 'style'):
|
for prop in ('width', 'color', 'style'):
|
||||||
setattr(self, 'border_bottom_%s' % prop, getattr(self, 'border_between_%s' % prop))
|
setattr(self, f'border_bottom_{prop}', getattr(self, f'border_between_{prop}'))
|
||||||
|
|
||||||
def has_visible_border(self):
|
def has_visible_border(self):
|
||||||
for edge in border_edges[:-1]:
|
for edge in border_edges[:-1]:
|
||||||
bw, bs = getattr(self, 'border_%s_width' % edge), getattr(self, 'border_%s_style' % edge)
|
bw, bs = getattr(self, f'border_{edge}_width'), getattr(self, f'border_{edge}_style')
|
||||||
if bw is not inherit and bw and bs is not inherit and bs != 'none':
|
if bw is not inherit and bw and bs is not inherit and bs != 'none':
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -149,7 +149,7 @@ def read_font(parent, dest, XPath, get):
|
|||||||
for col in XPath('./w:rFonts')(parent):
|
for col in XPath('./w:rFonts')(parent):
|
||||||
val = get(col, 'w:asciiTheme')
|
val = get(col, 'w:asciiTheme')
|
||||||
if val:
|
if val:
|
||||||
val = '|%s|' % val
|
val = f'|{val}|'
|
||||||
else:
|
else:
|
||||||
val = get(col, 'w:ascii')
|
val = get(col, 'w:ascii')
|
||||||
if val:
|
if val:
|
||||||
@ -168,7 +168,7 @@ def read_font_cs(parent, dest, XPath, get):
|
|||||||
for col in XPath('./w:rFonts')(parent):
|
for col in XPath('./w:rFonts')(parent):
|
||||||
val = get(col, 'w:csTheme')
|
val = get(col, 'w:csTheme')
|
||||||
if val:
|
if val:
|
||||||
val = '|%s|' % val
|
val = f'|{val}|'
|
||||||
else:
|
else:
|
||||||
val = get(col, 'w:cs')
|
val = get(col, 'w:cs')
|
||||||
if val:
|
if val:
|
||||||
@ -248,9 +248,9 @@ class RunStyle:
|
|||||||
for x in ('color', 'style', 'width'):
|
for x in ('color', 'style', 'width'):
|
||||||
val = getattr(self, 'border_'+x)
|
val = getattr(self, 'border_'+x)
|
||||||
if x == 'width' and val is not inherit:
|
if x == 'width' and val is not inherit:
|
||||||
val = '%.3gpt' % val
|
val = f'{val:.3g}pt'
|
||||||
if val is not inherit:
|
if val is not inherit:
|
||||||
ans['border-%s' % x] = val
|
ans[f'border-{x}'] = val
|
||||||
|
|
||||||
def clear_border_css(self):
|
def clear_border_css(self):
|
||||||
for x in ('color', 'style', 'width'):
|
for x in ('color', 'style', 'width'):
|
||||||
@ -282,7 +282,7 @@ class RunStyle:
|
|||||||
|
|
||||||
self.get_border_css(c)
|
self.get_border_css(c)
|
||||||
if self.padding is not inherit:
|
if self.padding is not inherit:
|
||||||
c['padding'] = '%.3gpt' % self.padding
|
c['padding'] = f'{self.padding:.3g}pt'
|
||||||
|
|
||||||
for x in ('color', 'background_color'):
|
for x in ('color', 'background_color'):
|
||||||
val = getattr(self, x)
|
val = getattr(self, x)
|
||||||
@ -292,10 +292,10 @@ class RunStyle:
|
|||||||
for x in ('letter_spacing', 'font_size'):
|
for x in ('letter_spacing', 'font_size'):
|
||||||
val = getattr(self, x)
|
val = getattr(self, x)
|
||||||
if val is not inherit:
|
if val is not inherit:
|
||||||
c[x.replace('_', '-')] = '%.3gpt' % val
|
c[x.replace('_', '-')] = f'{val:.3g}pt'
|
||||||
|
|
||||||
if self.position is not inherit:
|
if self.position is not inherit:
|
||||||
c['vertical-align'] = '%.3gpt' % self.position
|
c['vertical-align'] = f'{self.position:.3g}pt'
|
||||||
|
|
||||||
if self.highlight is not inherit and self.highlight != 'transparent':
|
if self.highlight is not inherit and self.highlight != 'transparent':
|
||||||
c['background-color'] = self.highlight
|
c['background-color'] = self.highlight
|
||||||
|
@ -127,7 +127,7 @@ def cleanup_markup(log, root, styles, dest_dir, detect_cover, XPath, uuid):
|
|||||||
span[-1].tail = '\xa0'
|
span[-1].tail = '\xa0'
|
||||||
|
|
||||||
# Move <hr>s outside paragraphs, if possible.
|
# Move <hr>s outside paragraphs, if possible.
|
||||||
pancestor = XPath('|'.join('ancestor::%s[1]' % x for x in ('p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6')))
|
pancestor = XPath('|'.join(f'ancestor::{x}[1]' for x in ('p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6')))
|
||||||
for hr in root.xpath('//span/hr'):
|
for hr in root.xpath('//span/hr'):
|
||||||
p = pancestor(hr)
|
p = pancestor(hr)
|
||||||
if p:
|
if p:
|
||||||
@ -156,7 +156,7 @@ def cleanup_markup(log, root, styles, dest_dir, detect_cover, XPath, uuid):
|
|||||||
# Process dir attributes
|
# Process dir attributes
|
||||||
class_map = dict(itervalues(styles.classes))
|
class_map = dict(itervalues(styles.classes))
|
||||||
parents = ('p', 'div') + tuple('h%d' % i for i in range(1, 7))
|
parents = ('p', 'div') + tuple('h%d' % i for i in range(1, 7))
|
||||||
for parent in root.xpath('//*[(%s)]' % ' or '.join('name()="%s"' % t for t in parents)):
|
for parent in root.xpath('//*[({})]'.format(' or '.join(f'name()="{t}"' for t in parents))):
|
||||||
# Ensure that children of rtl parents that are not rtl have an
|
# Ensure that children of rtl parents that are not rtl have an
|
||||||
# explicit dir set. Also, remove dir from children if it is the same as
|
# explicit dir set. Also, remove dir from children if it is the same as
|
||||||
# that of the parent.
|
# that of the parent.
|
||||||
@ -172,7 +172,7 @@ def cleanup_markup(log, root, styles, dest_dir, detect_cover, XPath, uuid):
|
|||||||
|
|
||||||
# Remove unnecessary span tags that are the only child of a parent block
|
# Remove unnecessary span tags that are the only child of a parent block
|
||||||
# element
|
# element
|
||||||
for parent in root.xpath('//*[(%s) and count(span)=1]' % ' or '.join('name()="%s"' % t for t in parents)):
|
for parent in root.xpath('//*[({}) and count(span)=1]'.format(' or '.join(f'name()="{t}"' for t in parents))):
|
||||||
if len(parent) == 1 and not parent.text and not parent[0].tail and not parent[0].get('id', None):
|
if len(parent) == 1 and not parent.text and not parent[0].tail and not parent[0].get('id', None):
|
||||||
# We have a block whose contents are entirely enclosed in a <span>
|
# We have a block whose contents are entirely enclosed in a <span>
|
||||||
span = parent[0]
|
span = parent[0]
|
||||||
|
@ -137,7 +137,7 @@ class DOCX:
|
|||||||
try:
|
try:
|
||||||
raw = self.read('[Content_Types].xml')
|
raw = self.read('[Content_Types].xml')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InvalidDOCX('The file %s docx file has no [Content_Types].xml' % self.name)
|
raise InvalidDOCX(f'The file {self.name} docx file has no [Content_Types].xml')
|
||||||
root = fromstring(raw)
|
root = fromstring(raw)
|
||||||
self.content_types = {}
|
self.content_types = {}
|
||||||
self.default_content_types = {}
|
self.default_content_types = {}
|
||||||
@ -159,7 +159,7 @@ class DOCX:
|
|||||||
try:
|
try:
|
||||||
raw = self.read('_rels/.rels')
|
raw = self.read('_rels/.rels')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InvalidDOCX('The file %s docx file has no _rels/.rels' % self.name)
|
raise InvalidDOCX(f'The file {self.name} docx file has no _rels/.rels')
|
||||||
root = fromstring(raw)
|
root = fromstring(raw)
|
||||||
self.relationships = {}
|
self.relationships = {}
|
||||||
self.relationships_rmap = {}
|
self.relationships_rmap = {}
|
||||||
@ -177,7 +177,7 @@ class DOCX:
|
|||||||
if name is None:
|
if name is None:
|
||||||
names = tuple(n for n in self.names if n == 'document.xml' or n.endswith('/document.xml'))
|
names = tuple(n for n in self.names if n == 'document.xml' or n.endswith('/document.xml'))
|
||||||
if not names:
|
if not names:
|
||||||
raise InvalidDOCX('The file %s docx file has no main document' % self.name)
|
raise InvalidDOCX(f'The file {self.name} docx file has no main document')
|
||||||
name = names[0]
|
name = names[0]
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
@ -145,11 +145,11 @@ class Fields:
|
|||||||
field_types = ('hyperlink', 'xe', 'index', 'ref', 'noteref')
|
field_types = ('hyperlink', 'xe', 'index', 'ref', 'noteref')
|
||||||
parsers = {x.upper():getattr(self, 'parse_'+x) for x in field_types}
|
parsers = {x.upper():getattr(self, 'parse_'+x) for x in field_types}
|
||||||
parsers.update({x:getattr(self, 'parse_'+x) for x in field_types})
|
parsers.update({x:getattr(self, 'parse_'+x) for x in field_types})
|
||||||
field_parsers = {f.upper():globals()['parse_%s' % f] for f in field_types}
|
field_parsers = {f.upper():globals()[f'parse_{f}'] for f in field_types}
|
||||||
field_parsers.update({f:globals()['parse_%s' % f] for f in field_types})
|
field_parsers.update({f:globals()[f'parse_{f}'] for f in field_types})
|
||||||
|
|
||||||
for f in field_types:
|
for f in field_types:
|
||||||
setattr(self, '%s_fields' % f, [])
|
setattr(self, f'{f}_fields', [])
|
||||||
unknown_fields = {'TOC', 'toc', 'PAGEREF', 'pageref'} # The TOC and PAGEREF fields are handled separately
|
unknown_fields = {'TOC', 'toc', 'PAGEREF', 'pageref'} # The TOC and PAGEREF fields are handled separately
|
||||||
|
|
||||||
for field in self.fields:
|
for field in self.fields:
|
||||||
@ -159,7 +159,7 @@ class Fields:
|
|||||||
if func is not None:
|
if func is not None:
|
||||||
func(field, field_parsers[field.name], log)
|
func(field, field_parsers[field.name], log)
|
||||||
elif field.name not in unknown_fields:
|
elif field.name not in unknown_fields:
|
||||||
log.warn('Encountered unknown field: %s, ignoring it.' % field.name)
|
log.warn(f'Encountered unknown field: {field.name}, ignoring it.')
|
||||||
unknown_fields.add(field.name)
|
unknown_fields.add(field.name)
|
||||||
|
|
||||||
def get_runs(self, field):
|
def get_runs(self, field):
|
||||||
|
@ -64,7 +64,7 @@ class Family:
|
|||||||
|
|
||||||
self.embedded = {}
|
self.embedded = {}
|
||||||
for x in ('Regular', 'Bold', 'Italic', 'BoldItalic'):
|
for x in ('Regular', 'Bold', 'Italic', 'BoldItalic'):
|
||||||
for y in XPath('./w:embed%s[@r:id]' % x)(elem):
|
for y in XPath(f'./w:embed{x}[@r:id]')(elem):
|
||||||
rid = get(y, 'r:id')
|
rid = get(y, 'r:id')
|
||||||
key = get(y, 'w:fontKey')
|
key = get(y, 'w:fontKey')
|
||||||
subsetted = get(y, 'w:subsetted') in {'1', 'true', 'on'}
|
subsetted = get(y, 'w:subsetted') in {'1', 'true', 'on'}
|
||||||
@ -166,14 +166,14 @@ class Fonts:
|
|||||||
os.mkdir(dest_dir)
|
os.mkdir(dest_dir)
|
||||||
fname = self.write(name, dest_dir, docx, variant)
|
fname = self.write(name, dest_dir, docx, variant)
|
||||||
if fname is not None:
|
if fname is not None:
|
||||||
d = {'font-family':'"%s"' % name.replace('"', ''), 'src': 'url("fonts/%s")' % fname}
|
d = {'font-family':'"{}"'.format(name.replace('"', '')), 'src': f'url("fonts/{fname}")'}
|
||||||
if 'Bold' in variant:
|
if 'Bold' in variant:
|
||||||
d['font-weight'] = 'bold'
|
d['font-weight'] = 'bold'
|
||||||
if 'Italic' in variant:
|
if 'Italic' in variant:
|
||||||
d['font-style'] = 'italic'
|
d['font-style'] = 'italic'
|
||||||
d = [f'{k}: {v}' for k, v in iteritems(d)]
|
d = [f'{k}: {v}' for k, v in iteritems(d)]
|
||||||
d = ';\n\t'.join(d)
|
d = ';\n\t'.join(d)
|
||||||
defs.append('@font-face {\n\t%s\n}\n' % d)
|
defs.append(f'@font-face {{\n\t{d}\n}}\n')
|
||||||
return '\n'.join(defs)
|
return '\n'.join(defs)
|
||||||
|
|
||||||
def write(self, name, dest_dir, docx, variant):
|
def write(self, name, dest_dir, docx, variant):
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user