Passed
Push — master ( aeb165...d9ae97 )
by Dean
03:04
created

ControlsMenu()   F

Complexity

Conditions 9

Size

Total Lines 118

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 118
rs 3.1304
cc 9

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
from core.helpers import timestamp, pad_title, function_path, redirect
2
from core.localization import localization
3
from core.logger import Logger
4
5
from plugin.core.constants import PLUGIN_PREFIX
6
from plugin.core.filters import Filters
7
from plugin.core.helpers.variable import normalize
8
from plugin.managers.account import AccountManager
9
from plugin.models import Account, SyncResult
10
from plugin.sync import SyncData, SyncMode
11
from plugin.sync.main import Sync, QueueError
12
13
from ago import human
14
from datetime import datetime
15
from plex import Plex
16
17
L, LF = localization('interface.m_sync')
18
19
log = Logger('interface.m_sync')
20
21
22
# NOTE: pad_title(...) is used to force the UI to use 'media-details-list'
23
24
@route(PLUGIN_PREFIX + '/sync/accounts')
25
def AccountsMenu(refresh=None):
26
    oc = ObjectContainer(
27
        title2=L('accounts:title'),
28
        no_cache=True
29
    )
30
31
    # Active sync status
32
    Active.create(
33
        oc,
34
        callback=Callback(AccountsMenu, refresh=timestamp()),
35
    )
36
37
    # Accounts
38
    for account in Accounts.list():
39
        oc.add(DirectoryObject(
40
            key=Callback(ControlsMenu, account_id=account.id),
41
            title=account.name,
42
43
            art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts),
44
            thumb=function_path('Thumb.png', account_id=account.id, refresh=account.refreshed_ts)
45
        ))
46
47
    return oc
48
49
50
@route(PLUGIN_PREFIX + '/sync')
51
def ControlsMenu(account_id=1, title=None, message=None, refresh=None, message_only=False):
52
    account = AccountManager.get(Account.id == account_id)
53
54
    # Build sync controls menu
55
    oc = ObjectContainer(
56
        title2=LF('controls:title', account.name),
57
        no_cache=True,
58
59
        art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts)
60
    )
61
62
    # Start result message
63
    if title and message:
64
        oc.add(DirectoryObject(
65
            key=Callback(ControlsMenu, account_id=account.id, refresh=timestamp()),
66
            title=pad_title(title),
67
            summary=message
68
        ))
69
70
        if message_only:
71
            return oc
72
73
    # Active sync status
74
    Active.create(
75
        oc,
76
        callback=Callback(ControlsMenu, account_id=account.id, refresh=timestamp()),
77
        account=account
78
    )
79
80
    #
81
    # Full
82
    #
83
84
    oc.add(DirectoryObject(
85
        key=Callback(Synchronize, account_id=account.id, refresh=timestamp()),
86
        title=pad_title(SyncMode.title(SyncMode.Full)),
87
        summary=Status.build(account, SyncMode.Full),
88
89
        thumb=R("icon-sync.png"),
90
        art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts)
91
    ))
92
93
    #
94
    # Pull
95
    #
96
97
    oc.add(DirectoryObject(
98
        key=Callback(Pull, account_id=account.id, refresh=timestamp()),
99
        title=pad_title('%s from trakt' % SyncMode.title(SyncMode.Pull)),
100
        summary=Status.build(account, SyncMode.Pull),
101
102
        thumb=R("icon-sync_down.png"),
103
        art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts)
104
    ))
105
106
    oc.add(DirectoryObject(
107
        key=Callback(FastPull, account_id=account.id, refresh=timestamp()),
108
        title=pad_title('%s from trakt' % SyncMode.title(SyncMode.FastPull)),
109
        summary=Status.build(account, SyncMode.FastPull),
110
111
        thumb=R("icon-sync_down.png"),
112
        art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts)
113
    ))
114
115
    #
116
    # Push
117
    #
118
119
    p_account = account.plex
120
121
    try:
122
        # Retrieve account libraries/sections
123
        with p_account.authorization():
124
            sections = Plex['library'].sections()
125
    except Exception, ex:
126
        # Build message
127
        if p_account is None:
128
            message = "Plex account hasn't been authenticated"
129
        else:
130
            message = str(ex.message or ex)
131
132
        # Redirect to error message
133
        log.warn('Unable to retrieve account libraries/sections: %s', message, exc_info=True)
134
135
        return redirect('/sync',
136
            account_id=account_id,
137
            title='Error',
138
            message=message,
139
            message_only=True
140
        )
141
142
    section_keys = []
143
144
    f_allow, f_deny = Filters.get('filter_sections')
145
146
    for section in sections.filter(['show', 'movie'], titles=f_allow):
147
        oc.add(DirectoryObject(
148
            key=Callback(Push, account_id=account.id, section=section.key, refresh=timestamp()),
149
            title=pad_title('%s "%s" to trakt' % (SyncMode.title(SyncMode.Push), section.title)),
150
            summary=Status.build(account, SyncMode.Push, section.key),
151
152
            thumb=R("icon-sync_up.png"),
153
            art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts)
154
        ))
155
        section_keys.append(section.key)
156
157
    if len(section_keys) > 1:
158
        oc.add(DirectoryObject(
159
            key=Callback(Push, account_id=account.id, refresh=timestamp()),
160
            title=pad_title('%s all to trakt' % SyncMode.title(SyncMode.Push)),
161
            summary=Status.build(account, SyncMode.Push),
162
163
            thumb=R("icon-sync_up.png"),
164
            art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts)
165
        ))
166
167
    return oc
