v20 setSetting doesn't update values sometimes?
#1
Hi,

I wrote a legal video addon for a local TV broadcaster. The code is awful, because I am a noob at this, but so far I got everything I wanted done.

However I am using Kodi settings to store login tokens and other values I obtain during the login process. I know this is somewhat of an abuse of the Kodi settings, but when I tried ORMs, they all failed me since on a certain Kodi 20.x version the built-in sqlite module's close method caused a deadlock and so the whole python interpreter hung. Since I want to support all recent 20.x versions I decided to ditch the idea of using sqlite. Settings also has the nice benefit that I can have disabled values so the user can see their tokens if they want to.

But I also use services and threads in order to do certain tasks such as playback reporting, a small web service and timed EPG exporting.

The latter has problems. When Kodi starts, my service is spawned which makes sure that the login is successful by calling an authorize method and spawns a thread that periodically grabs the EPG. So far so good, this works normally. However if authorize() figures that the access token expired and obtains a new one, it tries to save these in settings and that never seems to happen. Here is the code that sets it. https://github.com/movieshark/VodkaTV/bl...#L285-L288

If I put an xbmc.log call right before those setSettings, I can see that it runs. Therefore setSetting is the culprit. It doesn't save the new values.

When I open the addon and it runs in foreground, it calls the same authorize method and the new token gets correctly saved.

So what am I doing wrong?

Is setSetting not thread safe? Or is it not supposed to be used from services?
Reply
#2
i cannot offer "on topic" help but i feel my chosen and proven method works if you are open to alternatives

i use json to store and load settings into my addon_data folder

json.dump and json.load will work with files, you can save and load a python dict with your auth credentials

general example, needs error checking

to save -

python:
import json
authfile="special://profile/addon_data/(your addon id)/auth.json"

auth = {'token': value}

with open(authfile, "w") as fp:
    json.dump(auth, fp, indent=4)

to load -

python:
with open(authfile, "r") as fp:
    auth = json.load(fp, strict=False)
Reply
#3
Yeah, I was thinking of doing that as well, but I can see some issues with that such as killing kodi at the wrong moment and therefore having ended up with a corrupted file... Or well, reading files isn't exactly thread safe either.

But I guess in case I ever run into a corrupted file, I can just redo the login process. Thanks for the suggestion.
Reply
#4
Here's another 'square peg round hole' option. I save things in 'addon memory' and 'kodi session' memory with success:

python:

#Save to kodi session memory
HOME_ID = 10000 #https://kodi.wiki/view/Window_IDs
xbmcgui.Window(HOME_ID).setProperty(my_key,json.dumps(my_dict))

#Get from kodi session memory
my_dict = json.loads(xbmcgui.Window(HOME_ID).getProperty(my_key))

#Save to my addon use session memory
MYWINDOW_ID = xbmcgui.getCurrentWindowId()
xbmcgui.Window(MYWINDOW_ID).setProperty(my_key,json.dumps(my_dict))

#Get from addon session memory
my_dict = json.loads(xbmcgui.Window(MYWINDOW_ID).getProperty(my_key))

Saving the key in the HOME_ID, the property will be saved until Kodi is exited, and then i have additional claptrap to expire the key after so long.
Reply
#5
Seems useful yeah, but I need the data to persist across kodi restarts.
Reply
#6
(2023-08-18, 18:15)Mr Dini Wrote: Hi,


The latter has problems. When Kodi starts, my service is spawned which makes sure that the login is successful by calling an authorize method and spawns a thread that periodically grabs the EPG. So far so good, this works normally. However if authorize() figures that the access token expired and obtains a new one, it tries to save these in settings and that never seems to happen. Here is the code that sets it. https://github.com/movieshark/VodkaTV/bl...#L285-L288

If I put an xbmc.log call right before those setSettings, I can see that it runs. Therefore setSetting is the culprit. It doesn't save the new values.

When I open the addon and it runs in foreground, it calls the same authorize method and the new token gets correctly saved.

So what am I doing wrong?

Is setSetting not thread safe? Or is it not supposed to be used from services?

