Why is the main route always called twice?

Let’s say we’ve got (very simplified version):

import time

PREFIX = '/video/subzero'

@handler(PREFIX)
@route(PREFIX)
def test(randomize=None):
    print "called"
    oc = ObjectContainer(title1="test")
    oc.add(DirectoryObject(
        key=Callback(test, randomize=int(time.time())),
        title="test2"
    ))

Clicking the item “test2” yields (not 100% exact, this is the Sub-Zero debug output):

2016-04-23 03:20:38,688 - com.plexapp.agents.subzero       (7ffb3c324700) :  DEBUG (runtime:717) - Handling request GET /video/subzero?randomize=1461374087
2016-04-23 03:20:38,689 - com.plexapp.agents.subzero       (7ffb3c324700) :  DEBUG (runtime:814) - Found route matching /video/subzero
called
2016-04-23 03:20:38,691 - com.plexapp.agents.subzero       (7ffb3c324700) :  DEBUG (runtime:88) - Sending packed state data (107 bytes)
2016-04-23 03:20:38,691 - com.plexapp.agents.subzero       (7ffb3c324700) :  DEBUG (runtime:924) - Response: [200] MediaContainer, 1826 bytes
2016-04-23 03:20:38,744 - com.plexapp.agents.subzero       (7ffb3c324700) :  DEBUG (runtime:717) - Handling request GET /video/subzero?randomize=1461374427
2016-04-23 03:20:38,746 - com.plexapp.agents.subzero       (7ffb3c324700) :  DEBUG (runtime:814) - Found route matching /video/subzero
called
2016-04-23 03:20:38,748 - com.plexapp.agents.subzero       (7ffb3c324700) :  DEBUG (runtime:88) - Sending packed state data (107 bytes)
2016-04-23 03:20:38,748 - com.plexapp.agents.subzero       (7ffb3c324700) :  DEBUG (runtime:924) - Response: [200] MediaContainer, 1826 bytes

No matter how I modify this, the function test always gets called twice when clicking the menu item in the menu. Removing the @route doesn’t change anything, the randomize parameter doesn’t change anything there (anymore).

If I remember correctly, this used to work - I could “redirect” the user to the main view of my channel by returning the main view itself with a randomized URL parameter.

The main menu should just have the handler. It does not need a route. Your other functions will just have a route.

If your change is not showing up either you are not existing out far enough for the Plex player to pick up the changes or your are using a Plex player like Chrome that doesn’t allow you to see updates because of the cache. See forums.plex.tv/discussion/208369/whats-your-channel-development-and-debugging-workflow-and-why-is-mine-so-painful#latest

Sometimes it takes a few minutes to see the results of any changes even with the Roku.

I’m sorry, but I don’t really get what you’re trying to tell me.
What has the fact, that my main route always gets called twice when I click on the item “test” to do with caching?

When I skip the randomize attribute, the click on the test item will do nothing, because PMS’s routing thinks it’s the same route as the current one.

Is there an official way to refresh the current view? I’ve picked up the randomize technique from the traktscrobbler plugin if I remember correctly. And it should work without calling the main handler twice, from a logical point of view.

As for the redundant handler/route combination: I’ve said in the initial post, that removing the route from the main handler doesn’t change anything (it actually squelches the warning for an unregistered route).

Edit: There’s been another person with a similar problem here.

some clients seem to call routes twice. I did a test and PHT and Android both do it.
Plex Web,iOS and PMP just do a single request.

When a client does two requests, the plex media server log shows:

Request: [192.168.1.113:44795] GET /video/test/testroute (13 live) TLS GZIP
Request: [192.168.1.113:44795] GET /video/test/testroute (13 live) TLS Page 0-49 GZIP

the difference in the requests is in Request.Headers

1462471575.21 OpenPHT: GET
    # no container headers

1462471575.22 OpenPHT: GET
    X-Plex-Container-Size: 50
    X-Plex-Container-Start: 0

afaik channels don’t even make use of these paging headers.

test code:

import time

NAME = 'Test'
PREFIX = '/video/test'


