~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to lib/messages.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Miro - an RSS based video player application
 
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
17
#
 
18
# In addition, as a special exception, the copyright holders give
 
19
# permission to link the code of portions of this program with the OpenSSL
 
20
# library.
 
21
#
 
22
# You must obey the GNU General Public License in all respects for all of
 
23
# the code used other than OpenSSL. If you modify file(s) with this
 
24
# exception, you may extend this exception to your version of the file(s),
 
25
# but you are not obligated to do so. If you do not wish to do so, delete
 
26
# this exception statement from your version. If you delete this exception
 
27
# statement from all source files in the program, then also delete it here.
 
28
 
 
29
"""``miro.messages`` -- Message passing between the frontend thread
 
30
and the backend thread.
 
31
 
 
32
The backend thread is the eventloop, which processes things like feed
 
33
updates and handles reading and writing to the database.  The frontend
 
34
thread is the thread of the UI toolkit we're using.  Communication
 
35
between the two threads is handled by passing messages between the
 
36
two.  These messages are handled asynchronously.
 
37
 
 
38
This module defines the messages that are passed between the two
 
39
threads.
 
40
"""
 
41
 
 
42
import logging
 
43
import re
 
44
import urlparse
 
45
 
 
46
from miro.gtcache import gettext as _
 
47
from miro.folder import ChannelFolder, PlaylistFolder
 
48
from miro.plat import resources
 
49
from miro import config
 
50
from miro import feed
 
51
from miro import guide
 
52
from miro import prefs
 
53
from miro import util
 
54
from miro import filetypes
 
55
 
 
56
class MessageHandler(object):
 
57
    def __init__(self):
 
58
        self.message_map = {} # maps message classes to method names
 
59
        self.complained_about = set()
 
60
 
 
61
    def call_handler(self, method, message):
 
62
        """Arrange for a message handler method to be called in the correct
 
63
        thread.  Must be implemented by subclasses.
 
64
        """
 
65
        raise NotImplementedError()
 
66
 
 
67
    def handle(self, message):
 
68
        """Handles a given message.
 
69
        """
 
70
        handler_name = self.get_message_handler_name(message)
 
71
        try:
 
72
            handler = getattr(self, handler_name)
 
73
        except AttributeError:
 
74
            if handler_name not in self.complained_about:
 
75
                logging.warn("MessageHandler doesn't have a %s method "
 
76
                        "to handle the %s message" % (handler_name,
 
77
                            message.__class__))
 
78
                self.complained_about.add(handler_name)
 
79
        else:
 
80
            self.call_handler(handler, message)
 
81
 
 
82
    def get_message_handler_name(self, message):
 
83
        try:
 
84
            return self.message_map[message.__class__]
 
85
        except KeyError:
 
86
            self.message_map[message.__class__] = \
 
87
                    self.calc_message_handler_name(message.__class__)
 
88
            return self.message_map[message.__class__]
 
89
 
 
90
    def calc_message_handler_name(self, message_class):
 
91
        def replace(match):
 
92
            return '%s_%s' % (match.group(1), match.group(2))
 
93
        underscores = re.sub(r'([a-z])([A-Z])', replace,
 
94
                message_class.__name__)
 
95
        return 'handle_' + util.ascii_lower(underscores)
 
96
 
 
97
class Message(object):
 
98
    """Base class for all Messages.
 
99
    """
 
100
    @classmethod
 
101
    def install_handler(cls, handler):
 
102
        """Install a new message handler for this class.  When
 
103
        send_to_frontend() or send_to_backend() is called, this handler will
 
104
        be invoked.
 
105
        """
 
106
        cls.handler = handler
 
107
 
 
108
    @classmethod
 
109
    def reset_handler(cls):
 
110
        del cls.handler
 
111
 
 
112
class BackendMessage(Message):
 
113
    """Base class for Messages that get sent to the backend.
 
114
    """
 
115
    def send_to_backend(self):
 
116
        try:
 
117
            handler = self.handler
 
118
        except AttributeError:
 
119
            logging.warn("No handler for backend messages")
 
120
        else:
 
121
            handler.handle(self)
 
122
 
 
123
class FrontendMessage(Message):
 
124
    """Base class for Messages that get sent to the frontend.
 
125
    """
 
126
    def send_to_frontend(self):
 
127
        try:
 
128
            handler = self.handler
 
129
        except AttributeError:
 
130
            logging.warn("No handler for frontend messages")
 
131
        else:
 
132
            handler.handle(self)
 
133
 
 
134
# Backend Messages
 
135
 
 
136
class FrontendStarted(BackendMessage):
 
137
    """Inform the backend that the frontend has finished starting up.
 
138
    """
 
139
    pass
 
140
 
 
141
class TrackChannels(BackendMessage):
 
142
    """Begin tracking channels.
 
143
 
 
144
    After this message is sent, the backend will send back a ChannelList
 
145
    message, then it will send ChannelsChanged messages whenever the channel
 
146
    list changes.
 
147
    """
 
148
    pass
 
149
 
 
150
class StopTrackingChannels(BackendMessage):
 
151
    """Stop tracking channels.
 
152
    """
 
153
    pass
 
154
 
 
155
class QuerySearchInfo(BackendMessage):
 
156
    """Ask the backend to send a CurrentSearchInfo message.
 
157
    """
 
158
    pass
 
159
 
 
160
class TrackPlaylists(BackendMessage):
 
161
    """Begin tracking playlists.
 
162
 
 
163
    After this message is sent, the backend will send back a PlaylistList
 
164
    message, then it will send PlaylistsChanged messages whenever the list of
 
165
    playlists changes.
 
166
    """
 
167
    pass
 
168
 
 
169
class StopTrackingPlaylists(BackendMessage):
 
170
    """Stop tracking playlists.
 
171
    """
 
172
    pass
 
173
 
 
174
class TrackGuides(BackendMessage):
 
175
    """Begin tracking guides.
 
176
 
 
177
    After this message is sent, the backend will send back a GuideList
 
178
    message, then it will send GuidesChanged messages whenever the guide
 
179
    list changes.
 
180
    """
 
181
    pass
 
182
 
 
183
class StopTrackingGuides(BackendMessage):
 
184
    """Stop tracking guides.
 
185
    """
 
186
    pass
 
187
 
 
188
class TrackItems(BackendMessage):
 
189
    """Begin tracking items for a feed
 
190
 
 
191
    After this message is sent, the backend will send back a ItemList message,
 
192
    then it will send ItemsChanged messages for items in the feed.
 
193
 
 
194
    type is the type of object that we are tracking items for.  It can be one
 
195
    of the following:
 
196
 
 
197
    * feed -- Items in a feed
 
198
    * playlist -- Items in a playlist
 
199
    * new -- Items that haven't been watched
 
200
    * downloading -- Items being downloaded
 
201
    * library -- All items
 
202
 
 
203
    id should be the id of a feed/playlist. For new, downloading and library
 
204
    it is ignored.
 
205
    """
 
206
    def __init__(self, type, id):
 
207
        self.type = type
 
208
        self.id = id
 
209
 
 
210
class TrackItemsManually(BackendMessage):
 
211
    """Track a manually specified list of items.
 
212
 
 
213
    ItemList and ItemsChanged messages will have "manual" as the type and
 
214
    will use the id specified in the constructed.
 
215
    """
 
216
    def __init__(self, id, ids_to_track):
 
