Passed
Push — beta ( a8392a...918fe2 )
by Dean
02:58
created

PlexAccount.refresh_details()   B

Complexity

Conditions 5

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 26.5972

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 39
ccs 1
cts 21
cp 0.0476
rs 8.0894
cc 5
crap 26.5972
1 1
from plugin.core.exceptions import AccountAuthenticationError
2 1
from plugin.models.core import db
3 1
from plugin.models.account import Account
4
5 1
from datetime import datetime, timedelta
6 1
from playhouse.apsw_ext import *
7 1
from plex import Plex
8 1
from urllib import urlencode
9 1
from urlparse import urlparse, parse_qsl
10 1
from xml.etree import ElementTree
11 1
import logging
12 1
import requests
13
14 1
REFRESH_INTERVAL = timedelta(days=1)
15
16 1
log = logging.getLogger(__name__)
17
18
19 1
class PlexAccount(Model):
20 1
    class Meta:
21 1
        database = db
22 1
        db_table = 'plex.account'
23
24 1
    account = ForeignKeyField(Account, 'plex_accounts', unique=True)
25
26 1
    key = IntegerField(null=True, unique=True)
27 1
    username = CharField(null=True, unique=True)
28
29 1
    title = CharField(null=True)
30 1
    thumb = TextField(null=True)
31
32 1
    refreshed_at = DateTimeField(null=True)
33
34 1
    def __init__(self, *args, **kwargs):
35
        super(PlexAccount, self).__init__(*args, **kwargs)
36
37
        self._basic_credential = None
38
39 1
    @property
40
    def account_id(self):
41
        return self._data.get('account')
42
43 1
    @property
44
    def basic(self):
45
        if self._basic_credential:
46
            return self._basic_credential
47
48
        return self.basic_credentials.first()
49
50 1
    @basic.setter
51
    def basic(self, value):
52
        self._basic_credential = value
53
54 1
    def authorization(self):
55
        # Basic
56
        basic = self.basic
57
58
        if basic:
59
            return self.basic_authorization(basic)
60
61
        # No account authorization available
62
        raise AccountAuthenticationError("Plex account hasn't been authenticated")
63
64 1
    def basic_authorization(self, basic_credential=None):
65
        if basic_credential is None:
66
            basic_credential = self.basic
67
68
        # Ensure token exists
69
        if basic_credential.token_server is None:
70
            raise AccountAuthenticationError("Plex account is missing the server token")
71
72
        # Handle anonymous authentication
73
        if basic_credential.token_server == 'anonymous':
74
            log.debug('Using anonymous authorization for %r', self)
75
            return Plex.configuration.authentication(token=None)
76
77
        # Configure client
78
        log.debug('Using basic authorization for %r', self)
79
        return Plex.configuration.authentication(basic_credential.token_server)
80
81 1
    def refresh(self, force=False, save=True):
82
        # Retrieve credentials
83
        basic = self.basic
84
85
        if not basic:
86
            return False
87
88
        # Check if refresh is required
89
        if self.refresh_required(basic):
90
            force = True
91
92
        # Only refresh account every `REFRESH_INTERVAL`
93
        if not force and self.refreshed_at:
94
            since_refresh = datetime.utcnow() - self.refreshed_at
95
96
            if since_refresh < REFRESH_INTERVAL:
97
                return False
98
99
        # Refresh account details
100
        if not self.refresh_details(basic):
101
            return False
102
103
        if not basic.refresh(force=True):
104
            return False
105
106
        # Store changes in database
107
        self.refreshed_at = datetime.utcnow()
108
109
        if save:
110
            self.save()
111
112
        return True
113
114 1
    def refresh_details(self, basic):
115
        if basic.token_plex == 'anonymous':
116
            return self.refresh_anonymous()
117
118
        log.info('Refreshing plex account: %r', self)
119
120
        # Fetch account details
121
        response = requests.get('https://plex.tv/users/account', headers={
122
            'X-Plex-Token': basic.token_plex
123
        })
124
125
        if not (200 <= response.status_code < 300):
126
            log.warn('Unable to retrieve account details from plex.tv (status_code: %s)', response.status_code)
127
            return False
128
129
        user = ElementTree.fromstring(response.content)
130
131
        # Update details
132
        self.username = user.attrib.get('username') or None
133
134
        self.title = user.attrib.get('title')
135
        self.thumb = user.attrib.get('thumb')
136
137
        # Update `key`
138
        if self.id == 1:
139
            # Use administrator `key`
140
            self.key = 1
141
        else:
142
            # Retrieve user id from plex.tv details
143
            try:
144
                user_id = int(user.attrib.get('id'))
145
            except Exception, ex:
146
                log.warn('Unable to cast user id to integer: %s', ex, exc_info=True)
147
                user_id = None
148
149
            # Update `key`
150
            self.key = user_id
151
152
        return True
153
154 1
    def refresh_anonymous(self):
155
        log.info('Refreshing plex account: %r (anonymous)', self)
156
157
        self.username = 'administrator'
158
159
        self.title = 'Administrator'
160
        self.thumb = None
161
162
        if self.id == 1:
163
            self.key = 1
164
        else:
165
            self.key = None
166
167
        return True
168
169 1
    def refresh_required(self, basic):
170
        if self.key is None:
171
            return True
172
173
        if self.title is None:
174
            return True
175
176
        if not basic.token_server:
177
            return True
178
179
        return False
180
181 1
    def thumb_url(self, default=None, rating='pg', size=256):
182
        if not self.thumb:
183
            return None
184
185
        thumb = urlparse(self.thumb)
186
187
        if thumb.netloc.endswith('plex.tv'):
188
            return self.thumb
189
190
        if not thumb.netloc.endswith('gravatar.com'):
191
            return None
192
193
        result = 'https://secure.gravatar.com%s' % thumb.path
194
195
        if default is None:
196
            query = dict(parse_qsl(thumb.query))
197
198
            default = query.get('d') or query.get('default')
199
200
        return result + '?' + urlencode({
201
            'd': default,
202
            'r': rating,
203
            's': size
204
        })
205
206 1
    def to_json(self, full=False):
207
        result = {
208
            'id': self.id,
209
            'username': self.username,
210
211
            'title': self.title,
212
            'thumb_url': self.thumb_url()
213
        }
214
215
        if not full:
216
            return result
217
218
        # Merge authorization details
219
        result['authorization'] = {
220
            'basic': {'state': 'empty'}
221
        }
222
223
        # - Basic credentials
224
        basic = self.basic
225
226
        if basic is not None:
227
            result['authorization']['basic'] = basic.to_json(self)
228
229
        return result
230
231 1
    def __repr__(self):
232
        return '<PlexAccount username: %r>' % (
233
            self.username,
234
        )
235