Passed
Push — develop ( c6fa42...950d5f )
by Dean
02:50
created

Status.format_error()   A

Complexity

Conditions 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 2
1
from core.helpers import catch_errors, timestamp, pad_title, function_path, redirect
0 ignored issues
show
Bug introduced by
The name helpers does not seem to exist in module core.
Loading history...
Configuration introduced by
The import core.helpers 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
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...
3
4
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...
5
from plugin.core.environment import translate as _
0 ignored issues
show
Configuration introduced by
The import plugin.core.environment 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...
6
from plugin.core.filters import Filters
0 ignored issues
show
Configuration introduced by
The import plugin.core.filters 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...
7
from plugin.core.helpers.variable import normalize
0 ignored issues
show
Configuration introduced by
The import plugin.core.helpers.variable 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
from plugin.managers.account import AccountManager
0 ignored issues
show
Configuration introduced by
The import plugin.managers.account 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...
9
from plugin.models import Account, Message, SyncResult
0 ignored issues
show
Configuration introduced by
The import plugin.models 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...
10
from plugin.sync import SyncMode
0 ignored issues
show
Configuration introduced by
The import plugin.sync 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...
11
from plugin.sync.main import Sync, QueueError
0 ignored issues
show
Configuration introduced by
The import plugin.sync.main 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...
12
13
from ago import human
0 ignored issues
show
Configuration introduced by
The import ago 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...
14
from datetime import datetime
15
from plex import Plex
0 ignored issues
show
Configuration introduced by
The import plex 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...
16
17
log = Logger('interface.m_sync')
18
19
20
# NOTE: pad_title(...) is used to force the UI to use 'media-details-list'
21
22
@route(PLUGIN_PREFIX + '/sync/accounts')
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'route'
Loading history...
23
@catch_errors
24
def AccountsMenu(refresh=None, *args, **kwargs):
0 ignored issues
show
Unused Code introduced by
The argument kwargs seems to be unused.
Loading history...
Unused Code introduced by
The argument args seems to be unused.
Loading history...
Unused Code introduced by
The argument refresh seems to be unused.
Loading history...
25
    oc = ObjectContainer(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'ObjectContainer'
Loading history...
26
        title2=_("Accounts"),
27
        no_cache=True
28
    )
29
30
    # Active sync status
31
    Active.create(
32
        oc,
33
        callback=Callback(AccountsMenu, refresh=timestamp()),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Callback'
Loading history...
34
    )
35
36
    # Accounts
37
    for account in Accounts.list():
38
        oc.add(DirectoryObject(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
39
            key=Callback(ControlsMenu, account_id=account.id),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Callback'
Loading history...
40
            title=account.name,
41
42
            art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts),
43
            thumb=function_path('Thumb.png', account_id=account.id, refresh=account.refreshed_ts)
44
        ))
45
46
    return oc
47
48
49
@route(PLUGIN_PREFIX + '/sync')
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'route'
Loading history...
50
@catch_errors
51
def ControlsMenu(account_id=1, title=None, message=None, refresh=None, message_only=False, *args, **kwargs):
0 ignored issues
show
Unused Code introduced by
The argument refresh seems to be unused.
Loading history...
Unused Code introduced by
The argument kwargs seems to be unused.
Loading history...
Unused Code introduced by
The argument args seems to be unused.
Loading history...
52
    account = AccountManager.get(Account.id == account_id)
53
54
    # Build sync controls menu
55
    oc = ObjectContainer(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'ObjectContainer'
Loading history...
56
        title2=_("Sync (%s)") % 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(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
65
            key=Callback(ControlsMenu, account_id=account.id, refresh=timestamp()),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Callback'
Loading history...
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()),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Callback'
Loading history...
77
        account=account
78
    )
79
80
    #
81
    # Full
82
    #
83
84
    oc.add(DirectoryObject(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
85
        key=Trigger.callback(Synchronize, account),
86
        title=pad_title(SyncMode.title(SyncMode.Full)),
87
        summary=Status.build(account, SyncMode.Full),
88
89
        thumb=R("icon-sync.png"),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'R'
Loading history...
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(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
98
        key=Trigger.callback(Pull, account),
99
        title=pad_title(_('%s from Trakt.tv') % SyncMode.title(SyncMode.Pull)),
100
        summary=Status.build(account, SyncMode.Pull),
101
102
        thumb=R("icon-sync_down.png"),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'R'
Loading history...
103
        art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts)
104
    ))