217
        self.id = id
 
218
        self.ids_to_track = ids_to_track
 
219
        self.type = 'manual'
 
220
 
 
221
class StopTrackingItems(BackendMessage):
 
222
    """Stop tracking items for a feed.
 
223
    """
 
224
    def __init__(self, type, id):
 
225
        self.type = type
 
226
        self.id = id
 
227
 
 
228
class TrackDownloadCount(BackendMessage):
 
229
    """Start tracking the number of downloading items.  After this message is
 
230
    received the backend will send a corresponding DownloadCountChanged
 
231
    message.  It will also send DownloadCountChanged whenever the count
 
232
    changes.
 
233
    """
 
234
    pass
 
235
 
 
236
class StopTrackingDownloadCount(BackendMessage):
 
237
    """Stop tracking the download count.
 
238
    """
 
239
    pass
 
240
 
 
241
class TrackPausedCount(BackendMessage):
 
242
    """Start tracking the number of paused downloading items.  After
 
243
    this message is received, the backend will send a corresponding
 
244
    PausedCountChanged message.  It will also send PausedCountChanged
 
245
    whenever the count changes.
 
246
    """
 
247
    pass
 
248
 
 
249
class StopTrackingPausedCount(BackendMessage):
 
250
    """Stop tracking the paused count."""
 
251
    pass
 
252
 
 
253
class TrackNewVideoCount(BackendMessage):
 
254
    """Start tracking the number of new videos.  When this message is
 
255
    received the backend will send a corresponding
 
256
    NewVideoCountChanged message.  It will also send
 
257
    NewVideoCountChanged whenever the count changes.
 
258
    """
 
259
    pass
 
260
 
 
261
class StopTrackingNewVideoCount(BackendMessage):
 
262
    """Stop tracking the new videos count.
 
263
    """
 
264
    pass
 
265
 
 
266
class TrackNewAudioCount(BackendMessage):
 
267
    """Start tracking the number of new audio items.  When this
 
268
    message is received the backend will send a corresponding
 
269
    NewAudioCountChanged message.  It will also send
 
270
    NewAudioCountChanged whenever the count changes.
 
271
    """
 
272
    pass
 
273
 
 
274
class StopTrackingNewAudioCount(BackendMessage):
 
275
    """Stop tracking the new audio items count.
 
276
    """
 
277
    pass
 
278
 
 
279
class TrackUnwatchedCount(BackendMessage):
 
280
    """Start tracking the number of unwatched items.  When this
 
281
    message is received, the backend will send a corresponding
 
282
    UnwatchedCountChanged message.  It will also send
 
283
    UnwatchedCountChanged whenever the count changes.
 
284
    """
 
285
    pass
 
286
 
 
287
class StopTrackingUnwatchedCount(BackendMessage):
 
288
    """Stop tracking the unwatched items count.
 
289
    """
 
290
    pass
 
291
 
 
292
class TrackWatchedFolders(BackendMessage):
 
293
    """Begin tracking watched folders
 
294
 
 
295
    After this message is sent, the backend will send back a
 
296
    WatchedFolderList message, then it will send WatchedFoldersChanged
 
297
    messages whenever the list changes.
 
298
    """
 
299
    pass
 
300
 
 
301
class StopTrackingWatchedFolders(BackendMessage):
 
302
    """Stop tracking watched folders.
 
303
    """
 
304
    pass
 
305
 
 
306
class SetFeedExpire(BackendMessage):
 
307
    """Sets the expiration for a feed.
 
308
    """
 
309
    def __init__(self, channel_info, expire_type, expire_time):
 
310
        self.channel_info = channel_info
 
311
        self.expire_type = expire_type
 
312
        self.expire_time = expire_time
 
313
 
 
314
class SetFeedMaxNew(BackendMessage):
 
315
    """Sets the feed's max new property.
 
316
    """
 
317
    def __init__(self, channel_info, max_new):
 
318
        self.channel_info = channel_info
 
319
        self.max_new = max_new
 
320
 
 
321
class SetFeedMaxOldItems(BackendMessage):
 
322
    """Sets the feed's max old items property.
 
323
    """
 
324
    def __init__(self, channel_info, max_old_items):
 
325
        self.channel_info = channel_info
 
326
        self.max_old_items = max_old_items
 
327
 
 
328
class CleanFeed(BackendMessage):
 
329
    """Tells the backend to clean the old items from a feed.
 
330
    """
 
331
    def __init__(self, channel_id):
 
332
        self.channel_id = channel_id
 
333
 
 
334
class ImportFeeds(BackendMessage):
 
335
    """Tell the backend to import feeds from an .opml file.
 
336
 
 
337
    :param filename: file name that exists
 
338
    """
 
339
    def __init__(self, filename):
 
340
        self.filename = filename
 
341
 
 
342
class ExportSubscriptions(BackendMessage):
 
343
    """Tell the backend to export subscriptions to an .opml file.
 
344
 
 
345
    :param filename: file name to export to
 
346
    """
 
347
    def __init__(self, filename):
 
348
        self.filename = filename
 
349
 
 
350
class RenameObject(BackendMessage):
 
351
    """Tell the backend to rename a feed/playlist/folder.
 
352
    
 
353
    :param type: ``feed``, ``playlist``, ``feed-folder`` or
 
354
                 ``playlist-folder``
 
355
    :param id: id of the object to rename
 
356
    :param new_name: new name for the object
 
357
    """
 
358
    def __init__(self, type, id, new_name):
 
359
        self.type = type
 
360
        self.id = id
 
361
        self.new_name = util.to_uni(new_name)
 
362
 
 
363
class UpdateFeed(BackendMessage):
 
364
    """Updates a feed.
 
365
    """
 
366
    def __init__(self, id):
 
367
        self.id = id
 
368
 
 
369
class UpdateFeedFolder(BackendMessage):
 
370
    """Updates the feeds in a feed folder.
 
371
    """
 
372
    def __init__(self, id):
 
373
        self.id = id
 
374
 
 
375
class MarkFeedSeen(BackendMessage):
 
376
    """Mark a feed as seen.
 
377
    """
 
378
    def __init__(self, id):
 
379
        self.id = id
 
380
 
 
381
class MarkItemWatched(BackendMessage):
 
382
    """Mark an item as watched.
 
383
    """
 
384
    def __init__(self, id):
 
385
        self.id = id
 
386
 
 
387
class MarkItemUnwatched(BackendMessage):
 
388
    """Mark an item as unwatched.
 
389
    """
 
390
    def __init__(self, id):
 
391
        self.id = id
 
392
 
 
393
class SetItemSubtitleEncoding(BackendMessage):
 
394
    """Mark an item as watched.
 
395
    """
 
396
    def __init__(self, id, encoding):
 
397
        self.id = id
 
398
        self.encoding = encoding
 
399
 
 
400
class SetItemResumeTime(BackendMessage):
 
401
    """Set an item resume time.
 
402
    """
 
403
    def __init__(self, id, time):
 
404
        self.id = id
 
405
        self.resume_time = time
 
406
 
 
407
class SetItemMediaType(BackendMessage):
 
408
    """Adds a list of videos to a playlist.
 
409
    """
 
410
    def __init__(self, media_type, video_ids):
 
411
        self.media_type = media_type
 
412
        self.video_ids = video_ids
 
413
 
 
414
class UpdateAllFeeds(BackendMessage):
 