I have a similar setup in one of my addons and I have seen scenarios in the past where I don't think it is thread safe.  Here's what I did to resolve.  I have a single function which does all of my getting and setting of addon  settings, whether from my service or the main addon GUI.  In the GUI example I also use Python Pickle to load a complex set of items into a single Kodi addon setting.  It works very well vs. a bunch of individual settings.  I've been using this approach for some time and have had no issues with settings getting wiped out or not being set properly.


Thanks,

Jeff
Running with the Mezzmo Kodi addon.  The easier way to share your media with multiple Kodi clients.
Service.autostop , CBC Sports, Kodi Selective Cleaner and Mezzmo Kodi addon author.
Reply
#7
Back to the question of whether setSettings() can be run from a service or another thread:
Here is a little test add-on that, I think, demonstrates that setSettings() works just fine in a service, and in a another thread.
Your issue may be due to the fact that you had the settings page open while testing the add-on.

service.py:
python:

import threading
import xbmc
import xbmcaddon

addon = xbmcaddon.Addon()

def print_settings(prefix='Thread -'):
    current_addon = xbmcaddon.Addon()
    xbmc.log("{} Setting 1 = {}".format(prefix, addon.getSetting('Setting1')), xbmc.LOGWARNING)
    xbmc.log("{} Setting 2 = {}".format(prefix, addon.getSetting('Setting2')), xbmc.LOGWARNING)
    xbmc.log("{} Setting 1 current addon = {}".format(prefix, current_addon.getSetting('Setting1')), xbmc.LOGWARNING)
    xbmc.log("{} Setting 2 current_addon = {}".format(prefix, current_addon.getSetting('Setting2')), xbmc.LOGWARNING)


def run():
    monitor = xbmc.Monitor()
    state = False
    while not monitor.abortRequested():
        print_settings()
        state = not state
        addon.setSetting('Setting2', str(state).lower())
        monitor.waitForAbort(10)


if __name__ == '__main__':
    # run()
    threading.Thread(target=run, daemon=True).start()
    monitor = xbmc.Monitor()
    monitor.waitForAbort(5)
    while not monitor.abortRequested():
        print_settings('Main -')
        monitor.waitForAbort(10)

If you run the addon and check the log after a while, you'll notice that Setting2 changes just as expected.
However, if you open the settings page of the add-on, you can see Settings2 change on the page, but no changes will be logged while the page is open. Settings wait for user confirmation before changes are applied.
This may seem a little odd at first, but from a normal user's perspective it would be rather odd if he closes settings by pressing cancel and finds some changes have still been applied regardless.
Reply
#8
Hold on! There's more going on.
It appears that when there are multiple instances of xbmcaddon.Addon, only the instance created first receives updated settings.

I got another test service addon to demonstrate at https://github.com/Kereltje/kodi-tests/t...ngs-via-ui

The code is in service.py:
python:

import xbmc
import xbmcaddon

addon1 = xbmcaddon.Addon()
addon2 = xbmcaddon.Addon()
addon3 = xbmcaddon.Addon()


def print_settings():
    current_addon = xbmcaddon.Addon()
    xbmc.log("Setting addon1 = {}".format(addon1.getSetting('Setting1')), xbmc.LOGWARNING)
    xbmc.log("Setting addon2 = {}".format(addon2.getSetting('Setting1')), xbmc.LOGWARNING)
    xbmc.log("Setting addon3 = {}".format(addon3.getSetting('Setting1')), xbmc.LOGWARNING)
    xbmc.log("Setting new addon = {}".format(current_addon.getSetting('Setting1')), xbmc.LOGWARNING)


def run():
    monitor = xbmc.Monitor()
    while not monitor.abortRequested():
        print_settings()
        monitor.waitForAbort(10)


if __name__ == '__main__':
    run()

If you run this service, open the addon's settings, change 'setting 1' and confirm by pressing OK, the log shows that only addon1 and the newly instantiated current_addon reflect the changes. The others keep the settings like they were at instantiation.
I haven't tested with setSettings() though, but I would be surprised if that was any different. (Well, not too surprised actually)
Reply
#9
(2023-08-24, 03:02)kereltje Wrote: If you run this service, open the addon's settings, change 'setting 1' and confirm by pressing OK, the log shows that only addon1 and the newly instantiated current_addon reflect the changes. The others keep the settings like they were at instantiation.
I haven't tested with setSettings() though, but I would be surprised if that was any different. (Well, not too surprised actually)