def Start():
    pass


@handler(PREFIX, NAME)
def MainMenu():
    oc = ObjectContainer(title2='test')
    oc.add(DirectoryObject(key=Callback(Test), title='test'))
    return oc


@route(PREFIX + '/testroute')
def Test():
    Log.Info('{} {}: {}'.format(time.time(), Client.Product, Request.Method))

    for k, v in sorted(Request.Headers.iteritems()):
        Log.Info('    {}: {}'.format(k, v))

    oc = ObjectContainer()
    oc.add(DirectoryObject(key=Callback(Test), title='test'))
    return oc

so some really hacky solution could be to put

if (Client.Product in ['Plex for Android', 'OpenPHT'] and
            'X-Plex-Container-Size' not in Request.Headers and
            'X-Plex-Container-Start' not in Request.Headers):
        Log.Info('skipping request')
        return ObjectContainer()

at the start of a route function so those clients only run code below it for the 2nd request.

@coryo123 thanks - I really don’t know why Plex behaves like that. It doesn’t really make any sense to me.
Also in my case it’s not related to any direct pagination requests.

Does that also apply to my example, where I try to “refresh” the main route by returning the original ObjectContainer with a randomized parameter?

I think using the handler and route decorator together is not intended.

if you want a single menu with a button that updates the current menu, you can do this

@handler(PREFIX, NAME)
def MainMenu(no_history=False, randomize=None):
    Log.Info('MainMenu: {} {}: {}'.format(randomize, Client.Product, Request.Method))
    Log.Info('    X-Plex-Container-Size: {}'.format(Request.Headers.get('X-Plex-Container-Size')))
    Log.Info('    X-Plex-Container-Start: {}'.format(Request.Headers.get('X-Plex-Container-Start')))
    oc = ObjectContainer(title1="main menu", no_history=no_history)
    oc.add(DirectoryObject(
        key=Callback(SubMenu),
        title="go to sub menu {}".format(randomize)
    ))
    return oc


@route(PREFIX + '/submenu')
def SubMenu():
    Log.Info('Sub Menu')
    return MainMenu(no_history=True, randomize=time.time())
# initial menu
INFO (logkit:16) - MainMenu: None OpenPHT: GET
INFO (logkit:16) -     X-Plex-Container-Size: 50
INFO (logkit:16) -     X-Plex-Container-Start: 0
# press button, double request
INFO (logkit:16) - Sub Menu
INFO (logkit:16) - MainMenu: 1462486849.5 OpenPHT: GET
INFO (logkit:16) -     X-Plex-Container-Size: None
INFO (logkit:16) -     X-Plex-Container-Start: None
INFO (logkit:16) - Sub Menu
INFO (logkit:16) - MainMenu: 1462486849.51 OpenPHT: GET
INFO (logkit:16) -     X-Plex-Container-Size: 50
INFO (logkit:16) -     X-Plex-Container-Start: 0

no_history only works on PHT though, so every other client is going to end up with a large history stack to back out of. and you will also have the double calls because of the client paging.

Thanks for the tip, I’ll try that. Though I mainly noticed that problem with PlexWeb.

I’m using the technique I’ve posted above for a “refresh” button in the main menu. That results in a real refresh of the main menu and the user doesn’t have to go through another menu for a refresh.

As for handler+route: not using @route with @handler results (resulted?) in warnings in the log and it didn’t impact anything else.

It’s weird that the most used (?) client, PlexWeb, seems to support the least of the available channel development features (no_history, replace parent etc.).

based on the trends with client development, I think it’s safe to assume the history related properties (no_history, replace_parent …) are deprecated. they work for PHT, but plexinc doesn’t develop it anymore.

what you’re trying to do definitely isn’t intended behaviour for the new clients, so it will take some dumb hacks to get something working and even then it might only work on a few clients.

Don’t get me wrong, I want to ditch those hacky solutions. But what’s happening is, that documented features get abandoned and channel development gets ridiculous.

A refresh-current-view feature wouldn’t be too hard for plexinc I think :slight_smile: