From 74d0c6be6d1ee6a482d49856f6661ee557a679f9 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Wed, 15 Oct 2008 18:56:53 +0000 Subject: Add a "Download link" option to the link palette --- diff --git a/downloadmanager.py b/downloadmanager.py index 4b6e84f..8d5fae9 100644 --- a/downloadmanager.py +++ b/downloadmanager.py @@ -23,6 +23,7 @@ import urlparse import gtk import hulahop +import xpcom from xpcom.nsError import * from xpcom import components from xpcom.components import interfaces @@ -43,7 +44,8 @@ if dbus.version >= (0, 82, 3): else: DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND = 1000 -NS_BINDING_ABORTED = 0x804b0002 # From nsNetError.h +NS_BINDING_ABORTED = 0x804b0002 # From nsNetError.h +NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020 # From nsURILoader.h DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' @@ -285,3 +287,89 @@ components.registrar.registerFactory('{23c51569-e9a1-4a92-adeb-3723db82ef7c}', 'Sugar Download', '@mozilla.org/transfer;1', Factory(Download)) + +def save_link(url, text, owner_document): + # Inspired on Firefox' browser/base/content/nsContextMenu.js:saveLink() + + cls = components.classes["@mozilla.org/network/io-service;1"] + io_service = cls.getService(interfaces.nsIIOService) + uri = io_service.newURI(url, None, None) + channel = io_service.newChannelFromURI(uri) + + auth_prompt_callback = xpcom.server.WrapObject( + _AuthPromptCallback(owner_document.defaultView), + interfaces.nsIInterfaceRequestor) + channel.notificationCallbacks = auth_prompt_callback + + channel.loadFlags = channel.loadFlags | \ + interfaces.nsIRequest.LOAD_BYPASS_CACHE | \ + interfaces.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS + + if _implements_interface(channel, interfaces.nsIHttpChannel): + channel.referrer = io_service.newURI(owner_document.documentURI, None, + None) + + # kick off the channel with our proxy object as the listener + listener = xpcom.server.WrapObject( + _SaveLinkProgressListener(owner_document), + interfaces.nsIStreamListener) + channel.asyncOpen(listener, None) + +def _implements_interface(obj, interface): + try: + obj.QueryInterface(interface) + return True + except xpcom.Exception, e: + if e.errno == NS_NOINTERFACE: + return False + else: + raise + +class _AuthPromptCallback(object): + _com_interfaces_ = interfaces.nsIInterfaceRequestor + + def __init__(self, dom_window): + self._dom_window = dom_window + + def getInterface(self, uuid): + if uuid in [interfaces.nsIAuthPrompt, interfaces.nsIAuthPrompt2]: + cls = components.classes["@mozilla.org/embedcomp/window-watcher;1"] + window_watcher = cls.getService(interfaces.nsIPromptFactory) + return window_watcher.getPrompt(self._dom_window, uuid) + return None + +class _SaveLinkProgressListener(object): + _com_interfaces_ = interfaces.nsIStreamListener + + """ an object to proxy the data through to + nsIExternalHelperAppService.doContent, which will wait for the appropriate + MIME-type headers and then prompt the user with a file picker + """ + + def __init__(self, owner_document): + self._owner_document = owner_document + self._external_listener = None + + def onStartRequest(self, request, context): + if request.status != NS_OK: + logging.error("Error downloading link") + return + + cls = components.classes[ + "@mozilla.org/uriloader/external-helper-app-service;1"] + external_helper = cls.getService(interfaces.nsIExternalHelperAppService) + + channel = request.QueryInterface(interfaces.nsIChannel) + + self._external_listener = \ + external_helper.doContent(channel.contentType, request, + self._owner_document.defaultView, True) + self._external_listener.onStartRequest(request, context) + + def onStopRequest(self, request, context, statusCode): + self._external_listener.onStopRequest(request, context, statusCode) + + def onDataAvailable(self, request, context, inputStream, offset, count): + self._external_listener.onDataAvailable(request, context, inputStream, + offset, count); + diff --git a/palettes.py b/palettes.py index bae47a5..feb6ddb 100644 --- a/palettes.py +++ b/palettes.py @@ -30,6 +30,8 @@ from sugar.graphics.icon import Icon from sugar import profile from sugar.activity import activity +import downloadmanager + class ContentInvoker(Invoker): _com_interfaces_ = interfaces.nsIDOMEventListener @@ -59,7 +61,8 @@ class ContentInvoker(Invoker): else: title = None - self.palette = LinkPalette(self._browser, title, target.href) + self.palette = LinkPalette(self._browser, title, target.href, + target.ownerDocument) self.notify_right_click() elif target.tagName.lower() == 'img': if target.title: @@ -75,11 +78,13 @@ class ContentInvoker(Invoker): self.notify_right_click() class LinkPalette(Palette): - def __init__(self, browser, title, url): + def __init__(self, browser, title, url, owner_document): Palette.__init__(self) - self._url = url self._browser = browser + self._title = title + self._url = url + self._owner_document = owner_document if title is not None: self.props.primary_text = title @@ -100,6 +105,11 @@ class LinkPalette(Palette): self.menu.append(menu_item) menu_item.show() + menu_item = MenuItem(_('Download link')) + menu_item.connect('activate', self.__download_activate_cb) + self.menu.append(menu_item) + menu_item.show() + def __follow_activate_cb(self, menu_item): self._browser.load_uri(self._url) self._browser.grab_focus() @@ -130,6 +140,9 @@ class LinkPalette(Palette): def __clipboard_clear_func_cb(self, clipboard, data): pass + def __download_activate_cb(self, menu_item): + downloadmanager.save_link(self._url, self._title, self._owner_document) + class ImagePalette(Palette): def __init__(self, title, url): Palette.__init__(self) -- cgit v0.9.1