def _Edit(self):

        selected_mappings = self._mappings_list.GetData(only_selected=True)

        if len(selected_mappings) > 0:

            selected_mapping = selected_mappings[0]

            (description, internal_ip, internal_port, old_external_port,
             old_protocol, duration) = selected_mapping

            with ClientGUIDialogs.DialogInputUPnPMapping(
                    self, old_external_port, old_protocol, internal_port,
                    description, duration) as dlg:

                if dlg.exec() == QW.QDialog.Accepted:

                    (external_port, protocol, internal_port, description,
                     duration) = dlg.GetInfo()

                    remove_old = self._MappingExists(old_external_port,
                                                     old_protocol)
                    remove_existing = (
                        old_external_port != external_port
                        or old_protocol != protocol) and self._MappingExists(
                            external_port, protocol)

                    def work_callable():

                        if remove_old:

                            HydrusNATPunch.RemoveUPnPMapping(
                                old_external_port, old_protocol)

                        if remove_existing:

                            HydrusNATPunch.RemoveUPnPMapping(
                                external_port, protocol)

                        internal_client = HydrusNATPunch.GetLocalIP()

                        HydrusNATPunch.AddUPnPMapping(internal_client,
                                                      internal_port,
                                                      external_port,
                                                      protocol,
                                                      description,
                                                      duration=duration)

                        return True

                    def publish_callable(result):

                        self._mappings_listctrl_panel.setEnabled(True)

                        self._RefreshMappings()

                    self._mappings_listctrl_panel.setEnabled(False)

                    async_job = ClientGUIAsync.AsyncQtJob(
                        self, work_callable, publish_callable)

                    async_job.start()
    def _RefreshAccounts(self):

        account_identifiers = self._account_identifiers
        service = self._service

        selected_account_keys = self.GetCheckedAccountKeys()

        def work_callable():

            account_errors = set()

            account_keys_to_accounts = {}
            account_keys_to_account_info = {}

            for account_identifier in account_identifiers:

                try:

                    result = service.Request(
                        HC.GET, 'other_account',
                        {'subject_identifier': account_identifier})

                except Exception as e:

                    account_errors.add(str(e))

                    continue

                if 'account' in result:

                    account = result['account']

                    account_key = account.GetAccountKey()

                    if account_key in account_keys_to_accounts:

                        continue

                    account_keys_to_accounts[account_key] = account

                    try:

                        response = self._service.Request(
                            HC.GET, 'account_info', {
                                'subject_identifier':
                                HydrusNetwork.AccountIdentifier(
                                    account_key=account_key)
                            })

                    except Exception as e:

                        HydrusData.PrintException(e)

                        continue

                    account_string = str(response['account_info'])

                    account_keys_to_account_info[account_key] = account_string

            return (account_keys_to_accounts, account_keys_to_account_info,
                    account_errors)

        def publish_callable(result):

            (self._account_keys_to_accounts,
             self._account_keys_to_account_info, account_errors) = result

            if len(account_errors) > 0:

                account_errors = sorted(account_errors)

                QW.QMessageBox.information(
                    self, 'Information',
                    'Errors were encountered during account fetch:{}{}'.format(
                        os.linesep * 2, os.linesep.join(account_errors)))

            if not self._done_first_fetch:

                # if we launched with CPU-expensive mapping identifiers, let's move to nice account ids for future refreshes

                self._account_identifiers = [
                    HydrusNetwork.AccountIdentifier(account_key=account_key)
                    for account_key in self._account_keys_to_accounts.keys()
                ]

            #

            account_keys_sorted = sorted(
                list(self._account_keys_to_accounts.keys()),
                key=lambda sak: (self._account_keys_to_accounts[sak].
                                 GetAccountType().GetTitle(), sak.hex()))

            my_admin_account = self._service.GetAccount()

            my_admin_account_key = my_admin_account.GetAccountKey()

            for account_key in account_keys_sorted:

                item = QW.QListWidgetItem()

                item.setFlags(item.flags() | QC.Qt.ItemIsUserCheckable)

                account = self._account_keys_to_accounts[account_key]

                text = account.GetSingleLineTitle()

                if account_key == my_admin_account_key:

                    text = 'THIS IS YOU: {}'.format(text)

                item.setText(text)

                if not self._done_first_fetch or account_key in selected_account_keys:

                    item.setCheckState(QC.Qt.Checked)

                else:

                    item.setCheckState(QC.Qt.Unchecked)

                item.setData(QC.Qt.UserRole, account_key)

                self._account_list.addItem(item)

            #

            self._status_st.setVisible(False)
            self._status_st.setText('')

            if self._account_list.count() > 0:

                self._account_list.item(0).setSelected(True)

                self._AccountClicked()

            self._accounts_loaded = True
            self._done_first_fetch = True

            self.accountsFetchFinished.emit()

        self._status_st.setVisible(True)
        self._status_st.setText('fetching accounts\u2026')

        self._accounts_loaded = False

        self._account_list.clear()

        self._account_info_box.clear()

        self._account_keys_to_accounts = {}
        self._account_keys_to_account_info = {}

        self.accountsFetchStarted.emit()

        job = ClientGUIAsync.AsyncQtJob(self, work_callable, publish_callable)

        job.start()
    def _Add(self):

        external_port = HC.DEFAULT_SERVICE_PORT
        protocol = 'TCP'
        internal_port = HC.DEFAULT_SERVICE_PORT
        description = 'hydrus service'
        duration = 0

        with ClientGUIDialogs.DialogInputUPnPMapping(self, external_port,
                                                     protocol, internal_port,
                                                     description,
                                                     duration) as dlg:

            if dlg.exec() == QW.QDialog.Accepted:

                (external_port, protocol, internal_port, description,
                 duration) = dlg.GetInfo()

                remove_existing = False

                if self._MappingExists(external_port, protocol):

                    remove_existing = True

                    text = '{}:({}) is already mapped! Ok to overwrite whatever it currently is?'

                    result = ClientGUIDialogsQuick.GetYesNo(
                        self, text, yes_label='do it', no_label='forget it')

                    if result != QW.QDialog.Accepted:

                        return

                def work_callable():

                    if remove_existing:

                        HydrusNATPunch.RemoveUPnPMapping(
                            external_port, protocol)

                    internal_client = HydrusNATPunch.GetLocalIP()

                    HydrusNATPunch.AddUPnPMapping(internal_client,
                                                  internal_port,
                                                  external_port,
                                                  protocol,
                                                  description,
                                                  duration=duration)

                    return True

                def publish_callable(result):

                    self._mappings_listctrl_panel.setEnabled(True)

                    self._RefreshMappings()

                self._mappings_listctrl_panel.setEnabled(False)

                async_job = ClientGUIAsync.AsyncQtJob(self, work_callable,
                                                      publish_callable)

                async_job.start()
    def _DoBan(self):

        subject_accounts = self._account_panel.GetCheckedAccounts()

        if len(subject_accounts) == 0:

            QW.QMessageBox.information(self, 'Information',
                                       'No accounts selected for action!')

            return

        some_are_banned = True in (subject_account.IsBanned()
                                   for subject_account in subject_accounts)

        if some_are_banned:

            message = 'Some of these selected accounts are already banned. Sure you want to overwrite the bans?'

            result = ClientGUIDialogsQuick.GetYesNo(self, message)

            if result != QW.QDialog.Accepted:

                return

        subject_account_keys = [
            subject_account.GetAccountKey()
            for subject_account in subject_accounts
        ]

        reason = self._ban_reason.text()

        if reason == '':

            QW.QMessageBox.information(self, 'Information',
                                       'The ban reason is empty!')

            return

        message = 'Ban these user(s)? All of their pending petitions will be deleted serverside.'

        result = ClientGUIDialogsQuick.GetYesNo(self, message)

        if result != QW.QDialog.Accepted:

            return

        expires = self._ban_expires.GetValue()

        if expires is not None:

            expires += HydrusData.GetNow()

        service = self._service

        def work_callable():

            for subject_account_key in subject_account_keys:

                service.Request(
                    HC.POST, 'modify_account_ban', {
                        'subject_identifier':
                        HydrusNetwork.AccountIdentifier(
                            account_key=subject_account_key),
                        'reason':
                        reason,
                        'expires':
                        expires
                    })

            return 1

        def publish_callable(gumpf):

            QW.QMessageBox.information(self, 'Information', 'Done!')

            self._account_panel.RefreshAccounts()

        self._DisableUIForJob('banning\u2026')

        job = ClientGUIAsync.AsyncQtJob(self, work_callable, publish_callable)

        job.start()