import itertools
import json
import os

from qtpy import QtWidgets as QW

from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core.networking import HydrusNATPunch
from hydrus.core import HydrusTime

from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientGlobals as CG
from hydrus.client.gui import ClientGUIAsync
from hydrus.client.gui import ClientGUIDialogs
from hydrus.client.gui import ClientGUIDialogsMessage
from hydrus.client.gui import ClientGUIDialogsQuick
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIRatings
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.gui.widgets import ClientGUICommon
from hydrus.client.metadata import ClientContentUpdates
from hydrus.client.metadata import ClientRatings

# Option Enums

class DialogManageRatings( CAC.ApplicationCommandProcessorMixin, ClientGUIDialogs.Dialog ):
    
    def __init__( self, parent, media ):
        
        self._hashes = set()
        
        for m in media:
            
            self._hashes.update( m.GetHashes() )
            
        
        ClientGUIDialogs.Dialog.__init__( self, parent, 'manage ratings for ' + HydrusData.ToHumanInt( len( self._hashes ) ) + ' files', position = 'topleft' )
        CAC.ApplicationCommandProcessorMixin.__init__( self )
        
        #
        
        like_services = CG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, ) )
        numerical_services = CG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_NUMERICAL, ) )
        incdec_services = CG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_INCDEC, ) )
        
        self._panels = []
        
        if len( like_services ) > 0:
            
            self._panels.append( self._LikePanel( self, like_services, media ) )
            
        
        if len( numerical_services ) > 0:
            
            self._panels.append( self._NumericalPanel( self, numerical_services, media ) )
            
        
        if len( incdec_services ) > 0:
            
            self._panels.append( self._IncDecPanel( self, incdec_services, media ) )
            
        
        self._copy_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().copy, self._Copy )
        self._copy_button.setToolTip( 'Copy ratings to the clipboard.' )
        
        self._paste_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().paste, self._Paste )
        self._paste_button.setToolTip( 'Paste ratings from the clipboard.' )
        
        self._apply = QW.QPushButton( 'apply', self )
        self._apply.clicked.connect( self.EventOK )
        self._apply.setObjectName( 'HydrusAccept' )
        
        self._cancel = QW.QPushButton( 'cancel', self )
        self._cancel.clicked.connect( self.reject )
        self._cancel.setObjectName( 'HydrusCancel' )
        
        #
        
        vbox = QP.VBoxLayout()
        
        for panel in self._panels:
            
            QP.AddToLayout( vbox, panel, CC.FLAGS_EXPAND_PERPENDICULAR )
            
        
        buttonbox = QP.HBoxLayout()
        
        QP.AddToLayout( buttonbox, self._copy_button, CC.FLAGS_CENTER_PERPENDICULAR )
        QP.AddToLayout( buttonbox, self._paste_button, CC.FLAGS_CENTER_PERPENDICULAR )
        
        QP.AddToLayout( vbox, buttonbox, CC.FLAGS_ON_RIGHT )
        
        buttonbox = QP.HBoxLayout()
        
        QP.AddToLayout( buttonbox, self._apply, CC.FLAGS_CENTER_PERPENDICULAR )
        QP.AddToLayout( buttonbox, self._cancel, CC.FLAGS_CENTER_PERPENDICULAR )
        
        QP.AddToLayout( vbox, buttonbox, CC.FLAGS_ON_RIGHT )
        
        self.setLayout( vbox )
        
        size_hint = self.sizeHint()
        
        QP.SetInitialSize( self, size_hint )
        
        #
        
        self._my_shortcut_handler = ClientGUIShortcuts.ShortcutsHandler( self, [ 'global', 'media' ] )
        
    
    def _Copy( self ):
        
        rating_clipboard_pairs = []
        
        for panel in self._panels:
            
            rating_clipboard_pairs.extend( panel.GetRatingClipboardPairs() )
            
        
        text = json.dumps( [ ( service_key.hex(), rating ) for ( service_key, rating ) in rating_clipboard_pairs ] )
        
        CG.client_controller.pub( 'clipboard', 'text', text )
        
    
    def _Paste( self ):
        
        try:
            
            raw_text = CG.client_controller.GetClipboardText()
            
        except HydrusExceptions.DataMissing as e:
            
            ClientGUIDialogsMessage.ShowCritical( self, 'Error', str( e ) )
            
            return
            
        
        try:
            
            rating_clipboard_pairs_encoded = json.loads( raw_text )
            
            rating_clipboard_pairs = [ ( bytes.fromhex( service_key_encoded ), rating ) for ( service_key_encoded, rating ) in rating_clipboard_pairs_encoded ]
            
        except Exception as e:
            
            ClientGUIFunctions.PresentClipboardParseError( self, raw_text, 'JSON pairs of service keys and rating values', e )
            
            return
            
        
        for panel in self._panels:
            
            panel.SetRatingClipboardPairs( rating_clipboard_pairs )
            
        
    
    def EventOK( self ):
        
        try:
            
            content_update_package = ClientContentUpdates.ContentUpdatePackage()
            
            for panel in self._panels:
                
                content_update_package.AddContentUpdatePackage( panel.GetContentUpdatePackage() )
                
            
            if content_update_package.HasContent():
                
                CG.client_controller.Write( 'content_updates', content_update_package )
                
            
        finally:
            
            self.done( QW.QDialog.Accepted )
            
        
    
    def ProcessApplicationCommand( self, command: CAC.ApplicationCommand ):
        
        command_processed = True
        
        if command.IsSimpleCommand():
            
            action = command.GetSimpleAction()
            
            if action == CAC.SIMPLE_MANAGE_FILE_RATINGS:
                
                self.EventOK()
                
            else:
                
                command_processed = False
                
            
        else:
            
            command_processed = False
            
        
        return command_processed
        
    
    class _IncDecPanel( QW.QWidget ):
        
        def __init__( self, parent, services, media ):
            
            QW.QWidget.__init__( self, parent )
            
            self._services = services
            
            self._media = media
            
            self._service_keys_to_controls = {}
            self._service_keys_to_original_ratings_states = {}
            
            rows = []
            
            for service in self._services:
                
                name = service.GetName()
                
                service_key = service.GetServiceKey()
                
                ( rating_state, rating ) = ClientRatings.GetIncDecStateFromMedia( self._media, service_key )
                
                control = ClientGUIRatings.RatingIncDecDialog( self, service_key )
                
                if rating_state != ClientRatings.SET:
                    
                    control.SetRatingState( rating_state, rating )
                    
                else:
                    
                    control.SetRating( rating )
                    
                
                self._service_keys_to_controls[ service_key ] = control
                self._service_keys_to_original_ratings_states[ service_key ] = ( rating_state, rating )
                
                rows.append( ( name + ': ', control ) )
                
            
            gridbox = ClientGUICommon.WrapInGrid( self, rows, expand_text = True )
            
            self.setLayout( gridbox )
            
        
        def GetContentUpdatePackage( self ):
            
            content_update_package = ClientContentUpdates.ContentUpdatePackage()
            
            hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
            
            for ( service_key, control ) in self._service_keys_to_controls.items():
                
                ( original_rating_state, original_rating ) = self._service_keys_to_original_ratings_states[ service_key ]
                
                rating_state = control.GetRatingState()
                
                if rating_state == ClientRatings.MIXED:
                    
                    continue
                    
                else:
                    
                    rating = control.GetRating()
                    
                
                if rating != original_rating:
                    
                    content_update = ClientContentUpdates.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
                    
                    content_update_package.AddContentUpdate( service_key, content_update )
                    
                
            
            return content_update_package
            
        
        def GetRatingClipboardPairs( self ):
            
            rating_clipboard_pairs = []
            
            for ( service_key, control ) in self._service_keys_to_controls.items():
                
                rating_state = control.GetRatingState()
                
                if rating_state == ClientRatings.SET:
                    
                    rating = control.GetRating()
                    
                else:
                    
                    continue
                    
                
                rating_clipboard_pairs.append( ( service_key, rating ) )
                
            
            return rating_clipboard_pairs
            
        
        def SetRatingClipboardPairs( self, rating_clipboard_pairs ):
            
            for ( service_key, rating ) in rating_clipboard_pairs:
                
                if service_key in self._service_keys_to_controls:
                    
                    control = self._service_keys_to_controls[ service_key ]
                    
                    if isinstance( rating, ( int, float ) ):
                        
                        control.SetRating( rating )
                        
                    
                
            
        
    
    class _LikePanel( QW.QWidget ):
        
        def __init__( self, parent, services, media ):
            
            QW.QWidget.__init__( self, parent )
            
            self._services = services
            
            self._media = media
            
            self._service_keys_to_controls = {}
            self._service_keys_to_original_ratings_states = {}
            
            rows = []
            
            for service in self._services:
                
                name = service.GetName()
                
                service_key = service.GetServiceKey()
                
                rating_state = ClientRatings.GetLikeStateFromMedia( self._media, service_key )
                
                control = ClientGUIRatings.RatingLikeDialog( self, service_key )
                
                control.SetRatingState( rating_state )
                
                self._service_keys_to_controls[ service_key ] = control
                self._service_keys_to_original_ratings_states[ service_key ] = rating_state
                
                rows.append( ( name + ': ', control ) )
                
            
            gridbox = ClientGUICommon.WrapInGrid( self, rows, expand_text = True )
            
            self.setLayout( gridbox )
            
        
        def GetContentUpdatePackage( self ):
            
            content_update_package = ClientContentUpdates.ContentUpdatePackage()
            
            hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
            
            for ( service_key, control ) in list(self._service_keys_to_controls.items()):
                
                original_rating_state = self._service_keys_to_original_ratings_states[ service_key ]
                
                rating_state = control.GetRatingState()
                
                if rating_state != original_rating_state:
                    
                    if rating_state == ClientRatings.MIXED:
                        
                        continue
                        
                    elif rating_state == ClientRatings.LIKE:
                        
                        rating = 1
                        
                    elif rating_state == ClientRatings.DISLIKE:
                        
                        rating = 0
                        
                    else:
                        
                        rating = None
                        
                    
                    content_update = ClientContentUpdates.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
                    
                    content_update_package.AddContentUpdate( service_key, content_update )
                    
                
            
            return content_update_package
            
        
        def GetRatingClipboardPairs( self ):
            
            rating_clipboard_pairs = []
            
            for ( service_key, control ) in self._service_keys_to_controls.items():
                
                rating_state = control.GetRatingState()
                
                if rating_state == ClientRatings.LIKE:
                    
                    rating = 1
                    
                elif rating_state == ClientRatings.DISLIKE:
                    
                    rating = 0
                    
                elif rating_state == ClientRatings.NULL:
                    
                    rating = None
                    
                else:
                    
                    continue
                    
                
                rating_clipboard_pairs.append( ( service_key, rating ) )
                
            
            return rating_clipboard_pairs
            
        
        def SetRatingClipboardPairs( self, rating_clipboard_pairs ):
            
            for ( service_key, rating ) in rating_clipboard_pairs:
                
                if rating == 1:
                    
                    rating_state = ClientRatings.LIKE
                    
                elif rating == 0:
                    
                    rating_state = ClientRatings.DISLIKE
                    
                elif rating is None:
                    
                    rating_state = ClientRatings.NULL
                    
                else:
                    
                    continue
                    
                
                if service_key in self._service_keys_to_controls:
                    
                    control = self._service_keys_to_controls[ service_key ]
                    
                    control.SetRatingState( rating_state )
                    
                
            
        
    
    class _NumericalPanel( QW.QWidget ):
        
        def __init__( self, parent, services, media ):
            
            QW.QWidget.__init__( self, parent )
            
            self._services = services
            
            self._media = media
            
            self._service_keys_to_controls = {}
            self._service_keys_to_original_ratings_states = {}
            
            rows = []
            
            for service in self._services:
                
                name = service.GetName()
                
                service_key = service.GetServiceKey()
                
                ( rating_state, rating ) = ClientRatings.GetNumericalStateFromMedia( self._media, service_key )
                
                control = ClientGUIRatings.RatingNumericalDialog( self, service_key )
                
                if rating_state != ClientRatings.SET:
                    
                    control.SetRatingState( rating_state )
                    
                else:
                    
                    control.SetRating( rating )
                    
                
                self._service_keys_to_controls[ service_key ] = control
                self._service_keys_to_original_ratings_states[ service_key ] = ( rating_state, rating )
                
                rows.append( ( name + ': ', control ) )
                
            
            gridbox = ClientGUICommon.WrapInGrid( self, rows, expand_text = True )
            
            self.setLayout( gridbox )
            
        
        def GetContentUpdatePackage( self ):
            
            content_update_package = ClientContentUpdates.ContentUpdatePackage()
            
            hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
            
            for ( service_key, control ) in list(self._service_keys_to_controls.items()):
                
                ( original_rating_state, original_rating ) = self._service_keys_to_original_ratings_states[ service_key ]
                
                rating_state = control.GetRatingState()
                
                if rating_state == ClientRatings.MIXED:
                    
                    continue
                    
                elif rating_state == ClientRatings.NULL:
                    
                    rating = None
                    
                else:
                    
                    rating = control.GetRating()
                    
                
                if rating != original_rating:
                    
                    content_update = ClientContentUpdates.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
                    
                    content_update_package.AddContentUpdate( service_key, content_update )
                    
                
            
            return content_update_package
            
        
        def GetRatingClipboardPairs( self ):
            
            rating_clipboard_pairs = []
            
            for ( service_key, control ) in self._service_keys_to_controls.items():
                
                rating_state = control.GetRatingState()
                
                if rating_state == ClientRatings.NULL:
                    
                    rating = None
                    
                elif rating_state == ClientRatings.SET:
                    
                    rating = control.GetRating()
                    
                else:
                    
                    continue
                    
                
                rating_clipboard_pairs.append( ( service_key, rating ) )
                
            
            return rating_clipboard_pairs
            
        
        def SetRatingClipboardPairs( self, rating_clipboard_pairs ):
            
            for ( service_key, rating ) in rating_clipboard_pairs:
                
                if service_key in self._service_keys_to_controls:
                    
                    control = self._service_keys_to_controls[ service_key ]
                    
                    if rating is None:
                        
                        control.SetRatingState( ClientRatings.NULL )
                        
                    elif isinstance( rating, ( int, float ) ) and 0 <= rating <= 1:
                        
                        control.SetRating( rating )
                        
                    
                
            
        
    
