Move httpupload into modules
This commit is contained in:
		
							parent
							
								
									0eeb111a02
								
							
						
					
					
						commit
						4bcdbde2c8
					
				
					 9 changed files with 63 additions and 66 deletions
				
			
		| 
						 | 
				
			
			@ -274,6 +274,7 @@ class ChatControl(ChatControlBase):
 | 
			
		|||
    def update_actions(self):
 | 
			
		||||
        win = self.parent_win.window
 | 
			
		||||
        online = app.account_is_connected(self.account)
 | 
			
		||||
        con = app.connections[self.account]
 | 
			
		||||
 | 
			
		||||
        # Add to roster
 | 
			
		||||
        if not isinstance(self.contact, GC_Contact) \
 | 
			
		||||
| 
						 | 
				
			
			@ -297,7 +298,7 @@ class ChatControl(ChatControlBase):
 | 
			
		|||
        httpupload = win.lookup_action(
 | 
			
		||||
            'send-file-httpupload-' + self.control_id)
 | 
			
		||||
        httpupload.set_enabled(
 | 
			
		||||
            online and app.connections[self.account].httpupload)
 | 
			
		||||
            online and con.get_module('HTTPUpload').available)
 | 
			
		||||
 | 
			
		||||
        # Send file (Jingle)
 | 
			
		||||
        jingle_conditions = (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -759,19 +759,19 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
 | 
			
		|||
                # groupchat only supports httpupload on drag and drop
 | 
			
		||||
                if httpupload.get_enabled():
 | 
			
		||||
                    # use httpupload
 | 
			
		||||
                    con.check_file_before_transfer(
 | 
			
		||||
                    con.get_module('HTTPUpload').check_file_before_transfer(
 | 
			
		||||
                        path, self.encryption, contact,
 | 
			
		||||
                        self.session, groupchat=True)
 | 
			
		||||
            else:
 | 
			
		||||
                if httpupload.get_enabled() and jingle.get_enabled():
 | 
			
		||||
                    if ft_pref == 'httpupload':
 | 
			
		||||
                        con.check_file_before_transfer(
 | 
			
		||||
                        con.get_module('HTTPUpload').check_file_before_transfer(
 | 
			
		||||
                            path, self.encryption, contact, self.session)
 | 
			
		||||
                    else:
 | 
			
		||||
                        ft = app.interface.instances['file_transfers']
 | 
			
		||||
                        ft.send_file(self.account, contact, path)
 | 
			
		||||
                elif httpupload.get_enabled():
 | 
			
		||||
                    con.check_file_before_transfer(
 | 
			
		||||
                    con.get_module('HTTPUpload').check_file_before_transfer(
 | 
			
		||||
                        path, self.encryption, contact, self.session)
 | 
			
		||||
                elif jingle.get_enabled():
 | 
			
		||||
                    ft = app.interface.instances['file_transfers']
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,6 +83,7 @@ from gajim.common.modules.user_tune import UserTune
 | 
			
		|||
from gajim.common.modules.user_mood import UserMood
 | 
			
		||||
from gajim.common.modules.user_location import UserLocation
 | 
			
		||||
from gajim.common.modules.user_nickname import UserNickname
 | 
			
		||||
from gajim.common.modules.httpupload import HTTPUpload
 | 
			
		||||
from gajim.common.connection_handlers import *
 | 
			
		||||
from gajim.common.contacts import GC_Contact
 | 
			
		||||
from gajim.gtkgui_helpers import get_action
 | 
			
		||||
| 
						 | 
				
			
			@ -681,6 +682,7 @@ class Connection(CommonConnection, ConnectionHandlers):
 | 
			
		|||
        self.register_module('UserMood', UserMood, self)
 | 
			
		||||
        self.register_module('UserLocation', UserLocation, self)
 | 
			
		||||
        self.register_module('UserNickname', UserNickname, self)
 | 
			
		||||
        self.register_module('HTTPUpload', HTTPUpload, self)
 | 
			
		||||
 | 
			
		||||
        app.ged.register_event_handler('privacy-list-received', ged.CORE,
 | 
			
		||||
            self._nec_privacy_list_received)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,7 +53,6 @@ from gajim.common.protocol.caps import ConnectionCaps
 | 
			
		|||
from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
 | 
			
		||||
from gajim.common.protocol.bytestream import ConnectionIBBytestream
 | 
			
		||||
from gajim.common.message_archiving import ConnectionArchive313
 | 
			
		||||
from gajim.common.httpupload import ConnectionHTTPUpload
 | 
			
		||||
from gajim.common.connection_handlers_events import *
 | 
			
		||||
 | 
			
		||||
from gajim.common import ged
 | 
			
		||||
| 
						 | 
				
			
			@ -754,14 +753,12 @@ class ConnectionHandlersBase:
 | 
			
		|||
class ConnectionHandlers(ConnectionArchive313,
 | 
			
		||||
ConnectionSocks5Bytestream, ConnectionDisco,
 | 
			
		||||
ConnectionCommands, ConnectionCaps,
 | 
			
		||||
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream,
 | 
			
		||||
ConnectionHTTPUpload):
 | 
			
		||||
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        ConnectionArchive313.__init__(self)
 | 
			
		||||
        ConnectionSocks5Bytestream.__init__(self)
 | 
			
		||||
        ConnectionIBBytestream.__init__(self)
 | 
			
		||||
        ConnectionCommands.__init__(self)
 | 
			
		||||
        ConnectionHTTPUpload.__init__(self)
 | 
			
		||||
 | 
			
		||||
        # Handle presences BEFORE caps
 | 
			
		||||
        app.nec.register_incoming_event(PresenceReceivedEvent)
 | 
			
		||||
| 
						 | 
				
			
			@ -819,7 +816,6 @@ ConnectionHTTPUpload):
 | 
			
		|||
        ConnectionHandlersBase.cleanup(self)
 | 
			
		||||
        ConnectionCaps.cleanup(self)
 | 
			
		||||
        ConnectionArchive313.cleanup(self)
 | 
			
		||||
        ConnectionHTTPUpload.cleanup(self)
 | 
			
		||||
        app.ged.remove_event_handler('roster-set-received',
 | 
			
		||||
            ged.CORE, self._nec_roster_set_received)
 | 
			
		||||
        app.ged.remove_event_handler('roster-received', ged.CORE,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2545,17 +2545,3 @@ class BlockingEvent(nec.NetworkIncomingEvent):
 | 
			
		|||
            app.log('blocking').info(
 | 
			
		||||
                'Blocking Push - unblocked JIDs: %s', self.unblocked_jids)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
class HTTPUploadStartEvent(nec.NetworkIncomingEvent):
 | 
			
		||||
    name = 'httpupload-start'
 | 
			
		||||
    base_network_events = []
 | 
			
		||||
 | 
			
		||||
    def generate(self):
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
class HTTPUploadProgressEvent(nec.NetworkIncomingEvent):
 | 
			
		||||
    name = 'httpupload-progress'
 | 
			
		||||
    base_network_events = []
 | 
			
		||||
 | 
			
		||||
    def generate(self):
 | 
			
		||||
        return True
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,3 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# This file is part of Gajim.
 | 
			
		||||
#
 | 
			
		||||
# Gajim is free software; you can redistribute it and/or modify
 | 
			
		||||
| 
						 | 
				
			
			@ -14,13 +12,16 @@
 | 
			
		|||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with Gajim.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
# XEP-0363: HTTP File Upload
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import threading
 | 
			
		||||
import ssl
 | 
			
		||||
import urllib
 | 
			
		||||
from urllib.request import Request, urlopen
 | 
			
		||||
from urllib.parse import urlparse, quote
 | 
			
		||||
from urllib.parse import urlparse
 | 
			
		||||
import io
 | 
			
		||||
import mimetypes
 | 
			
		||||
import logging
 | 
			
		||||
| 
						 | 
				
			
			@ -31,26 +32,28 @@ from gi.repository import GLib
 | 
			
		|||
 | 
			
		||||
from gajim.common import app
 | 
			
		||||
from gajim.common import ged
 | 
			
		||||
from gajim.common.nec import NetworkIncomingEvent
 | 
			
		||||
from gajim.common.connection_handlers_events import InformationEvent
 | 
			
		||||
from gajim.common.connection_handlers_events import HTTPUploadProgressEvent
 | 
			
		||||
from gajim.common.connection_handlers_events import MessageOutgoingEvent
 | 
			
		||||
from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
 | 
			
		||||
 | 
			
		||||
if sys.platform in ('win32', 'darwin'):
 | 
			
		||||
    import certifi
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger('gajim.c.httpupload')
 | 
			
		||||
log = logging.getLogger('gajim.c.m.httpupload')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
NS_HTTPUPLOAD_0 = NS_HTTPUPLOAD + ':0'
 | 
			
		||||
 | 
			
		||||
class ConnectionHTTPUpload:
 | 
			
		||||
    """
 | 
			
		||||
    Implement HTTP File Upload
 | 
			
		||||
    (XEP-0363, https://xmpp.org/extensions/xep-0363.html)
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.httpupload = False
 | 
			
		||||
        self.encrypted_upload = False
 | 
			
		||||
 | 
			
		||||
class HTTPUpload:
 | 
			
		||||
    def __init__(self, con):
 | 
			
		||||
        self._con = con
 | 
			
		||||
        self._account = con.name
 | 
			
		||||
 | 
			
		||||
        self.handlers = []
 | 
			
		||||
 | 
			
		||||
        self.available = False
 | 
			
		||||
        self.component = None
 | 
			
		||||
        self.httpupload_namespace = None
 | 
			
		||||
        self._allowed_headers = ['Authorization', 'Cookie', 'Expires']
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +84,7 @@ class ConnectionHTTPUpload:
 | 
			
		|||
 | 
			
		||||
    def handle_agent_info_received(self, event):
 | 
			
		||||
        account = event.conn.name
 | 
			
		||||
        if account != self.name:
 | 
			
		||||
        if account != self._account:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if not app.jid_is_transport(event.jid):
 | 
			
		||||
| 
						 | 
				
			
			@ -112,14 +115,14 @@ class ConnectionHTTPUpload:
 | 
			
		|||
            log.warning('%s does not provide maximum file size', account)
 | 
			
		||||
        else:
 | 
			
		||||
            log.info('%s has a maximum file size of: %s MiB',
 | 
			
		||||
                     account, self.max_file_size/(1024*1024))
 | 
			
		||||
                     account, self.max_file_size / (1024 * 1024))
 | 
			
		||||
 | 
			
		||||
        self.httpupload = True
 | 
			
		||||
        for ctrl in app.interface.msg_win_mgr.get_controls(acct=self.name):
 | 
			
		||||
        self.available = True
 | 
			
		||||
        for ctrl in app.interface.msg_win_mgr.get_controls(acct=self._account):
 | 
			
		||||
            ctrl.update_actions()
 | 
			
		||||
 | 
			
		||||
    def handle_outgoing_stanza(self, event):
 | 
			
		||||
        if event.conn.name != self.name:
 | 
			
		||||
        if event.conn.name != self._account:
 | 
			
		||||
            return
 | 
			
		||||
        message = event.msg_iq.getTagData('body')
 | 
			
		||||
        if message and message in self.messages:
 | 
			
		||||
| 
						 | 
				
			
			@ -177,9 +180,9 @@ class ConnectionHTTPUpload:
 | 
			
		|||
            return
 | 
			
		||||
 | 
			
		||||
        if encryption is not None:
 | 
			
		||||
            app.interface.encrypt_file(file, self.request_slot)
 | 
			
		||||
            app.interface.encrypt_file(file, self._request_slot)
 | 
			
		||||
        else:
 | 
			
		||||
            self.request_slot(file)
 | 
			
		||||
            self._request_slot(file)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def raise_progress_event(status, file, seen=None, total=None):
 | 
			
		||||
| 
						 | 
				
			
			@ -191,13 +194,13 @@ class ConnectionHTTPUpload:
 | 
			
		|||
        app.nec.push_incoming_event(InformationEvent(
 | 
			
		||||
            None, dialog_name=dialog_name, args=args))
 | 
			
		||||
 | 
			
		||||
    def request_slot(self, file):
 | 
			
		||||
    def _request_slot(self, file):
 | 
			
		||||
        GLib.idle_add(self.raise_progress_event, 'request', file)
 | 
			
		||||
        iq = self._build_request(file)
 | 
			
		||||
        log.info("Sending request for slot")
 | 
			
		||||
        app.connections[self.name].connection.SendAndCallForResponse(
 | 
			
		||||
            iq, self.received_slot, {'file': file})
 | 
			
		||||
        
 | 
			
		||||
        self._con.connection.SendAndCallForResponse(
 | 
			
		||||
            iq, self._received_slot, {'file': file})
 | 
			
		||||
 | 
			
		||||
    def _build_request(self, file):
 | 
			
		||||
        iq = nbxmpp.Iq(typ='get', to=self.component)
 | 
			
		||||
        id_ = app.get_an_id()
 | 
			
		||||
| 
						 | 
				
			
			@ -230,7 +233,7 @@ class ConnectionHTTPUpload:
 | 
			
		|||
 | 
			
		||||
        return stanza.getErrorMsg()
 | 
			
		||||
 | 
			
		||||
    def received_slot(self, conn, stanza, file):
 | 
			
		||||
    def _received_slot(self, conn, stanza, file):
 | 
			
		||||
        log.info("Received slot")
 | 
			
		||||
        if stanza.getType() == 'error':
 | 
			
		||||
            self.raise_progress_event('close', file)
 | 
			
		||||
| 
						 | 
				
			
			@ -279,11 +282,11 @@ class ConnectionHTTPUpload:
 | 
			
		|||
        log.info('Uploading file to %s', file.put)
 | 
			
		||||
        log.info('Please download from %s', file.get)
 | 
			
		||||
 | 
			
		||||
        thread = threading.Thread(target=self.upload_file, args=(file,))
 | 
			
		||||
        thread = threading.Thread(target=self._upload_file, args=(file,))
 | 
			
		||||
        thread.daemon = True
 | 
			
		||||
        thread.start()
 | 
			
		||||
 | 
			
		||||
    def upload_file(self, file):
 | 
			
		||||
    def _upload_file(self, file):
 | 
			
		||||
        GLib.idle_add(self.raise_progress_event, 'upload', file)
 | 
			
		||||
        try:
 | 
			
		||||
            file.headers['User-Agent'] = 'Gajim %s' % app.version
 | 
			
		||||
| 
						 | 
				
			
			@ -294,7 +297,7 @@ class ConnectionHTTPUpload:
 | 
			
		|||
                file.put, data=file.stream, headers=file.headers, method='PUT')
 | 
			
		||||
            log.info("Opening Urllib upload request...")
 | 
			
		||||
 | 
			
		||||
            if not app.config.get_per('accounts', self.name, 'httpupload_verify'):
 | 
			
		||||
            if not app.config.get_per('accounts', self._account, 'httpupload_verify'):
 | 
			
		||||
                context = ssl.create_default_context()
 | 
			
		||||
                context.check_hostname = False
 | 
			
		||||
                context.verify_mode = ssl.CERT_NONE
 | 
			
		||||
| 
						 | 
				
			
			@ -309,7 +312,7 @@ class ConnectionHTTPUpload:
 | 
			
		|||
            file.stream.close()
 | 
			
		||||
            log.info('Urllib upload request done, response code: %s',
 | 
			
		||||
                     transfer.getcode())
 | 
			
		||||
            GLib.idle_add(self.upload_complete, transfer.getcode(), file)
 | 
			
		||||
            GLib.idle_add(self._upload_complete, transfer.getcode(), file)
 | 
			
		||||
            return
 | 
			
		||||
        except UploadAbortedException as exc:
 | 
			
		||||
            log.info(exc)
 | 
			
		||||
| 
						 | 
				
			
			@ -326,9 +329,9 @@ class ConnectionHTTPUpload:
 | 
			
		|||
            log.exception("Exception during upload")
 | 
			
		||||
            error_msg = exc
 | 
			
		||||
        GLib.idle_add(self.raise_progress_event, 'close', file)
 | 
			
		||||
        GLib.idle_add(self.on_upload_error, file, error_msg)
 | 
			
		||||
        GLib.idle_add(self._on_upload_error, file, error_msg)
 | 
			
		||||
 | 
			
		||||
    def upload_complete(self, response_code, file):
 | 
			
		||||
    def _upload_complete(self, response_code, file):
 | 
			
		||||
        self.raise_progress_event('close', file)
 | 
			
		||||
        if 200 <= response_code < 300:
 | 
			
		||||
            log.info("Upload completed successfully")
 | 
			
		||||
| 
						 | 
				
			
			@ -341,12 +344,12 @@ class ConnectionHTTPUpload:
 | 
			
		|||
 | 
			
		||||
            if file.groupchat:
 | 
			
		||||
                app.nec.push_outgoing_event(GcMessageOutgoingEvent(
 | 
			
		||||
                    None, account=self.name, jid=file.contact.jid,
 | 
			
		||||
                    None, account=self._account, jid=file.contact.jid,
 | 
			
		||||
                    message=message, automatic_message=False,
 | 
			
		||||
                    session=file.session))
 | 
			
		||||
            else:
 | 
			
		||||
                app.nec.push_outgoing_event(MessageOutgoingEvent(
 | 
			
		||||
                    None, account=self.name, jid=file.contact.jid,
 | 
			
		||||
                    None, account=self._account, jid=file.contact.jid,
 | 
			
		||||
                    message=message, keyID=file.keyID, type_='chat',
 | 
			
		||||
                    automatic_message=False, session=file.session))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -356,7 +359,7 @@ class ConnectionHTTPUpload:
 | 
			
		|||
            self.raise_information_event('httpupload-response-error',
 | 
			
		||||
                                         response_code)
 | 
			
		||||
 | 
			
		||||
    def on_upload_error(self, file, reason):
 | 
			
		||||
    def _on_upload_error(self, file, reason):
 | 
			
		||||
        self.raise_progress_event('close', file)
 | 
			
		||||
        self.raise_information_event('httpupload-error', str(reason))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -428,3 +431,8 @@ class StreamFileWithProgress:
 | 
			
		|||
class UploadAbortedException(Exception):
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Upload Aborted"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPUploadProgressEvent(NetworkIncomingEvent):
 | 
			
		||||
    name = 'httpupload-progress'
 | 
			
		||||
    base_network_events = []
 | 
			
		||||
| 
						 | 
				
			
			@ -574,6 +574,7 @@ class GroupchatControl(ChatControlBase):
 | 
			
		|||
        contact = app.contacts.get_gc_contact(
 | 
			
		||||
            self.account, self.room_jid, self.nick)
 | 
			
		||||
        online = app.gc_connected[self.account][self.room_jid]
 | 
			
		||||
        con = app.connections[self.account]
 | 
			
		||||
 | 
			
		||||
        # Destroy Room
 | 
			
		||||
        win.lookup_action('destroy-' + self.control_id).set_enabled(
 | 
			
		||||
| 
						 | 
				
			
			@ -615,7 +616,7 @@ class GroupchatControl(ChatControlBase):
 | 
			
		|||
        httpupload = win.lookup_action(
 | 
			
		||||
            'send-file-httpupload-' + self.control_id)
 | 
			
		||||
        httpupload.set_enabled(
 | 
			
		||||
            online and app.connections[self.account].httpupload)
 | 
			
		||||
            online and con.get_module('HTTPUpload').available)
 | 
			
		||||
        win.lookup_action('send-file-' + self.control_id).set_enabled(
 | 
			
		||||
            httpupload.get_enabled())
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -93,8 +93,9 @@ from gajim.common import passwords
 | 
			
		|||
from gajim.common import logging_helpers
 | 
			
		||||
from gajim.common.connection_handlers_events import (
 | 
			
		||||
    OurShowEvent, FileRequestErrorEvent, FileTransferCompletedEvent,
 | 
			
		||||
    UpdateRosterAvatarEvent, UpdateGCAvatarEvent, UpdateRoomAvatarEvent,
 | 
			
		||||
    HTTPUploadProgressEvent)
 | 
			
		||||
    UpdateRosterAvatarEvent, UpdateGCAvatarEvent, UpdateRoomAvatarEvent)
 | 
			
		||||
 | 
			
		||||
from gajim.common.modules.httpupload import HTTPUploadProgressEvent
 | 
			
		||||
from gajim.common.connection import Connection
 | 
			
		||||
from gajim.common.file_props import FilesProp
 | 
			
		||||
from gajim import emoticons
 | 
			
		||||
| 
						 | 
				
			
			@ -1142,11 +1143,12 @@ class Interface:
 | 
			
		|||
        con = app.connections[chat_control.account]
 | 
			
		||||
        groupchat = chat_control.type_id == message_control.TYPE_GC
 | 
			
		||||
        for path in paths:
 | 
			
		||||
            con.check_file_before_transfer(path,
 | 
			
		||||
                                           chat_control.encryption,
 | 
			
		||||
                                           chat_control.contact,
 | 
			
		||||
                                           chat_control.session,
 | 
			
		||||
                                           groupchat)
 | 
			
		||||
            con.get_module('HTTPUpload').check_file_before_transfer(
 | 
			
		||||
                path,
 | 
			
		||||
                chat_control.encryption,
 | 
			
		||||
                chat_control.contact,
 | 
			
		||||
                chat_control.session,
 | 
			
		||||
                groupchat)
 | 
			
		||||
 | 
			
		||||
    def encrypt_file(self, file, callback):
 | 
			
		||||
        app.nec.push_incoming_event(HTTPUploadProgressEvent(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -177,7 +177,8 @@ class ServerInfoDialog(Gtk.Dialog):
 | 
			
		|||
                    con.archiving_namespace, con.archiving_namespace,
 | 
			
		||||
                    mam_enabled),
 | 
			
		||||
            Feature('XEP-0363: HTTP File Upload',
 | 
			
		||||
                    con.httpupload, con.httpupload_namespace, None)]
 | 
			
		||||
                    con.get_module('HTTPUpload').available,
 | 
			
		||||
                    con.get_module('HTTPUpload').httpupload_namespace, None)]
 | 
			
		||||
 | 
			
		||||
    def add_info(self, info):
 | 
			
		||||
        self.info_listbox.add(ServerInfoItem(info))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue