Passed
Push — develop ( 289973...c6fa42 )
by Dean
02:49
created

redirect()   F

Complexity

Conditions 9

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
c 1
b 0
f 0
dl 0
loc 48
rs 3.5294
1
from core.logger import Logger
0 ignored issues
show
Bug introduced by
The name logger does not seem to exist in module core.
Loading history...
Configuration introduced by
The import core.logger could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
2
3
from plugin.core.constants import PLUGIN_PREFIX
0 ignored issues
show
Configuration introduced by
The import plugin.core.constants could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
4
from plugin.core.message import InterfaceMessages
0 ignored issues
show
Configuration introduced by
The import plugin.core.message could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
5
6
import base64
7
import cerealizer
0 ignored issues
show
Configuration introduced by
The import cerealizer could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
8
import functools
9
import hashlib
10
import inspect
11
import logging
12
import sys
13
import threading
14
import thread
0 ignored issues
show
Configuration introduced by
The import thread could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
15
import time
16
import urllib
17
18
log = Logger('core.helpers')
19
20
21
PY25 = sys.version_info[0] == 2 and sys.version_info[1] == 5
22
23
24
def try_convert(value, value_type, default=None):
25
    try:
26
        return value_type(value)
27
    except ValueError:
28
        return default
29
    except TypeError:
30
        return default
31
32
33
def add_attribute(target, source, key, value_type=str, func=None, target_key=None):
34
    if target_key is None:
35
        target_key = key
36
37
    value = try_convert(source.get(key, None), value_type)
38
39
    if value:
40
        target[target_key] = func(value) if func else value
41
42
43
def merge(a, b):
44
    a.update(b)
45
    return a
46
47
48
def all(items):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in all.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
49
    for item in items:
50
        if not item:
51
            return False
52
    return True
53
54
55
def any(items):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in any.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
56
    for item in items:
57
        if item:
58
            return True
59
60
    return False
61
62
63
def json_import():
64
    try:
65
        import simplejson as json
66
67
        log.info("Using 'simplejson' module for JSON serialization")
68
        return json, 'json'
69
    except ImportError:
0 ignored issues
show
Unused Code introduced by
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
70
        pass
71
72
    # Try fallback to 'json' module
73
    try:
74
        import json
75
76
        log.info("Using 'json' module for JSON serialization")
77
        return json, 'json'
78
    except ImportError:
0 ignored issues
show
Unused Code introduced by
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
79
        pass
80
81
    # Try fallback to 'demjson' module
82
    try:
83
        import demjson
84
85
        log.info("Using 'demjson' module for JSON serialization")
86
        return demjson, 'demjson'
87
    except ImportError:
88
        log.warn("Unable to find json module for serialization")
89
        raise Exception("Unable to find json module for serialization")
90
91
# Import json serialization module
92
JSON, JSON_MODULE = json_import()
93
94
95
# JSON serialization wrappers to simplejson/json or demjson
96
def json_decode(s):
97
    if JSON_MODULE == 'json':
98
        return JSON.loads(s)
99
100
    if JSON_MODULE == 'demjson':
101
        return JSON.decode(s)
0 ignored issues
show
Bug introduced by
The Module json does not seem to have a member named decode.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
102
103
    raise NotImplementedError()
104
105
106
def json_encode(obj):
107
    if JSON_MODULE == 'json':
108
        return JSON.dumps(obj)
109
110
    if JSON_MODULE == 'demjson':
111
        return JSON.encode(obj)
0 ignored issues
show
Bug introduced by
The Module json does not seem to have a member named encode.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
112
113
    raise NotImplementedError()
114
115
116
def str_format(s, *args, **kwargs):
117
    """Return a formatted version of S, using substitutions from args and kwargs.
118
119
    (Roughly matches the functionality of str.format but ensures compatibility with Python 2.5)
120
    """
121
122
    args = list(args)
123
124
    x = 0
125
    while x < len(s):
126
        # Skip non-start token characters
127
        if s[x] != '{':
128
            x += 1
129
            continue
130
131
        end_pos = s.find('}', x)
132
133
        # If end character can't be found, move to next character
134
        if end_pos == -1:
135
            x += 1
136
            continue
137
138
        name = s[x + 1:end_pos]
139
140
        # Ensure token name is alpha numeric
141
        if not name.isalnum():
142
            x += 1
143
            continue
144
145
        # Try find value for token
146
        value = args.pop(0) if args else kwargs.get(name)
147
148
        if value:
