URI: 
       tkivy: restore channel list to working state, add [force-]closing functionality - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit f803bb571d196dcc02a7a97406aa221b800f8b41
   DIR parent 1520338f37ad601c328df087bdf7bc7f0025ce95
  HTML Author: Janus <ysangkok@gmail.com>
       Date:   Wed,  7 Nov 2018 19:41:30 +0100
       
       kivy: restore channel list to working state, add [force-]closing functionality
       
       Diffstat:
         M electrum/gui/kivy/uix/dialogs/ligh… |      57 ++++++++++++++++++++++++-------
       
       1 file changed, 45 insertions(+), 12 deletions(-)
       ---
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/lightning_channels.py b/electrum/gui/kivy/uix/dialogs/lightning_channels.py
       t@@ -1,9 +1,12 @@
       +import asyncio
        import binascii
        from kivy.lang import Builder
        from kivy.factory import Factory
        from kivy.uix.popup import Popup
        from kivy.clock import Clock
        from electrum.gui.kivy.uix.context_menu import ContextMenu
       +from electrum.util import bh2u
       +from electrum.lnutil import LOCAL, REMOTE
        
        Builder.load_string('''
        <LightningChannelItem@CardItem>
       t@@ -15,6 +18,7 @@ Builder.load_string('''
        
        <LightningChannelsDialog@Popup>:
            name: 'lightning_channels'
       +    title: 'Lightning channels. Tap to select.'
            BoxLayout:
                id: box
                orientation: 'vertical'
       t@@ -86,19 +90,38 @@ class LightningChannelsDialog(Factory.Popup):
                self.clocks = []
                self.app = app
                self.context_menu = None
       -        self.app.wallet.lnworker.subscribe_channel_list_updates_from_other_thread(self.rpc_result_handler)
       +        self.app.wallet.network.register_callback(self.channels_update, ['channels'])
       +        self.channels_update('bogus evt')
        
            def show_channel_details(self, obj):
                p = Factory.ChannelDetailsPopup()
       +        p.title = 'Lightning channels details for ' + self.presentable_chan_id(obj._chan)
                p.data = [{'keyName': key, 'value': str(obj.details[key])} for key in obj.details.keys()]
                p.open()
        
            def close_channel(self, obj):
       -        print("UNIMPLEMENTED asked to close channel", obj.channelId) # TODO
       +        loop = self.app.wallet.network.asyncio_loop
       +        coro = asyncio.run_coroutine_threadsafe(self.app.wallet.lnworker.close_channel(obj._chan.channel_id), loop)
       +        try:
       +            coro.result(5)
       +            self.app.show_info('Channel closed')
       +        except Exception as e:
       +            self.app.show_info('Could not close channel: ' + repr(e)) # repr because str(Exception()) == ''
       +
       +    def force_close_channel(self, obj):
       +        loop = self.app.wallet.network.asyncio_loop
       +        coro = asyncio.run_coroutine_threadsafe(self.app.wallet.lnworker.force_close_channel(obj._chan.channel_id), loop)
       +        try:
       +            coro.result(1)
       +            self.app.show_info('Channel closed, you may need to wait at least ' + str(obj._chan.config[REMOTE].to_self_delay) + ' blocks, because of CSV delays')
       +        except Exception as e:
       +            self.app.show_info('Could not force close channel: ' + repr(e)) # repr because str(Exception()) == ''
        
            def show_menu(self, obj):
                self.hide_menu()
       -        self.context_menu = ContextMenu(obj, [("Close", self.close_channel),
       +        self.context_menu = ContextMenu(obj, [
       +            ("Force close", self.force_close_channel),
       +            ("Co-op close", self.close_channel),
                    ("Details", self.show_channel_details)])
                self.ids.box.add_widget(self.context_menu)
        
       t@@ -107,17 +130,27 @@ class LightningChannelsDialog(Factory.Popup):
                    self.ids.box.remove_widget(self.context_menu)
                    self.context_menu = None
        
       -    def rpc_result_handler(self, res):
       +    def presentable_chan_id(self, i):
       +        return bh2u(i.short_channel_id) if i.short_channel_id else bh2u(i.channel_id)[:16]
       +
       +    def channels_update(self, evt):
                channel_cards = self.ids.lightning_channels_container
                channel_cards.clear_widgets()
       -        if "channels" in res:
       -          for i in res["channels"]:
       +        lnworker = self.app.wallet.lnworker
       +        for i in lnworker.channels.values():
                    item = Factory.LightningChannelItem()
                    item.screen = self
       -            print(i)
       -            item.channelId = i["chan_id"]
       -            item.active = i["active"]
       -            item.details = i
       +            item.channelId = self.presentable_chan_id(i)
       +            item.active = i.node_id in lnworker.peers
       +            item.details = self.channel_details(i)
       +            item._chan = i
                    channel_cards.add_widget(item)
       -        else:
       -          self.app.show_info(res)
       +
       +    def channel_details(self, chan):
       +        return {'Node ID': bh2u(chan.node_id),
       +                'Channel ID': bh2u(chan.channel_id),
       +                'Capacity': self.app.format_amount_and_units(chan.constraints.capacity),
       +                'Funding TXID': chan.funding_outpoint.txid,
       +                'Short Chan ID': bh2u(chan.short_channel_id) if chan.short_channel_id else 'Not available',
       +                'Available to spend': self.app.format_amount_and_units(chan.available_to_spend(LOCAL) // 1000),
       +                'State': chan.get_state()}