415
    """Updates all feeds.
 
416
    """
 
417
    pass
 
418
 
 
419
class DeleteFeed(BackendMessage):
 
420
    """Delete a feed.
 
421
    """
 
422
    def __init__(self, id, is_folder, keep_items):
 
423
        self.id = id
 
424
        self.is_folder = is_folder
 
425
        self.keep_items = keep_items
 
426
 
 
427
class DeleteWatchedFolder(BackendMessage):
 
428
    """Delete a watched folder.
 
429
 
 
430
    .. Note::
 
431
 
 
432
       This separate from DeleteFeed since not all watched folders are
 
433
       visible.
 
434
    """
 
435
    def __init__(self, id):
 
436
        self.id = id
 
437
 
 
438
class DeletePlaylist(BackendMessage):
 
439
    """Delete a playlist.
 
440
    """
 
441
    def __init__(self, id, is_folder):
 
442
        self.id = id
 
443
        self.is_folder = is_folder
 
444
 
 
445
class DeleteSite(BackendMessage):
 
446
    """Delete an external channel guide.
 
447
    """
 
448
    def __init__(self, id):
 
449
        self.id = id
 
450
 
 
451
class NewGuide(BackendMessage):
 
452
    """Create a new channel guide.
 
453
    """
 
454
    def __init__(self, url):
 
455
        self.url = util.to_uni(url)
 
456
 
 
457
class NewFeed(BackendMessage):
 
458
    """Creates a new feed.
 
459
    """
 
460
    def __init__(self, url, section=u"video"):
 
461
        self.url = util.to_uni(url)
 
462
        self.section = section
 
463
 
 
464
class NewFeedSearchFeed(BackendMessage):
 
465
    """Creates a new feed based on a search through a feed.
 
466
    """
 
467
    def __init__(self, channel_info, search_term, section=u"video"):
 
468
        self.channel_info = channel_info
 
469
        self.search_term = search_term
 
470
        self.section = section
 
471
 
 
472
class NewFeedSearchEngine(BackendMessage):
 
473
    """Creates a new feed from a search engine.
 
474
    """
 
475
    def __init__(self, search_engine_info, search_term, section=u"video"):
 
476
        self.search_engine_info = search_engine_info
 
477
        self.search_term = search_term
 
478
        self.section = section
 
479
 
 
480
class NewFeedSearchURL(BackendMessage):
 
481
    """Creates a new feed from a url.
 
482
    """
 
483
    def __init__(self, url, search_term, section):
 
484
        self.url = url
 
485
        self.search_term = search_term
 
486
        self.section = section
 
487
 
 
488
class NewWatchedFolder(BackendMessage):
 
489
    """Creates a new watched folder.
 
490
    """
 
491
    def __init__(self, path):
 
492
        self.path = path
 
493
 
 
494
class SetWatchedFolderVisible(BackendMessage):
 
495
    """Changes if a watched folder is visible in the tab list or not.
 
496
    """
 
497
    def __init__(self, id, visible):
 
498
        self.id = id
 
499
        self.visible = visible
 
500
 
 
501
class NewPlaylist(BackendMessage):
 
502
    """Create a new playlist.
 
503
    """
 
504
    def __init__(self, name, ids):
 
505
        self.name = util.to_uni(name)
 
506
        self.ids = ids
 
507
 
 
508
class NewFeedFolder(BackendMessage):
 
509
    """Create a new feed folder.
 
510
    """
 
511
    def __init__(self, name, section, child_feed_ids):
 
512
        self.name = util.to_uni(name)
 
513
        self.section = section
 
514
        self.child_feed_ids = child_feed_ids
 
515
 
 
516
class NewPlaylistFolder(BackendMessage):
 
517
    """Create a new playlist folder.
 
518
    """
 
519
    def __init__(self, name, child_playlist_ids):
 
520
        self.name = util.to_uni(name)
 
521
        self.child_playlist_ids = child_playlist_ids
 
522
 
 
523
class ChangeMoviesDirectory(BackendMessage):
 
524
    """Change the current movies directory.
 
525
 
 
526
    If migrate is True, then the backend will send a series of
 
527
    ProgressDialog messages while the migration happens.
 
528
    """
 
529
    def __init__(self, path, migrate):
 
530
        self.path = path
 
531
        self.migrate = migrate
 
532
 
 
533
class AddVideosToPlaylist(BackendMessage):
 
534
    """Adds a list of videos to a playlist.
 
535
    """
 
536
    def __init__(self, playlist_id, video_ids):
 
537
        self.playlist_id = playlist_id
 
538
        self.video_ids = video_ids
 
539
 
 
540
class RemoveVideosFromPlaylist(BackendMessage):
 
541
    """Removes a list of videos from a playlist.
 
542
    """
 
543
    def __init__(self, playlist_id, video_ids):
 
544
        self.playlist_id = playlist_id
 
545
        self.video_ids = video_ids
 
546
 
 
547
class DownloadURL(BackendMessage):
 
548
    """Downloads the item at a url.
 
549
 
 
550
    :param url: the url of the thing to download
 
551
    :handle_unknown_callback: if the thing at the url isn't something that
 
552
                              Miro knows what to do with, then it calls
 
553
                              this callback.  The handler should take a
 
554
                              single argument which is the url that
 
555
                              couldn't be handled.
 
556
    :param metadata: dict of name/value pairs to include in the item.
 
557
    """
 
558
    def __init__(self, url, handle_unknown_callback=None, metadata=None):
 
559
        self.url = util.to_uni(url)
 
560
        self.handle_unknown_callback = handle_unknown_callback
 
561
        self.metadata = metadata
 
562
 
 
563
class OpenIndividualFile(BackendMessage):
 
564
    """Open a single file item in Miro.
 
565
    """
 
566
    def __init__(self, filename):
 
567
        self.filename = filename
 
568
 
 
569
class OpenIndividualFiles(BackendMessage):
 
570
    """Open a list of file items in Miro.
 
571
    """
 
572
    def __init__(self, filenames):
 
573
        self.filenames = filenames
 
574
 
 
575
class AddFiles(BackendMessage):
 
576
    """This is like OpenIndividualFiles, but is handled differently in
 
577
    that adding files doesn't cause videos that were added to be
 
578
    played.
 
579
    """
 
580
    def __init__(self, filenames):
 
581
        self.filenames = filenames
 
582
 
 
583
class CheckVersion(BackendMessage):
 
584
    """Checks whether Miro is the most recent version.
 
585
    """
 
586
    def __init__(self, up_to_date_callback):
 
587
        self.up_to_date_callback = up_to_date_callback
 
588
 
 
589
class Search(BackendMessage):
 
590
    """Search a search engine with a search term.
 
591
    
 
592
    The backend will send a SearchComplete message.
 
593
    """
 
594
    def __init__(self, searchengine_id, terms):
 
595
        self.id = searchengine_id
 
596
        self.terms = terms
 
597
 
 
598
class CancelAutoDownload(BackendMessage):
 
599
    """Cancels the autodownload for an item.
 
600
    """
 
601
    def __init__(self, id):
 
602
        self.id = id
 
603
 
 
604
class StartDownload(BackendMessage):
 
605
    """Start downloading an item.
 
606
    """
 
607
    def __init__(self, id):
 
608
        self.id = id
 
609
    def __repr__(self):
 