149
            value = str(value)
150
151
            # Replace token with value
152
            s = s[:x] + value + s[end_pos + 1:]
153
154
            # Update current position
155
            x = x + len(value) - 1
156
157
        x += 1
158
159
    return s
160
161
162
def str_pad(s, length, align='left', pad_char=' ', trim=False):
163
    if not s:
164
        return s
165
166
    if not isinstance(s, (str, unicode)):
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'unicode'
Loading history...
167
        s = str(s)
168
169
    if len(s) == length:
170
        return s
171
    elif len(s) > length and not trim:
172
        return s
173
174
    if align == 'left':
175
        if len(s) > length:
176
            return s[:length]
177
        else:
178
            return s + (pad_char * (length - len(s)))
179
    elif align == 'right':
180
        if len(s) > length:
181
            return s[len(s) - length:]
182
        else:
183
            return (pad_char * (length - len(s))) + s
184
    else:
185
        raise ValueError("Unknown align type, expected either 'left' or 'right'")
186
187
188
def pad_title(value):
189
    """Pad a title to 30 characters to force the 'details' view."""
190
    return str_pad(value, 30, pad_char=' ')
191
192
193
def total_seconds(span):
194
    return (span.microseconds + (span.seconds + span.days * 24 * 3600) * 1e6) / 1e6
195
196
197
def sum(values):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in sum.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
198
    result = 0
199
200
    for x in values:
201
        result = result + x
202
203
    return result
204
205
206
def timestamp():
207
    return str(time.time())
208
209
210
# <bound method type.start of <class 'Scrobbler'>>
211
RE_BOUND_METHOD = Regex(r"<bound method (type\.)?(?P<name>.*?) of <(class '(?P<class>.*?)')?")
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Regex'
Loading history...
212
213
214
def get_func_name(obj):
215
    if inspect.ismethod(obj):
216
        match = RE_BOUND_METHOD.match(repr(obj))
217
218
        if match:
219
            cls = match.group('class')
220
            if not cls:
221
                return match.group('name')
222
223
            return '%s.%s' % (
224
                match.group('class'),
225
                match.group('name')
226
            )
227
228
    return None
229
230
231
def get_class_name(cls):
232
    if not inspect.isclass(cls):
233
        cls = getattr(cls, '__class__')
234
235
    return getattr(cls, '__name__')
236
237
238
def spawn(func, *args, **kwargs):
239
    thread_name = kwargs.pop('thread_name', None) or get_func_name(func)
240
241
    th = threading.Thread(target=thread_wrapper, name=thread_name, kwargs={
242
        'func': func,
243
        'args': args,
244
        'kwargs': kwargs,
245
        'thread_name': thread_name
246
    })
247
248
    try:
249
        th.start()
250
        log.debug("Spawned thread with name '%s'" % thread_name)
251
    except thread.error as ex:
252
        log.error('Unable to spawn thread: %s', ex, exc_info=True, extra={
253
            'data': {
254
                'active_count': threading.active_count()
255
            }
256
        })
257
        return None
258
259
    return th
260
261
262
def thread_wrapper(func, args=None, kwargs=None, thread_name=None):
263
    if args is None:
264
        args = ()
265
266
    if kwargs is None:
267
        kwargs = {}
268
269
    try:
270
        func(*args, **kwargs)
271
    except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
272
        log.error('Exception raised in thread "%s": %s', thread_name, ex, exc_info=True)
273
274
275
def schedule(func, seconds, *args, **kwargs):
276
    def schedule_sleep():
277
        time.sleep(seconds)
278
        func(*args, **kwargs)
279
280
    spawn(schedule_sleep)
281
282
283
def build_repr(obj, keys):
284
    key_part = ', '.join([
285
        ('%s: %s' % (key, repr(getattr(obj, key))))
286
        for key in keys
287
    ])
288
289
    cls = getattr(obj, '__class__')
290
291
    return '<%s %s>' % (getattr(cls, '__name__'), key_part)
292
293
294
def plural(value):
295
    if type(value) is list:
296
        value = len(value)
297
298
    if value == 1:
299
        return ''
300
301
    return 's'
302
303
304
def join_attributes(**kwargs):
305
    fragments = [
306
        (('%s: %s' % (key, value)) if value else None)
307
        for (key, value) in kwargs.items()
308
    ]
309
310
    return ', '.join([x for x in fragments if x])
311
312
313
def md5(value):
314
    # Generate MD5 hash of key
315
    m = hashlib.md5()
