%PDF- %PDF-
Direktori : /lib/calibre/calibre/gui2/ |
Current File : //lib/calibre/calibre/gui2/dnd.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import os import posixpath import re from qt.core import ( QDialog, QDialogButtonBox, QImageReader, QLabel, QPixmap, QProgressBar, Qt, QTimer, QUrl, QVBoxLayout ) from threading import Thread from contextlib import suppress from calibre import as_unicode, browser, prints from calibre.constants import DEBUG, iswindows from calibre.gui2 import error_dialog from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.filenames import make_long_path_useable from calibre.utils.imghdr import what from polyglot.queue import Empty, Queue from polyglot.urllib import unquote, urlparse def image_extensions(): if not hasattr(image_extensions, 'ans'): image_extensions.ans = [x.data().decode('utf-8') for x in QImageReader.supportedImageFormats()] return image_extensions.ans # This is present for compatibility with old plugins, do not use IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp'] class Worker(Thread): # {{{ def __init__(self, url, fpath, rq): Thread.__init__(self) self.url, self.fpath = url, fpath self.daemon = True self.rq = rq self.err = self.tb = None def run(self): try: br = browser() br.retrieve(self.url, self.fpath, self.callback) except Exception as e: self.err = as_unicode(e) import traceback self.tb = traceback.format_exc() def callback(self, a, b, c): self.rq.put((a, b, c)) # }}} class DownloadDialog(QDialog): # {{{ def __init__(self, url, fname, parent): QDialog.__init__(self, parent) self.setWindowTitle(_('Download %s')%fname) self.l = QVBoxLayout(self) self.purl = urlparse(url) self.msg = QLabel(_('Downloading <b>%(fname)s</b> from %(url)s')%dict( fname=fname, url=self.purl.netloc)) self.msg.setWordWrap(True) self.l.addWidget(self.msg) self.pb = QProgressBar(self) self.pb.setMinimum(0) self.pb.setMaximum(0) self.l.addWidget(self.pb) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Cancel, Qt.Orientation.Horizontal, self) self.l.addWidget(self.bb) self.bb.rejected.connect(self.reject) sz = self.sizeHint() self.resize(max(sz.width(), 400), sz.height()) fpath = PersistentTemporaryFile(os.path.splitext(fname)[1]) fpath.close() self.fpath = fpath.name self.worker = Worker(url, self.fpath, Queue()) self.rejected = False def reject(self): self.rejected = True QDialog.reject(self) def start_download(self): self.worker.start() QTimer.singleShot(50, self.update) self.exec() if self.worker.err is not None: error_dialog(self.parent(), _('Download failed'), _('Failed to download from %(url)r with error: %(err)s')%dict( url=self.worker.url, err=self.worker.err), det_msg=self.worker.tb, show=True) def update(self): if self.rejected: return try: progress = self.worker.rq.get_nowait() except Empty: pass else: self.update_pb(progress) if not self.worker.is_alive(): return self.accept() QTimer.singleShot(50, self.update) def update_pb(self, progress): transferred, block_size, total = progress if total == -1: self.pb.setMaximum(0) self.pb.setMinimum(0) self.pb.setValue(0) else: so_far = transferred * block_size self.pb.setMaximum(max(total, so_far)) self.pb.setValue(so_far) @property def err(self): return self.worker.err # }}} def dnd_has_image(md): # Chromium puts image data into application/octet-stream return md.hasImage() or md.hasFormat('application/octet-stream') and what(None, bytes(md.data('application/octet-stream'))) in image_extensions() def data_as_string(f, md): raw = bytes(md.data(f)) if '/x-moz' in f: try: raw = raw.decode('utf-16') except: pass return raw remote_protocols = {'http', 'https', 'ftp'} def urls_from_md(md): ans = list(md.urls()) if md.hasText(): # Chromium returns the url as text/plain on drag and drop of image text = md.text() if text and text.lstrip().partition(':')[0] in remote_protocols: u = QUrl(text.strip()) if u.isValid(): ans.append(u) return ans def path_from_qurl(qurl, allow_remote=False): lf = qurl.toLocalFile() if lf: if iswindows: from calibre_extensions.winutil import get_long_path_name with suppress(OSError): lf = get_long_path_name(lf) lf = make_long_path_useable(lf) return lf if not allow_remote: return '' if qurl.scheme() in remote_protocols: path = qurl.path() if path and '.' in path: return path.rpartition('.')[-1] return '' def remote_urls_from_qurl(qurls, allowed_exts): for qurl in qurls: if qurl.scheme() in remote_protocols and posixpath.splitext( qurl.path())[1][1:].lower() in allowed_exts: yield bytes(qurl.toEncoded()).decode('utf-8'), posixpath.basename(qurl.path()) def extension(path): return path.rpartition('.')[-1].lower() def dnd_has_extension(md, extensions, allow_all_extensions=False, allow_remote=False): if DEBUG: prints('\nDebugging DND event') for f in md.formats(): f = str(f) raw = data_as_string(f, md) prints(f, len(raw), repr(raw[:300]), '\n') print() if has_firefox_ext(md, extensions): return True urls = urls_from_md(md) paths = [path_from_qurl(u, allow_remote=allow_remote) for u in urls] exts = frozenset(filter(None, (extension(u) for u in paths if u))) if DEBUG: repr_urls = [bytes(u.toEncoded()).decode('utf-8') for u in urls] prints('URLS:', repr(repr_urls)) prints('Paths:', paths) prints('Extensions:', exts) if allow_all_extensions: return bool(exts) return bool(exts.intersection(frozenset(extensions))) def dnd_get_local_image_and_pixmap(md, image_exts=None): if md.hasImage(): for x in md.formats(): x = str(x) if x.startswith('image/'): cdata = bytes(md.data(x)) pmap = QPixmap() pmap.loadFromData(cdata) if not pmap.isNull(): return pmap, cdata if md.hasFormat('application/octet-stream'): cdata = bytes(md.data('application/octet-stream')) pmap = QPixmap() pmap.loadFromData(cdata) if not pmap.isNull(): return pmap, cdata if image_exts is None: image_exts = image_extensions() # No image, look for an URL pointing to an image urls = urls_from_md(md) paths = [path_from_qurl(u) for u in urls] # Look for a local file images = [xi for xi in paths if extension(xi) in image_exts] images = [xi for xi in images if os.path.exists(xi)] for path in images: try: with open(path, 'rb') as f: cdata = f.read() except Exception: continue p = QPixmap() p.loadFromData(cdata) if not p.isNull(): return p, cdata return None, None def dnd_get_image(md, image_exts=None): ''' Get the image in the QMimeData object md. :return: None, None if no image is found QPixmap, None if an image is found, the pixmap is guaranteed not null url, filename if a URL that points to an image is found ''' if image_exts is None: image_exts = image_extensions() pmap, data = dnd_get_local_image_and_pixmap(md, image_exts) if pmap is not None: return pmap, None # Look for a remote image urls = urls_from_md(md) # First, see if this is from Firefox rurl, fname = get_firefox_rurl(md, image_exts) if rurl and fname: return rurl, fname # Look through all remaining URLs for remote_url, filename in remote_urls_from_qurl(urls, image_exts): return remote_url, filename return None, None def dnd_get_files(md, exts, allow_all_extensions=False, filter_exts=()): ''' Get the file in the QMimeData object md with an extension that is one of the extensions in exts. :return: None, None if no file is found [paths], None if a local file is found [urls], [filenames] if URLs that point to a files are found ''' # Look for a URL pointing to a file urls = urls_from_md(md) # First look for a local file local_files = [path_from_qurl(x) for x in urls] def is_ok(path): ext = extension(path) if allow_all_extensions and ext and ext not in filter_exts: return True return ext in exts and ext not in filter_exts local_files = [p for p in local_files if is_ok(unquote(p))] local_files = [x for x in local_files if os.path.exists(x)] if local_files: return local_files, None # No local files, look for remote ones # First, see if this is from Firefox rurl, fname = get_firefox_rurl(md, exts) if rurl and fname: return [rurl], [fname] # Look through all remaining URLs rurls, filenames = [], [] for rurl, fname in remote_urls_from_qurl(urls, exts): rurls.append(rurl), filenames.append(fname) if rurls: return rurls, filenames return None, None def _get_firefox_pair(md, exts, url, fname): url = bytes(md.data(url)).decode('utf-16') fname = bytes(md.data(fname)).decode('utf-16') while url.endswith('\x00'): url = url[:-1] while fname.endswith('\x00'): fname = fname[:-1] if not url or not fname: return None, None ext = posixpath.splitext(fname)[1][1:].lower() # Weird firefox bug on linux ext = {'jpe':'jpg', 'epu':'epub', 'mob':'mobi'}.get(ext, ext) fname = os.path.splitext(fname)[0] + '.' + ext if DEBUG: prints('Firefox file promise:', url, fname) if ext not in exts: fname = url = None return url, fname def get_firefox_rurl(md, exts): formats = frozenset(str(x) for x in md.formats()) url = fname = None if 'application/x-moz-file-promise-url' in formats and \ 'application/x-moz-file-promise-dest-filename' in formats: try: url, fname = _get_firefox_pair(md, exts, 'application/x-moz-file-promise-url', 'application/x-moz-file-promise-dest-filename') except: if DEBUG: import traceback traceback.print_exc() if url is None and 'text/x-moz-url-data' in formats and \ 'text/x-moz-url-desc' in formats: try: url, fname = _get_firefox_pair(md, exts, 'text/x-moz-url-data', 'text/x-moz-url-desc') except: if DEBUG: import traceback traceback.print_exc() if url is None and '_NETSCAPE_URL' in formats: try: raw = bytes(md.data('_NETSCAPE_URL')) raw = raw.decode('utf-8') lines = raw.splitlines() if len(lines) > 1 and re.match(r'[a-z]+://', lines[1]) is None: url, fname = lines[:2] ext = posixpath.splitext(fname)[1][1:].lower() if ext not in exts: fname = url = None except: if DEBUG: import traceback traceback.print_exc() if DEBUG: prints('Firefox rurl:', url, fname) return url, fname def has_firefox_ext(md, exts): return bool(get_firefox_rurl(md, exts)[0])