mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Finish up find_programs implementation for linux
This commit is contained in:
parent
667f42ba1a
commit
0ef6a7fbc3
@ -6,23 +6,159 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os
|
||||
import os, re, shlex, cPickle
|
||||
from collections import defaultdict
|
||||
|
||||
from calibre import force_unicode, walk
|
||||
from calibre.constants import iswindows, isosx, filesystem_encoding
|
||||
from calibre import force_unicode, walk, guess_type, prints
|
||||
from calibre.constants import iswindows, isosx, filesystem_encoding, cache_dir
|
||||
from calibre.utils.localization import canonicalize_lang, get_lang
|
||||
|
||||
if iswindows:
|
||||
pass
|
||||
elif isosx:
|
||||
pass
|
||||
else:
|
||||
def parse_localized_key(key):
|
||||
name, rest = key.partition('[')[0::2]
|
||||
if not rest:
|
||||
return name, None
|
||||
rest = rest[:-1]
|
||||
lang = re.split(r'[_.@]', rest)[0]
|
||||
return name, canonicalize_lang(lang)
|
||||
|
||||
def unquote_exec(val):
|
||||
val = val.replace(r'\\', '\\')
|
||||
return shlex.split(val)
|
||||
|
||||
def parse_desktop_file(path):
|
||||
gpat = re.compile(r'^\[(.+?)\]\s*$')
|
||||
kpat = re.compile(r'^([-a-zA-Z0-9\[\]@_.]+)\s*=\s*(.+)$')
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
raw = f.read()
|
||||
raw
|
||||
except EnvironmentError:
|
||||
raw = f.read().decode('utf-8')
|
||||
except (EnvironmentError, UnicodeDecodeError):
|
||||
return
|
||||
group = None
|
||||
ans = {}
|
||||
for line in raw.splitlines():
|
||||
m = gpat.match(line)
|
||||
if m is not None:
|
||||
if group == 'Desktop Entry':
|
||||
break
|
||||
group = m.group(1)
|
||||
continue
|
||||
if group == 'Desktop Entry':
|
||||
m = kpat.match(line)
|
||||
if m is not None:
|
||||
k, v = m.group(1), m.group(2)
|
||||
if k == 'Hidden' and v == 'true':
|
||||
return
|
||||
if k == 'Type' and v != 'Application':
|
||||
return
|
||||
if k == 'Exec':
|
||||
cmdline = unquote_exec(v)
|
||||
if cmdline and (not os.path.isabs(cmdline[0]) or os.access(cmdline[0], os.X_OK)):
|
||||
ans[k] = cmdline
|
||||
elif k == 'MimeType':
|
||||
ans[k] = frozenset(x.strip() for x in v.split(';'))
|
||||
elif k in {'Name', 'GenericName', 'Comment', 'Icon'} or '[' in k:
|
||||
name, lang = parse_localized_key(k)
|
||||
if name not in ans:
|
||||
ans[name] = {}
|
||||
ans[name][lang] = v
|
||||
else:
|
||||
ans[k] = v
|
||||
if 'Exec' in ans and 'MimeType' in ans:
|
||||
return ans
|
||||
|
||||
icon_data = None
|
||||
|
||||
def find_icons():
|
||||
global icon_data
|
||||
if icon_data is not None:
|
||||
return icon_data
|
||||
base_dirs = [os.path.expanduser('~/.icons')] + [
|
||||
os.path.join(b, 'icons') for b in os.environ.get(
|
||||
'XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(os.pathsep)] + [
|
||||
'/usr/share/pixmaps']
|
||||
ans = defaultdict(list)
|
||||
sz_pat = re.compile(r'/((?:\d+x\d+)|scalable)/')
|
||||
cache_file = os.path.join(cache_dir(), 'icon-theme-cache.pickle')
|
||||
exts = {'.svg', '.png', '.xpm'}
|
||||
|
||||
def read_icon_theme_dir(dirpath):
|
||||
ans = defaultdict(list)
|
||||
for path in walk(dirpath):
|
||||
bn = os.path.basename(path)
|
||||
name, ext = os.path.splitext(bn)
|
||||
if ext in exts:
|
||||
sz = sz_pat.findall(path)
|
||||
if sz:
|
||||
sz = sz[-1]
|
||||
if sz == 'scalable':
|
||||
sz = 100000
|
||||
else:
|
||||
sz = int(sz.partition('x')[0])
|
||||
idx = len(ans[name])
|
||||
ans[name].append((-sz, idx, sz, path))
|
||||
for icons in ans.itervalues():
|
||||
icons.sort()
|
||||
return {k:(-v[0][2], v[0][3]) for k, v in ans.iteritems()}
|
||||
|
||||
try:
|
||||
with open(cache_file, 'rb') as f:
|
||||
cache = cPickle.load(f)
|
||||
mtimes, cache = cache['mtimes'], cache['data']
|
||||
except Exception:
|
||||
mtimes, cache = defaultdict(int), defaultdict(dict)
|
||||
|
||||
seen_dirs = set()
|
||||
changed = False
|
||||
|
||||
for loc in base_dirs:
|
||||
try:
|
||||
subdirs = os.listdir(loc)
|
||||
except EnvironmentError:
|
||||
continue
|
||||
for dname in subdirs:
|
||||
d = os.path.join(loc, dname)
|
||||
if os.path.isdir(d):
|
||||
try:
|
||||
mtime = os.stat(d).st_mtime
|
||||
except EnvironmentError:
|
||||
continue
|
||||
seen_dirs.add(d)
|
||||
if mtime != mtimes[d]:
|
||||
changed = True
|
||||
try:
|
||||
cache[d] = read_icon_theme_dir(d)
|
||||
except Exception:
|
||||
prints('Failed to read icon theme dir: %r with error:' % d)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
mtimes[d] = mtime
|
||||
for name, data in cache[d].iteritems():
|
||||
ans[name].append(data)
|
||||
for removed in set(mtimes) - seen_dirs:
|
||||
mtimes.pop(removed), cache.pop(removed)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
try:
|
||||
with open(cache_file, 'wb') as f:
|
||||
cPickle.dump({'data':cache, 'mtimes':mtimes}, f, -1)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
for icons in ans.itervalues():
|
||||
icons.sort()
|
||||
icon_data = {k:v[0][1] for k, v in ans.iteritems()}
|
||||
return icon_data
|
||||
|
||||
def localize_string(data):
|
||||
lang = canonicalize_lang(get_lang())
|
||||
return data.get(lang, data.get(None)) or ''
|
||||
|
||||
def find_programs(extensions):
|
||||
extensions = {ext.lower() for ext in extensions}
|
||||
@ -31,13 +167,31 @@ else:
|
||||
data_dirs = [force_unicode(x, filesystem_encoding).rstrip(os.sep) for x in data_dirs]
|
||||
data_dirs = [x for x in data_dirs if x and os.path.isdir(x)]
|
||||
desktop_files = {}
|
||||
mime_types = {guess_type('file.' + ext)[0] for ext in extensions}
|
||||
for base in data_dirs:
|
||||
for f in walk(os.path.join(base, 'applications')):
|
||||
if f.endswith('.desktop'):
|
||||
bn = os.path.basename(f)
|
||||
if f not in desktop_files:
|
||||
desktop_files[bn] = f
|
||||
for bn, path in desktop_files.iteritems():
|
||||
try:
|
||||
data = parse_desktop_file(path)
|
||||
except Exception:
|
||||
continue
|
||||
if data is not None and mime_types.intersection(data['MimeType']):
|
||||
icon = data.get('Icon', {}).get(None)
|
||||
if icon and not os.path.isabs(icon):
|
||||
icon = find_icons().get(icon)
|
||||
if icon:
|
||||
data['Icon'] = icon
|
||||
else:
|
||||
data.pop('Icon')
|
||||
for k in ('Name', 'GenericName', 'Comment'):
|
||||
val = data.get(k)
|
||||
if val:
|
||||
data[k] = localize_string(val)
|
||||
yield data
|
||||
|
||||
if __name__ == '__main__':
|
||||
print (find_programs('jpg jpeg'.split()))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user