610
        return BackendMessage.__repr__(self) + (", id: %s" % self.id)
 
611
 
 
612
class CancelDownload(BackendMessage):
 
613
    """Cancel downloading an item.
 
614
    """
 
615
    def __init__(self, id):
 
616
        self.id = id
 
617
 
 
618
class CancelAllDownloads(BackendMessage):
 
619
    """Cancels all downloading items.
 
620
    """
 
621
    pass
 
622
 
 
623
class PauseAllDownloads(BackendMessage):
 
624
    """Pauses all downloading items.
 
625
    """
 
626
    pass
 
627
 
 
628
class PauseDownload(BackendMessage):
 
629
    """Pause downloading an item.
 
630
    """
 
631
    def __init__(self, id):
 
632
        self.id = id
 
633
 
 
634
class ResumeAllDownloads(BackendMessage):
 
635
    """Resumes all downloading items.
 
636
    """
 
637
    pass
 
638
 
 
639
class ResumeDownload(BackendMessage):
 
640
    """Resume downloading an item.
 
641
    """
 
642
    def __init__(self, id):
 
643
        self.id = id
 
644
 
 
645
class StartUpload(BackendMessage):
 
646
    """Start uploading a torrent.
 
647
    """
 
648
    def __init__(self, id):
 
649
        self.id = id
 
650
 
 
651
class StopUpload(BackendMessage):
 
652
    """Stop uploading a torrent.
 
653
    """
 
654
    def __init__(self, id):
 
655
        self.id = id
 
656
 
 
657
class KeepVideo(BackendMessage):
 
658
    """Cancel the auto-expiration of an item's video.
 
659
    """
 
660
    def __init__(self, id):
 
661
        self.id = id
 
662
    def __repr__(self):
 
663
        return BackendMessage.__repr__(self) + (", id: %s" % self.id)
 
664
 
 
665
class SaveItemAs(BackendMessage):
 
666
    """Saves an item in the dark clutches of Miro to somewhere else.
 
667
    """
 
668
    def __init__(self, id, filename):
 
669
        self.id = id
 
670
        self.filename = filename
 
671
 
 
672
class RemoveVideoEntry(BackendMessage):
 
673
    """Remove the entry for an external video.
 
674
    """
 
675
    def __init__(self, id):
 
676
        self.id = id
 
677
 
 
678
class DeleteVideo(BackendMessage):
 
679
    """Delete the video for an item's video.
 
680
    """
 
681
    def __init__(self, id):
 
682
        self.id = id
 
683
    def __repr__(self):
 
684
        return BackendMessage.__repr__(self) + (", id: %s" % self.id)
 
685
 
 
686
class EditItem(BackendMessage):
 
687
    """Changes a bunch of things on an item.
 
688
    """
 
689
    def __init__(self, item_id, change_dict):
 
690
        self.item_id = item_id
 
691
        self.change_dict = change_dict
 
692
 
 
693
class RevertFeedTitle(BackendMessage):
 
694
    """Reverts the feed's title back to the original.
 
695
    """
 
696
    def __init__(self, id):
 
697
        self.id = id
 
698
 
 
699
class PlayAllUnwatched(BackendMessage):
 
700
    """Figures out all the unwatched items and plays them.
 
701
    """
 
702
    def __init__(self):
 
703
        pass
 
704
 
 
705
class FolderExpandedChange(BackendMessage):
 
706
    """Inform the backend when a folder gets expanded/collapsed.
 
707
    """
 
708
    def __init__(self, type, id, expanded):
 
709
        self.type = type
 
710
        self.id = id
 
711
        self.expanded = expanded
 
712
 
 
713
class AutodownloadChange(BackendMessage):
 
714
    """Inform the backend that the user changed the auto-download
 
715
    setting for a feed.  The possible setting values are ``all``,
 
716
    ``new`` and ``off``.
 
717
    """
 
718
    def __init__(self, id, setting):
 
719
        self.id = id
 
720
        self.setting = setting
 
721
 
 
722
class TabsReordered(BackendMessage):
 
723
    """Inform the backend when the channel tabs are rearranged.  This
 
724
    includes simple position changes and also changes to which folders
 
725
    the channels are in.
 
726
 
 
727
    :param toplevels: a dict of {'type': [channelinfo1,
 
728
                      channelinfo2]}, where ``channelinfo`` is a
 
729
                      ChannelInfo object without parents
 
730
    :param folder_children: dict mapping channel folder ids to a list of
 
731
                            ChannelInfo objects for their children
 
732
    """
 
733
    def __init__(self):
 
734
        self.toplevels = {
 
735
            u'feed': [],
 
736
            u'audio-feed': [],
 
737
            u'playlist': []}
 
738
        self.folder_children = {}
 
739
 
 
740
    def append(self, info, type):
 
741
        self.toplevels[type].append(info)
 
742
        if info.is_folder:
 
743
            self.folder_children[info.id] = []
 
744
 
 
745
    def append_child(self, parent_id, info):
 
746
        self.folder_children[parent_id].append(info)
 
747
 
 
748
class PlaylistReordered(BackendMessage):
 
749
    """Inform the backend when the items in a playlist are re-ordered.
 
750
 
 
751
    :param id: playlist that was re-ordered.
 
752
    :param item_ids: List of ids for item in the playlist, in their new
 
753
                     order.
 
754
    """
 
755
    def __init__(self, id, item_ids):
 
756
        self.id = id
 
757
        self.item_ids = item_ids
 
758
 
 
759
class SubscriptionLinkClicked(BackendMessage):
 
760
    """Inform the backend that the user clicked on a subscription link
 
761
    in a web browser.
 
762
    """
 
763
    def __init__(self, url):
 
764
        self.url = url
 
765
 
 
766
class ReportCrash(BackendMessage):
 
767
    """Sends a crash report.
 
768
    """
 
769
    def __init__(self, report, text, send_report):
 
770
        self.report = report
 
771
        self.text = text
 
772
        self.send_report = send_report
 
773
 
 
774
class SaveFrontendState(BackendMessage):
 
775
    """Save data for the frontend.
 
776
    """
 
777
    def __init__(self, list_view_displays, sort_states, active_filters):
 
778
        self.list_view_displays = list_view_displays
 
779
        self.sort_states = sort_states
 
780
        self.active_filters = active_filters
 
781
 
 
782
class QueryFrontendState(BackendMessage):
 
783
    """Ask for a CurrentFrontendState message to be sent back.
 
784
    """
 
785
    pass
 
786
 
 
787
# Frontend Messages
 
788
 
 
789
class FrontendQuit(FrontendMessage):
 
790
    """The frontend should exit."""
 
791
    pass
 
792
 
 
793
class DatabaseUpgradeStart(FrontendMessage):
 
794
    """We're about to do a database upgrade.
 
795
    """
 
796
    pass
 
797
 
 
798
class DatabaseUpgradeProgress(FrontendMessage):
 
799
    """We're about to do a database upgrade.
 
800
    """
 
801
    def __init__(self, stage, stage_progress, total_progress):
 
802
        self.stage = stage
 
803
        self.stage_progress = stage_progress
 
804
        self.total_progress = total_progress
 
805
 
 
806
class DatabaseUpgradeEnd(FrontendMessage):
 
807
    """We're done with the database upgrade.
 
808
    """
 
809
    pass
 
810
 
 
811
class StartupSuccess(FrontendMessage):
 
