Merge pull request #601 from JonnyWong16/server_browse

Add ability to browse and walk the Plex server system file directories
This commit is contained in:
Steffen Fredriksen 2020-11-23 07:48:53 +01:00 committed by GitHub
commit ae59620e9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 1 deletions

4
.gitignore vendored
View file

@ -26,3 +26,7 @@ lib/
pip-selfcheck.json pip-selfcheck.json
pyvenv.cfg pyvenv.cfg
MANIFEST MANIFEST
# path for the test lib.
tools/plex

View file

@ -1570,3 +1570,54 @@ class Collections(PlexPartialObject):
# def edit(self, **kwargs): # def edit(self, **kwargs):
# TODO # TODO
@utils.registerPlexObject
class Path(PlexObject):
""" Represents a single directory Path.
Attributes:
TAG (str): 'Path'
home (bool): True if the path is the home directory
key (str): API URL (/services/browse/<base64path>)
network (bool): True if path is a network location
path (str): Full path to folder
title (str): Folder name
"""
TAG = 'Path'
def _loadData(self, data):
self.home = utils.cast(bool, data.attrib.get('home'))
self.key = data.attrib.get('key')
self.network = utils.cast(bool, data.attrib.get('network'))
self.path = data.attrib.get('path')
self.title = data.attrib.get('title')
def browse(self, includeFiles=True):
""" Alias for :func:`~plexapi.server.PlexServer.browse`. """
return self._server.browse(self, includeFiles)
def walk(self):
""" Alias for :func:`~plexapi.server.PlexServer.walk`. """
for path, paths, files in self._server.walk(self):
yield path, paths, files
@utils.registerPlexObject
class File(PlexObject):
""" Represents a single File.
Attributes:
TAG (str): 'File'
key (str): API URL (/services/browse/<base64path>)
path (str): Full path to file
title (str): File name
"""
TAG = 'File'
def _loadData(self, data):
self.key = data.attrib.get('key')
self.path = data.attrib.get('path')
self.title = data.attrib.get('title')

View file

@ -15,7 +15,7 @@ from plexapi.alert import AlertListener
from plexapi.base import PlexObject from plexapi.base import PlexObject
from plexapi.client import PlexClient from plexapi.client import PlexClient
from plexapi.exceptions import BadRequest, NotFound, Unauthorized from plexapi.exceptions import BadRequest, NotFound, Unauthorized
from plexapi.library import Hub, Library from plexapi.library import Hub, Library, Path, File
from plexapi.media import Conversion, Optimized from plexapi.media import Conversion, Optimized
from plexapi.playlist import Playlist from plexapi.playlist import Playlist
from plexapi.playqueue import PlayQueue from plexapi.playqueue import PlayQueue
@ -247,6 +247,53 @@ class PlexServer(PlexObject):
log.warning('Unable to fetch client ports from myPlex: %s', err) log.warning('Unable to fetch client ports from myPlex: %s', err)
return ports return ports
def browse(self, path=None, includeFiles=True):
""" Browse the system file path using the Plex API.
Returns list of :class:`~plexapi.library.Path` and :class:`~plexapi.library.File` objects.
Parameters:
path (:class:`~plexapi.library.Path` or str, optional): Full path to browse.
includeFiles (bool): True to include files when browsing (Default).
False to only return folders.
"""
if isinstance(path, Path):
key = path.key
elif path is not None:
base64path = utils.base64str(path)
key = '/services/browse/%s' % base64path
else:
key = '/services/browse'
if includeFiles:
key += '?includeFiles=1'
return self.fetchItems(key)
def walk(self, path=None):
""" Walk the system file tree using the Plex API similar to `os.walk`.
Yields a 3-tuple `(path, paths, files)` where
`path` is a string of the directory path,
`paths` is a list of :class:`~plexapi.library.Path` objects, and
`files` is a list of :class:`~plexapi.library.File` objects.
Parameters:
path (:class:`~plexapi.library.Path` or str, optional): Full path to walk.
"""
paths = []
files = []
for item in self.browse(path):
if isinstance(item, Path):
paths.append(item)
elif isinstance(item, File):
files.append(item)
if isinstance(path, Path):
path = path.path
yield path or '', paths, files
for _path in paths:
for path, paths, files in self.walk(_path):
yield path, paths, files
def clients(self): def clients(self):
""" Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """ """ Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
items = [] items = []

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import base64
import logging import logging
import os import os
import re import re
@ -411,3 +412,7 @@ def getAgentIdentifier(section, agent):
agents += identifiers agents += identifiers
raise NotFound('Couldnt find "%s" in agents list (%s)' % raise NotFound('Couldnt find "%s" in agents list (%s)' %
(agent, ', '.join(agents))) (agent, ', '.join(agents)))
def base64str(text):
return base64.b64encode(text.encode('utf-8')).decode('utf-8')

View file

@ -287,6 +287,23 @@ def test_server_downloadDatabases(tmpdir, plex):
assert len(tmpdir.listdir()) > 1 assert len(tmpdir.listdir()) > 1
def test_server_browse(plex, movies):
movies_path = movies.locations[0]
# browse root
paths = plex.browse()
assert len(paths)
# browse the path of the movie library
paths = plex.browse(movies_path)
assert len(paths)
# browse the path of the movie library without files
paths = plex.browse(movies_path, includeFiles=False)
assert not len([f for f in paths if f.TAG == 'File'])
# walk the path of the movie library
for path, paths, files in plex.walk(movies_path):
assert path.startswith(movies_path)
assert len(paths) or len(files)
def test_server_allowMediaDeletion(account): def test_server_allowMediaDeletion(account):
plex = PlexServer(utils.SERVER_BASEURL, account.authenticationToken) plex = PlexServer(utils.SERVER_BASEURL, account.authenticationToken)
# Check server current allowMediaDeletion setting # Check server current allowMediaDeletion setting