class DialogManageUPnP( ClientGUIDialogs.Dialog ):
    
    def __init__( self, parent ):
        
        title = 'manage local upnp'
        
        ClientGUIDialogs.Dialog.__init__( self, parent, title )
        
        self._status_st = ClientGUICommon.BetterStaticText( self )
        
        self._mappings_listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
        
        self._mappings_list = ClientGUIListCtrl.BetterListCtrl( self._mappings_listctrl_panel, CGLC.COLUMN_LIST_MANAGE_UPNP_MAPPINGS.ID, 12, self._ConvertDataToListCtrlTuples, delete_key_callback = self._Remove, activation_callback = self._Edit )
        
        self._mappings_listctrl_panel.SetListCtrl( self._mappings_list )
        
        self._mappings_listctrl_panel.AddButton( 'add custom mapping', self._Add )
        self._mappings_listctrl_panel.AddButton( 'edit mapping', self._Edit, enabled_only_on_single_selection = True )
        self._mappings_listctrl_panel.AddButton( 'remove mapping', self._Remove, enabled_only_on_selection = True )
        
        self._ok = QW.QPushButton( 'ok', self )
        self._ok.clicked.connect( self.EventOK )
        self._ok.setObjectName( 'HydrusAccept' )
        
        #
        
        vbox = QP.VBoxLayout()
        
        QP.AddToLayout( vbox, self._status_st, CC.FLAGS_EXPAND_PERPENDICULAR )
        QP.AddToLayout( vbox, self._mappings_listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
        QP.AddToLayout( vbox, self._ok, CC.FLAGS_ON_RIGHT )
        
        self.setLayout( vbox )
        
        size_hint = self.sizeHint()
        
        size_hint.setWidth( max( size_hint.width(), 760 ) )
        
        QP.SetInitialSize( self, size_hint )
        
        #
        
        self._mappings_lookup = set()
        
        self._mappings_list.Sort()
        
        self._external_ip = None
        
        self._started_external_ip_fetch = False
        
        self._RefreshMappings()
        
    
    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 _ConvertDataToListCtrlTuples( self, mapping ):
        
        ( description, internal_ip, internal_port, external_port, protocol, duration ) = mapping
        
        if duration == 0:
            
            pretty_duration = 'indefinite'
            
        else:
            
            pretty_duration = HydrusTime.TimeDeltaToPrettyTimeDelta( duration )
            
        
        display_tuple = ( description, internal_ip, str( internal_port ), str( external_port ), protocol, pretty_duration )
        sort_tuple = mapping
        
        return ( display_tuple, sort_tuple )
        
    
    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 _MappingExists( self, external_port, protocol ):
        
        return ( external_port, protocol ) in self._mappings_lookup
        
    
    def _RefreshExternalIP( self ):
        
        def work_callable():
            
            try:
                
                external_ip = HydrusNATPunch.GetExternalIP()
                
                external_ip_text = 'External IP: {}'.format( external_ip )
                
            except Exception as e:
                
                external_ip_text = 'Error finding external IP: ' + str( e )
                
            
            return external_ip_text
            
        
        def publish_callable( external_ip_text ):
            
            self._external_ip = external_ip_text
            
            self._status_st.setText( self._external_ip )
            
        
        self._status_st.setText( 'Loading external IP' + HC.UNICODE_ELLIPSIS )
        
        async_job = ClientGUIAsync.AsyncQtJob( self, work_callable, publish_callable )
        
        async_job.start()
        
    
    def _RefreshMappings( self ):
        
        def work_callable():
            
            try:
                
                mappings = HydrusNATPunch.GetUPnPMappings()
                
            except Exception as e:
                
                HydrusData.ShowException( e )
                
                return e
                
            
            return mappings
            
        
        def publish_callable( result ):
            
            if isinstance( result, Exception ):
                
                e = result
                
                ClientGUIDialogsMessage.ShowCritical( self, 'Error', 'Could not load mappings:' + '\n' * 2 + str(e) )
                
                self._status_st.setText( str( e ) )
                
                return
                
            
            self._mappings_listctrl_panel.setEnabled( True )
            
            mappings = result
            
            self._mappings_lookup = { ( external_port, protocol ) for ( description, internal_ip, internal_port, external_port, protocol, duration ) in mappings }
            
            self._mappings_list.SetData( mappings )
            
            self._status_st.clear()
            
            if self._external_ip is not None:
                
                self._status_st.setText( self._external_ip )
                
            elif not self._started_external_ip_fetch:
                
                self._started_external_ip_fetch = True
                
                self._RefreshExternalIP()
                
            
        
        self._status_st.setText( 'Refreshing mappings--please wait' + HC.UNICODE_ELLIPSIS )
        
        self._mappings_list.SetData( [] )
        
        self._mappings_listctrl_panel.setEnabled( False )
        
        async_job = ClientGUIAsync.AsyncQtJob( self, work_callable, publish_callable )
        
        async_job.start()
        
    
    def _Remove( self ):
        
        text = 'Remove these port mappings?'
        text += os.linesep * 2
        text += 'If a mapping does not disappear after a remove, it may be hard-added in the router\'s settings interface. In this case, you will have to go there to fix it.'
        
        result = ClientGUIDialogsQuick.GetYesNo( self, text, yes_label = 'do it', no_label = 'forget it' )
        
        if result != QW.QDialog.Accepted:
            
            return
            
        
        selected_mappings = self._mappings_list.GetData( only_selected = True )
        
        def work_callable():
            
            for selected_mapping in selected_mappings:
                
                ( description, internal_ip, internal_port, external_port, protocol, duration ) = selected_mapping
                
                HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
                
            
            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 EventOK( self ):
        
        self.done( QW.QDialog.Accepted )
        
    
