16 | 16 |
import os
|
17 | 17 |
import logging
|
18 | 18 |
import shutil
|
|
19 |
import mimetypes
|
19 | 20 |
from pathlib import Path
|
20 | 21 |
from functools import partial
|
21 | 22 |
from urllib.parse import urlparse
|
|
23 | 24 |
|
24 | 25 |
from gi.repository import Gtk
|
25 | 26 |
from gi.repository import Gdk
|
|
27 |
from gi.repository import GdkPixbuf
|
|
28 |
from gi.repository import Gio
|
26 | 29 |
from gi.repository import GLib
|
27 | 30 |
from gi.repository import Soup
|
28 | |
from gi.repository import GdkPixbuf
|
29 | 31 |
|
30 | 32 |
from gajim.common import app
|
31 | 33 |
from gajim.common import configpaths
|
|
37 | 39 |
from gajim.common.helpers import get_user_proxy
|
38 | 40 |
from gajim.gtk.dialogs import ErrorDialog
|
39 | 41 |
from gajim.gtk.filechoosers import FileSaveDialog
|
|
42 |
from gajim.gtk.util import get_cursor
|
|
43 |
from gajim.gtk.util import get_monitor_scale_factor
|
40 | 44 |
from gajim.gtk.util import load_icon
|
41 | |
from gajim.gtk.util import get_monitor_scale_factor
|
42 | 45 |
|
43 | 46 |
from gajim.plugins import GajimPlugin
|
44 | 47 |
from gajim.plugins.helpers import get_builder
|
45 | 48 |
from gajim.plugins.plugins_i18n import _
|
46 | 49 |
|
47 | 50 |
from url_image_preview.config_dialog import UrlImagePreviewConfigDialog
|
48 | |
|
|
51 |
from url_image_preview.mime_types import MIME_TYPES
|
49 | 52 |
|
50 | 53 |
log = logging.getLogger('gajim.p.preview')
|
51 | 54 |
|
|
71 | 74 |
from url_image_preview.utils import parse_fragment
|
72 | 75 |
from url_image_preview.utils import create_thumbnail
|
73 | 76 |
from url_image_preview.utils import pixbuf_from_data
|
74 | |
from url_image_preview.utils import create_clickable_image
|
75 | 77 |
from url_image_preview.utils import filename_from_uri
|
76 | 78 |
# pylint: enable=ungrouped-imports
|
77 | 79 |
|
78 | |
def get_accepted_mime_types():
|
79 | |
accepted_mime_types = set()
|
|
80 |
|
|
81 |
def get_previewable_mime_types():
|
|
82 |
previewable_mime_types = set()
|
80 | 83 |
for fmt in GdkPixbuf.Pixbuf.get_formats():
|
81 | 84 |
for mime_type in fmt.get_mime_types():
|
82 | |
accepted_mime_types.add(mime_type.lower())
|
|
85 |
previewable_mime_types.add(mime_type.lower())
|
83 | 86 |
if Image is not None:
|
84 | 87 |
Image.init()
|
85 | 88 |
for mime_type in Image.MIME.values():
|
86 | |
accepted_mime_types.add(mime_type.lower())
|
|
89 |
previewable_mime_types.add(mime_type.lower())
|
87 | 90 |
return tuple(filter(
|
88 | 91 |
lambda mime_type: mime_type.startswith('image'),
|
89 | |
accepted_mime_types
|
|
92 |
previewable_mime_types
|
90 | 93 |
))
|
91 | 94 |
|
92 | |
ACCEPTED_MIME_TYPES = get_accepted_mime_types()
|
93 | |
|
|
95 |
|
|
96 |
PREVIEWABLE_MIME_TYPES = get_previewable_mime_types()
|
|
97 |
mime_types = set(MIME_TYPES)
|
|
98 |
# Merge both: if it’s a previewable image, it should be allowed
|
|
99 |
ALLOWED_MIME_TYPES = mime_types.union(PREVIEWABLE_MIME_TYPES)
|
94 | 100 |
|
95 | 101 |
class UrlImagePreviewPlugin(GajimPlugin):
|
96 | 102 |
def init(self):
|
|
108 | 114 |
self._on_disconnect_chat_control_base),
|
109 | 115 |
'history_window': (self._on_connect_history_window,
|
110 | 116 |
self._on_disconnect_history_window),
|
111 | |
'print_real_text': (self._print_real_text, None), }
|
|
117 |
'print_real_text': (self._print_real_text, None),
|
|
118 |
}
|
112 | 119 |
|
113 | 120 |
self.config_default_values = {
|
114 | 121 |
'PREVIEW_SIZE': (150, 'Preview size (100-1000)'),
|
115 | |
'MAX_FILE_SIZE': (5242880, 'Max file size for image preview'),
|
|
122 |
'MAX_FILE_SIZE': ('10485760', 'Max file size for image preview'),
|
116 | 123 |
'ALLOW_ALL_IMAGES': (False, ''),
|
117 | 124 |
'LEFTCLICK_ACTION': ('open_menuitem', 'Open'),
|
118 | 125 |
'ANONYMOUS_MUC': (False, ''),
|
119 | |
'VERIFY': (True, ''),}
|
|
126 |
'VERIFY': (True, ''),
|
|
127 |
}
|
120 | 128 |
|
121 | 129 |
self._textviews = {}
|
122 | 130 |
self._sessions = {}
|
|
130 | 138 |
if GLib.mkdir_with_parents(str(self._thumb_dir), 0o700) != 0:
|
131 | 139 |
log.error('Failed to create: %s', self._thumb_dir)
|
132 | 140 |
|
|
141 |
if app.config.get('use_kib_mib'):
|
|
142 |
self._units = GLib.FormatSizeFlags.IEC_UNITS
|
|
143 |
else:
|
|
144 |
self._units = GLib.FormatSizeFlags.DEFAULT
|
|
145 |
|
133 | 146 |
self._migrate_config()
|
|
147 |
self._load_css()
|
134 | 148 |
|
135 | 149 |
def _migrate_config(self):
|
136 | 150 |
action = self.config['LEFTCLICK_ACTION']
|
137 | 151 |
if action.endswith('_menuitem'):
|
138 | 152 |
self.config['LEFTCLICK_ACTION'] = action[:-9]
|
|
153 |
|
|
154 |
@staticmethod
|
|
155 |
def _load_css():
|
|
156 |
path = Path(__file__).parent / 'preview.css'
|
|
157 |
try:
|
|
158 |
with path.open('r') as file:
|
|
159 |
css = file.read()
|
|
160 |
except Exception as exc:
|
|
161 |
log.error('Error loading css: %s', exc)
|
|
162 |
return
|
|
163 |
|
|
164 |
try:
|
|
165 |
provider = Gtk.CssProvider()
|
|
166 |
provider.load_from_data(bytes(css.encode('utf-8')))
|
|
167 |
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
|
|
168 |
provider, 610)
|
|
169 |
except Exception:
|
|
170 |
log.exception('Error loading application css')
|
139 | 171 |
|
140 | 172 |
def _on_connect_chat_control_base(self, chat_control):
|
141 | 173 |
account = chat_control.account
|
|
160 | 192 |
if textview == textview_:
|
161 | 193 |
return control_id
|
162 | 194 |
|
163 | |
def _create_session(self, account):
|
|
195 |
@staticmethod
|
|
196 |
def _create_session(account):
|
164 | 197 |
session = Soup.Session()
|
165 | 198 |
session.add_feature_by_type(Soup.ContentSniffer)
|
166 | 199 |
session.props.https_aliases = ['aesgcm']
|
|
210 | 243 |
size=preview.size,
|
211 | 244 |
scale=get_monitor_scale_factor(),
|
212 | 245 |
pixbuf=True)
|
213 | |
self._update_textview(pixbuf, preview)
|
|
246 |
self._update_textview(preview, pixbuf)
|
214 | 247 |
return
|
215 | 248 |
|
216 | 249 |
preview = self._process_web_uri(uri,
|
|
286 | 319 |
account):
|
287 | 320 |
try:
|
288 | 321 |
split_geo_uri(uri)
|
289 | |
except Exception as error:
|
|
322 |
except Exception as err:
|
290 | 323 |
log.error(uri)
|
291 | |
log.error(error)
|
|
324 |
log.error(err)
|
292 | 325 |
return
|
293 | 326 |
|
294 | 327 |
return Preview(uri,
|
|
330 | 363 |
log.error('%s: %s', preview.orig_path.name, error)
|
331 | 364 |
return
|
332 | 365 |
|
333 | |
if preview.create_thumbnail(data):
|
334 | |
write_file_async(preview.thumb_path,
|
335 | |
preview.thumbnail,
|
336 | |
self._on_thumb_write_finished,
|
337 | |
preview)
|
|
366 |
preview.mime_type = self._guess_mime_type(preview.orig_path)
|
|
367 |
preview.file_size = os.path.getsize(preview.orig_path)
|
|
368 |
if preview.is_previewable:
|
|
369 |
if preview.create_thumbnail(data):
|
|
370 |
write_file_async(preview.thumb_path,
|
|
371 |
preview.thumbnail,
|
|
372 |
self._on_thumb_write_finished,
|
|
373 |
preview)
|
|
374 |
else:
|
|
375 |
self._update_textview(preview, None)
|
338 | 376 |
|
339 | 377 |
def _on_thumb_load_finished(self, data, error, preview):
|
340 | 378 |
if data is None:
|
|
342 | 380 |
return
|
343 | 381 |
|
344 | 382 |
preview.thumbnail = data
|
|
383 |
preview.mime_type = self._guess_mime_type(preview.orig_path)
|
|
384 |
preview.file_size = os.path.getsize(preview.orig_path)
|
345 | 385 |
|
346 | 386 |
try:
|
347 | 387 |
pixbuf = pixbuf_from_data(preview.thumbnail)
|
|
350 | 390 |
preview.thumb_path.name,
|
351 | 391 |
err)
|
352 | 392 |
return
|
353 | |
self._update_textview(pixbuf, preview)
|
354 | |
|
355 | |
def _download_content(self, preview):
|
|
393 |
self._update_textview(preview, pixbuf)
|
|
394 |
|
|
395 |
def _download_content(self, preview, force=False):
|
356 | 396 |
if preview.account is None:
|
357 | 397 |
# History Window can be opened without account context
|
358 | 398 |
# This means we can not apply proxy settings
|
|
360 | 400 |
log.info('Start downloading: %s', preview.request_uri)
|
361 | 401 |
message = Soup.Message.new('GET', preview.request_uri)
|
362 | 402 |
message.connect('starting', self._check_certificate, preview)
|
363 | |
message.connect('content-sniffed', self._on_content_sniffed, preview)
|
|
403 |
message.connect(
|
|
404 |
'content-sniffed', self._on_content_sniffed, preview, force)
|
364 | 405 |
|
365 | 406 |
session = self._get_session(preview.account)
|
366 | 407 |
session.queue_message(message, self._on_finished, preview)
|
|
378 | 419 |
session.cancel_message(message, Soup.Status.CANCELLED)
|
379 | 420 |
return
|
380 | 421 |
|
381 | |
def _on_content_sniffed(self, message, type_, _params, preview):
|
382 | |
size = message.props.response_headers.get_content_length()
|
|
422 |
def _on_content_sniffed(self, message, type_, _params, preview, force):
|
|
423 |
file_size = message.props.response_headers.get_content_length()
|
383 | 424 |
uri = message.props.uri.to_string(False)
|
384 | 425 |
session = self._get_session(preview.account)
|
385 | |
if type_ not in ACCEPTED_MIME_TYPES:
|
386 | |
log.info('Not allowed content type: %s, %s', type_, uri)
|
|
426 |
preview.mime_type = type_
|
|
427 |
preview.file_size = file_size
|
|
428 |
|
|
429 |
if type_ not in ALLOWED_MIME_TYPES:
|
|
430 |
log.info('Not an allowed content type: %s, %s', type_, uri)
|
387 | 431 |
session.cancel_message(message, Soup.Status.CANCELLED)
|
388 | 432 |
return
|
389 | 433 |
|
390 | |
if size == 0 or size > int(self.config['MAX_FILE_SIZE']):
|
|
434 |
if file_size == 0 or file_size > int(self.config['MAX_FILE_SIZE']):
|
391 | 435 |
log.info('File size (%s) too big or unknown (zero) for URL: \'%s\'',
|
392 | |
size, uri)
|
393 | |
session.cancel_message(message, Soup.Status.CANCELLED)
|
394 | |
return
|
|
436 |
file_size, uri)
|
|
437 |
if not force:
|
|
438 |
session.cancel_message(message, Soup.Status.CANCELLED)
|
|
439 |
|
|
440 |
self._update_textview(preview, None)
|
395 | 441 |
|
396 | 442 |
def _on_finished(self, _session, message, preview):
|
397 | 443 |
if message.status_code != Soup.Status.OK:
|
|
411 | 457 |
self._on_orig_write_finished,
|
412 | 458 |
preview)
|
413 | 459 |
|
414 | |
if preview.create_thumbnail(data):
|
415 | |
write_file_async(preview.thumb_path,
|
416 | |
preview.thumbnail,
|
417 | |
self._on_thumb_write_finished,
|
418 | |
preview)
|
|
460 |
if preview.is_previewable:
|
|
461 |
if preview.create_thumbnail(data):
|
|
462 |
write_file_async(preview.thumb_path,
|
|
463 |
preview.thumbnail,
|
|
464 |
self._on_thumb_write_finished,
|
|
465 |
preview)
|
|
466 |
else:
|
|
467 |
self._update_textview(preview, None)
|
419 | 468 |
|
420 | 469 |
@staticmethod
|
421 | 470 |
def _on_orig_write_finished(_result, error, preview):
|
|
424 | 473 |
return
|
425 | 474 |
|
426 | 475 |
log.info('File stored: %s', preview.orig_path.name)
|
|
476 |
preview.file_size = os.path.getsize(preview.orig_path)
|
427 | 477 |
|
428 | 478 |
def _on_thumb_write_finished(self, _result, error, preview):
|
429 | 479 |
if error is not None:
|
|
434 | 484 |
|
435 | 485 |
try:
|
436 | 486 |
pixbuf = pixbuf_from_data(preview.thumbnail)
|
437 | |
except Exception as err:
|
|
487 |
except Exception as error:
|
438 | 488 |
log.error('Unable to load: %s, %s',
|
439 | 489 |
preview.thumb_path.name,
|
440 | |
err)
|
441 | |
return
|
442 | |
self._update_textview(pixbuf, preview)
|
443 | |
|
444 | |
def _update_textview(self, pixbuf, preview):
|
|
490 |
error)
|
|
491 |
return
|
|
492 |
self._update_textview(preview, pixbuf)
|
|
493 |
|
|
494 |
@staticmethod
|
|
495 |
def _guess_mime_type(data):
|
|
496 |
mime_type, _ = mimetypes.MimeTypes().guess_type(data)
|
|
497 |
if mime_type is None:
|
|
498 |
# Try to guess MIME type by file name
|
|
499 |
mime_type, _ = Gio.content_type_guess(str(data), None)
|
|
500 |
log.debug('Guessed MIME type: %s', str(mime_type))
|
|
501 |
return mime_type
|
|
502 |
|
|
503 |
@staticmethod
|
|
504 |
def _get_icon_for_mime_type(mime_type):
|
|
505 |
if mime_type is None:
|
|
506 |
return Gio.Icon.new_for_string('mail-attachment')
|
|
507 |
return Gio.content_type_get_icon(mime_type)
|
|
508 |
|
|
509 |
def _update_textview(self, preview, data):
|
445 | 510 |
textview = self._textviews.get(preview.control_id)
|
446 | 511 |
if textview is None:
|
447 | 512 |
# Control closed
|
|
453 | 518 |
anchor = buffer_.create_child_anchor(iter_)
|
454 | 519 |
anchor.plaintext = preview.uri
|
455 | 520 |
|
456 | |
image = create_clickable_image(pixbuf, preview)
|
457 | |
|
458 | |
textview.tv.add_child_at_anchor(image, anchor)
|
|
521 |
preview_widget = self._create_preview_widget(preview, data)
|
|
522 |
|
|
523 |
textview.tv.add_child_at_anchor(preview_widget, anchor)
|
459 | 524 |
buffer_.delete(iter_,
|
460 | 525 |
buffer_.get_iter_at_mark(preview.end_mark))
|
461 | 526 |
|
462 | |
image.connect('button-press-event',
|
463 | |
self._on_button_press_event,
|
464 | |
preview)
|
465 | |
|
466 | 527 |
if textview.autoscroll:
|
467 | 528 |
textview.scroll_to_end()
|
468 | 529 |
|
|
530 |
def _create_preview_widget(self, preview, data):
|
|
531 |
if isinstance(data, GdkPixbuf.PixbufAnimation):
|
|
532 |
image = Gtk.Image.new_from_animation(data)
|
|
533 |
elif isinstance(data, GdkPixbuf.Pixbuf):
|
|
534 |
image = Gtk.Image.new_from_pixbuf(data)
|
|
535 |
else:
|
|
536 |
icon = self._get_icon_for_mime_type(preview.mime_type)
|
|
537 |
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.DIALOG)
|
|
538 |
|
|
539 |
def _on_realize(box):
|
|
540 |
box.get_window().set_cursor(get_cursor('pointer'))
|
|
541 |
|
|
542 |
def _on_enter_leave(button, event):
|
|
543 |
if event.type == Gdk.EventType.ENTER_NOTIFY:
|
|
544 |
button.get_window().set_cursor(get_cursor('default'))
|
|
545 |
else:
|
|
546 |
button.get_window().set_cursor(get_cursor('text'))
|
|
547 |
|
|
548 |
path = self.local_file_path('preview.ui')
|
|
549 |
ui = get_builder(path)
|
|
550 |
|
|
551 |
ui.download_button.set_no_show_all(True)
|
|
552 |
ui.download_button.connect('enter-notify-event', _on_enter_leave)
|
|
553 |
ui.download_button.connect('leave-notify-event', _on_enter_leave)
|
|
554 |
ui.download_button.connect('clicked', self._on_download, preview)
|
|
555 |
|
|
556 |
ui.save_as_button.set_no_show_all(True)
|
|
557 |
ui.save_as_button.connect('enter-notify-event', _on_enter_leave)
|
|
558 |
ui.save_as_button.connect('leave-notify-event', _on_enter_leave)
|
|
559 |
ui.save_as_button.connect('clicked', self._on_save_as, preview)
|
|
560 |
|
|
561 |
ui.open_folder_button.set_no_show_all(True)
|
|
562 |
ui.open_folder_button.connect('enter-notify-event', _on_enter_leave)
|
|
563 |
ui.open_folder_button.connect('leave-notify-event', _on_enter_leave)
|
|
564 |
ui.open_folder_button.connect('clicked', self._on_open_folder, preview)
|
|
565 |
|
|
566 |
ui.event_box.set_tooltip_text(preview.filename)
|
|
567 |
ui.event_box.add(image)
|
|
568 |
ui.event_box.connect('realize', _on_realize)
|
|
569 |
ui.event_box.connect('button-press-event',
|
|
570 |
self._on_button_press_event,
|
|
571 |
preview)
|
|
572 |
|
|
573 |
ui.preview_box.show_all()
|
|
574 |
|
|
575 |
if preview.is_geo_uri:
|
|
576 |
ui.file_name.set_text(_('Click to view location'))
|
|
577 |
ui.save_as_button.hide()
|
|
578 |
ui.open_folder_button.hide()
|
|
579 |
ui.download_button.hide()
|
|
580 |
location = split_geo_uri(preview.uri)
|
|
581 |
ui.file_size.set_text(_('Lat: %s Lon: %s') % (
|
|
582 |
location.lat, location.lon))
|
|
583 |
ui.event_box.set_tooltip_text(_('Location at Lat: %s Lon: %s') % (
|
|
584 |
location.lat, location.lon))
|
|
585 |
ui.event_box.set_halign(Gtk.Align.CENTER)
|
|
586 |
ui.preview_box.set_size_request(160, -1)
|
|
587 |
return ui.preview_box
|
|
588 |
|
|
589 |
if preview.is_previewable and preview.orig_exists():
|
|
590 |
ui.event_box.set_halign(Gtk.Align.CENTER)
|
|
591 |
else:
|
|
592 |
image.set_property('pixel-size', 64)
|
|
593 |
|
|
594 |
if preview.orig_exists():
|
|
595 |
ui.download_button.hide()
|
|
596 |
else:
|
|
597 |
ui.save_as_button.hide()
|
|
598 |
ui.open_folder_button.hide()
|
|
599 |
|
|
600 |
file_size_string = _('File size unknown')
|
|
601 |
if preview.file_size != 0:
|
|
602 |
file_size_string = GLib.format_size_full(
|
|
603 |
preview.file_size, self._units)
|
|
604 |
ui.file_size.set_text(file_size_string)
|
|
605 |
|
|
606 |
ui.preview_box.set_size_request(300, -1)
|
|
607 |
ui.file_name.set_text(preview.filename)
|
|
608 |
ui.file_name.set_tooltip_text(preview.filename)
|
|
609 |
|
|
610 |
return ui.preview_box
|
|
611 |
|
469 | 612 |
def _get_context_menu(self, preview):
|
|
613 |
def destroy(menu, _pspec):
|
|
614 |
visible = menu.get_property('visible')
|
|
615 |
if not visible:
|
|
616 |
GLib.idle_add(menu.destroy)
|
|
617 |
|
470 | 618 |
path = self.local_file_path('context_menu.ui')
|
471 | 619 |
ui = get_builder(path)
|
472 | |
if preview.is_aes_encrypted:
|
473 | |
ui.open_link_in_browser.hide()
|
474 | |
|
475 | |
if preview.is_geo_uri:
|
476 | |
ui.open_link_in_browser.hide()
|
477 | |
ui.save_as.hide()
|
478 | |
ui.open_folder.hide()
|
479 | |
|
|
620 |
|
|
621 |
ui.download.connect(
|
|
622 |
'activate', self._on_download, preview)
|
480 | 623 |
ui.open.connect(
|
481 | 624 |
'activate', self._on_open, preview)
|
482 | 625 |
ui.save_as.connect(
|
|
487 | 630 |
'activate', self._on_open_link_in_browser, preview)
|
488 | 631 |
ui.copy_link_location.connect(
|
489 | 632 |
'activate', self._on_copy_link_location, preview)
|
490 | |
|
491 | |
def destroy(menu, _pspec):
|
492 | |
visible = menu.get_property('visible')
|
493 | |
if not visible:
|
494 | |
GLib.idle_add(menu.destroy)
|
495 | |
|
496 | 633 |
ui.context_menu.connect('notify::visible', destroy)
|
|
634 |
|
|
635 |
if preview.is_aes_encrypted:
|
|
636 |
ui.open_link_in_browser.hide()
|
|
637 |
|
|
638 |
if preview.is_geo_uri:
|
|
639 |
ui.download.hide()
|
|
640 |
ui.open_link_in_browser.hide()
|
|
641 |
ui.save_as.hide()
|
|
642 |
ui.open_folder.hide()
|
|
643 |
return ui.context_menu
|
|
644 |
|
|
645 |
if preview.orig_exists():
|
|
646 |
ui.download.hide()
|
|
647 |
else:
|
|
648 |
ui.open.hide()
|
|
649 |
ui.save_as.hide()
|
|
650 |
ui.open_folder.hide()
|
|
651 |
|
497 | 652 |
return ui.context_menu
|
498 | 653 |
|
499 | |
@staticmethod
|
500 | |
def _on_open(_menu, preview):
|
|
654 |
def _on_download(self, _menu, preview):
|
|
655 |
if not preview.orig_exists():
|
|
656 |
self._download_content(preview, force=True)
|
|
657 |
|
|
658 |
def _on_open(self, _menu, preview):
|
501 | 659 |
if preview.is_geo_uri:
|
502 | 660 |
open_uri(preview.uri)
|
503 | 661 |
return
|
|
662 |
|
|
663 |
if not preview.orig_exists():
|
|
664 |
self._download_content(preview, force=True)
|
|
665 |
return
|
|
666 |
|
504 | 667 |
open_file(preview.orig_path)
|
505 | 668 |
|
506 | |
@staticmethod
|
507 | |
def _on_save_as(_menu, preview):
|
|
669 |
def _on_save_as(self, _menu, preview):
|
508 | 670 |
def on_ok(target_path):
|
509 | 671 |
dirname = Path(target_path).parent
|
510 | 672 |
if not os.access(dirname, os.W_OK):
|
|
516 | 678 |
return
|
517 | 679 |
shutil.copyfile(str(preview.orig_path), target_path)
|
518 | 680 |
|
|
681 |
if not preview.orig_exists():
|
|
682 |
self._download_content(preview, force=True)
|
|
683 |
return
|
|
684 |
|
519 | 685 |
FileSaveDialog(on_ok,
|
520 | 686 |
path=app.config.get('last_save_dir'),
|
521 | 687 |
file_name=preview.filename,
|
522 | 688 |
transient_for=app.app.get_active_window())
|
523 | 689 |
|
524 | |
@staticmethod
|
525 | |
def _on_open_folder(_menu, preview):
|
|
690 |
def _on_open_folder(self, _menu, preview):
|
|
691 |
if not preview.orig_exists():
|
|
692 |
self._download_content(preview, force=True)
|
|
693 |
return
|
526 | 694 |
open_file(preview.orig_path.parent)
|
527 | 695 |
|
528 | 696 |
@staticmethod
|
|
559 | 727 |
self._uri = uri
|
560 | 728 |
self._urlparts = urlparts
|
561 | 729 |
self._filename = filename_from_uri(self._uri)
|
|
730 |
|
562 | 731 |
self.size = size
|
563 | 732 |
self.control_id = control_id
|
564 | 733 |
self.orig_path = orig_path
|
565 | 734 |
self.thumb_path = thumb_path
|
566 | 735 |
self.start_mark = start_mark
|
567 | 736 |
self.end_mark = end_mark
|
|
737 |
self.account = account
|
|
738 |
|
568 | 739 |
self.thumbnail = None
|
569 | |
self.account = account
|
|
740 |
self.mime_type = None
|
|
741 |
self.file_size = 0
|
570 | 742 |
|
571 | 743 |
self.key, self.iv = None, None
|
572 | 744 |
if self.is_aes_encrypted:
|
|
579 | 751 |
@property
|
580 | 752 |
def is_web_uri(self):
|
581 | 753 |
return not self.is_geo_uri
|
|
754 |
|
|
755 |
@property
|
|
756 |
def is_previewable(self):
|
|
757 |
return self.mime_type in PREVIEWABLE_MIME_TYPES
|
582 | 758 |
|
583 | 759 |
@property
|
584 | 760 |
def uri(self):
|
|
611 | 787 |
def create_thumbnail(self, data):
|
612 | 788 |
self.thumbnail = create_thumbnail(data, self.size)
|
613 | 789 |
if self.thumbnail is None:
|
614 | |
log.warning('creating thumbnail failed for: %s', self.orig_path)
|
|
790 |
log.warning('Creating thumbnail failed for: %s', self.orig_path)
|
615 | 791 |
return False
|
616 | 792 |
return True
|