812
    """The startup process is complete.  The frontend should wait for
 
813
    this signal to show the UI to the user.
 
814
    """
 
815
    pass
 
816
 
 
817
class StartupFailure(FrontendMessage):
 
818
    """The startup process failed.  The frontend should inform the
 
819
    user that this happened and quit.
 
820
 
 
821
    Attributes:
 
822
    :param summary: Short, user-friendly, summary of the problem.
 
823
    :param description: Longer explanation of the problem.
 
824
    """
 
825
    def __init__(self, summary, description):
 
826
        self.summary = summary
 
827
        self.description = description
 
828
 
 
829
class StartupDatabaseFailure(FrontendMessage):
 
830
    """The startup process failed due to a database error.  The
 
831
    frontend should inform the user that this happened and quit.
 
832
 
 
833
    Attributes:
 
834
    :param summary: Short, user-friendly, summary of the problem.
 
835
    :param description: Longer explanation of the problem.
 
836
    """
 
837
    def __init__(self, summary, description):
 
838
        self.summary = summary
 
839
        self.description = description
 
840
 
 
841
class ChannelInfo(object):
 
842
    """Tracks the state of a channel
 
843
 
 
844
    :param name: channel name
 
845
    :param url: channel url (None for channel folders)
 
846
    :param id: object id
 
847
    :param section: which section this is in (audio or video)
 
848
    :param tab_icon: path to this channel's tab icon
 
849
    :param thumbnail: path to this channel's thumbnail
 
850
    :param num_downloaded: number of downloaded items in the feed
 
851
    :param unwatched: number of unwatched videos
 
852
    :param available: number of newly downloaded videos
 
853
    :param is_folder: is this a channel folder?
 
854
    :param is_directory_feed: is this channel is a watched directory?
 
855
    :param parent_id: id of parent folder or None
 
856
    :param is_updating: whether or not the feed is currently updating
 
857
    :param has_downloading: are videos currently being downloaded for
 
858
                            this channel?
 
859
    :param base_href: url to use for relative links for items in this
 
860
                      channel.  This will be None for ChannelFolders.
 
861
    :param autodownload_mode: current autodownload mode (``all``,
 
862
                              ``new`` or ``off``)
 
863
    :param search_term: the search term used for this feed or None
 
864
    :param expire: expire type (``system``, ``never``, or ``feed``)
 
865
    :param expire_time: expire time in hours
 
866
    :param max_new: maximum number of items this feed wants
 
867
    :param max_old_items: maximum number of old items to remember
 
868
    """
 
869
    def __init__(self, channel_obj):
 
870
        import time
 
871
        start = time.time()
 
872
        self.name = channel_obj.get_title()
 
873
        self.id = channel_obj.id
 
874
        self.section = channel_obj.section
 
875
        self.unwatched = channel_obj.num_unwatched()
 
876
        self.available = channel_obj.num_available()
 
877
        self.has_downloading = channel_obj.has_downloading_items()
 
878
        if hasattr(channel_obj, "searchTerm"):
 
879
            self.search_term = channel_obj.searchTerm
 
880
        else:
 
881
            self.search_term = None
 
882
        if not isinstance(channel_obj, ChannelFolder):
 
883
            self.has_original_title = channel_obj.has_original_title()
 
884
            self.is_updating = channel_obj.is_updating()
 
885
            self.parent_id = channel_obj.folder_id
 
886
            self.url = channel_obj.get_url()
 
887
            self.thumbnail = channel_obj.get_thumbnail_path()
 
888
            self.base_href = channel_obj.get_base_href()
 
889
            self.autodownload_mode = channel_obj.get_autodownload_mode()
 
890
            self.is_folder = False
 
891
            self.is_directory_feed = (self.url is not None and
 
892
                    self.url.startswith('dtv:directoryfeed'))
 
893
            self.tab_icon = channel_obj.get_thumbnail_path()
 
894
            self.expire = channel_obj.get_expiration_type()
 
895
            self.expire_time = channel_obj.get_expiration_time()
 
896
            self.max_new = channel_obj.get_max_new()
 
897
            self.max_old_items = channel_obj.get_max_old_items()
 
898
            self.num_downloaded = channel_obj.num_downloaded()
 
899
        else:
 
900
            self.is_updating = False
 
901
            self.parent_id = None
 
902
            self.url = None
 
903
            self.thumbnail = resources.path('images/folder-icon.png')
 
904
            self.autodownload_mode = self.base_href = None
 
905
            self.is_folder = True
 
906
            self.tab_icon = resources.path('images/icon-folder.png')
 
907
            self.is_directory_feed = False
 
908
            self.expire = None
 
909
            self.expire_time = None
 
910
            self.max_new = None
 
911
            self.max_old_items = None
 
912
            self.num_downloaded = None
 
913
 
 
914
class PlaylistInfo(object):
 
915
    """Tracks the state of a playlist
 
916
 
 
917
    :param name: playlist name
 
918
    :param id: object id
 
919
    :param is_folder: is this a playlist folder?
 
920
    """
 
921
    def __init__(self, playlist_obj):
 
922
        self.name = playlist_obj.get_title()
 
923
        self.id = playlist_obj.id
 
924
        self.is_folder = isinstance(playlist_obj, PlaylistFolder)
 
925
        if self.is_folder:
 
926
            self.parent_id = None
 
927
        else:
 
928
            self.parent_id = playlist_obj.folder_id
 
929
 
 
930
class GuideInfo(object):
 
931
    """Tracks the state of a channel guide
 
932
 
 
933
    :param name: channel name
 
934
    :param id: object id
 
935
    :param url: URL for the guide
 
936
    :param allowed_urls: URLs that should be also considered part of the guide
 
937
    :param default: is this the default channel guide?
 
938
    :param favicon: the favicon for the guide
 
939
    :param faviconIsDefault: true if the guide is using the default site
 
940
                             icon and not a favicon from the web
 
941
    """
 
942
    def __init__(self, guide):
 
943
        self.name = guide.get_title()
 
944
        self.id = guide.id
 
945
        self.url = guide.get_url()
 
946
        self.default = guide.is_default()
 
947
        self.allowed_urls = guide.allowedURLs
 
948
        self.favicon = guide.get_favicon_path()
 
949
        self.faviconIsDefault = not (guide.icon_cache and
 
950
                                     guide.icon_cache.get_filename())
 
951
 
 
952
class ItemInfo(object):
 
