parent
59855a6c8d
commit
78f439c611
|
@ -44,6 +44,7 @@ from string import Template
|
|||
from urllib.request import urlopen
|
||||
from urllib.error import URLError
|
||||
|
||||
from gi.repository import GLib
|
||||
if sys.platform in ('win32', 'darwin'):
|
||||
import certifi
|
||||
import OpenSSL.crypto
|
||||
|
@ -85,7 +86,6 @@ class CommonConnection:
|
|||
# ...
|
||||
self.connected = 0
|
||||
self.connection = None # xmpppy ClientCommon instance
|
||||
self.on_purpose = False
|
||||
self.is_zeroconf = False
|
||||
self.password = ''
|
||||
self.server_resource = self._compute_resource()
|
||||
|
@ -98,6 +98,7 @@ class CommonConnection:
|
|||
self.old_show = ''
|
||||
self.priority = app.get_priority(name, 'offline')
|
||||
self.time_to_reconnect = None
|
||||
self._reconnect_timer_source = None
|
||||
|
||||
self.pep = {}
|
||||
# Do we continue connection when we get roster (send presence,get vcard..)
|
||||
|
@ -151,7 +152,7 @@ class CommonConnection:
|
|||
|
||||
def quit(self, kill_core):
|
||||
if kill_core and app.account_is_connected(self.name):
|
||||
self.disconnect(on_purpose=True)
|
||||
self.disconnect(reconnect=False)
|
||||
|
||||
def test_gpg_passphrase(self, password):
|
||||
"""
|
||||
|
@ -190,14 +191,6 @@ class CommonConnection:
|
|||
conn=self))
|
||||
return signed
|
||||
|
||||
def _on_disconnected(self):
|
||||
"""
|
||||
Called when a disconnect request has completed successfully
|
||||
"""
|
||||
self.disconnect(on_purpose=True)
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
show='offline'))
|
||||
|
||||
def get_status(self):
|
||||
return app.SHOW_LIST[self.connected]
|
||||
|
||||
|
@ -453,7 +446,6 @@ class CommonConnection:
|
|||
# set old_show to requested 'show' in case we need to
|
||||
# recconect before we auth to server
|
||||
self.old_show = show
|
||||
self.on_purpose = False
|
||||
self.server_resource = self._compute_resource()
|
||||
if app.is_installed('GPG'):
|
||||
self.USE_GPG = True
|
||||
|
@ -464,7 +456,6 @@ class CommonConnection:
|
|||
return
|
||||
|
||||
if show == 'offline':
|
||||
self.connected = 0
|
||||
if self.connection:
|
||||
app.nec.push_incoming_event(BeforeChangeShowEvent(None,
|
||||
conn=self, show=show, message=msg))
|
||||
|
@ -474,11 +465,8 @@ class CommonConnection:
|
|||
status=msg,
|
||||
caps=False)
|
||||
|
||||
self.connection.RegisterDisconnectHandler(self._on_disconnected)
|
||||
self.connection.send(p, now=True)
|
||||
self.connection.start_disconnect()
|
||||
else:
|
||||
self._on_disconnected()
|
||||
self.disconnect(reconnect=False)
|
||||
return
|
||||
|
||||
if show != 'offline' and self.connected > 0:
|
||||
|
@ -625,29 +613,22 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
self.time_to_reconnect = None
|
||||
self.retrycount = 0
|
||||
|
||||
# We are doing disconnect at so many places, better use one function in all
|
||||
def disconnect(self, on_purpose=False):
|
||||
log.info('Disconnect: on_purpose: %s', on_purpose)
|
||||
self.on_purpose = on_purpose
|
||||
self.connected = 0
|
||||
self.time_to_reconnect = None
|
||||
|
||||
def disconnect(self, reconnect=True, immediately=False):
|
||||
if self.connection is None:
|
||||
if not reconnect:
|
||||
self._sm_resume_data = {}
|
||||
self._disconnect()
|
||||
app.nec.push_incoming_event(OurShowEvent(
|
||||
None, conn=self, show='offline'))
|
||||
return
|
||||
|
||||
app.interface.music_track_changed(None, None, self.name)
|
||||
self.get_module('PEP').reset_stored_publish()
|
||||
self.get_module('VCardAvatars').avatar_advertised = False
|
||||
|
||||
if not on_purpose:
|
||||
self._sm_resume_data = self.connection.get_resume_data()
|
||||
|
||||
app.proxy65_manager.disconnect(self.connection)
|
||||
self.terminate_sessions()
|
||||
self.remove_all_transfers()
|
||||
log.info('Starting to disconnect %s', self.name)
|
||||
disconnect_cb = partial(self._on_disconnect, reconnect)
|
||||
self.connection.disconnect_handlers = [disconnect_cb]
|
||||
if immediately:
|
||||
self.connection.disconnect()
|
||||
ConnectionHandlers._unregister_handlers(self)
|
||||
self.connection = None
|
||||
else:
|
||||
self.connection.start_disconnect()
|
||||
|
||||
def set_oldst(self): # Set old state
|
||||
if self.old_show:
|
||||
|
@ -659,31 +640,52 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
app.nec.push_incoming_event(OurShowEvent(
|
||||
None, conn=self, show=app.SHOW_LIST[self.connected]))
|
||||
|
||||
def disconnectedReconnCB(self):
|
||||
"""
|
||||
Called when we are disconnected
|
||||
"""
|
||||
log.info('disconnectedReconnCB called')
|
||||
def _on_disconnect(self, reconnect=True):
|
||||
# Clear disconnect handlers
|
||||
self.connection.disconnect_handlers = []
|
||||
|
||||
if reconnect:
|
||||
reconnect = app.config.get_per(
|
||||
'accounts', self.name, 'autoreconnect')
|
||||
|
||||
log.info('Disconnect %s, reconnect: %s', self.name, reconnect)
|
||||
|
||||
if reconnect:
|
||||
if app.account_is_connected(self.name):
|
||||
# we cannot change our status to offline or connecting
|
||||
# after we auth to server
|
||||
self.old_show = app.SHOW_LIST[self.connected]
|
||||
self.connected = 0
|
||||
if not self.on_purpose:
|
||||
|
||||
if not self._sm_resume_data:
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
show='offline'))
|
||||
self._sm_resume_data = self.connection.get_resume_data()
|
||||
self._disconnect()
|
||||
self._set_reconnect_timer()
|
||||
|
||||
else:
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
show='error'))
|
||||
if self.connection:
|
||||
self.connection.UnregisterDisconnectHandler(
|
||||
self.disconnectedReconnCB)
|
||||
self.disconnect()
|
||||
if app.config.get_per('accounts', self.name, 'autoreconnect'):
|
||||
self._sm_resume_data = {}
|
||||
self._disconnect()
|
||||
app.nec.push_incoming_event(OurShowEvent(
|
||||
None, conn=self, show='offline'))
|
||||
|
||||
def _disconnect(self):
|
||||
log.info('Set state disconnected')
|
||||
self.connected = 0
|
||||
self.disable_reconnect_timer()
|
||||
|
||||
app.interface.music_track_changed(None, None, self.name)
|
||||
self.get_module('PEP').reset_stored_publish()
|
||||
self.get_module('VCardAvatars').avatar_advertised = False
|
||||
|
||||
app.proxy65_manager.disconnect(self.connection)
|
||||
self.terminate_sessions()
|
||||
self.remove_all_transfers()
|
||||
ConnectionHandlers._unregister_handlers(self)
|
||||
self.connection = None
|
||||
|
||||
def _set_reconnect_timer(self):
|
||||
self.connected = -1
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
show='error'))
|
||||
app.nec.push_incoming_event(OurShowEvent(
|
||||
None, conn=self, show='error'))
|
||||
if app.status_before_autoaway[self.name]:
|
||||
# We were auto away. So go back online
|
||||
self.status = app.status_before_autoaway[self.name]
|
||||
|
@ -698,22 +700,17 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
self.last_time_to_reconnect *= 1.5
|
||||
self.time_to_reconnect = int(self.last_time_to_reconnect)
|
||||
log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect)
|
||||
app.idlequeue.set_alarm(self._reconnect_alarm,
|
||||
self.time_to_reconnect)
|
||||
elif self.on_connect_failure:
|
||||
self.on_connect_failure()
|
||||
self.on_connect_failure = None
|
||||
else:
|
||||
# show error dialog
|
||||
self._connection_lost()
|
||||
else:
|
||||
self.disconnect()
|
||||
self.on_purpose = False
|
||||
# END disconnectedReconnCB
|
||||
self._reconnect_timer_source = GLib.timeout_add_seconds(
|
||||
self.time_to_reconnect, self._reconnect_alarm)
|
||||
|
||||
def disable_reconnect_timer(self):
|
||||
self.time_to_reconnect = None
|
||||
if self._reconnect_timer_source is not None:
|
||||
GLib.source_remove(self._reconnect_timer_source)
|
||||
self._reconnect_timer_source = None
|
||||
|
||||
def _connection_lost(self):
|
||||
log.info('_connection_lost')
|
||||
self.disconnect(on_purpose=False)
|
||||
if self.removing_account:
|
||||
return
|
||||
app.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
|
||||
|
@ -786,7 +783,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
if self.connection:
|
||||
self.connection.UnregisterDisconnectHandler(
|
||||
self._on_new_account)
|
||||
self.disconnect(on_purpose=True)
|
||||
self.disconnect(reconnect=False)
|
||||
# it's the second time we get the form, we have info user
|
||||
# typed, so send them
|
||||
if is_form:
|
||||
|
@ -813,7 +810,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
conn=self, config=conf, is_form=is_form))
|
||||
self.connection.UnregisterDisconnectHandler(
|
||||
self._on_new_account)
|
||||
self.disconnect(on_purpose=True)
|
||||
self.disconnect(reconnect=False)
|
||||
return
|
||||
if not data[1]: # wrong answer
|
||||
app.nec.push_incoming_event(InformationEvent(
|
||||
|
@ -1002,7 +999,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
self._connection_lost()
|
||||
else:
|
||||
# try reconnect if connection has failed before auth to server
|
||||
self.disconnectedReconnCB()
|
||||
self._set_reconnect_timer()
|
||||
|
||||
return
|
||||
|
||||
|
@ -1104,7 +1101,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
if not con_type:
|
||||
# we are not retrying, and not conecting
|
||||
if not self.retrycount and self.connected != 0:
|
||||
self.disconnect(on_purpose=True)
|
||||
self._disconnect()
|
||||
if self._proxy:
|
||||
pritxt = _('Could not connect to "%(host)s" via proxy "%(proxy)s"') %\
|
||||
{'host': self._hostname, 'proxy': self._proxy['host']}
|
||||
|
@ -1129,14 +1126,11 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
log.error('Connection to proxy failed: %s', reason)
|
||||
self.time_to_reconnect = None
|
||||
self.on_connect_failure = None
|
||||
self.disconnect(on_purpose=True)
|
||||
self._disconnect()
|
||||
app.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
|
||||
title=_('Connection to proxy failed'), msg=reason))
|
||||
|
||||
def _connect_success(self, con, con_type):
|
||||
if not self.connected: # We went offline during connecting process
|
||||
# FIXME - not possible, maybe it was when we used threads
|
||||
return
|
||||
log.info('Connect successfull')
|
||||
_con_type = con_type
|
||||
if _con_type != self._current_type:
|
||||
|
@ -1144,7 +1138,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
'is %s and returned is %s', self._current_type, _con_type)
|
||||
self._connect_to_next_host()
|
||||
return
|
||||
con.RegisterDisconnectHandler(self._on_disconnected)
|
||||
con.RegisterDisconnectHandler(self.disconnect)
|
||||
if _con_type == 'plain' and app.config.get_per('accounts', self.name,
|
||||
'action_when_plaintext_connection') == 'warn':
|
||||
app.nec.push_incoming_event(PlainConnectionEvent(None, conn=self,
|
||||
|
@ -1152,7 +1146,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
return True
|
||||
if _con_type == 'plain' and app.config.get_per('accounts', self.name,
|
||||
'action_when_plaintext_connection') == 'disconnect':
|
||||
self.disconnect(on_purpose=True)
|
||||
self.disconnect(reconnect=False)
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
show='offline'))
|
||||
return False
|
||||
|
@ -1168,7 +1162,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
|
||||
def connection_accepted(self, con, con_type):
|
||||
if not con or not con.Connection:
|
||||
self.disconnect(on_purpose=True)
|
||||
self._disconnect()
|
||||
app.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
|
||||
title=_('Could not connect to account %s') % self.name,
|
||||
msg=_('Connection with account %s has been lost. Retry '
|
||||
|
@ -1179,8 +1173,8 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
self.connection_auto_accepted = False
|
||||
self.connected_hostname = self._current_host['host']
|
||||
self.on_connect_failure = None
|
||||
con.UnregisterDisconnectHandler(self._on_disconnected)
|
||||
con.RegisterDisconnectHandler(self.disconnectedReconnCB)
|
||||
con.UnregisterDisconnectHandler(self.disconnect)
|
||||
con.RegisterDisconnectHandler(self._on_disconnect)
|
||||
log.debug('Connected to server %s:%s with %s',
|
||||
self._current_host['host'], self._current_host['port'],
|
||||
con_type)
|
||||
|
@ -1287,7 +1281,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
|
||||
def ssl_certificate_accepted(self):
|
||||
if not self.connection:
|
||||
self.disconnect(on_purpose=True)
|
||||
self._disconnect()
|
||||
app.nec.push_incoming_event(
|
||||
ConnectionLostEvent(
|
||||
None, conn=self,
|
||||
|
@ -1337,7 +1331,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
# Forget password, it's wrong
|
||||
self.password = None
|
||||
log.debug("Couldn't authenticate to %s", self._hostname)
|
||||
self.disconnect(on_purpose=True)
|
||||
self.disconnect(reconnect=False)
|
||||
app.nec.push_incoming_event(
|
||||
OurShowEvent(None, conn=self, show='offline'))
|
||||
app.nec.push_incoming_event(InformationEvent(
|
||||
|
@ -1400,8 +1394,6 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
# If we are already connected, and privacy rules are supported, send
|
||||
# offline presence first as it's required by XEP-0126
|
||||
if self.connected > 1 and self.get_module('PrivacyLists').supported:
|
||||
self.on_purpose = True
|
||||
|
||||
self.remove_all_transfers()
|
||||
self.get_module('Presence').send_presence(
|
||||
typ='unavailable',
|
||||
|
@ -1460,6 +1452,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
self.connect()
|
||||
|
||||
def connect_and_init(self, show, msg, sign_msg):
|
||||
self.disable_reconnect_timer()
|
||||
self.continue_connect_info = [show, msg, sign_msg]
|
||||
self.connect_and_auth()
|
||||
|
||||
|
@ -1816,7 +1809,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
type_ != 'ANONYMOUS':
|
||||
app.nec.push_incoming_event(
|
||||
NonAnonymousServerErrorEvent(None, conn=self))
|
||||
self._on_disconnected()
|
||||
self.disconnect(reconnect=False)
|
||||
return
|
||||
self.pasword_callback = (callback, type_)
|
||||
if type_ == 'X-MESSENGER-OAUTH2':
|
||||
|
@ -1843,7 +1836,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
'%s&scope=wl.messenger%%20wl.offline_access&'
|
||||
'response_type=code&redirect_uri=%s') % (client_id, script_url)
|
||||
helpers.launch_browser_mailer('url', token_url)
|
||||
self.disconnect(on_purpose=True)
|
||||
self.disconnect(reconnect=False)
|
||||
app.nec.push_incoming_event(
|
||||
Oauth2CredentialsRequiredEvent(None, conn=self))
|
||||
return
|
||||
|
|
|
@ -71,7 +71,7 @@ class Ping:
|
|||
|
||||
# We haven't got the pong in time, disco and reconnect
|
||||
log.warning('No reply received for keepalive ping. Reconnecting...')
|
||||
self._con.disconnectedReconnCB()
|
||||
self._con.disconnect(immediately=True)
|
||||
|
||||
def send_ping(self, contact: ContactsT) -> None:
|
||||
if not app.account_is_connected(self._account):
|
||||
|
|
|
@ -676,7 +676,7 @@ class ClientZeroconf:
|
|||
def zeroconf_init(self, show, msg):
|
||||
self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service,
|
||||
self.caller._on_remove_service, self.caller._on_name_conflictCB,
|
||||
self.caller._on_disconnected, self.caller._on_error,
|
||||
self.caller._on_disconnect, self.caller._on_error,
|
||||
self.caller.username, self.caller.host, self.port)
|
||||
self.zeroconf.txt['msg'] = msg
|
||||
self.zeroconf.txt['status'] = show
|
||||
|
|
|
@ -127,7 +127,7 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
|
|||
def _on_resolve_timeout(self):
|
||||
if self.connected:
|
||||
if not self.connection.resolve_all():
|
||||
self._on_disconnected()
|
||||
self.disconnect()
|
||||
return False
|
||||
diffs = self.roster.getDiffs()
|
||||
for key in diffs:
|
||||
|
@ -162,22 +162,6 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
|
|||
app.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
|
||||
None, conn=self, fjid=jid, show='offline', status=''))
|
||||
|
||||
def disconnectedReconnCB(self):
|
||||
"""
|
||||
Called when we are disconnected. Comes from network manager for example
|
||||
we don't try to reconnect, network manager will tell us when we can
|
||||
"""
|
||||
if app.account_is_connected(self.name):
|
||||
# we cannot change our status to offline or connecting
|
||||
# after we auth to server
|
||||
self.old_show = STATUS_LIST[self.connected]
|
||||
self.connected = 0
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
show='offline'))
|
||||
# random number to show we wait network manager to send us a reconenct
|
||||
self.time_to_reconnect = 5
|
||||
self.on_purpose = False
|
||||
|
||||
def _on_name_conflictCB(self, alt_name):
|
||||
self.disconnect()
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
|
@ -241,14 +225,33 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
|
|||
GLib.timeout_add_seconds(5, self._on_resolve_timeout)
|
||||
return True
|
||||
|
||||
def disconnect(self, on_purpose=False):
|
||||
self.connected = 0
|
||||
def disconnect(self, reconnect=True, immediately=True):
|
||||
log.info('Start disconnecting zeroconf')
|
||||
if reconnect:
|
||||
if app.account_is_connected(self.name):
|
||||
# we cannot change our status to offline or connecting
|
||||
# after we auth to server
|
||||
self.old_show = STATUS_LIST[self.connected]
|
||||
|
||||
# random number to show we wait network manager to send
|
||||
# us a reconenct
|
||||
self.time_to_reconnect = 5
|
||||
else:
|
||||
self.time_to_reconnect = None
|
||||
|
||||
self.connected = 0
|
||||
if self.connection:
|
||||
self.connection.disconnect()
|
||||
self.connection = None
|
||||
# stop calling the timeout
|
||||
self.call_resolve_timeout = False
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
show='offline'))
|
||||
|
||||
def _on_disconnect(self):
|
||||
self.connected = 0
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
|
||||
show='offline'))
|
||||
|
||||
def reannounce(self):
|
||||
if self.connected:
|
||||
|
|
|
@ -185,7 +185,7 @@ class RemoveAccountWindow:
|
|||
# Close all opened windows
|
||||
app.interface.roster.close_all(self.account, force=True)
|
||||
if self.account in app.connections:
|
||||
app.connections[self.account].disconnect(on_purpose=True)
|
||||
app.connections[self.account].disconnect(reconnect=False)
|
||||
app.connections[self.account].cleanup()
|
||||
del app.connections[self.account]
|
||||
app.logger.remove_roster(app.get_jid_from_account(self.account))
|
||||
|
|
|
@ -1341,7 +1341,7 @@ class Interface:
|
|||
|
||||
def on_cancel():
|
||||
del self.instances[account]['online_dialog']['ssl_error']
|
||||
obj.conn.disconnect(on_purpose=True)
|
||||
obj.conn.disconnect(reconnect=False)
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
|
||||
show='offline'))
|
||||
|
||||
|
@ -1404,7 +1404,7 @@ class Interface:
|
|||
def on_cancel():
|
||||
del self.instances[obj.conn.name]['online_dialog']\
|
||||
['plain_connection']
|
||||
obj.conn.disconnect(on_purpose=True)
|
||||
obj.conn.disconnect(reconnect=False)
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
|
||||
show='offline'))
|
||||
|
||||
|
@ -1436,7 +1436,7 @@ class Interface:
|
|||
|
||||
def on_cancel():
|
||||
del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
|
||||
obj.conn.disconnect(on_purpose=True)
|
||||
obj.conn.disconnect(reconnect=False)
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
|
||||
show='offline'))
|
||||
|
||||
|
@ -1478,7 +1478,7 @@ class Interface:
|
|||
def on_cancel():
|
||||
del self.instances[obj.conn.name]['online_dialog']\
|
||||
['insecure_password']
|
||||
obj.conn.disconnect(on_purpose=True)
|
||||
obj.conn.disconnect(reconnect=False)
|
||||
app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
|
||||
show='offline'))
|
||||
|
||||
|
@ -2458,12 +2458,12 @@ class Interface:
|
|||
for connection in app.connections.values():
|
||||
if connection.connected <= 0 and connection.time_to_reconnect:
|
||||
log.info('Connect %s', connection.name)
|
||||
connection.reconnect()
|
||||
GLib.timeout_add_seconds(2, connection.reconnect)
|
||||
else:
|
||||
for connection in app.connections.values():
|
||||
if connection.connected > 1:
|
||||
log.info('Disconnect %s', connection.name)
|
||||
connection.disconnectedReconnCB()
|
||||
connection.disconnect(immediately=True)
|
||||
|
||||
def create_zeroconf_default_config(self):
|
||||
if app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, 'name'):
|
||||
|
|
Loading…
Reference in New Issue