105
106
    oc.add(DirectoryObject(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
107
        key=Trigger.callback(FastPull, account),
108
        title=pad_title(_('%s from Trakt.tv') % SyncMode.title(SyncMode.FastPull)),
109
        summary=Status.build(account, SyncMode.FastPull),
110
111
        thumb=R("icon-sync_down.png"),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'R'
Loading history...
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 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...
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)
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...
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')
0 ignored issues
show
Unused Code introduced by
The variable f_deny seems to be unused.
Loading history...
145
146
    for section in sections.filter(['show', 'movie'], titles=f_allow):
147
        oc.add(DirectoryObject(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
148
            key=Trigger.callback(Push, account, section),
149
            title=pad_title(_('%s "%s" to Trakt.tv') % (SyncMode.title(SyncMode.Push), section.title)),
150
            summary=Status.build(account, SyncMode.Push, section.key),
151
152
            thumb=R("icon-sync_up.png"),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'R'
Loading history...
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(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
159
            key=Trigger.callback(Push, account),
160
            title=pad_title(_('%s all to Trakt.tv') % SyncMode.title(SyncMode.Push)),
161
            summary=Status.build(account, SyncMode.Push),
162
163
            thumb=R("icon-sync_up.png"),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'R'
Loading history...
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')
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'route'
Loading history...
171
@catch_errors
172
def Synchronize(account_id=1, *args, **kwargs):
0 ignored issues
show
Unused Code introduced by
The argument args seems to be unused.
Loading history...
173
    return Trigger.run(int(account_id), SyncMode.Full, **kwargs)
174
175
176
@route(PLUGIN_PREFIX + '/sync/fast_pull')
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'route'
Loading history...
177
@catch_errors
178
def FastPull(account_id=1, *args, **kwargs):
0 ignored issues
show
Unused Code introduced by
The argument args seems to be unused.
Loading history...
179
    return Trigger.run(int(account_id), SyncMode.FastPull, **kwargs)
180
181
182
@route(PLUGIN_PREFIX + '/sync/push')
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'route'
Loading history...
183
@catch_errors
184
def Push(account_id=1, section=None, *args, **kwargs):
0 ignored issues
show
Unused Code introduced by
The argument args seems to be unused.
Loading history...
185
    return Trigger.run(int(account_id), SyncMode.Push, section=section, **kwargs)
186
187
188
@route(PLUGIN_PREFIX + '/sync/pull')
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'route'
Loading history...
189
@catch_errors
190
def Pull(account_id=1, *args, **kwargs):
0 ignored issues
show
Unused Code introduced by
The argument args seems to be unused.
Loading history...
191
    return Trigger.run(int(account_id), SyncMode.Pull, **kwargs)
192
193
194
@route(PLUGIN_PREFIX + '/sync/cancel')
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'route'
Loading history...
195
@catch_errors
196
def Cancel(account_id, id, *args, **kwargs):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

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

Loading history...
Unused Code introduced by
The argument kwargs seems to be unused.
Loading history...
Unused Code introduced by
The argument args seems to be unused.
Loading history...
197
    id = int(id)
198
199
    # Cancel sync task
200
    if not Sync.cancel(id):
201
        # Unable to cancel task
202
        return redirect('/sync', account_id=account_id, title='Error', message='Unable to cancel current sync')
203
204
    # Success
205
    return redirect('/sync', account_id=account_id)
206
207
208
class Accounts(object):
209
    @classmethod
210
    def count(cls):
211
        return cls.list().count()
212
213
    @classmethod
214
    def list(cls):
215
        return AccountManager.get.all().where(
216
            Account.id != 0,
217
            Account.deleted == False
218
        )
219
220
221
class Active(object):
222
    @classmethod
223
    def create(cls, oc, callback, account=None):
224
        current = Sync.current
225
226
        if not current:
227
            # No task running
228
            return
229
230
        if account and current.account.id != account.id:
231
            # Only display status if `current` task matches provided `account`
232
            return
233
234
        # Create objects
235
        title = cls.build_title(current, account)
236
237
        oc.add(cls.build_status(current, title, callback))
238
        oc.add(cls.build_cancel(current, title))
239
240
    @staticmethod
241
    def build_title(current, account):
242
        # <mode>
243
        title = normalize(SyncMode.title(current.mode))
244
245
        # Task Progress
246
        percent = current.progress.percent
247
248
        if percent is not None:
249
            title += ' (%2d%%)' % percent
250
251
        # Account Name (only display outside of account-specific menus)
252
        if account is None:
253
            title += ' (%s)' % current.account.name
254
255
        return title
256
257
    #
258
    # Status
259
    #
260
261
    @classmethod
262
    def build_status(cls, current, title, callback=None):
263
        return DirectoryObject(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
264
            key=callback,
265
            title=pad_title(_('%s - Status') % title),
266
            summary=cls.build_status_summary(current)
267
        )
268
269
    @staticmethod
270
    def build_status_summary(current):
271
        summary = _('Working')
272
273
        # Estimated time remaining
274
        remaining_seconds = current.progress.remaining_seconds
275
276
        if remaining_seconds is not None:
277
            summary += _(', %.02f seconds remaining') % remaining_seconds
278
279
        return summary
280
281
    #
282
    # Cancel
283
    #
284
285
    @classmethod
286
    def build_cancel(cls, current, title):
287
        return DirectoryObject(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'DirectoryObject'
Loading history...
288
            key=Callback(Cancel, account_id=current.account.id, id=current.id),
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Callback'
Loading history...
289
            title=pad_title(_('%s - Cancel') % title)
290
        )
291
292
293
class Status(object):
294
    @classmethod
295
    def build(cls, account, mode, section=None):
296
        status = SyncResult.get_latest(account, mode, section).first()
297
298
        if status is None or status.latest is None:
299
            return _('Not run yet.')
300
301
        # Build status fragments
302
        fragments = []
303
304
        if status.latest.ended_at:
305
            # Build "Last run [...] ago" fragment
306
            fragments.append(cls.build_since(status))
307
308
            if status.latest.started_at:
309
                # Build "taking [...] seconds" fragment
310
                fragments.append(cls.build_elapsed(status))
311
312
        # Build result fragment (success, errors)
313
        fragments.append(cls.build_result(status))
314
315
        # Merge fragments
316
        if len(fragments):
317
            return ', '.join(fragments) + '.'
318
319
        return _('Not run yet.')
320
321
    @staticmethod
322
    def build_elapsed(status):
323
        elapsed = status.latest.ended_at - status.latest.started_at
324
325
        if elapsed.seconds < 1:
326
            return _('taking less than a second')
327
328
        return _('taking %s') % human(
329
            elapsed,
330
            precision=1,
331
            past_tense='%s'
332
        )
333
334
    @classmethod
335
    def build_result(cls, status):
336
        if status.latest.success:
337
            return _('was successful')
338
339
        message = _('failed')
340
341
        # Resolve errors
342
        errors = list(status.latest.get_errors())
343
344
        if len(errors) > 1:
345
            # Multiple errors
346
            message += _(' (%d errors, %s)') % (len(errors), cls.format_error(errors[0]))
347
        elif len(errors) == 1:
348
            # Single error
349
            message += _(' (%s)') % cls.format_error(errors[0])
350
351
        return message
352
353
    @staticmethod
354
    def build_since(status):
355
        since = datetime.utcnow() - status.latest.ended_at
356
357
        if since.seconds < 1:
358
            return _('Last run just a moment ago')
359
360
        return _('Last run %s') % human(since, precision=1)
361
362
    @staticmethod
363
    def format_error(error):
364
        if error.type in [Message.Type.Trakt, Message.Type.Plex, Message.Type.Sentry]:
365
            return '%s: %s' % (Message.Type.title(error.type), error.summary or 'Unknown Connection Error')
366
367
        return error.summary
368
369
370
class Trigger(object):
371
    triggered = {}
372
373
    @classmethod
374
    def callback(cls, func, account, section=None):
375
        if section:
376
            return Callback(func, account_id=account.id, section=section.key, t=timestamp())
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Callback'
Loading history...
377
378
        return Callback(func, account_id=account.id, t=timestamp())
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'Callback'
Loading history...
379
380
    @classmethod
381
    def run(cls, account_id, mode, t, **kwargs):
382
        # Check for duplicate trigger
383
        key = (account_id, mode, t)
384
385
        if key in cls.triggered:
386
            log.info('Ignored duplicate sync trigger action')
387
            return redirect('/sync', account_id=account_id)
388
389
        # Mark triggered
390
        cls.triggered[key] = True
391
392
        # Trigger sync
393
        try:
394
            Sync.queue(account_id, mode, **kwargs)
395
        except QueueError as ex:
396
            return redirect('/sync', account_id=account_id, title=ex.title, message=ex.message)
397
398
        return redirect('/sync', account_id=account_id)
399