953
    """Tracks the state of an item
 
954
 
 
955
    :param name: name of the item
 
956
    :param id: object id
 
957
    :param feed_id: id for the items feed
 
958
    :param feed_name: name of the feed item is attached to
 
959
    :param feed_url: URL of the feed item is attached to 
 
960
    :param description: longer description for the item (HTML)
 
961
    :param state: see Item.get_state()
 
962
    :param release_date: datetime object when the item was published
 
963
    :param size: size of the item in bytes
 
964
    :param duration: length of the video in seconds
 
965
    :param resume_time: time at which playback should restart
 
966
    :param permalink: URL to a permalink to the item (or None)
 
967
    :param commentslink: URL to a comments page for the item (or None)
 
968
    :param payment_link: URL of the payment page associated with the item
 
969
                         (or empty string)
 
970
    :param has_sharable_url: does this item have a sharable URL?
 
971
    :param can_be_saved: is this an expiring downloaded item?
 
972
    :param downloaded: has the item been downloaded?
 
973
    :param is_external: is this item external (true) or from a channel
 
974
                        (false)?
 
975
    :param expiration_date: datetime object for when the item will expire
 
976
                            (or None)
 
977
    :param item_viewed: has the user ever seen the item?
 
978
    :param video_watched: has the user watched the video for the item?
 
979
    :param video_path: the file path to the video for this item (or None)
 
980
    :param file_type: type of the downloaded file (video/audio/other)
 
981
    :param subtitle_encoding: encoding for subtitle display
 
982
    :param media_type_checked: has the movie data util checked file_type?
 
983
    :param seeding_status: Torrent seeding status ('seeding', 'stopped',
 
984
                           or None)
 
985
    :param thumbnail: path to the thumbnail for this file
 
986
    :param thumbnail_url: URL for the item's thumbnail (or None)
 
987
    :param file_format: User-facing format description.  Possibly the
 
988
                        file type,  pulled from the mime_type, or more
 
989
                        generic, like "audio"
 
990
    :param license: this file's license, if known.
 
991
    :param mime_type: mime-type of the enclosure that would be downloaded
 
992
    :param file_url: URL of the enclosure that would be downloaded
 
993
    :param download_info: DownloadInfo object containing info about the
 
994
                          download (or None)
 
995
    :param is_container_item: whether or not this item is actually a
 
996
                              collection of files as opposed to an
 
997
                              individual item
 
998
    :param children: for container items the children of the item.
 
999
    :param is_playable: is this item a audio/video file, or a container that
 
1000
                        contains audio/video files inside.
 
1001
    :param leechers: (Torrent only) number of leeching clients
 
1002
    :param seeders: (Torrent only) number of seeding clients
 
1003
    :param up_rate: (Torrent only) how fast we're uploading data
 
1004
    :param down_rate: (Torrent only) how fast we're downloading data
 
1005
    :param up_total: (Torrent only) total amount we've uploaded
 
1006
    :param down_total: (Torrent only) total amount we've downloaded
 
1007
    :param up_down_ratio: (Torrent only) ratio of uploaded to downloaded
 
1008
    """
 
1009
    def __init__(self, item):
 
1010
        self.name = item.get_title()
 
1011
        self.id = item.id
 
1012
        self.feed_id = item.feed_id
 
1013
        self.feed_name = item.get_source()
 
1014
        self.feed_url = item.get_feed_url()
 
1015
        self.description = item.get_description()
 
1016
        self.state = item.get_state()
 
1017
        self.release_date = item.get_release_date_obj()
 
1018
        self.size = item.get_size()
 
1019
        self.duration = item.get_duration_value()
 
1020
        self.resume_time = item.resumeTime
 
1021
        self.permalink = item.get_link()
 
1022
        self.commentslink = item.get_comments_link()
 
1023
        self.payment_link = item.get_payment_link()
 
1024
        self.has_sharable_url = item.has_shareable_url()
 
1025
        self.can_be_saved = item.show_save_button()
 
1026
        self.pending_manual_dl = item.is_pending_manual_download()
 
1027
        self.pending_auto_dl = item.is_pending_auto_download()
 
1028
        if not item.keep and not item.is_external():
 
1029
            self.expiration_date = item.get_expiration_time()
 
1030
        else:
 
1031
            self.expiration_date = None
 
1032
        self.item_viewed = item.get_viewed()
 
1033
        self.downloaded = item.is_downloaded()
 
1034
        self.is_external = item.is_external()
 
1035
        self.video_watched = item.get_seen()
 
1036
        self.video_path = item.get_filename()
 
1037
        self.thumbnail = item.get_thumbnail()
 
1038
        self.thumbnail_url = item.get_thumbnail_url()
 
1039
        self.file_format = item.get_format()
 
1040
        self.license = item.get_license()
 
1041
        self.file_url = item.get_url()
 
1042
        self.is_container_item = item.isContainerItem
 
1043
        self.is_playable = item.is_playable()
 
1044
        if item.isContainerItem:
 
1045
            self.children = [ItemInfo(i) for i in item.get_children()]
 
1046
        else:
 
1047
            self.children = []
 
1048
        self.file_type = item.file_type
 
1049
        self.subtitle_encoding = item.subtitle_encoding
 
1050
        self.media_type_checked = item.media_type_checked
 
1051
        self.seeding_status = item.torrent_seeding_status()
 
1052
        self.mime_type = item.enclosure_type
 
1053
        self.file_url = item.url
 
1054
 
 
1055
        if item.downloader:
 
1056
            self.download_info = DownloadInfo(item.downloader)
 
1057
        elif self.state == 'downloading':
 
1058
            self.download_info = PendingDownloadInfo()
 
1059
        else:
 
1060
            self.download_info = None
 
1061
 
 
1062
        ## Torrent-specific stuff
 
1063
        self.leechers = self.seeders = self.up_rate = None
 
1064
        self.down_rate = self.up_total = self.down_total = None
 
1065
        self.up_down_ratio = 0.0
 
1066
        if item.looks_like_torrent() and hasattr(item.downloader, 'status'):
 
1067
            status = item.downloader.status
 
1068
            if item.is_transferring():
 
1069
                # gettorrentdetails only
 
1070
                self.leechers = status.get('leechers', 0)
 
1071
                self.seeders = status.get('seeders', 0)
 
1072
                self.up_rate = status.get('upRate', 0)
 
1073
                self.down_rate = status.get('rate', 0)
 
1074
 
 
1075
            # gettorrentdetailsfinished & gettorrentdetails
 
1076
            self.up_total = status.get('uploaded', 0)
 
1077
            self.down_total = status.get('currentSize', 0)
 
1078
            if self.down_total <= 0:
 
1079
                self.up_down_ratio = 0.0
 
1080
            else:
 
1081
                self.up_down_ratio = self.up_total * 1.0 / self.down_total
 
1082
 
 
1083
class DownloadInfo(object):
 
1084
    """Tracks the download state of an item.
 
1085
 
 
1086
    :param downloaded_size: bytes downloaded
 
1087
    :param rate: current download rate, in bytes per second
 
1088
    :param state: one of ``downloading``, ``uploading``, ``finished``,
 
1089
                  ``failed`` or ``paused``.  ``uploading`` is for
 
1090
                  torrents only.  It means that we've finished
 
1091
                  downloading the torrent and are now seeding it.
 
1092
    :param eta: Estimated seconds before the download is finished
 
1093
    :param startup_activity: The current stage of starting up
 
1094
    :param finished: True if the item has finished downloading
 
1095
    :param torrent: Is this a Torrent download?
 
1096
    """
 
1097
    def __init__(self, downloader):
 
1098
        self.downloaded_size = downloader.get_current_size()
 
1099
        self.rate = downloader.get_rate()
 
1100
        self.state = downloader.get_state()
 
1101
        self.startup_activity = downloader.get_startup_activity()
 
1102
        self.finished = downloader.is_finished()
 
1103
        self.torrent = (downloader.get_type() == 'bittorrent')
 
1104
        if self.state == 'failed':
 
1105
            self.reason_failed = downloader.get_reason_failed()
 
1106
            self.short_reason_failed = downloader.get_short_reason_failed()
 
1107
        else:
 
1108
            self.reason_failed = u""
 
1109
            self.short_reason_failed = u""
 
