%PDF- %PDF-
| Direktori : /usr/lib/calibre/calibre/utils/open_with/ |
| Current File : //usr/lib/calibre/calibre/utils/open_with/osx.py |
#!/usr/bin/env python3
__license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import os, re, mimetypes, subprocess
from collections import defaultdict
from plistlib import loads
from calibre.ptempfile import TemporaryDirectory
from calibre.utils.icu import numeric_sort_key
from polyglot.builtins import iteritems, string_or_bytes
application_locations = ('/Applications', '~/Applications', '~/Desktop')
# Public UTI MAP {{{
def generate_public_uti_map():
from lxml import etree
from polyglot.urllib import urlopen
from html5_parser import parse
raw = urlopen(
'https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html').read()
root = parse(raw)
tables = root.xpath('//table')[0::2]
data = {}
for table in tables:
for tr in table.xpath('descendant::tr')[1:]:
td = tr.xpath('descendant::td')
identifier = etree.tostring(td[0], method='text', encoding='unicode').strip()
tags = etree.tostring(td[2], method='text', encoding='unicode').strip()
identifier = identifier.split()[0].replace('\u200b', '')
exts = [x.strip()[1:].lower() for x in tags.split(',') if x.strip().startswith('.')]
for ext in exts:
data[ext] = identifier
lines = ['PUBLIC_UTI_MAP = {']
for ext in sorted(data):
r = ("'" + ext + "':").ljust(16)
lines.append((' ' * 4) + r + "'" + data[ext] + "',")
lines.append('}')
with open(__file__, 'r+b') as f:
raw = f.read()
f.seek(0)
nraw = re.sub(r'^PUBLIC_UTI_MAP = .+?}', '\n'.join(lines), raw, flags=re.MULTILINE | re.DOTALL)
f.truncate(), f.write(nraw)
# Generated by generate_public_uti_map()
PUBLIC_UTI_MAP = {
'3g2': 'public.3gpp2',
'3gp': 'public.3gpp',
'3gp2': 'public.3gpp2',
'3gpp': 'public.3gpp',
'ai': 'com.adobe.illustrator.ai-image',
'aif': 'public.aiff-audio',
'aifc': 'public.aifc-audio',
'aiff': 'public.aiff-audio',
'app': 'com.apple.application-bundle',
'applescript': 'com.apple.applescript.text',
'asf': 'com.microsoft.advanced-systems-format',
'asx': 'com.microsoft.advanced-stream-redirector',
'au': 'public.ulaw-audio',
'avi': 'public.avi',
'bin': 'com.apple.macbinary-archive',
'bmp': 'com.microsoft.bmp',
'bundle': 'com.apple.bundle',
'c': 'public.c-source',
'c++': 'public.c-plus-plus-source',
'caf': 'com.apple.coreaudio-format',
'cc': 'public.c-plus-plus-source',
'class': 'com.sun.java-class',
'command': 'public.shell-script',
'cp': 'public.c-plus-plus-source',
'cpio': 'public.cpio-archive',
'cpp': 'public.c-plus-plus-source',
'csh': 'public.csh-script',
'cxx': 'public.c-plus-plus-source',
'defs': 'public.mig-source',
'dfont': 'com.apple.truetype-datafork-suitcase-font',
'dll': 'com.microsoft.windows-dynamic-link-library',
'doc': 'com.microsoft.word.doc',
'efx': 'com.js.efx-fax',
'eps': 'com.adobe.encapsulated-postscript',
'exe': 'com.microsoft.windows-executable',
'exp': 'com.apple.symbol-export',
'exr': 'com.ilm.openexr-image',
'fpx': 'com.kodak.flashpix.image',
'framework': 'com.apple.framework',
'gif': 'com.compuserve.gif',
'gtar': 'org.gnu.gnu-tar-archive',
'gz': 'org.gnu.gnu-zip-archive',
'gzip': 'org.gnu.gnu-zip-archive',
'h': 'public.c-header',
'h++': 'public.c-plus-plus-header',
'hpp': 'public.c-plus-plus-header',
'hqx': 'com.apple.binhex-archive',
'htm': 'public.html',
'html': 'public.html',
'hxx': 'public.c-plus-plus-header',
'icc': 'com.apple.colorsync-profile',
'icm': 'com.apple.colorsync-profile',
'icns': 'com.apple.icns',
'ico': 'com.microsoft.ico',
'jar': 'com.sun.java-archive',
'jav': 'com.sun.java-source',
'java': 'com.sun.java-source',
'javascript': 'com.netscape.javascript-source',
'jfx': 'com.j2.jfx-fax',
'jnlp': 'com.sun.java-web-start',
'jp2': 'public.jpeg-2000',
'jpeg': 'public.jpeg',
'jpg': 'public.jpeg',
'js': 'com.netscape.javascript-source',
'jscript': 'com.netscape.javascript-source',
'key': 'com.apple.keynote.key',
'kth': 'com.apple.keynote.kth',
'm': 'public.objective-c-source',
'm15': 'public.mpeg',
'm4a': 'public.mpeg-4-audio',
'm4b': 'com.apple.protected-mpeg-4-audio',
'm4p': 'com.apple.protected-mpeg-4-audio',
'm75': 'public.mpeg',
'mdimporter': 'com.apple.metadata-importer',
'mig': 'public.mig-source',
'mm': 'public.objective-c-plus-plus-source',
'mov': 'com.apple.quicktime-movie',
'mp3': 'public.mp3',
'mp4': 'public.mpeg-4',
'mpeg': 'public.mpeg',
'mpg': 'public.mpeg',
'o': 'public.object-code',
'otf': 'public.opentype-font',
'pct': 'com.apple.pict',
'pdf': 'com.adobe.pdf',
'pf': 'com.apple.colorsync-profile',
'pfa': 'com.adobe.postscript.pfa-font',
'pfb': 'com.adobe.postscript-pfb-font',
'ph3': 'public.php-script',
'ph4': 'public.php-script',
'php': 'public.php-script',
'php3': 'public.php-script',
'php4': 'public.php-script',
'phtml': 'public.php-script',
'pic': 'com.apple.pict',
'pict': 'com.apple.pict',
'pl': 'public.perl-script',
'plugin': 'com.apple.plugin',
'pm': 'public.perl-script',
'png': 'public.png',
'pntg': 'com.apple.macpaint-image',
'ppt': 'com.microsoft.powerpoint.ppt',
'ps': 'com.adobe.postscript',
'psd': 'com.adobe.photoshop-image',
'py': 'public.python-script',
'qif': 'com.apple.quicktime-image',
'qt': 'com.apple.quicktime-movie',
'qtif': 'com.apple.quicktime-image',
'qtz': 'com.apple.quartz-composer-composition',
'r': 'com.apple.rez-source',
'ra': 'com.real.realaudio',
'ram': 'com.real.realaudio',
'rb': 'public.ruby-script',
'rbw': 'public.ruby-script',
'rm': 'com.real.realmedia',
'rtf': 'public.rtf',
'rtfd': 'com.apple.rtfd',
's': 'public.assembly-source',
'scpt': 'com.apple.applescript.script',
'sd2': 'com.digidesign.sd2-audio',
'sgi': 'com.sgi.sgi-image',
'sh': 'public.shell-script',
'sit': 'com.allume.stuffit-archive',
'sitx': 'com.allume.stuffit-archive',
'smil': 'com.real.smil',
'snd': 'public.ulaw-audio',
'suit': 'com.apple.font-suitcase',
'tar': 'public.tar-archive',
'tga': 'com.truevision.tga-image',
'tgz': 'org.gnu.gnu-zip-tar-archive',
'tif': 'public.tiff',
'tiff': 'public.tiff',
'ttc': 'public.truetype-collection-font',
'ttf': 'public.truetype-ttf-font',
'txt': 'public.plain-text',
'ulw': 'public.ulaw-audio',
'vcard': 'public.vcard',
'vcf': 'public.vcard',
'vfw': 'public.avi',
'wav': 'com.microsoft.waveform-audio',
'wave': 'com.microsoft.waveform-audio',
'wax': 'com.microsoft.windows-media-wax',
'wdgt': 'com.apple.dashboard-widget',
'wm': 'com.microsoft.windows-media-wm',
'wma': 'com.microsoft.windows-media-wma',
'wmp': 'com.microsoft.windows-media-wmp',
'wmv': 'com.microsoft.windows-media-wmv',
'wmx': 'com.microsoft.windows-media-wmx',
'wvx': 'com.microsoft.windows-media-wvx',
'xbm': 'public.xbitmap-image',
'xls': 'com.microsoft.excel.xls',
'xml': 'public.xml',
'zip': 'com.pkware.zip-archive',
}
PUBLIC_UTI_RMAP = defaultdict(set)
for ext, uti in iteritems(PUBLIC_UTI_MAP):
PUBLIC_UTI_RMAP[uti].add(ext)
PUBLIC_UTI_RMAP = dict(PUBLIC_UTI_RMAP)
# }}}
def find_applications_in(base):
try:
entries = os.listdir(base)
except OSError:
return
for name in entries:
path = os.path.join(base, name)
if os.path.isdir(path):
if name.lower().endswith('.app'):
yield path
else:
yield from find_applications_in(path)
def find_applications():
for base in application_locations:
base = os.path.expanduser(base)
yield from find_applications_in(base)
def get_extensions_from_utis(utis, plist):
declared_utis = defaultdict(set)
for key in ('UTExportedTypeDeclarations', 'UTImportedTypeDeclarations'):
for decl in plist.get(key, ()):
if isinstance(decl, dict):
uti = decl.get('UTTypeIdentifier')
if isinstance(uti, string_or_bytes):
spec = decl.get('UTTypeTagSpecification')
if isinstance(spec, dict):
ext = spec.get('public.filename-extension')
if ext:
declared_utis[uti] |= set(ext)
types = spec.get('public.mime-type')
if types:
for mt in types:
for ext in mimetypes.guess_all_extensions(mt, strict=False):
declared_utis[uti].add(ext.lower()[1:])
ans = set()
for uti in utis:
ans |= declared_utis[uti]
ans |= PUBLIC_UTI_RMAP.get(uti, set())
return ans
def get_bundle_data(path):
path = os.path.abspath(path)
info = os.path.join(path, 'Contents', 'Info.plist')
ans = {
'name': os.path.splitext(os.path.basename(path))[0],
'path': path,
}
try:
with open(info, 'rb') as f:
plist = loads(f.read())
except Exception:
import traceback
traceback.print_exc()
return None
ans['name'] = plist.get('CFBundleDisplayName') or plist.get('CFBundleName') or ans['name']
icfile = plist.get('CFBundleIconFile')
if icfile:
icfile = os.path.join(path, 'Contents', 'Resources', icfile)
if not os.path.exists(icfile):
icfile += '.icns'
if os.path.exists(icfile):
ans['icon_file'] = icfile
bid = plist.get('CFBundleIdentifier')
if bid:
ans['identifier'] = bid
ans['extensions'] = extensions = set()
for dtype in plist.get('CFBundleDocumentTypes', ()):
utis = frozenset(dtype.get('LSItemContentTypes', ()))
if utis:
extensions |= get_extensions_from_utis(utis, plist)
else:
for ext in dtype.get('CFBundleTypeExtensions', ()):
if isinstance(ext, string_or_bytes):
extensions.add(ext.lower())
for mt in dtype.get('CFBundleTypeMIMETypes', ()):
if isinstance(mt, string_or_bytes):
for ext in mimetypes.guess_all_extensions(mt, strict=False):
extensions.add(ext.lower())
return ans
def find_programs(extensions):
extensions = frozenset(extensions)
ans = []
for app in find_applications():
try:
app = get_bundle_data(app)
except Exception:
import traceback
traceback.print_exc()
continue
if app and app['extensions'].intersection(extensions):
ans.append(app)
return ans
def get_icon(path, pixmap_to_data=None, as_data=False, size=64):
if not path:
return
with TemporaryDirectory() as tdir:
iconset = os.path.join(tdir, 'output.iconset')
try:
subprocess.check_call(['iconutil', '-c', 'iconset', '-o', 'output.iconset', path], cwd=tdir)
except subprocess.CalledProcessError:
return
try:
names = os.listdir(iconset)
except OSError:
return
if not names:
return
from qt.core import QImage, Qt
names.sort(key=numeric_sort_key)
for name in names:
m = re.search(r'(\d+)x\d+', name)
if m is not None and int(m.group(1)) >= size:
ans = QImage(os.path.join(iconset, name))
if not ans.isNull():
break
else:
return
ans = ans.scaled(size, size, transformMode=Qt.TransformationMode.SmoothTransformation)
if as_data:
ans = pixmap_to_data(ans)
return ans
def entry_to_cmdline(entry, path):
app = entry['path']
if os.path.isdir(app):
return ['open', '-a', app, path]
if 'identifier' in entry:
return ['open', '-b', entry['identifier'], path]
return [app, path]