Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ lib/
pip-selfcheck.json
pyvenv.cfg
MANIFEST


# path for the test lib.
tools/plex
51 changes: 51 additions & 0 deletions plexapi/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -1561,3 +1561,54 @@ def setArt(self, art):

# def edit(self, **kwargs):
# 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')
49 changes: 48 additions & 1 deletion plexapi/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from plexapi.base import PlexObject
from plexapi.client import PlexClient
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.playlist import Playlist
from plexapi.playqueue import PlayQueue
Expand Down Expand Up @@ -247,6 +247,53 @@ def _myPlexClientPorts(self):
log.warning('Unable to fetch client ports from myPlex: %s', err)
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):
""" Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
items = []
Expand Down
5 changes: 5 additions & 0 deletions plexapi/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import base64
import logging
import os
import re
Expand Down Expand Up @@ -411,3 +412,7 @@ def getAgentIdentifier(section, agent):
agents += identifiers
raise NotFound('Couldnt find "%s" in agents list (%s)' %
(agent, ', '.join(agents)))


def base64str(text):
return base64.b64encode(text.encode('utf-8')).decode('utf-8')
17 changes: 17 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,23 @@ def test_server_downloadDatabases(tmpdir, plex):
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):
plex = PlexServer(utils.SERVER_BASEURL, account.authenticationToken)
# Check server current allowMediaDeletion setting
Expand Down