1110
        self.eta = downloader.get_eta()
 
1111
 
 
1112
class PendingDownloadInfo(DownloadInfo):
 
1113
    """DownloadInfo object for pending downloads (downloads queued,
 
1114
    but not started because we've reached some limit)
 
1115
    """
 
1116
    def __init__(self):
 
1117
        self.downloaded_size = 0
 
1118
        self.rate = 0
 
1119
        self.state = 'pending'
 
1120
        self.startup_activity = _('queued for download')
 
1121
        self.finished = False
 
1122
        self.torrent = False
 
1123
        self.reason_failed = u""
 
1124
        self.short_reason_failed = u""
 
1125
        self.eta = 0
 
1126
 
 
1127
class WatchedFolderInfo(object):
 
1128
    """Tracks the state of a watched folder.
 
1129
    
 
1130
    :param id: ID of the channel
 
1131
    :param path: Path to the folder being watched
 
1132
    :param visible: Is the watched folder shown on the tab list?
 
1133
    """
 
1134
    def __init__(self, channel):
 
1135
        self.id = channel.id
 
1136
        self.path = channel.dir
 
1137
        self.visible = channel.visible
 
1138
 
 
1139
class GuideList(FrontendMessage):
 
1140
    """Sends the frontend the initial list of channel guides
 
1141
 
 
1142
    :param default_guide: The Default channel guide
 
1143
    :param guides: list added channel guides
 
1144
    """
 
1145
    def __init__(self, guides):
 
1146
        self.default_guide = [g for g in guides if g.default]
 
1147
        if len(self.default_guide) == 0:
 
1148
            # The problem here is that Miro persists guides and it's possible
 
1149
            # for it to have a default channel guide persisted, but when you
 
1150
            # set the channel guide via the DTV_CHANNELGUIDE_URL, then there's
 
1151
            # no default guide.  So we generate one here.  Bug #11027.
 
1152
            cg = guide.ChannelGuide(util.to_uni(config.get(prefs.CHANNEL_GUIDE_URL)))
 
1153
            cg_info = GuideInfo(cg)
 
1154
            self.default_guide = [cg_info]
 
1155
        elif len(self.default_guide) > 1:
 
1156
            logging.warning("Multiple default guides!  Picking the first one.")
 
1157
            self.default_guide = [self.default_guide[0]]
 
1158
        self.default_guide = self.default_guide[0]
 
1159
        self.added_guides = [g for g in guides if not g.default]
 
1160
 
 
1161
class TabList(FrontendMessage):
 
1162
    """Sends the frontend the current list of channels and playlists
 
1163
 
 
1164
    This is sent at startup and when the changes to the list of
 
1165
    channels/playlists is too complex to describe with a TabsChanged message.
 
1166
 
 
1167
    :param type: ``feed`` or ``playlist``
 
1168
    :param toplevels: the list of ChannelInfo/PlaylistInfo objects
 
1169
                      without parents
 
1170
    :param folder_children: dict mapping channel folder ids to a list of
 
1171
                            ChannelInfo/PlaylistInfo objects for their
 
1172
                            children
 
1173
    :param expanded_folders: set containing ids of the folders that should
 
1174
                             be initially expanded.
 
1175
    """
 
1176
    def __init__(self, type):
 
1177
        self.type = type
 
1178
        self.toplevels = []
 
1179
        self.folder_children = {}
 
1180
        self.expanded_folders = set()
 
1181
 
 
1182
    def append(self, info):
 
1183
        self.toplevels.append(info)
 
1184
        if info.is_folder:
 
1185
            self.folder_children[info.id] = []
 
1186
 
 
1187
    def append_child(self, parent_id, info):
 
1188
        self.folder_children[parent_id].append(info)
 
1189
 
 
1190
    def expand_folder(self, folder_id):
 
1191
        self.expanded_folders.add(folder_id)
 
1192
 
 
1193
class TabsChanged(FrontendMessage):
 
1194
    """Informs the frontend that the channel list or playlist list has been 
 
1195
    changed.
 
1196
 
 
1197
    :param type: ``feed``, ``playlist`` or ``guide``
 
1198
    :param added: ChannelInfo/PlaylistInfo object for each added tab.  The
 
1199
                  list will be in the same order that the tabs were added.
 
1200
    :param changed: list of ChannelInfo/PlaylistInfos for each changed tab.
 
1201
    :param removed: list of ids for each tab that was removed
 
1202
    :param section: ``audio``, ``video``, or None (used for channels and
 
1203
                    channel folders)
 
1204
    """
 
1205
    def __init__(self, type, added, changed, removed, section=None):
 
1206
        self.type = type
 
1207
        self.added = added
 
1208
        self.changed = changed
 
1209
        self.removed = removed
 
1210
        self.section = section
 
1211
 
 
1212
class ItemList(FrontendMessage):
 
1213
    """Sends the frontend the initial list of items for a feed
 
1214
 
 
1215
    :param type: type of object being tracked (same as in TrackItems)
 
1216
    :param id: id of the object being tracked (same as in TrackItems)
 
1217
    :param items: list of ItemInfo objects
 
1218
    """
 
1219
    def __init__(self, type, id, item_infos):
 
1220
        self.type = type
 
1221
        self.id = id
 
1222
        self.items = item_infos
 
1223
 
 
1224
class ItemsChanged(FrontendMessage):
 
1225
    """Informs the frontend that the items in a feed have changed.
 
1226
 
 
1227
    :param type: type of object being tracked (same as in TrackItems)
 
1228
    :param id: id of the object being tracked (same as in TrackItems)
 
1229
    :param added: list containing an ItemInfo object for each added item.
 
1230
                  The order will be the order they were added.
 
1231
    :param changed: set containing an ItemInfo for each changed item.
 
1232
    :param removed: set containing ids for each item that was removed
 
1233
    """
 
1234
    def __init__(self, type, id, added, changed, removed):
 
1235
        self.type = type
 
1236
        self.id = id
 
1237
        self.added = added
 
1238
        self.changed = changed
 
1239
        self.removed = removed
 
1240
 
 
1241
class WatchedFolderList(FrontendMessage):
 
1242
    """Sends the frontend the initial list of watched folders.
 
1243
 
 
1244
    :param watched_folders: List of watched folders
 
1245
    """
 
1246
    def __init__(self, watched_folders):
 
1247
        self.watched_folders = watched_folders
 
1248
 
 
1249
class WatchedFoldersChanged(FrontendMessage):
 
1250
    """Informs the frontend that the watched folder list has changed.
 
1251
 
 
1252
    :param added: WatchedFolderInfo object for each added watched folder.
 
1253
                  The list will be in the same order that they were added.
 
1254
    :param changed: The list of WatchedFolderInfo for each changed watched
 
1255
                    folder.
 
1256
    :param removed: list of ids for each watched folder that was removed.
 
1257
    """
 
1258
    def __init__(self, added, changed, removed):
 
1259
        self.added = added
 
1260
        self.changed = changed
 
1261
        self.removed = removed
 
1262
 
 
1263
class CurrentSearchInfo(FrontendMessage):
 
1264
    """Informs the frontend of the current search settings.
 
1265
    """
 
1266
    def __init__(self, engine, text):
 
1267
        self.engine = engine
 
1268
        self.text = text
 
1269
 
 
1270
class DownloadCountChanged(FrontendMessage):
 
