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 anywaypickle.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 dropsettingson every pickle, so the object is always serialisable regardless of call order.settingsis rebuilt lazily after unpickling.write_cache()serialises before opening the file, returnsFalseon failure instead ofTrue, 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_idare 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 legacycom.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.
yearis 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'vs2. dplex.json:search_episodeasserts onseason/episodeand passesep_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 fullElementTreeon repeat reads. Bounded (128 entries) with TTL and oldest-first eviction. Cleared bydelete_cache()so "clear cache" isn't silently defeated by RAM. is_valid()usedxbmcvfs.exists()andxbmcvfs.Stat(); a singleStat()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__/.pycfiles shipped inside the ZIP (~1.3 MB). Stale bytecode can also conflict with updated sources in some Kodi environments. - Fixed mojibake in
plexserver.pycomments (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). defusedxmlwas evaluated and not adopted: on the Python 3 shipped with current Kodi,ElementTreealready blocks external-entity (XXE) resolution and enforces an entity-amplification limit, so it would add a dependency without adding protection.