This is why I moved to the single function approach and call it from the service and main addon. I've had no further issues.


Thanks,

Jeff
Running with the Mezzmo Kodi addon.  The easier way to share your media with multiple Kodi clients.
Service.autostop , CBC Sports, Kodi Selective Cleaner and Mezzmo Kodi addon author.
Reply
#10
(2023-08-24, 03:02)kereltje Wrote: Hold on! There's more going on.
It appears that when there are multiple instances of xbmcaddon.Addon, only the instance created first receives updated settings.

I got another test service addon to demonstrate at https://github.com/Kereltje/kodi-tests/t...ngs-via-ui

The code is in service.py:
python:

import xbmc
import xbmcaddon

addon1 = xbmcaddon.Addon()
addon2 = xbmcaddon.Addon()
addon3 = xbmcaddon.Addon()


def print_settings():
    current_addon = xbmcaddon.Addon()
    xbmc.log("Setting addon1 = {}".format(addon1.getSetting('Setting1')), xbmc.LOGWARNING)
    xbmc.log("Setting addon2 = {}".format(addon2.getSetting('Setting1')), xbmc.LOGWARNING)
    xbmc.log("Setting addon3 = {}".format(addon3.getSetting('Setting1')), xbmc.LOGWARNING)
    xbmc.log("Setting new addon = {}".format(current_addon.getSetting('Setting1')), xbmc.LOGWARNING)


def run():
    monitor = xbmc.Monitor()
    while not monitor.abortRequested():
        print_settings()
        monitor.waitForAbort(10)


if __name__ == '__main__':
    run()

If you run this service, open the addon's settings, change 'setting 1' and confirm by pressing OK, the log shows that only addon1 and the newly instantiated current_addon reflect the changes. The others keep the settings like they were at instantiation.
I haven't tested with setSettings() though, but I would be surprised if that was any different. (Well, not too surprised actually)
That's the way object oriented programming works.  When you declare xbmcaddon.Addon() multiple times, they are separate, unrelated objects.  You either need to declare xbmcaddon.Addon() at the global level so it's accessible everywhere or do as suggested above and have one function where that work gets done.
Reply
#11
(2023-08-24, 12:53)pkscout Wrote: That's the way object oriented programming works.  When you declare xbmcaddon.Addon() multiple times, they are separate, unrelated objects.  You either need to declare xbmcaddon.Addon() at the global level so it's accessible everywhere or do as suggested above and have one function where that work gets done.

Thank you for addressing my "duhhh moment" explanation with this. I had solved my problem but never went back to look at why nor thought about xbmcaddon.Addon() being an object with separate istaniations.

Another option could be to delete the object and then istaniate another one but that would have a lot of overhead. Probably not an issue for infrequent settings changes but I like my single object / function approach.


Thanks,

Jeff
Running with the Mezzmo Kodi addon.  The easier way to share your media with multiple Kodi clients.
Service.autostop , CBC Sports, Kodi Selective Cleaner and Mezzmo Kodi addon author.
Reply
#12
(2023-08-24, 12:53)pkscout Wrote: That's the way object oriented programming works.
I'm afraid I have to disagree.
xbmcaddon.Addon is not like a regular python object that only performs operations on its own internal data, completely independent of every thing else. The whole purpose of an Addon object is to provide an interface to Kodi. By proxy, apparently, so it will have a local copy of the data, but that's an implementation detail.

My test revealed that every instance can set settings, that every instance will hold settings updated on itself, but only the first instance created will get new values when a setting is changed by other means, e.g. via the gui, or by setSettings() on another instance of Addon.

That only the first instance of a proxy object is capable of doing two-way synchronisation has nothing to do with the principles of object-oriented programming. Yes, they are separate objects, that was in fact the purpose of my test - create multiple objects that have the same interface to the same data set. And it turns out that despite that they don't behave the same. That's not an aspect of OOP, but just the way Kodi works.
Reply

Logout Mark Read Team Forum Stats Members Help
setSetting doesn't update values sometimes?0