1271
    """Informs the frontend that number of downloads has changed. Includes the
 
1272
    number of non downloading items which should be displayed.
 
1273
    """
 
1274
    def __init__(self, count, non_downloading_count):
 
1275
        self.count = count
 
1276
        self.non_downloading_count = non_downloading_count
 
1277
 
 
1278
class PausedCountChanged(FrontendMessage):
 
1279
    """Informs the frontend that number of paused downloading items
 
1280
    has changed.
 
1281
    """
 
1282
    def __init__(self, count):
 
1283
        self.count = count
 
1284
 
 
1285
class NewVideoCountChanged(FrontendMessage):
 
1286
    """Informs the frontend that number of new videos has changed.
 
1287
    """
 
1288
    def __init__(self, count):
 
1289
        self.count = count
 
1290
 
 
1291
class NewAudioCountChanged(FrontendMessage):
 
1292
    """Informs the frontend that number of new videos has changed.
 
1293
    """
 
1294
    def __init__(self, count):
 
1295
        self.count = count
 
1296
 
 
1297
class UnwatchedCountChanged(FrontendMessage):
 
1298
    """Informs the frontend that number of unwatched items has changed.
 
1299
    """
 
1300
    def __init__(self, count):
 
1301
        self.count = count
 
1302
 
 
1303
class VideoConversionTaskInfo(object):
 
1304
    """Tracks the state of an conversion task.
 
1305
 
 
1306
    :param key: id for the conversion task
 
1307
    :param state: current state of the conversion.  One of: "pending",
 
1308
        "running", "failed", or "finished"
 
1309
    :param progress: how far the conversion task is
 
1310
    :param error: user-friendly string for describing conversion errors (if any)
 
1311
    :param output_path: path to the converted video (or None)
 
1312
    :param log_path: path to the log file for the conversion
 
1313
    :param item_name: name of the item being converted
 
1314
    :param item_thumbnail: thumbnail for the item being converted
 
1315
    """
 
1316
    def __init__(self, task):
 
1317
        self.key = task.key
 
1318
        if task.is_finished():
 
1319
            self.state = 'finished'
 
1320
            self.output_path = task.final_output_path
 
1321
        else:
 
1322
            self.output_path = None
 
1323
            if task.is_failed():
 
1324
                self.state = 'failed'
 
1325
            elif task.is_running():
 
1326
                self.state = "running"
 
1327
            else:
 
1328
                self.state = "pending"
 
1329
        self.log_path = task.log_path
 
1330
        self.progress = task.progress
 
1331
        self.error = task.error
 
1332
        self.item_name = task.item_info.name
 
1333
        self.item_thumbnail = task.item_info.thumbnail
 
1334
        self.eta = task.get_eta()
 
1335
        self.target = task.converter_info.displayname
 
1336
 
 
1337
class VideoConversionTasksList(FrontendMessage):
 
1338
    """Send the current list of running and pending conversion tasks to the 
 
1339
       frontend.
 
1340
    """
 
1341
    def __init__(self, running_tasks, pending_tasks, finished_tasks):
 
1342
        self.running_tasks = running_tasks
 
1343
        self.pending_tasks = pending_tasks
 
1344
        self.finished_tasks = finished_tasks
 
1345
 
 
1346
class VideoConversionsCountChanged(FrontendMessage):
 
1347
    """Informs the frontend that number of running conversions has changed.
 
1348
    """
 
1349
    def __init__(self, running_count, other_count):
 
1350
        self.running_count = running_count
 
1351
        self.other_count = other_count
 
1352
 
 
1353
class VideoConversionTaskCreated(FrontendMessage):
 
1354
    """Informs the frontend that a conversion task has been created.
 
1355
    """
 
1356
    def __init__(self, task):
 
1357
        self.task = task
 
1358
 
 
1359
class VideoConversionTaskRemoved(FrontendMessage):
 
1360
    """Informs the frontend that a conversion task has been removed.
 
1361
    """
 
1362
    def __init__(self, task):
 
1363
        self.task = task
 
1364
 
 
1365
class AllVideoConversionTaskRemoved(FrontendMessage):
 
1366
    """Informs the frontend that all conversion tasks have been removed.
 
1367
    """
 
1368
    pass
 
1369
 
 
1370
class VideoConversionTaskChanged(FrontendMessage):
 
1371
    """Informs the frontend that a conversion task has changed.
 
1372
 
 
1373
    This is sent when a conversion task changes state, or when a running task
 
1374
    changes it's progress.
 
1375
    """
 
1376
    def __init__(self, task):
 
1377
        self.task = task
 
1378
 
 
1379
class MessageToUser(FrontendMessage):
 
1380
    """Lets the backend send messages directly to the user.
 
1381
    """
 
1382
    def __init__(self, title, desc):
 
1383
        self.title = title
 
1384
        self.desc = desc
 
1385
 
 
1386
class PlayMovie(FrontendMessage):
 
1387
    """Starts playing a specific movie.
 
1388
    """
 
1389
    def __init__(self, item_infos):
 
1390
        self.item_infos = item_infos
 
1391
 
 
1392
class NotifyUser(FrontendMessage):
 
1393
    """Sends a notification to the user.
 
1394
 
 
1395
    Can optionally give a notification type, so we can filter based on
 
1396
    whether the user has selected that they are interested in
 
1397
    receiving notifications of this type.
 
1398
    """
 
1399
    def __init__(self, title, body, notify_type=None):
 
1400
        self.title = title
 
1401
        self.body = body
 
1402
        self.notify_type = notify_type
 
1403
    
 
1404
class SearchComplete(FrontendMessage):
 
1405
    """Notifies the backend that the search was complete.
 
1406
    """
 
1407
    def __init__(self, engine, query, result_count):
 
1408
        self.engine = engine
 
1409
        self.query = query
 
1410
        self.result_count = result_count
 
1411
 
 
1412
class CurrentFrontendState(FrontendMessage):
 
1413
    """Returns the latest data saved with SaveFrontendState.
 
1414
    """
 
1415
    def __init__(self, list_view_displays, sort_states, active_filters):
 
1416
        self.list_view_displays = list_view_displays
 
1417
        self.sort_states = sort_states
 
1418
        self.active_filters = active_filters
 
1419
 
 
1420
class OpenInExternalBrowser(FrontendMessage):
 
1421
    """Opens the specified url in an external browser.
 
1422
    """
 
1423
    def __init__(self, url):
 
1424
        self.url = url
 
1425
 
 
1426
class ProgressDialogStart(FrontendMessage):
 
1427
    def __init__(self, title):
 
1428
        self.title = title
 
1429
 
 
1430
class ProgressDialog(FrontendMessage):
 
1431
    """Inform the frontend of progress while we perform a long-running task.
 
1432
 
 
1433
    :param progress: current progress for the task [0.0 - 1.0] or -1 if we
 
1434
        can't estimate the progress.  The frontend should show a throbber in
 
1435
        this case)
 
1436
    """
 
1437
    def __init__(self, description, progress):
 
1438
        self.description = description
 
1439
        self.progress = progress
 
1440
 
 
1441
class ProgressDialogFinished(FrontendMessage):
 
1442
    pass
 
1443
 
 
1444
class FeedlessDownloadStarted(FrontendMessage):
 
1445
    """Inform the frontend that a new video started downloading because a
 
1446
    subscribe link was clicked.
 
1447
    """
 
1448
    pass