mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-23 04:03:05 +00:00
Merge pull request #135 from Hellowlol/logs
Add download log/db and stop playback
This commit is contained in:
commit
621d6fe667
8 changed files with 94 additions and 22 deletions
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import threading
|
||||
import websocket
|
||||
from plexapi import log
|
||||
from plexapi.exceptions import Unsupported
|
||||
|
||||
|
||||
class AlertListener(threading.Thread):
|
||||
|
@ -12,9 +12,6 @@ class AlertListener(threading.Thread):
|
|||
alerts you must call .start() on the object once it's created. When calling
|
||||
`PlexServer.startAlertListener()`, the thread will be started for you.
|
||||
|
||||
In order to use this feature, you must have websocket-client installed in your Python path.
|
||||
This can be installed vis pip `pip install websocket-client`.
|
||||
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer this listener is connected to.
|
||||
callback (func): Callback function to call on recieved messages. The callback function
|
||||
|
@ -30,16 +27,10 @@ class AlertListener(threading.Thread):
|
|||
super(AlertListener, self).__init__()
|
||||
|
||||
def run(self):
|
||||
# try importing websocket-client package
|
||||
try:
|
||||
import websocket
|
||||
except:
|
||||
raise Unsupported('Websocket-client package is required to use this feature.')
|
||||
# create the websocket connection
|
||||
url = self._server.url(self.key).replace('http', 'ws')
|
||||
log.info('Starting AlertListener: %s', url)
|
||||
self._ws = websocket.WebSocketApp(url,
|
||||
on_message=self._onMessage,
|
||||
self._ws = websocket.WebSocketApp(url, on_message=self._onMessage,
|
||||
on_error=self._onError)
|
||||
self._ws.run_forever()
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from plexapi import log, utils
|
||||
from plexapi.compat import urlencode
|
||||
from plexapi.compat import quote_plus, urlencode
|
||||
from plexapi.exceptions import BadRequest, NotFound, UnknownType, Unsupported
|
||||
|
||||
OPERATORS = {
|
||||
|
@ -354,6 +354,7 @@ class Playable(object):
|
|||
sessionKey (int): Active session key.
|
||||
username (str): Username of the person playing this item (for active sessions).
|
||||
players (:class:`~plexapi.client.PlexClient`): Client objects playing this item (for active sessions).
|
||||
session (:class:`~plexapi.media.Session`): Session object, for a playing media file.
|
||||
transcodeSession (:class:`~plexapi.media.TranscodeSession`): Transcode Session object
|
||||
if item is being transcoded (None otherwise).
|
||||
viewedAt (datetime): Datetime item was last viewed (history).
|
||||
|
@ -364,6 +365,7 @@ class Playable(object):
|
|||
self.usernames = self.listAttrs(data, 'title', etag='User') # session
|
||||
self.players = self.findItems(data, etag='Player') # session
|
||||
self.transcodeSessions = self.findItems(data, etag='TranscodeSession') # session
|
||||
self.session = self.findItems(data, etag='Session') # session
|
||||
self.viewedAt = utils.toDatetime(data.attrib.get('viewedAt')) # history
|
||||
self.playlistItemID = utils.cast(int, data.attrib.get('playlistItemID')) # playlist
|
||||
|
||||
|
@ -444,3 +446,8 @@ class Playable(object):
|
|||
if filepath:
|
||||
filepaths.append(filepath)
|
||||
return filepaths
|
||||
|
||||
def stop(self, reason=''):
|
||||
""" Stop playback for a media item. """
|
||||
key = '/status/sessions/terminate?sessionId=%s&reason=%s' % (self.session[0].id, quote_plus(reason))
|
||||
return self._server.query(key)
|
||||
|
|
|
@ -17,6 +17,11 @@ try:
|
|||
except ImportError:
|
||||
from urllib import quote
|
||||
|
||||
try:
|
||||
from urllib.parse import quote_plus
|
||||
except ImportError:
|
||||
from urllib import quote_plus
|
||||
|
||||
try:
|
||||
from urllib.parse import unquote
|
||||
except ImportError:
|
||||
|
@ -31,4 +36,3 @@ try:
|
|||
from xml.etree import cElementTree as ElementTree
|
||||
except ImportError:
|
||||
from xml.etree import ElementTree
|
||||
|
|
@ -253,6 +253,17 @@ class SubtitleStream(MediaPartStream):
|
|||
self.title = data.attrib.get('title')
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class Session(PlexObject):
|
||||
""" Represents a current session. """
|
||||
TAG = 'Session'
|
||||
|
||||
def _loadData(self, data):
|
||||
self.id = data.attrib.get('id')
|
||||
self.bandwidth = utils.cast(int, data.attrib.get('bandwidth'))
|
||||
self.location = data.attrib.get('location')
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class TranscodeSession(PlexObject):
|
||||
""" Represents a current transcode session.
|
||||
|
|
|
@ -174,12 +174,28 @@ class PlexServer(PlexObject):
|
|||
|
||||
def clients(self):
|
||||
""" Returns a list of all :class:`~plexapi.client.PlexClient` objects
|
||||
connected to this server.
|
||||
"""
|
||||
connected to this server."""
|
||||
|
||||
items = []
|
||||
cache_resource = None
|
||||
from plexapi.myplex import MyPlexResource
|
||||
for elem in self.query('/clients'):
|
||||
baseurl = 'http://%s:%s' % (elem.attrib['host'], elem.attrib['port'])
|
||||
items.append(PlexClient(baseurl, server=self, data=elem))
|
||||
# Some shitty clients dont include a port..
|
||||
port = elem.attrib.get('port')
|
||||
if port is None:
|
||||
log.debug("%s didn't provide a port. Checking https://plex.tv/devices.xml" % elem.attrib.get('name'))
|
||||
data = cache_resource or self._server._session.get('https://plex.tv/devices.xml?X-Plex-Token=%s' % self.token) # noqa
|
||||
cache_resource = data
|
||||
resources = MyPlexResource(self, data)
|
||||
for resource in resources:
|
||||
if resource.clientIdentifier == elem.attrib.get('machineIdentifier'):
|
||||
for conn in resource.connection:
|
||||
if conn.local is True:
|
||||
port = conn.port
|
||||
break
|
||||
|
||||
baseurl = 'http://%s:%s' % (elem.attrib['host'], port)
|
||||
items.append(PlexClient(baseurl=baseurl, server=self, data=elem))
|
||||
return items
|
||||
|
||||
def client(self, name):
|
||||
|
@ -326,6 +342,16 @@ class PlexServer(PlexObject):
|
|||
return '%s%s%sX-Plex-Token=%s' % (self._baseurl, key, delim, self._token)
|
||||
return '%s%s' % (self._baseurl, key)
|
||||
|
||||
def downloadLogs(self, savepath=None, unpack=False):
|
||||
url = self.url('/diagnostics/databases')
|
||||
fp = utils.download(url, filename=None, savepath=savepath, unpack=unpack, session=self._session)
|
||||
return fp
|
||||
|
||||
def downloadDBS(self, savepath=None, unpack=False):
|
||||
url = self.url('/diagnostics/logs')
|
||||
fp = utils.download(url, filename=None, savepath=savepath, unpack=unpack, session=self._session)
|
||||
return fp
|
||||
|
||||
|
||||
class Account(PlexObject):
|
||||
""" Contains the locally cached MyPlex account information. The properties provided don't
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
import time
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from threading import Thread
|
||||
from plexapi.compat import quote, string_type
|
||||
from plexapi.exceptions import NotFound
|
||||
from threading import Thread
|
||||
|
||||
|
||||
# Search Types - Plex uses these to filter specific media types when searching.
|
||||
# Library Types - Populated at runtime
|
||||
|
@ -224,7 +227,7 @@ def downloadSessionImages(server, filename=None, height=150, width=150, opacity=
|
|||
return info
|
||||
|
||||
|
||||
def download(url, filename=None, savepath=None, session=None, chunksize=4024, mocked=False):
|
||||
def download(url, filename=None, savepath=None, session=None, chunksize=4024, mocked=False, unpack=False):
|
||||
""" Helper to download a thumb, videofile or other media item. Returns the local
|
||||
path to the downloaded file.
|
||||
|
||||
|
@ -234,6 +237,7 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, mo
|
|||
savepath (str): Defaults to current working dir.
|
||||
chunksize (int): What chunksize read/write at the time.
|
||||
mocked (bool): Helper to do evertything except write the file.
|
||||
unpack (bool): Unpack the zip file
|
||||
|
||||
Example:
|
||||
>>> download(a_episode.getStreamURL(), a_episode.location)
|
||||
|
@ -251,10 +255,20 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, mo
|
|||
except OSError:
|
||||
if not os.path.isdir(savepath): # pragma: no cover
|
||||
raise
|
||||
filename = os.path.basename(filename)
|
||||
fullpath = os.path.join(savepath, filename)
|
||||
|
||||
try:
|
||||
response = session.get(url, stream=True)
|
||||
|
||||
# Lets grab the name if we dont supply one.
|
||||
# This will be used for downloading logs/db etc.
|
||||
if filename is None and response.headers.get('Content-Disposition'):
|
||||
filename = re.findall(ur'filename=\"(.+)\"', response.headers.get('Content-Disposition'))
|
||||
if filename:
|
||||
filename = filename[0]
|
||||
|
||||
filename = os.path.basename(filename)
|
||||
fullpath = os.path.join(savepath, filename)
|
||||
|
||||
# images dont have a extention so we try
|
||||
# to guess it from content-type
|
||||
ext = os.path.splitext(fullpath)[-1]
|
||||
|
@ -266,16 +280,24 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, mo
|
|||
if 'image' in cp:
|
||||
ext = '.%s' % cp.split('/')[1]
|
||||
fullpath = '%s%s' % (fullpath, ext)
|
||||
|
||||
if mocked:
|
||||
log.debug('Mocked download %s', fullpath)
|
||||
return fullpath
|
||||
|
||||
with open(fullpath, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=chunksize):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
if fullpath.endswith('zip') and unpack is True:
|
||||
with zipfile.ZipFile(fullpath, 'r') as zp:
|
||||
zp.extractall(savepath)
|
||||
|
||||
# log.debug('Downloaded %s to %s from %s' % (filename, fullpath, url))
|
||||
return fullpath
|
||||
|
||||
except Exception as err: # pragma: no cover
|
||||
log.error('Error downloading file: %s' % err)
|
||||
log.exception('Error downloading file: %s' % err)
|
||||
raise
|
||||
# log.exception('Failed to download %s to %s %s' % (url, fullpath, e))
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
# pip install -r requirments.txt
|
||||
#---------------------------------------------------------
|
||||
requests
|
||||
websocket
|
|
@ -206,3 +206,13 @@ def test_server_account(pms):
|
|||
assert acc.subscriptionFeatures == []
|
||||
assert acc.subscriptionState == 'Unknown'
|
||||
assert acc.username == 'testplexapi@gmail.com'
|
||||
|
||||
|
||||
def test_server_downloadLogs(tmpdir, pms):
|
||||
pms.downloadLogs(savepath=str(tmpdir), unpack=True)
|
||||
assert len(tmpdir.listdir()) > 1
|
||||
|
||||
|
||||
def test_server_downloadDB(tmpdir, pms):
|
||||
pms.downloadDBS(savepath=str(tmpdir), unpack=True)
|
||||
assert len(tmpdir.listdir()) > 1
|
||||
|
|
Loading…
Reference in a new issue