How to run code on exiting an Add-on
#1
Although I've been programming JavaScript and C-style languages for years, this is my first foray into Python (which I'm really liking so far), so please forgive me if my questions have obvious answers.

I'm simply trying to run a function when I exit a plugin. I have code such as this:

Code:
@plugin.route('/')

def view_top():
  addDirectoryItem(plugin.handle, plugin.url_for("/funstuff"), ListItem("Do Fun Stuff"), True)
  endOfDirectory(plugin.handle)

@plugin.route('/funstuff')
def funstuff():
  doFunStuff()

if ( __name__ == "__main__" ):
  plugin.run()

And this is all working fine. However, I can't find a way to run a function when the plugin exits. I wondered if there might be a route such as
Code:
@plugin.route('../')
which would take me to the 'parent' folder, but there doesn't seem to be.

I've also tried putting a 'try / finally' around the 'plugin.run()' code but the 'finally' code gets called immediately. I've explored 'atexit' too but realise that won't work either.

I've come across some posts about an Action Handler but I can't find any examples sufficiently complete to guide me.

Any thoughts / tips would be much appreciated.

(The background to this: I'm trying to get a VPN to exit when I leave the Add-on... I plan to use 'os.startfile' to kick off an external script).
Reply
#2
(2012-09-26, 23:36)holf Wrote: Although I've been programming JavaScript and C-style languages for years, this is my first foray into Python (which I'm really liking so far), so please forgive me if my questions have obvious answers.

I'm simply trying to run a function when I exit a plugin. I have code such as this:

Code:
@plugin.route('/')

def view_top():
  addDirectoryItem(plugin.handle, plugin.url_for("/funstuff"), ListItem("Do Fun Stuff"), True)
  endOfDirectory(plugin.handle)

@plugin.route('/funstuff')
def funstuff():
  doFunStuff()

if ( __name__ == "__main__" ):
  plugin.run()

And this is all working fine. However, I can't find a way to run a function when the plugin exits. I wondered if there might be a route such as
Code:
@plugin.route('../')
which would take me to the 'parent' folder, but there doesn't seem to be.

I've also tried putting a 'try / finally' around the 'plugin.run()' code but the 'finally' code gets called immediately. I've explored 'atexit' too but realise that won't work either.

I've come across some posts about an Action Handler but I can't find any examples sufficiently complete to guide me.

Any thoughts / tips would be much appreciated.