316
    m.update(value)
317
318
    return m.hexdigest()
319
320
321
def safe_encode(string):
322
    string = str(string)
323
    return base64.b64encode(string).replace('/', '@').replace('+', '*').replace('=', '_')
324
325
326
def pack(obj):
327
    serialized_obj = cerealizer.dumps(obj)
328
    encoded_string = safe_encode(serialized_obj)
329
    return urllib.quote(encoded_string)
0 ignored issues
show
Bug introduced by
The Module urllib does not seem to have a member named quote.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
330
331
332
def function_path(name, ext=None, **kwargs):
333
    return '%s/:/function/%s%s?%s' % (
334
        PLUGIN_PREFIX,
335
        name,
336
        ('.%s' % ext) if ext else '',
337
338
        urllib.urlencode({
0 ignored issues
show
Bug introduced by
The Module urllib does not seem to have a member named urlencode.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
339
            'function_args': pack(kwargs)
340
        })
341
    )
342
343
344
def redirect(path, **kwargs):
345
    location = PLUGIN_PREFIX + path
346
347
    try:
348
        request = Core.sandbox.context.request
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Core'
Loading history...
349
350
        # Add request parameters (required for authentication on some clients)
351
        kwargs.update({
352
            # Client
353
            'X-Plex-Client-Identifier': request.headers.get('X-Plex-Client-Identifier'),
354
            'X-Plex-Product': request.headers.get('X-Plex-Product'),
355
            'X-Plex-Version': request.headers.get('X-Plex-Version'),
356
357
            # Platform
358
            'X-Plex-Platform': request.headers.get('X-Plex-Platform'),
359
            'X-Plex-Platform-Version': request.headers.get('X-Plex-Platform-Version'),
360
361
            # Device
362
            'X-Plex-Device': request.headers.get('X-Plex-Device'),
363
            'X-Plex-Device-Name': request.headers.get('X-Plex-Device-Name'),
364
            'X-Plex-Device-Screen-Resolution': request.headers.get('X-Plex-Device-Screen-Resolution'),
365
366
            # Authentication
367
            'X-Plex-Token': request.headers.get('X-Plex-Token')
368
        })
369
370
        # Retrieve protocol
371
        protocol = request.protocol
372
373
        if request.host.endswith('.plex.direct:32400'):
374
            # Assume secure connection
375
            protocol = 'https'
376
377
        # Prepend protocol and host (if not already in `location`)
378
        if request and request.host and location[0] == "/":
379
            location = protocol + "://" + request.host + location
380
    except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
381
        log.warn('Redirect - %s', str(ex), exc_info=True)
382
383
    # Append parameters
384
    if kwargs:
385
        location += '?' + urllib.urlencode([
0 ignored issues
show
Bug introduced by
The Module urllib does not seem to have a member named urlencode.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
386
            (key, value) for key, value in kwargs.items()
387
            if value is not None
388
        ])
389
390
    # Return redirect response
391
    return Redirect(location, True)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Redirect'
Loading history...
392
393
394
def catch_errors(func):
395
    @functools.wraps(func)
396
    def inner(*args, **kwargs):
397
        if InterfaceMessages.critical:
398
            return error_record_view(logging.CRITICAL, InterfaceMessages.record)
399
400
        try:
401
            return func(*args, **kwargs)
402
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
403
            if InterfaceMessages.critical:
404
                return error_record_view(logging.CRITICAL, InterfaceMessages.record)
405
406
            return error_view(
407
                'Exception',
408
                ex.message
0 ignored issues
show
Bug introduced by
The Instance of Exception does not seem to have a member named message.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
409
            )
410
411
    return inner
412
413
414
def error_record_view(level, record):
415
    # Retrieve level name
416
    if level == logging.CRITICAL:
417
        level_name = 'Critical Error'
418
    else:
419
        level_name = logging.getLevelName(level).capitalize()
420
421
    # Build error view
422
    if not record:
423
        return error_view(level_name)
424
425
    return error_view(
426
        level_name,
427
        record.message
428
    )
429
430
431
def error_view(title, message=None):
432
    oc = ObjectContainer(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'ObjectContainer'
Loading history...
433
        title2=title,
434
        no_cache=True
435
    )
436
437
    oc.add(DirectoryObject(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
438
        key=PLUGIN_PREFIX,
439
        title=pad_title('%s: %s' % (
440
            title,
441
            message or 'Unknown'
442
        ))
443
    ))
444
445
    return oc
446