Skip to content

Commit 1d77f32

Browse files
authored
Merge pull request #837 from pkkid/libraryEditUpdates
Library editing updates
2 parents a497613 + 34f0125 commit 1d77f32

File tree

3 files changed

+134
-12
lines changed

3 files changed

+134
-12
lines changed

plexapi/library.py

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def add(self, name='', type='', agent='', scanner='', location='', language='en'
169169
name (str): Name of the library
170170
agent (str): Example com.plexapp.agents.imdb
171171
type (str): movie, show, # check me
172-
location (str): /path/to/files
172+
location (str or list): /path/to/files, ["/path/to/files", "/path/to/morefiles"]
173173
language (str): Two letter language fx en
174174
kwargs (dict): Advanced options should be passed as a dict. where the id is the key.
175175
@@ -308,8 +308,16 @@ def add(self, name='', type='', agent='', scanner='', location='', language='en'
308308
40:South Africa, 41:Spain, 42:Sweden, 43:Switzerland, 44:Taiwan, 45:Trinidad,
309309
46:United Kingdom, 47:United States, 48:Uruguay, 49:Venezuela.
310310
"""
311-
part = '/library/sections?name=%s&type=%s&agent=%s&scanner=%s&language=%s&location=%s' % (
312-
quote_plus(name), type, agent, quote_plus(scanner), language, quote_plus(location)) # noqa E126
311+
if isinstance(location, str):
312+
location = [location]
313+
locations = []
314+
for path in location:
315+
if not self._server.isBrowsable(path):
316+
raise BadRequest('Path: %s does not exist.' % path)
317+
locations.append(('location', path))
318+
319+
part = '/library/sections?name=%s&type=%s&agent=%s&scanner=%s&language=%s&%s' % (
320+
quote_plus(name), type, agent, quote_plus(scanner), language, urlencode(locations, doseq=True)) # noqa E126
313321
if kwargs:
314322
part += urlencode(kwargs)
315323
return self._server.query(part, method=self._server._session.post)
@@ -486,16 +494,76 @@ def reload(self):
486494
return self
487495

488496
def edit(self, agent=None, **kwargs):
489-
""" Edit a library (Note: agent is required). See :class:`~plexapi.library.Library` for example usage.
497+
""" Edit a library. See :class:`~plexapi.library.Library` for example usage.
490498
491499
Parameters:
500+
agent (str, optional): The library agent.
492501
kwargs (dict): Dict of settings to edit.
493502
"""
494503
if not agent:
495504
agent = self.agent
496-
part = '/library/sections/%s?agent=%s&%s' % (self.key, agent, urlencode(kwargs))
505+
506+
locations = []
507+
if kwargs.get('location'):
508+
if isinstance(kwargs['location'], str):
509+
kwargs['location'] = [kwargs['location']]
510+
for path in kwargs.pop('location'):
511+
if not self._server.isBrowsable(path):
512+
raise BadRequest('Path: %s does not exist.' % path)
513+
locations.append(('location', path))
514+
515+
params = list(kwargs.items()) + locations
516+
517+
part = '/library/sections/%s?agent=%s&%s' % (self.key, agent, urlencode(params, doseq=True))
497518
self._server.query(part, method=self._server._session.put)
498519

520+
def addLocations(self, location):
521+
""" Add a location to a library.
522+
523+
Parameters:
524+
location (str or list): A single folder path, list of paths.
525+
526+
Example:
527+
528+
.. code-block:: python
529+
530+
LibrarySection.addLocations('/path/1')
531+
LibrarySection.addLocations(['/path/1', 'path/2', '/path/3'])
532+
"""
533+
locations = self.locations
534+
if isinstance(location, str):
535+
location = [location]
536+
for path in location:
537+
if not self._server.isBrowsable(path):
538+
raise BadRequest('Path: %s does not exist.' % path)
539+
locations.append(path)
540+
self.edit(location=locations)
541+
542+
def removeLocations(self, location):
543+
""" Remove a location from a library.
544+
545+
Parameters:
546+
location (str or list): A single folder path, list of paths.
547+
548+
Example:
549+
550+
.. code-block:: python
551+
552+
LibrarySection.removeLocations('/path/1')
553+
LibrarySection.removeLocations(['/path/1', 'path/2', '/path/3'])
554+
"""
555+
locations = self.locations
556+
if isinstance(location, str):
557+
location = [location]
558+
for path in location:
559+
if path in locations:
560+
locations.remove(path)
561+
else:
562+
raise BadRequest('Path: %s does not exist in the library.' % location)
563+
if len(locations) == 0:
564+
raise BadRequest('You are unable to remove all locations from a library.')
565+
self.edit(location=locations)
566+
499567
def get(self, title):
500568
""" Returns the media item with the specified title.
501569

plexapi/server.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from xml.etree import ElementTree
44

55
import requests
6+
import os
67
from plexapi import (BASE_HEADERS, CONFIG, TIMEOUT, X_PLEX_CONTAINER_SIZE, log,
78
logfilter)
89
from plexapi import utils
@@ -384,6 +385,18 @@ def walk(self, path=None):
384385
for path, paths, files in self.walk(_path):
385386
yield path, paths, files
386387

388+
def isBrowsable(self, path):
389+
""" Returns True if the Plex server can browse the given path.
390+
391+
Parameters:
392+
path (:class:`~plexapi.library.Path` or str): Full path to browse.
393+
"""
394+
if isinstance(path, Path):
395+
path = path.path
396+
path = os.path.normpath(path)
397+
paths = [p.path for p in self.browse(os.path.dirname(path), includeFiles=False)]
398+
return path in paths
399+
387400
def clients(self):
388401
""" Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
389402
items = []

tests/test_library.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,24 +121,65 @@ def test_library_recentlyAdded(plex):
121121
assert len(list(plex.library.recentlyAdded()))
122122

123123

124-
def test_library_add_edit_delete(plex):
125-
# Dont add a location to prevent scanning scanning
124+
def test_library_add_edit_delete(plex, movies, photos):
125+
# Create Other Videos library = No external metadata scanning
126126
section_name = "plexapi_test_section"
127+
movie_location = movies.locations[0]
128+
photo_location = photos.locations[0]
127129
plex.library.add(
128130
name=section_name,
129131
type="movie",
130-
agent="com.plexapp.agents.imdb",
131-
scanner="Plex Movie Scanner",
132+
agent="com.plexapp.agents.none",
133+
scanner="Plex Video Files Scanner",
132134
language="en",
135+
location=[movie_location, photo_location]
133136
)
134137
section = plex.library.section(section_name)
135138
assert section.title == section_name
139+
# Create library with an invalid path
140+
error_section_name = "plexapi_error_section"
141+
with pytest.raises(BadRequest):
142+
plex.library.add(
143+
name=error_section_name,
144+
type="movie",
145+
agent="com.plexapp.agents.none",
146+
scanner="Plex Video Files Scanner",
147+
language="en",
148+
location=[movie_location, photo_location[:-1]]
149+
)
150+
# Create library with no path
151+
with pytest.raises(BadRequest):
152+
plex.library.add(
153+
name=error_section_name,
154+
type="movie",
155+
agent="com.plexapp.agents.none",
156+
scanner="Plex Video Files Scanner",
157+
language="en",
158+
)
159+
with pytest.raises(NotFound):
160+
plex.library.section(error_section_name)
136161
new_title = "a renamed lib"
137-
section.edit(
138-
name=new_title, type="movie", agent="com.plexapp.agents.imdb"
139-
)
162+
section.edit(name=new_title)
140163
section.reload()
141164
assert section.title == new_title
165+
with pytest.raises(BadRequest):
166+
section.addLocations(movie_location[:-1])
167+
with pytest.raises(BadRequest):
168+
section.removeLocations(movie_location[:-1])
169+
section.removeLocations(photo_location)
170+
section.reload()
171+
assert len(section.locations) == 1
172+
section.addLocations(photo_location)
173+
section.reload()
174+
assert len(section.locations) == 2
175+
section.edit(**{'location': movie_location})
176+
section.reload()
177+
assert len(section.locations) == 1
178+
with pytest.raises(BadRequest):
179+
section.edit(**{'location': movie_location[:-1]})
180+
# Attempt to remove all locations
181+
with pytest.raises(BadRequest):
182+
section.removeLocations(section.locations)
142183
section.delete()
143184
assert section not in plex.library.sections()
144185

0 commit comments

Comments
 (0)