(The background to this: I'm trying to get a VPN to exit when I leave the Add-on... I plan to use 'os.startfile' to kick off an external script).

Can't you do:

Code:
if __name__ == '__main__':
    plugin.run()
    doFunStuff()
Reply
#3
Yeah, I thought the same. However, on starting the plugin from XBMC, the 'plugin.run()' code runs and then any following statements execute immediately. So doFunStuff() still happens when I start the plugin, not when I exit it.
This makes sense to me because the code seems to me to be associated functions with routes only. There is nothing in there that would suspend code execution so it would just run straight through without stopping.

But thanks very much for the suggestion as it's given me some more ideas... if I can somehow assign an action handler to a 'Back to previous menu' action of some kind then job done. I've no idea how to do this yet but it will inspire more Googling.

If I come up with an answer before anyone else I'll post it here. But I can't promise it'll be 'best practice' or anything. Smile
Reply
#4
(2012-09-27, 21:45)holf Wrote: Yeah, I thought the same. However, on starting the plugin from XBMC, the 'plugin.run()' code runs and then any following statements execute immediately. So doFunStuff() still happens when I start the plugin, not when I exit it.
This makes sense to me because the code seems to me to be associated functions with routes only. There is nothing in there that would suspend code execution so it would just run straight through without stopping.

But thanks very much for the suggestion as it's given me some more ideas... if I can somehow assign an action handler to a 'Back to previous menu' action of some kind then job done. I've no idea how to do this yet but it will inspire more Googling.

If I come up with an answer before anyone else I'll post it here. But I can't promise it'll be 'best practice' or anything. Smile

Ah, I think your term "exiting the plugin" meant something different to me.

Each time you click on a list item in an XBMC addon, the addon will run once and exit. The script does not stay running while you browse within the addon. If you want something to execute when a user is finished using your addon, you'll need to add an explicit option for the user to select or think about creating a service addon.
Reply
#5
Okay, I understand now, thanks (and I see why 'exiting the plugin' was ambiguous).

I'm actually trying to hack an existing plugin, such that before viewing a stream starts I can kick off a VPN connection and then when viewing stops, I can stop the VPN again (so other geographically local streaming add-ons continue to work).

I realised that in the top level menu, '@plugin.route('/')' I can simply always call a script to kill the VPN. And then in any of the sub-menus where streaming actually starts I can call a script to start the VPN.

Okay, I still have to work out how to kill the VPN if I go directly to the 'Home' page (by clicking on the little 'house' icon at the top left) but I can worry about that another day.

So, problem almost solved in a different (and probably simpler) way than I imagined.

Thanks for your help... you replies prompted me to think about it differently.

I like Python. I like the XMBC Add-on structure. This could be the start of a beautiful friendship...
Reply
#6
Maybe you could create a player class and start the VPN when the class is __init__'d? Then kill the VPN in the class's onPlaybackEnded method. That's assuming you can resolve the url without the VPN being enabled
Reply
#7
a plugin is not a script, it is a vfs entry. pretty much all attempts to pretend otherwise will potentially lead to deadlocks. dont do it.
Reply
#8
(2012-09-27, 22:56)holf Wrote: I realised that in the top level menu, '@plugin.route('/')' I can simply always call a script to kill the VPN. And then in any of the sub-menus where streaming actually starts I can call a script to start the VPN.
Still, there's no guarantee that the user, after visiting a sub-directory, will return to the top one before exiting. E.g by pressing the home button. Plugins are assumed to be stateless so trying to give them state is a bad idea..
Reply
#9
Thanks everyone for the further replies. Yes, I realise there are probably better ways to do this and there are issues with the way I'm trying to do it (such as missing the 'stop vpn' script by pressing the home button... I may have to hack in a 'stop VPN' script when other geographically local streaming add-ons start).

However, I don't know any better, as yet, though I've learnt a lot just from the answers in this thread. This is my first jump in at the deep end with XBMC development (and Python) so it's small steps for me to start with. I'm pretty impressed with what I've seen so far though. It's obvious a lot of thought has gone into all this.

Reply
#10
Well, I made some further progress on this which I thought I'd report back on to help others.

I found that stopping and starting the VPN was taking too long. So, instead I decided to adjust my routing table to divert traffic either via the VPN or through my usual Default Gateway. The VPN provider I use gives you OpenVPN Certificates and config, so I can run the VPN as a service continually whilst adjusting routing as and when I need to.

OpenVPN diverts traffic by adding routes with more specific subnet masks. I used this trick to apply even more specific subnet masks, and ended up with these three Python functions:

Code:
def adjustRoutingTable(deleteOrAdd, subnetMaskBit):
  defaultGateway = '192.168.53.254'
  routeCommand = 'route {0} {1}.0.0.0 mask 192.0.0.0 {2} metric 11'.format(deleteOrAdd, subnetMaskBit, defaultGateway)
  Popen(routeCommand, creationflags=0x08000000)


def routeToDefaultNetwork():
  adjustRoutingTable('add', '0')
  adjustRoutingTable('add', '64')
  adjustRoutingTable('add', '128')
  adjustRoutingTable('add', '192')
  

def routeToOpenVPNNetwork():
  adjustRoutingTable('delete', '0')
  adjustRoutingTable('delete', '64')
  adjustRoutingTable('delete', '128')
  adjustRoutingTable('delete', '192')

(Yes, I'm coding this on Windows for now, hence the format of the route statement and the creationflags argument, which suppresses the brief flash of the command line shell that appears otherwise).

I'm still calling these functions when hitting different menus within the streaming Addons I have, so there is still the issue that a root menu (which routes traffic away from the VPN) could get missed.

I guess next I'll explore Service Addons. Ideally I want to end up with something where I can specify a list of Addons which will cause traffic to get routed to the VPN. Any other Addon will cause traffic to get routed away from the VPN again. I can then give this some UI for user configuration, such you can specify your non-VPN Default Gateway and also the list of Addons for VPN routing.

If this goes well I may get it going on 'nix OSes as well and perhaps even publish it somewhere.
Reply

Logout Mark Read Team Forum Stats Members Help
How to run code on exiting an Add-on0