168
169
170
@route(PLUGIN_PREFIX + '/sync/synchronize')
171
def Synchronize(account_id=1, refresh=None):
172
    return Trigger(int(account_id), SyncMode.Full)
173
174
175
@route(PLUGIN_PREFIX + '/sync/fast_pull')
176
def FastPull(account_id=1, refresh=None):
177
    return Trigger(int(account_id), SyncMode.FastPull)
178
179
180
@route(PLUGIN_PREFIX + '/sync/push')
181
def Push(account_id=1, section=None, refresh=None):
182
    return Trigger(int(account_id), SyncMode.Push, section=section)
183
184
185
@route(PLUGIN_PREFIX + '/sync/pull')
186
def Pull(account_id=1, refresh=None):
187
    return Trigger(int(account_id), SyncMode.Pull)
188
189
190
def Trigger(account_id, mode, **kwargs):
191
    try:
192
        Sync.queue(account_id, mode, **kwargs)
193
    except QueueError, ex:
194
        return redirect('/sync', account_id=account_id, title=ex.title, message=ex.message)
195
196
    return redirect('/sync', account_id=account_id)
197
198
199
@route(PLUGIN_PREFIX + '/sync/cancel')
200
def Cancel(account_id, id):
201
    id = int(id)
202
203
    # Cancel sync task
204
    if not Sync.cancel(id):
205
        # Unable to cancel task
206
        return redirect('/sync', account_id=account_id, title='Error', message='Unable to cancel current sync')
207
208
    # Success
209
    return redirect('/sync', account_id=account_id)
210
211
212
class Accounts(object):
213
    @classmethod
214
    def count(cls):
215
        return cls.list().count()
216
217
    @classmethod
218
    def list(cls):
219
        return AccountManager.get.all().where(
220
            Account.id != 0,
221
            Account.deleted == False
222
        )
223
224
225
class Active(object):
226
    @classmethod
227
    def create(cls, oc, callback, account=None):
228
        current = Sync.current
229
230
        if not current:
231
            # No task running
232
            return
233
234
        if account and current.account.id != account.id:
235
            # Only display status if `current` task matches provided `account`
236
            return
237
238
        # Create objects
239
        title = cls.build_title(current, account)
240
241
        oc.add(cls.build_status(current, title, callback))
242
        oc.add(cls.build_cancel(current, title))
243
244
    @staticmethod
245
    def build_title(current, account):
246
        # <mode>
247
        title = normalize(SyncMode.title(current.mode))
248
249
        # Task Progress
250
        percent = current.progress.percent
251
252
        if percent is not None:
253
            title += ' (%2d%%)' % percent
254
255
        # Account Name (only display outside of account-specific menus)
256
        if account is None:
257
            title += ' (%s)' % current.account.name
258
259
        return title
260
261
    #
262
    # Status
263
    #
264
265
    @classmethod
266
    def build_status(cls, current, title, callback=None):
267
        return DirectoryObject(
268
            key=callback,
269
            title=pad_title('%s - Status' % title),
270
            summary=cls.build_status_summary(current)
271
        )
272
273
    @staticmethod
274
    def build_status_summary(current):
275
        summary = 'Working'
276
277
        # Estimated time remaining
278
        remaining_seconds = current.progress.remaining_seconds
279
280
        if remaining_seconds is not None:
281
            summary += ', %.02f seconds remaining' % remaining_seconds
282
283
        return summary
284
285
    #
286
    # Cancel
287
    #
288
289
    @classmethod
290
    def build_cancel(cls, current, title):
291
        return DirectoryObject(
292
            key=Callback(Cancel, account_id=current.account.id, id=current.id),
293
            title=pad_title('%s - Cancel' % title)
294
        )
295
296
297
class Status(object):
298
    @classmethod
299
    def build(cls, account, mode, section=None):
300
        status = SyncResult.get_latest(account, mode, section).first()
301
302
        if status is None or status.latest is None:
303
            return 'Not run yet.'
304
305
        # Build status fragments
306
        fragments = []
307
308
        if status.latest.ended_at:
309
            # Build "Last run [...] ago" fragment
310
            fragments.append(cls.build_since(status))
311
312
            if status.latest.started_at:
313
                # Build "taking [...] seconds" fragment
314
                fragments.append(cls.build_elapsed(status))
315
316
        # Build result fragment (success, errors)
317
        fragments.append(cls.build_result(status))
318
319
        # Merge fragments
320
        if len(fragments):
321
            return ', '.join(fragments) + '.'
322
323
        return 'Not run yet.'
324
325
    @staticmethod
326
    def build_elapsed(status):
327
        elapsed = status.latest.ended_at - status.latest.started_at
328
329
        if elapsed.seconds < 1:
330
            return 'taking less than a second'
331
332
        return 'taking %s' % human(
333
            elapsed,
334
            precision=1,
335
            past_tense='%s'
336
        )
337
338
    @staticmethod
339
    def build_result(status):
340
        if status.latest.success:
341
            return 'was successful'
342
343
        message = 'failed'
344
345
        # Resolve errors
346
        errors = list(status.latest.get_errors())
347
348
        if len(errors) > 1:
349
            # Multiple errors
350
            message += ' (%d errors, %s)' % (len(errors), errors[0].summary)
351
        elif len(errors) == 1:
352
            # Single error
353
            message += ' (%s)' % errors[0].summary
354
355
        return message
356
357
    @staticmethod
358
    def build_since(status):
359
        since = datetime.utcnow() - status.latest.ended_at
360
361
        if since.seconds < 1:
362
            return 'Last run just a moment ago'
363
364
        return 'Last run %s' % human(since, precision=1)
365