diff --git a/gajim/common/connection.py b/gajim/common/connection.py index 5cce4a5403b5a50508d0498e7df28c9639d7caab..84ac3c2efc13b1e1cd088dfe7cea0b55a9f3872b 100644 --- a/gajim/common/connection.py +++ b/gajim/common/connection.py @@ -438,9 +438,6 @@ def __init__(self, name): self.new_account_form = None self.last_sent = [] - self._unregister_account = False - self._unregister_account_cb = None - self.music_track_info = 0 self.register_supported = False @@ -541,7 +538,7 @@ def disconnect(self, reconnect=True, immediately=False): log.info('Starting to disconnect %s', self.name) disconnect_cb = partial(self._on_disconnect, reconnect) self.connection.disconnect_handlers = [disconnect_cb] - if immediately: + if immediately or self.removing_account: self.connection.disconnect() else: self.connection.start_disconnect() @@ -562,7 +559,7 @@ def _on_disconnect(self, reconnect=True): log.info('Disconnect %s, reconnect: %s', self.name, reconnect) - if reconnect: + if reconnect and not self.removing_account: if app.account_is_connected(self.name): # we cannot change our status to offline or connecting # after we auth to server @@ -1263,10 +1260,7 @@ def _register_handlers(self, con, con_type): self._register_new_handlers(con) def _on_auth_successful(self): - if self._unregister_account: - self._on_unregister_account_connect() - else: - self.connection.bind() + self.connection.bind() def _on_auth_failed(self, reason, text): if not app.config.get_per('accounts', self.name, 'savepass'): @@ -1675,41 +1669,12 @@ def send_gc_message(self, obj): stanza_id=obj.stanza_id, additional_data=obj.additional_data)) - def unregister_account(self, on_remove_success): - self._unregister_account = True - self._unregister_account_cb = on_remove_success - if self.connected == 0: - self.connect_and_auth() - else: - self._on_unregister_account_connect() - - def _on_unregister_account_connect(self): - self.removing_account = True - if app.account_is_connected(self.name): - hostname = app.config.get_per('accounts', self.name, 'hostname') - iq = nbxmpp.Iq(typ='set', to=hostname) - id_ = self.connection.getAnID() - iq.setID(id_) - iq.setTag(nbxmpp.NS_REGISTER + ' query').setTag('remove') - def _on_answer(con, result): - if result.getID() == id_: - self._on_unregister_finished(True) - return - app.nec.push_incoming_event(InformationEvent( - None, dialog_name='unregister-error', - kwargs={'server': hostname, 'error': result.getErrorMsg()})) - self._on_unregister_finished(False) - self.connection.RegisterHandler( - 'iq', _on_answer, 'result', system=True) - self.connection.SendAndWaitForResponse(iq) + def unregister_account(self, callback): + if not app.account_is_connected(self.name): return - self._on_unregister_finished(False) - self.removing_account = False - - def _on_unregister_finished(self, result): - self._unregister_account_cb(result) - self._unregister_account = False - self._unregister_account_cb = None + self.removing_account = True + self.connection.get_module('Register').unregister( + callback=callback) def _reconnect_alarm(self): if not app.config.get_per('accounts', self.name, 'active'): diff --git a/gajim/data/gui/remove_account_window.ui b/gajim/data/gui/remove_account_window.ui deleted file mode 100644 index 0fbe58ebac2c395ab97b4c449c2abdce4e4f125e..0000000000000000000000000000000000000000 --- a/gajim/data/gui/remove_account_window.ui +++ /dev/null @@ -1,161 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.22.1 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkWindow" id="remove_account_window"> - <property name="can_focus">False</property> - <property name="border_width">18</property> - <property name="resizable">False</property> - <property name="type_hint">dialog</property> - <signal name="destroy" handler="_on_destroy" swapped="no"/> - <child> - <placeholder/> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">18</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">What do you want to do?</property> - <property name="use_markup">True</property> - <style> - <class name="large-header"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">12</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">user-trash-symbolic</property> - <property name="icon_size">6</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkRadioButton" id="remove_only_radiobutton"> - <property name="label" translatable="yes">Remove account _only from Gajim</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="halign">start</property> - <property name="use_underline">True</property> - <property name="active">True</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="remove_and_unregister_radiobutton"> - <property name="label" translatable="yes">Remove account from Gajim and from _server</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="halign">start</property> - <property name="use_underline">True</property> - <property name="active">True</property> - <property name="draw_indicator">True</property> - <property name="group">remove_only_radiobutton</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButtonBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">12</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="cancel_button"> - <property name="label">_Cancel</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="on_cancel_button_clicked" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="remove_button"> - <property name="label" translatable="yes">_Remove</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="on_remove_button_clicked" swapped="no"/> - <style> - <class name="destructive-action"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">2</property> - </packing> - </child> - </object> - </child> - </object> -</interface> diff --git a/gajim/dialog_messages.py b/gajim/dialog_messages.py index 7ac8091f430c63f8ad6100278ddd75cad576bb5b..a2bff3f8f988f583d6ce741b6cfe3360db353eee 100644 --- a/gajim/dialog_messages.py +++ b/gajim/dialog_messages.py @@ -88,11 +88,6 @@ _('This account %s doesn\'t support invisibility.'), ErrorDialog), - 'unregister-error': Message( - _('Unregister Failed'), - _('Unregistration on server %(server)s failed: %(error)s'), - ErrorDialog), - 'agent-register-success': Message( _('Registration Succeeded'), _('Registration with agent %s succeeded.'), diff --git a/gajim/gtk/accounts.py b/gajim/gtk/accounts.py index 494c63dc96b1c26647bac96794e4ab399caac25d..d2ee05fb1902752993a74f99749a8f2e3f4a33e1 100644 --- a/gajim/gtk/accounts.py +++ b/gajim/gtk/accounts.py @@ -31,16 +31,12 @@ from gajim.gtk.dialogs import DialogButton from gajim.gtk.dialogs import NewConfirmationDialog -from gajim.gtk.dialogs import ConfirmationDialogDoubleRadio -from gajim.gtk.dialogs import ErrorDialog -from gajim.gtk.dialogs import PassphraseDialog from gajim.gtk.const import Setting from gajim.gtk.const import SettingKind from gajim.gtk.const import SettingType from gajim.gtk.settings import SettingsDialog from gajim.gtk.settings import SettingsBox -from gajim.gtk.util import get_builder -from gajim.gtk.util import get_app_window +from gajim.gtk.util import open_window log = logging.getLogger('gajim.gtk.accounts') @@ -159,7 +155,8 @@ def _get_relogin_settings(account): values.append(app.config.get_per('accounts', account, setting)) return values - def on_remove_account(self, account): + @staticmethod + def on_remove_account(account): if app.events.get_events(account): app.interface.raise_dialog('unread-events-on-remove-account') return @@ -168,39 +165,7 @@ def on_remove_account(self, account): # Should never happen as button is insensitive return - win_opened = False - if app.interface.msg_win_mgr.get_controls(acct=account): - win_opened = True - elif account in app.interface.instances: - for key in app.interface.instances[account]: - if (app.interface.instances[account][key] and - key != 'remove_account'): - win_opened = True - break - - # Detect if we have opened windows for this account - - def remove(): - if (account in app.interface.instances and - 'remove_account' in app.interface.instances[account]): - dialog = app.interface.instances[account]['remove_account'] - dialog.window.present() - else: - if account not in app.interface.instances: - app.interface.instances[account] = {} - app.interface.instances[account]['remove_account'] = \ - RemoveAccountWindow(account) - if win_opened: - NewConfirmationDialog( - _('Remove Account'), - _('You still have open chats in your account %s') % account, - _('All chat and group chat windows will be closed.'), - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - callback=remove)], - transient_for=self).show() - else: - remove() + open_window('RemoveAccount', account=account) def remove_account(self, account): del self._need_relogin[account] @@ -909,138 +874,3 @@ def on_destroy(self, *args): savepass = app.config.get_per('accounts', self.account, 'savepass') if not savepass: passwords.delete_password(self.account) - - -class RemoveAccountWindow: - """ - Ask whether to remove from gajim only or both from gajim and the server, - then remove the account given - """ - - def _on_destroy(self, _widget): - if self.account in app.interface.instances: - del app.interface.instances[self.account]['remove_account'] - - def on_cancel_button_clicked(self, _widget): - self._ui.remove_account_window.destroy() - - def __init__(self, account): - self.account = account - self._ui = get_builder('remove_account_window.ui') - active_window = app.app.get_active_window() - self._ui.remove_account_window.set_transient_for(active_window) - self._ui.remove_account_window.set_title( - _('Removing account %s') % self.account) - self._ui.connect_signals(self) - self._ui.remove_account_window.show_all() - - def on_remove_button_clicked(self, _widget): - def remove(): - if self.account in app.connections and \ - app.connections[self.account].connected and \ - not self._ui.remove_and_unregister_radiobutton.get_active(): - # change status to offline only if we will not - # remove this JID from server - app.connections[self.account].change_status('offline', - 'offline') - if self._ui.remove_and_unregister_radiobutton.get_active(): - if not self.account in app.connections: - ErrorDialog( - _('Account is disabled'), - _('To unregister from a server, the account must be ' - 'enabled.'), - transient_for=self._ui.remove_account_window) - return - if not app.connections[self.account].password: - def on_ok(passphrase, _checked): - if passphrase == -1: - # We don't remove account cause we - # canceled pw window - return - app.connections[self.account].password = passphrase - app.connections[self.account].unregister_account( - self._on_remove_success) - - PassphraseDialog( - _('Password required'), - _('Enter your password for account %s') % self.account, - _('Save password'), ok_handler=on_ok, - transient_for=self._ui.remove_account_window) - return - app.connections[self.account].unregister_account( - self._on_remove_success) - else: - self._on_remove_success(True) - - if (self.account in app.connections and - app.connections[self.account].connected): - NewConfirmationDialog( - _('Remove Account'), - _('Still connected to the server'), - _('Account \'%s\' is still connected to the server. If you ' - 'remove it, the connection will be lost.') % self.account, - [DialogButton.make('Cancel'), - DialogButton.make('Remove', - callback=remove)], - transient_for=self._ui.remove_account_window).show() - else: - remove() - - def on_remove_response_ok(self, is_checked): - if is_checked[0]: - self._on_remove_success(True) - - def _on_remove_success(self, res): - # action of unregistration has failed, we don't remove the account - # Error message is send by connect_and_auth() - if not res: - ConfirmationDialogDoubleRadio( - _('Connection to server %s failed') % self.account, - _('What would you like to do?'), - _('Remove only from Gajim'), - _('Don\'t remove anything. I\'ll try again later'), - on_response_ok=self.on_remove_response_ok, is_modal=False, - transient_for=self._ui.remove_account_window) - return - # Close all opened windows - app.interface.roster.close_all(self.account, force=True) - if self.account in app.connections: - 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)) - # Delete password must be before del_per() because it calls set_per() - # which would recreate the account with defaults values if not found - passwords.delete_password(self.account) - app.config.del_per('accounts', self.account) - del app.interface.instances[self.account] - if self.account in app.nicks: - del app.interface.minimized_controls[self.account] - del app.nicks[self.account] - del app.block_signed_in_notifications[self.account] - del app.groups[self.account] - app.contacts.remove_account(self.account) - del app.gc_connected[self.account] - del app.automatic_rooms[self.account] - del app.to_be_removed[self.account] - del app.newly_added[self.account] - del app.sleeper_state[self.account] - del app.last_message_time[self.account] - del app.status_before_autoaway[self.account] - del app.gajim_optional_features[self.account] - del app.caps_hash[self.account] - if len(app.connections) >= 2: # Do not merge accounts if only one exists - app.interface.roster.regroup = app.config.get('mergeaccounts') - else: - app.interface.roster.regroup = False - app.interface.roster.setup_and_draw_roster() - app.app.remove_account_actions(self.account) - gui_menu_builder.build_accounts_menu() - - window = get_app_window('AccountsWindow') - if window is not None: - window.remove_account(self.account) - self._ui.remove_account_window.destroy() - - def destroy(self): - self._ui.remove_account_window.destroy() diff --git a/gajim/gtk/const.py b/gajim/gtk/const.py index 498259b29463ca39f214ed1c3e074021638b7767..3dd001eb12a1dea839b9bc0e1cdfab2de7d2987a 100644 --- a/gajim/gtk/const.py +++ b/gajim/gtk/const.py @@ -129,4 +129,5 @@ def __str__(self): 'AdvancedConfig': 'gajim.gtk.advanced_config', 'CertificateDialog': 'gajim.gtk.dialogs', 'SubscriptionRequest': 'gajim.gtk.subscription_request', + 'RemoveAccount': 'gajim.gtk.remove_account', } diff --git a/gajim/gtk/remove_account.py b/gajim/gtk/remove_account.py new file mode 100644 index 0000000000000000000000000000000000000000..cc9ffcd957205fd167574324be3759cd4febfaff --- /dev/null +++ b/gajim/gtk/remove_account.py @@ -0,0 +1,174 @@ +# 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 gi.repository import Gtk + +from nbxmpp.util import is_error_result + +from gajim.common import app +from gajim.common.i18n import _ +from gajim.common.helpers import to_user_string + +from gajim.gtk.assistant import Assistant +from gajim.gtk.assistant import Page +from gajim.gtk.util import ensure_not_destroyed + +log = logging.getLogger('gajim.gtk.remove_account') + + +class RemoveAccount(Assistant): + def __init__(self, account): + Assistant.__init__(self) + + self.account = account + self._con = app.connections.get(account) + self._destroyed = False + + self.add_button('remove', _('Remove'), 'destructive-action') + self.add_button('close', _('Close')) + self.add_button('back', _('Back')) + + self.add_pages({'remove_choice': RemoveChoice(account)}) + + progress = self.add_default_page('progress') + progress.set_title(_('Removing Account...')) + progress.set_text(_('Trying to remove account...')) + + success = self.add_default_page('success') + success.set_title(_('Account Removed')) + success.set_heading(_('Account Removed')) + success.set_text( + _('Your account has has been unregistered successfully.')) + + error = self.add_default_page('error') + error.set_title(_('Account Removal Failed')) + error.set_heading(_('Account Removal Failed')) + + self.set_button_visible_func(self._visible_func) + + self.connect('button-clicked', self._on_button_clicked) + self.connect('page-changed', self._on_page_changed) + self.connect('destroy', self._on_destroy) + + self.show_all() + + @staticmethod + def _visible_func(_assistant, page_name): + if page_name == 'remove_choice': + return ['remove'] + + if page_name == 'progress': + return None + + if page_name == 'error': + return ['back'] + + if page_name == 'success': + return ['close'] + raise ValueError('page %s unknown' % page_name) + + def _on_button_clicked(self, _assistant, button_name): + page = self.get_current_page() + if button_name == 'remove': + if page == 'remove_choice': + self.show_page('progress', Gtk.StackTransitionType.SLIDE_LEFT) + self._on_remove() + return + + if button_name == 'back': + if page == 'error': + self.show_page('remove_choice', + Gtk.StackTransitionType.SLIDE_RIGHT) + return + + if button_name == 'close': + self.destroy() + + def _on_page_changed(self, _assistant, page_name): + if page_name == 'remove_choice': + self.set_default_button('remove') + + elif page_name == 'success': + self.set_default_button('close') + + elif page_name == 'error': + self.set_default_button('back') + + def _on_remove(self, *args): + if self.get_page('remove_choice').is_remove_from_server(): + self._con.unregister_account(self._on_remove_response) + else: + if app.account_is_connected(self.account): + self._con.change_status('offline', 'offline') + app.interface.remove_account(self.account) + self.destroy() + + @ensure_not_destroyed + def _on_remove_response(self, result): + if is_error_result(result): + self._con.removing_account = False + error_text = to_user_string(result) + self.get_page('error').set_text(error_text) + self.show_page('error') + else: + app.interface.remove_account(self.account) + self.show_page('success') + + def _on_destroy(self, *args): + self._destroyed = True + + +class RemoveChoice(Page): + def __init__(self, account): + Page.__init__(self) + self.title = _('Remove Account') + + heading = Gtk.Label(label=_('Remove Account')) + heading.get_style_context().add_class('large-header') + heading.set_max_width_chars(30) + heading.set_line_wrap(True) + heading.set_halign(Gtk.Align.CENTER) + heading.set_justify(Gtk.Justification.CENTER) + + label = Gtk.Label(label=_('This will remove your account from Gajim.')) + label.set_max_width_chars(50) + label.set_line_wrap(True) + label.set_halign(Gtk.Align.CENTER) + label.set_justify(Gtk.Justification.CENTER) + + service = app.connections[account].get_own_jid().getDomain() + check_label = Gtk.Label() + check_label.set_markup( + _('Do you want to unregister your account on <b>%s</b> as ' + 'well?') % service) + check_label.set_max_width_chars(50) + check_label.set_line_wrap(True) + check_label.set_halign(Gtk.Align.CENTER) + check_label.set_justify(Gtk.Justification.CENTER) + check_label.set_margin_top(40) + + self._server = Gtk.CheckButton.new_with_mnemonic( + _('_Unregister account from service')) + self._server.set_halign(Gtk.Align.CENTER) + + self.pack_start(heading, False, True, 0) + self.pack_start(label, False, True, 0) + self.pack_start(check_label, False, True, 0) + self.pack_start(self._server, False, True, 0) + self.show_all() + + def is_remove_from_server(self): + return self._server.get_active() diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 5596e3423a85572444df29ffb048d708a33d5863..8bf4ccfdebecb7814b2f7deca76d9a4957abab13 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -109,6 +109,7 @@ from gajim.gtk.util import get_show_in_roster from gajim.gtk.util import get_show_in_systray from gajim.gtk.util import open_window +from gajim.gtk.util import get_app_window from gajim.gtk.const import ControlType @@ -1797,6 +1798,46 @@ def disable_account(self, account): self.roster.setup_and_draw_roster() gui_menu_builder.build_accounts_menu() + def remove_account(self, account): + # Close all opened windows + self.roster.close_all(account, force=True) + if account in app.connections: + app.connections[account].disconnect(reconnect=False) + app.connections[account].cleanup() + del app.connections[account] + app.logger.remove_roster(app.get_jid_from_account(account)) + # Delete password must be before del_per() because it calls set_per() + # which would recreate the account with defaults values if not found + passwords.delete_password(account) + app.config.del_per('accounts', account) + del self.instances[account] + if account in app.nicks: + del self.minimized_controls[account] + del app.nicks[account] + del app.block_signed_in_notifications[account] + del app.groups[account] + app.contacts.remove_account(account) + del app.gc_connected[account] + del app.automatic_rooms[account] + del app.to_be_removed[account] + del app.newly_added[account] + del app.sleeper_state[account] + del app.last_message_time[account] + del app.status_before_autoaway[account] + del app.gajim_optional_features[account] + del app.caps_hash[account] + if len(app.connections) >= 2: # Do not merge accounts if only one exists + self.roster.regroup = app.config.get('mergeaccounts') + else: + self.roster.regroup = False + self.roster.setup_and_draw_roster() + app.app.remove_account_actions(account) + gui_menu_builder.build_accounts_menu() + + window = get_app_window('AccountsWindow') + if window is not None: + window.remove_account(account) + def autoconnect(self): """ Auto connect at startup