371 lines
14 KiB
Python
371 lines
14 KiB
Python
# This file is part of Gajim.
|
|
#
|
|
# Gajim is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published
|
|
# by the Free Software Foundation; version 3 only.
|
|
#
|
|
# Gajim is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import logging
|
|
from collections import namedtuple
|
|
from datetime import timedelta
|
|
|
|
import nbxmpp
|
|
from nbxmpp.errors import StanzaError
|
|
from nbxmpp.namespaces import Namespace
|
|
from gi.repository import Gtk
|
|
from gi.repository import Gdk
|
|
from gi.repository import Pango
|
|
|
|
from gajim.common import app
|
|
from gajim.common import ged
|
|
from gajim.common.helpers import open_uri
|
|
from gajim.common.i18n import _
|
|
|
|
from .util import get_builder
|
|
from .util import EventHelper
|
|
from .util import open_window
|
|
|
|
log = logging.getLogger('gajim.gui.server_info')
|
|
|
|
|
|
class ServerInfo(Gtk.ApplicationWindow, EventHelper):
|
|
def __init__(self, account):
|
|
Gtk.ApplicationWindow.__init__(self)
|
|
EventHelper.__init__(self)
|
|
self.set_name('ServerInfo')
|
|
self.set_application(app.app)
|
|
self.set_position(Gtk.WindowPosition.CENTER)
|
|
self.set_default_size(400, 600)
|
|
self.set_show_menubar(False)
|
|
self.set_title(_('Server Info'))
|
|
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
|
|
|
|
self.account = account
|
|
self._destroyed = False
|
|
|
|
self._ui = get_builder('server_info.ui')
|
|
self.add(self._ui.server_info_notebook)
|
|
|
|
self.connect('destroy', self.on_destroy)
|
|
self.connect('key-press-event', self._on_key_press)
|
|
self._ui.connect_signals(self)
|
|
|
|
self.register_events([
|
|
('server-disco-received', ged.GUI1, self._server_disco_received),
|
|
])
|
|
|
|
self.version = ''
|
|
self.hostname = app.get_hostname_from_account(account)
|
|
self._ui.server_hostname.set_text(self.hostname)
|
|
con = app.connections[account]
|
|
con.get_module('SoftwareVersion').request_software_version(
|
|
self.hostname, callback=self._software_version_received)
|
|
self.request_last_activity()
|
|
|
|
server_info = con.get_module('Discovery').server_info
|
|
self._add_contact_addresses(server_info.dataforms)
|
|
|
|
self.cert = con.certificate
|
|
self._add_connection_info()
|
|
|
|
self.feature_listbox = Gtk.ListBox()
|
|
self.feature_listbox.set_name('ServerInfo')
|
|
self.feature_listbox.set_selection_mode(Gtk.SelectionMode.NONE)
|
|
self._ui.features_scrolled.add(self.feature_listbox)
|
|
for feature in self.get_features():
|
|
self.add_feature(feature)
|
|
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
|
|
|
self.show_all()
|
|
|
|
def _on_key_press(self, _widget, event):
|
|
if event.keyval == Gdk.KEY_Escape:
|
|
self.destroy()
|
|
|
|
def _add_connection_info(self):
|
|
# Connection type
|
|
nbxmpp_client = app.connections[self.account].connection
|
|
address = nbxmpp_client.current_address
|
|
|
|
self._ui.connection_type.set_text(address.type.value)
|
|
if address.type.is_plain:
|
|
self._ui.connection_type.get_style_context().add_class(
|
|
'error-color')
|
|
|
|
# Connection proxy
|
|
proxy = address.proxy
|
|
if proxy is not None:
|
|
self._ui.proxy_type.set_text(proxy.type)
|
|
self._ui.proxy_host.set_text(proxy.host)
|
|
|
|
self._ui.cert_button.set_sensitive(self.cert)
|
|
|
|
self._ui.domain.set_text(address.domain)
|
|
|
|
visible = address.service is not None
|
|
self._ui.dns_label.set_visible(visible)
|
|
self._ui.dns.set_visible(visible)
|
|
self._ui.dns.set_text(address.service or '')
|
|
|
|
visible = nbxmpp_client.remote_address is not None
|
|
self._ui.ip_port_label.set_visible(visible)
|
|
self._ui.ip_port.set_visible(visible)
|
|
self._ui.ip_port.set_text(nbxmpp_client.remote_address or '')
|
|
|
|
visible = address.uri is not None
|
|
self._ui.websocket_label.set_visible(visible)
|
|
self._ui.websocket.set_visible(visible)
|
|
self._ui.websocket.set_text(address.uri or '')
|
|
|
|
def _on_cert_button_clicked(self, _button):
|
|
open_window('CertificateDialog',
|
|
account=self.account,
|
|
transient_for=self,
|
|
cert=self.cert)
|
|
|
|
def request_last_activity(self):
|
|
if not app.account_is_connected(self.account):
|
|
return
|
|
con = app.connections[self.account]
|
|
iq = nbxmpp.Iq(to=self.hostname, typ='get', queryNS=Namespace.LAST)
|
|
con.connection.SendAndCallForResponse(iq, self._on_last_activity)
|
|
|
|
def _add_contact_addresses(self, dataforms):
|
|
fields = {
|
|
'admin-addresses': _('Admin'),
|
|
'status-addresses': _('Status'),
|
|
'support-addresses': _('Support'),
|
|
'security-addresses': _('Security'),
|
|
'feedback-addresses': _('Feedback'),
|
|
'abuse-addresses': _('Abuse'),
|
|
'sales-addresses': _('Sales'),
|
|
}
|
|
|
|
addresses = self._get_addresses(fields, dataforms)
|
|
if addresses is None:
|
|
self._ui.no_addresses_label.set_visible(True)
|
|
return
|
|
|
|
row_count = 4
|
|
for address_type, values in addresses.items():
|
|
label = self._get_address_type_label(fields[address_type])
|
|
self._ui.server.attach(label, 0, row_count, 1, 1)
|
|
for index, value in enumerate(values):
|
|
last = index == len(values) - 1
|
|
label = self._get_address_label(value, last=last)
|
|
self._ui.server.attach(label, 1, row_count, 1, 1)
|
|
row_count += 1
|
|
|
|
@staticmethod
|
|
def _get_addresses(fields, dataforms):
|
|
addresses = {}
|
|
for form in dataforms:
|
|
field = form.vars.get('FORM_TYPE')
|
|
if field.value != 'http://jabber.org/network/serverinfo':
|
|
continue
|
|
|
|
for address_type in fields:
|
|
field = form.vars.get(address_type)
|
|
if field is None:
|
|
continue
|
|
|
|
if field.type_ != 'list-multi':
|
|
continue
|
|
|
|
if not field.values:
|
|
continue
|
|
addresses[address_type] = field.values
|
|
|
|
return addresses or None
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_address_type_label(text):
|
|
label = Gtk.Label(label=text)
|
|
label.set_halign(Gtk.Align.END)
|
|
label.set_valign(Gtk.Align.START)
|
|
label.get_style_context().add_class('dim-label')
|
|
return label
|
|
|
|
def _get_address_label(self, address, last=False):
|
|
label = Gtk.Label()
|
|
label.set_markup('<a href="%s">%s</a>' % (address, address))
|
|
label.set_ellipsize(Pango.EllipsizeMode.END)
|
|
label.set_xalign(0)
|
|
label.set_halign(Gtk.Align.START)
|
|
label.get_style_context().add_class('link-button')
|
|
label.connect('activate-link', self._on_activate_link)
|
|
if last:
|
|
label.set_margin_bottom(6)
|
|
return label
|
|
|
|
def _on_activate_link(self, label, *args):
|
|
open_uri(label.get_text(), account=self.account)
|
|
return Gdk.EVENT_STOP
|
|
|
|
def _on_last_activity(self, _nbxmpp_client, stanza):
|
|
if self._destroyed:
|
|
# Window got closed in the meantime
|
|
return
|
|
if not nbxmpp.isResultNode(stanza):
|
|
log.warning('Received malformed result: %s', stanza)
|
|
return
|
|
if stanza.getQueryNS() != Namespace.LAST:
|
|
log.warning('Wrong namespace on result: %s', stanza)
|
|
return
|
|
try:
|
|
seconds = int(stanza.getQuery().getAttr('seconds'))
|
|
except (ValueError, TypeError, AttributeError):
|
|
log.exception('Received malformed last activity result')
|
|
else:
|
|
delta = timedelta(seconds=seconds)
|
|
hours = 0
|
|
if seconds >= 3600:
|
|
hours = delta.seconds // 3600
|
|
uptime = _('%(days)s days, %(hours)s hours') % {
|
|
'days': delta.days, 'hours': hours}
|
|
self._ui.server_uptime.set_text(uptime)
|
|
|
|
def _software_version_received(self, task):
|
|
try:
|
|
result = task.finish()
|
|
except StanzaError:
|
|
self.version = _('Unknown')
|
|
else:
|
|
self.version = '%s %s' % (result.name, result.version)
|
|
|
|
self._ui.server_software.set_text(self.version)
|
|
|
|
@staticmethod
|
|
def update(func, listbox):
|
|
for index, item in enumerate(func()):
|
|
row = listbox.get_row_at_index(index)
|
|
row.get_child().update(item)
|
|
row.set_tooltip_text(row.get_child().tooltip)
|
|
|
|
def _server_disco_received(self, _event):
|
|
self.update(self.get_features, self.feature_listbox)
|
|
|
|
def add_feature(self, feature):
|
|
item = FeatureItem(feature)
|
|
self.feature_listbox.add(item)
|
|
item.get_parent().set_tooltip_text(item.tooltip or '')
|
|
|
|
def get_features(self):
|
|
con = app.connections[self.account]
|
|
Feature = namedtuple('Feature',
|
|
['name', 'available', 'tooltip', 'enabled'])
|
|
Feature.__new__.__defaults__ = (None, None) # type: ignore
|
|
|
|
# HTTP File Upload
|
|
http_upload_info = con.get_module('HTTPUpload').httpupload_namespace
|
|
if con.get_module('HTTPUpload').available:
|
|
max_file_size = con.get_module('HTTPUpload').max_file_size
|
|
if max_file_size is not None:
|
|
max_file_size = max_file_size / (1024 * 1024)
|
|
http_upload_info = http_upload_info + ' (max. %s MiB)' % \
|
|
max_file_size
|
|
|
|
return [
|
|
Feature('XEP-0045: Multi-User Chat',
|
|
con.get_module('MUC').supported),
|
|
Feature('XEP-0054: vcard-temp',
|
|
con.get_module('VCardTemp').supported),
|
|
Feature('XEP-0077: In-Band Registration',
|
|
con.get_module('Register').supported),
|
|
Feature('XEP-0163: Personal Eventing Protocol',
|
|
con.get_module('PEP').supported),
|
|
Feature('XEP-0163: #publish-options',
|
|
con.get_module('PubSub').publish_options),
|
|
Feature('XEP-0191: Blocking Command',
|
|
con.get_module('Blocking').supported,
|
|
Namespace.BLOCKING),
|
|
Feature('XEP-0198: Stream Management',
|
|
con.features.has_sm, Namespace.STREAM_MGMT),
|
|
Feature('XEP-0258: Security Labels in XMPP',
|
|
con.get_module('SecLabels').supported,
|
|
Namespace.SECLABEL),
|
|
Feature('XEP-0280: Message Carbons',
|
|
con.get_module('Carbons').supported,
|
|
Namespace.CARBONS),
|
|
Feature('XEP-0313: Message Archive Management',
|
|
con.get_module('MAM').available),
|
|
Feature('XEP-0363: HTTP File Upload',
|
|
con.get_module('HTTPUpload').available,
|
|
http_upload_info),
|
|
Feature('XEP-0398: Avatar Conversion',
|
|
con.get_module('VCardAvatars').avatar_conversion_available),
|
|
Feature('XEP-0411: Bookmarks Conversion',
|
|
con.get_module('Bookmarks').conversion),
|
|
Feature('XEP-0402: Bookmarks Compat',
|
|
con.get_module('Bookmarks').compat),
|
|
Feature('XEP-0402: Bookmarks Compat PEP',
|
|
con.get_module('Bookmarks').compat_pep)
|
|
]
|
|
|
|
def _on_clipboard_button_clicked(self, _widget):
|
|
server_software = 'Server Software: %s\n' % self.version
|
|
server_features = ''
|
|
|
|
for feature in self.get_features():
|
|
if feature.available:
|
|
available = 'Yes'
|
|
else:
|
|
available = 'No'
|
|
if feature.tooltip is not None:
|
|
tooltip = '(%s)' % feature.tooltip
|
|
else:
|
|
tooltip = ''
|
|
server_features += '%s: %s %s\n' % (
|
|
feature.name, available, tooltip)
|
|
|
|
clipboard_text = server_software + server_features
|
|
self.clipboard.set_text(clipboard_text, -1)
|
|
|
|
def on_destroy(self, *args):
|
|
self._destroyed = True
|
|
|
|
|
|
class FeatureItem(Gtk.Grid):
|
|
def __init__(self, feature):
|
|
super().__init__()
|
|
self.tooltip = feature.tooltip
|
|
self.set_column_spacing(6)
|
|
|
|
self.icon = Gtk.Image()
|
|
self.feature_label = Gtk.Label(label=feature.name)
|
|
self.set_feature(feature.available, feature.enabled)
|
|
|
|
self.add(self.icon)
|
|
self.add(self.feature_label)
|
|
|
|
def set_feature(self, available, enabled):
|
|
self.icon.get_style_context().remove_class('error-color')
|
|
self.icon.get_style_context().remove_class('warning-color')
|
|
self.icon.get_style_context().remove_class('success-color')
|
|
|
|
if not available:
|
|
self.icon.set_from_icon_name('window-close-symbolic',
|
|
Gtk.IconSize.MENU)
|
|
self.icon.get_style_context().add_class('error-color')
|
|
elif enabled is False:
|
|
self.icon.set_from_icon_name('dialog-warning-symbolic',
|
|
Gtk.IconSize.MENU)
|
|
self.tooltip += _('\nDisabled in preferences')
|
|
self.icon.get_style_context().add_class('warning-color')
|
|
else:
|
|
self.icon.set_from_icon_name('emblem-ok-symbolic',
|
|
Gtk.IconSize.MENU)
|
|
self.icon.get_style_context().add_class('success-color')
|
|
|
|
def update(self, feature):
|
|
self.tooltip = feature.tooltip
|
|
self.set_feature(feature.available, feature.enabled)
|