Skip to content

DPlex 17.0.8.30

Latest

Choose a tag to compare

@6ahd 6ahd released this 13 Jul 05:03
b4cb045

DPlex 17.0.8.30

Changes since 17.0.8.10.

Includes the QR/PIN sign-in work that landed in 17.0.8.23 (plexsignin.py), plus the fixes below.


Fixed

Server discovery ran on every navigation

The discovered-server cache was never usable, so full discovery (myPlex API + GDM multicast) re-ran constantly — the "Server Discovery" progress bar reappearing on every screen.

Root cause was a silent write failure in cache_control.write_cache():

cache = xbmcvfs.File(path, 'w')            # file opened first
try:
    cache.write(bytearray(pickle.dumps(obj)))   # this raises
except Exception as error:
    LOG.debug('Writing error')             # swallowed
finally:
    cache.close()
return True                                # reports success anyway

pickle.dumps() raised because PlexMediaServer holds an AddonSettings, which wraps a non-picklable xbmcaddon.Addon(). The file had already been opened, so a 0-byte file was left on disk and the function still returned True. The next read_cache() saw an empty file, reported a miss, and discovery re-ran — forever.

discover_all_servers() did try to guard against this by nulling settings by hand before caching, but get_settings() lazily recreates it, so anything touching the server object in between reintroduced the unpicklable field.

  • PlexMediaServer.__getstate__ / __setstate__ now drop settings on every pickle, so the object is always serialisable regardless of call order. settings is rebuilt lazily after unpickling.
  • write_cache() serialises before opening the file, returns False on failure instead of True, and deletes any partial file.

A single unreachable server forced full re-discovery

Any ConnectionError or ReadTimeout set the refresh.servers window property, which makes load() discard the cached server list entirely. One offline server (e.g. a shared server that is powered down) therefore triggered a full myPlex + GDM discovery on every navigation.

Marking the server offline is sufficient. The refresh property is still set from manage_servers when the user adds or edits a server manually.

HTTPS → HTTP fallback was dead code

_request() caught every exception and returned None, so the except requests.exceptions.ConnectionError handler in talk() never ran and the protocol fallback never fired. _request() now lets connection errors propagate.

TMDb Helper: episode search returned the wrong results

Searching for an episode from TMDb Helper returned unrelated titles — searching Silo S02E01 surfaced 100 Foot Wave, NCIS: Los Angeles and Chicago P.D., because Plex's text search matches any shared word.

  • GUID matching first. tmdb_id / imdb_id / tvdb_id are now used as the primary match via /library/all?guid=. Matching previously relied on comparing the English {showname} against the Plex title, which never matches on Arabic/localised libraries. Both modern <Guid> tags and the legacy com.plexapp.agents.* attribute are parsed.
  • Short-circuit on GUID hit. The fuzzy text search no longer runs after a successful GUID lookup.
  • Post-filter text results (_filter_results_by_guid) by GUID, grandparentGuid, and season/episode.
  • URL-encode the query. The search term was interpolated raw, so spaces and Arabic characters produced a malformed request.
  • year is now required only for movie/show lookups. TMDb Helper often omits a usable year for episodes, which aborted the search before it began.
  • Season/episode comparison via _num_eq() tolerates '02' vs '2' vs 2.
  • dplex.json: search_episode asserts on season/episode and passes ep_title.

Performance

Compression was disabled

_request() sent Accept-Encoding: identity, so every Plex XML response travelled uncompressed. Plex XML is highly repetitive and compresses well. Now gzip, deflate.

No connection reuse

Every request went through a bare requests.get(), paying a fresh TCP + TLS handshake. A single directory listing issues many small XML calls. Replaced with a shared requests.Session with connection pooling.

inspect.stack() on every log call

Logger.__print_message() called inspect.stack() purely to print the calling function's name. It builds a frame record for every frame on the stack and reads source files from disk. Replaced with sys._getframe(2).f_code.co_name, which yields the identical name — measured ~500× faster.

The level check also now happens before the privacy regexes and the frame lookup, so a disabled log call costs almost nothing. Debug logging previously defaulted to on (debug=0; 2 is off), so this ran on every call for most users. The default is now off.

Existing installs keep their saved value — Kodi does not reapply defaults. Set Settings → Debug → Off to benefit.

One HTTP request per episode

create_episode_item() called server.get_metadata(grandparentRatingKey) when the show's TMDB id was missing from grandparentGuid. Every episode in a season shares the same grandparent, so a 50-episode season issued 50 identical HTTP requests while building the listing. Now cached per grandparentRatingKey (misses included, so a show with no TMDB id doesn't retry on every episode).

Cache layer

  • In-memory cache in front of the disk cache, avoiding both the filesystem hit and the pickle.loads() of a full ElementTree on repeat reads. Bounded (128 entries) with TTL and oldest-first eviction. Cleared by delete_cache() so "clear cache" isn't silently defeated by RAM.
  • is_valid() used xbmcvfs.exists() and xbmcvfs.Stat(); a single Stat() is sufficient — halves the filesystem calls per cached fetch.

Connection test timeout

connection_test() used a 60s read timeout, so an unreachable server stalled startup for a full minute. Reduced to 10s.


Hardening

Restricted unpickler

The cache is deserialised with a pickle.Unpickler subclass that only permits ElementTree types, plain containers, and DPlex's own PlexMediaServer / PlexSection. Anything else (os.system, subprocess.Popen, …) raises UnpicklingError. Measured overhead ~2%. A rejected or corrupt cache degrades to a cache miss rather than an exception.

Bare except: — 53 occurrences

except: also swallows KeyboardInterrupt and SystemExit, so Kodi's shutdown signal could be discarded, and genuine bugs (a typo'd name raising NameError) surfaced as misleading messages like "No token found". All converted to except Exception:. Behaviour for ordinary errors is unchanged.


Housekeeping

  • Removed 137 __pycache__ / .pyc files shipped inside the ZIP (~1.3 MB). Stale bytecode can also conflict with updated sources in some Kodi environments.
  • Fixed mojibake in plexserver.py comments (UTF-8 text saved under the wrong encoding).

Notes for upgraders

  • The old 0-byte server cache fails cleanly as a cache miss and is rebuilt on first run. No manual cleanup needed.
  • After updating, reinstall the player file from DPlex settings so TMDb Helper picks up the new dplex.json (it caches its own copy).
  • defusedxml was evaluated and not adopted: on the Python 3 shipped with current Kodi, ElementTree already blocks external-entity (XXE) resolution and enforces an entity-amplification limit, so it would add a dependency without adding protection.
Image 13-07-2026 at 07 37