1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
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.
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.
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
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
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.
29
"""``miro.messages`` -- Message passing between the frontend thread
30
and the backend thread.
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.
38
This module defines the messages that are passed between the two
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
51
from miro import guide
52
from miro import prefs
54
from miro import filetypes
56
class MessageHandler(object):
58
self.message_map = {} # maps message classes to method names
59
self.complained_about = set()
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.
65
raise NotImplementedError()
67
def handle(self, message):
68
"""Handles a given message.
70
handler_name = self.get_message_handler_name(message)
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,
78
self.complained_about.add(handler_name)
80
self.call_handler(handler, message)
82
def get_message_handler_name(self, message):
84
return self.message_map[message.__class__]
86
self.message_map[message.__class__] = \
87
self.calc_message_handler_name(message.__class__)
88
return self.message_map[message.__class__]
90
def calc_message_handler_name(self, message_class):
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)
97
class Message(object):
98
"""Base class for all Messages.
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
106
cls.handler = handler
109
def reset_handler(cls):
112
class BackendMessage(Message):
113
"""Base class for Messages that get sent to the backend.
115
def send_to_backend(self):
117
handler = self.handler
118
except AttributeError:
119
logging.warn("No handler for backend messages")
123
class FrontendMessage(Message):
124
"""Base class for Messages that get sent to the frontend.
126
def send_to_frontend(self):
128
handler = self.handler
129
except AttributeError:
130
logging.warn("No handler for frontend messages")
136
class FrontendStarted(BackendMessage):
137
"""Inform the backend that the frontend has finished starting up.
141
class TrackChannels(BackendMessage):
142
"""Begin tracking channels.
144
After this message is sent, the backend will send back a ChannelList
145
message, then it will send ChannelsChanged messages whenever the channel
150
class StopTrackingChannels(BackendMessage):
151
"""Stop tracking channels.
155
class QuerySearchInfo(BackendMessage):
156
"""Ask the backend to send a CurrentSearchInfo message.
160
class TrackPlaylists(BackendMessage):
161
"""Begin tracking playlists.
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
169
class StopTrackingPlaylists(BackendMessage):
170
"""Stop tracking playlists.
174
class TrackGuides(BackendMessage):
175
"""Begin tracking guides.
177
After this message is sent, the backend will send back a GuideList
178
message, then it will send GuidesChanged messages whenever the guide
183
class StopTrackingGuides(BackendMessage):
184
"""Stop tracking guides.
188
class TrackItems(BackendMessage):
189
"""Begin tracking items for a feed
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.
194
type is the type of object that we are tracking items for. It can be one
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
203
id should be the id of a feed/playlist. For new, downloading and library
206
def __init__(self, type, id):
210
class TrackItemsManually(BackendMessage):
211
"""Track a manually specified list of items.
213
ItemList and ItemsChanged messages will have "manual" as the type and
214
will use the id specified in the constructed.
216
def __init__(self, id, ids_to_track):
218
self.ids_to_track = ids_to_track
221
class StopTrackingItems(BackendMessage):
222
"""Stop tracking items for a feed.
224
def __init__(self, type, id):
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
236
class StopTrackingDownloadCount(BackendMessage):
237
"""Stop tracking the download count.
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.
249
class StopTrackingPausedCount(BackendMessage):
250
"""Stop tracking the paused count."""
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.
261
class StopTrackingNewVideoCount(BackendMessage):
262
"""Stop tracking the new videos count.
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.
274
class StopTrackingNewAudioCount(BackendMessage):
275
"""Stop tracking the new audio items count.
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.
287
class StopTrackingUnwatchedCount(BackendMessage):
288
"""Stop tracking the unwatched items count.
292
class TrackWatchedFolders(BackendMessage):
293
"""Begin tracking watched folders
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.
301
class StopTrackingWatchedFolders(BackendMessage):
302
"""Stop tracking watched folders.
306
class SetFeedExpire(BackendMessage):
307
"""Sets the expiration for a feed.
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
314
class SetFeedMaxNew(BackendMessage):
315
"""Sets the feed's max new property.
317
def __init__(self, channel_info, max_new):
318
self.channel_info = channel_info
319
self.max_new = max_new
321
class SetFeedMaxOldItems(BackendMessage):
322
"""Sets the feed's max old items property.
324
def __init__(self, channel_info, max_old_items):
325
self.channel_info = channel_info
326
self.max_old_items = max_old_items
328
class CleanFeed(BackendMessage):
329
"""Tells the backend to clean the old items from a feed.
331
def __init__(self, channel_id):
332
self.channel_id = channel_id
334
class ImportFeeds(BackendMessage):
335
"""Tell the backend to import feeds from an .opml file.
337
:param filename: file name that exists
339
def __init__(self, filename):
340
self.filename = filename
342
class ExportSubscriptions(BackendMessage):
343
"""Tell the backend to export subscriptions to an .opml file.
345
:param filename: file name to export to
347
def __init__(self, filename):
348
self.filename = filename
350
class RenameObject(BackendMessage):
351
"""Tell the backend to rename a feed/playlist/folder.
353
:param type: ``feed``, ``playlist``, ``feed-folder`` or
355
:param id: id of the object to rename
356
:param new_name: new name for the object
358
def __init__(self, type, id, new_name):
361
self.new_name = util.to_uni(new_name)
363
class UpdateFeed(BackendMessage):
366
def __init__(self, id):
369
class UpdateFeedFolder(BackendMessage):
370
"""Updates the feeds in a feed folder.
372
def __init__(self, id):
375
class MarkFeedSeen(BackendMessage):
376
"""Mark a feed as seen.
378
def __init__(self, id):
381
class MarkItemWatched(BackendMessage):
382
"""Mark an item as watched.
384
def __init__(self, id):
387
class MarkItemUnwatched(BackendMessage):
388
"""Mark an item as unwatched.
390
def __init__(self, id):
393
class SetItemSubtitleEncoding(BackendMessage):
394
"""Mark an item as watched.
396
def __init__(self, id, encoding):
398
self.encoding = encoding
400
class SetItemResumeTime(BackendMessage):
401
"""Set an item resume time.
403
def __init__(self, id, time):
405
self.resume_time = time
407
class SetItemMediaType(BackendMessage):
408
"""Adds a list of videos to a playlist.
410
def __init__(self, media_type, video_ids):
411
self.media_type = media_type
412
self.video_ids = video_ids
414
class UpdateAllFeeds(BackendMessage):
415
"""Updates all feeds.
419
class DeleteFeed(BackendMessage):
422
def __init__(self, id, is_folder, keep_items):
424
self.is_folder = is_folder
425
self.keep_items = keep_items
427
class DeleteWatchedFolder(BackendMessage):
428
"""Delete a watched folder.
432
This separate from DeleteFeed since not all watched folders are
435
def __init__(self, id):
438
class DeletePlaylist(BackendMessage):
439
"""Delete a playlist.
441
def __init__(self, id, is_folder):
443
self.is_folder = is_folder
445
class DeleteSite(BackendMessage):
446
"""Delete an external channel guide.
448
def __init__(self, id):
451
class NewGuide(BackendMessage):
452
"""Create a new channel guide.
454
def __init__(self, url):
455
self.url = util.to_uni(url)
457
class NewFeed(BackendMessage):
458
"""Creates a new feed.
460
def __init__(self, url, section=u"video"):
461
self.url = util.to_uni(url)
462
self.section = section
464
class NewFeedSearchFeed(BackendMessage):
465
"""Creates a new feed based on a search through a feed.
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
472
class NewFeedSearchEngine(BackendMessage):
473
"""Creates a new feed from a search engine.
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
480
class NewFeedSearchURL(BackendMessage):
481
"""Creates a new feed from a url.
483
def __init__(self, url, search_term, section):
485
self.search_term = search_term
486
self.section = section
488
class NewWatchedFolder(BackendMessage):
489
"""Creates a new watched folder.
491
def __init__(self, path):
494
class SetWatchedFolderVisible(BackendMessage):
495
"""Changes if a watched folder is visible in the tab list or not.
497
def __init__(self, id, visible):
499
self.visible = visible
501
class NewPlaylist(BackendMessage):
502
"""Create a new playlist.
504
def __init__(self, name, ids):
505
self.name = util.to_uni(name)
508
class NewFeedFolder(BackendMessage):
509
"""Create a new feed folder.
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
516
class NewPlaylistFolder(BackendMessage):
517
"""Create a new playlist folder.
519
def __init__(self, name, child_playlist_ids):
520
self.name = util.to_uni(name)
521
self.child_playlist_ids = child_playlist_ids
523
class ChangeMoviesDirectory(BackendMessage):
524
"""Change the current movies directory.
526
If migrate is True, then the backend will send a series of
527
ProgressDialog messages while the migration happens.
529
def __init__(self, path, migrate):
531
self.migrate = migrate
533
class AddVideosToPlaylist(BackendMessage):
534
"""Adds a list of videos to a playlist.
536
def __init__(self, playlist_id, video_ids):
537
self.playlist_id = playlist_id
538
self.video_ids = video_ids
540
class RemoveVideosFromPlaylist(BackendMessage):
541
"""Removes a list of videos from a playlist.
543
def __init__(self, playlist_id, video_ids):
544
self.playlist_id = playlist_id
545
self.video_ids = video_ids
547
class DownloadURL(BackendMessage):
548
"""Downloads the item at a url.
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
556
:param metadata: dict of name/value pairs to include in the item.
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
563
class OpenIndividualFile(BackendMessage):
564
"""Open a single file item in Miro.
566
def __init__(self, filename):
567
self.filename = filename
569
class OpenIndividualFiles(BackendMessage):
570
"""Open a list of file items in Miro.
572
def __init__(self, filenames):
573
self.filenames = filenames
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
580
def __init__(self, filenames):
581
self.filenames = filenames
583
class CheckVersion(BackendMessage):
584
"""Checks whether Miro is the most recent version.
586
def __init__(self, up_to_date_callback):
587
self.up_to_date_callback = up_to_date_callback
589
class Search(BackendMessage):
590
"""Search a search engine with a search term.
592
The backend will send a SearchComplete message.
594
def __init__(self, searchengine_id, terms):
595
self.id = searchengine_id
598
class CancelAutoDownload(BackendMessage):
599
"""Cancels the autodownload for an item.
601
def __init__(self, id):
604
class StartDownload(BackendMessage):
605
"""Start downloading an item.
607
def __init__(self, id):
610
return BackendMessage.__repr__(self) + (", id: %s" % self.id)
612
class CancelDownload(BackendMessage):
613
"""Cancel downloading an item.
615
def __init__(self, id):
618
class CancelAllDownloads(BackendMessage):
619
"""Cancels all downloading items.
623
class PauseAllDownloads(BackendMessage):
624
"""Pauses all downloading items.
628
class PauseDownload(BackendMessage):
629
"""Pause downloading an item.
631
def __init__(self, id):
634
class ResumeAllDownloads(BackendMessage):
635
"""Resumes all downloading items.
639
class ResumeDownload(BackendMessage):
640
"""Resume downloading an item.
642
def __init__(self, id):
645
class StartUpload(BackendMessage):
646
"""Start uploading a torrent.
648
def __init__(self, id):
651
class StopUpload(BackendMessage):
652
"""Stop uploading a torrent.
654
def __init__(self, id):
657
class KeepVideo(BackendMessage):
658
"""Cancel the auto-expiration of an item's video.
660
def __init__(self, id):
663
return BackendMessage.__repr__(self) + (", id: %s" % self.id)
665
class SaveItemAs(BackendMessage):
666
"""Saves an item in the dark clutches of Miro to somewhere else.
668
def __init__(self, id, filename):
670
self.filename = filename
672
class RemoveVideoEntry(BackendMessage):
673
"""Remove the entry for an external video.
675
def __init__(self, id):
678
class DeleteVideo(BackendMessage):
679
"""Delete the video for an item's video.
681
def __init__(self, id):
684
return BackendMessage.__repr__(self) + (", id: %s" % self.id)
686
class EditItem(BackendMessage):
687
"""Changes a bunch of things on an item.
689
def __init__(self, item_id, change_dict):
690
self.item_id = item_id
691
self.change_dict = change_dict
693
class RevertFeedTitle(BackendMessage):
694
"""Reverts the feed's title back to the original.
696
def __init__(self, id):
699
class PlayAllUnwatched(BackendMessage):
700
"""Figures out all the unwatched items and plays them.
705
class FolderExpandedChange(BackendMessage):
706
"""Inform the backend when a folder gets expanded/collapsed.
708
def __init__(self, type, id, expanded):
711
self.expanded = expanded
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``,
718
def __init__(self, id, setting):
720
self.setting = setting
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
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
738
self.folder_children = {}
740
def append(self, info, type):
741
self.toplevels[type].append(info)
743
self.folder_children[info.id] = []
745
def append_child(self, parent_id, info):
746
self.folder_children[parent_id].append(info)
748
class PlaylistReordered(BackendMessage):
749
"""Inform the backend when the items in a playlist are re-ordered.
751
:param id: playlist that was re-ordered.
752
:param item_ids: List of ids for item in the playlist, in their new
755
def __init__(self, id, item_ids):
757
self.item_ids = item_ids
759
class SubscriptionLinkClicked(BackendMessage):
760
"""Inform the backend that the user clicked on a subscription link
763
def __init__(self, url):
766
class ReportCrash(BackendMessage):
767
"""Sends a crash report.
769
def __init__(self, report, text, send_report):
772
self.send_report = send_report
774
class SaveFrontendState(BackendMessage):
775
"""Save data for the frontend.
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
782
class QueryFrontendState(BackendMessage):
783
"""Ask for a CurrentFrontendState message to be sent back.
789
class FrontendQuit(FrontendMessage):
790
"""The frontend should exit."""
793
class DatabaseUpgradeStart(FrontendMessage):
794
"""We're about to do a database upgrade.
798
class DatabaseUpgradeProgress(FrontendMessage):
799
"""We're about to do a database upgrade.
801
def __init__(self, stage, stage_progress, total_progress):
803
self.stage_progress = stage_progress
804
self.total_progress = total_progress
806
class DatabaseUpgradeEnd(FrontendMessage):
807
"""We're done with the database upgrade.
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.
817
class StartupFailure(FrontendMessage):
818
"""The startup process failed. The frontend should inform the
819
user that this happened and quit.
822
:param summary: Short, user-friendly, summary of the problem.
823
:param description: Longer explanation of the problem.
825
def __init__(self, summary, description):
826
self.summary = summary
827
self.description = description
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.
834
:param summary: Short, user-friendly, summary of the problem.
835
:param description: Longer explanation of the problem.
837
def __init__(self, summary, description):
838
self.summary = summary
839
self.description = description
841
class ChannelInfo(object):
842
"""Tracks the state of a channel
844
:param name: channel name
845
:param url: channel url (None for channel folders)
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
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``,
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
869
def __init__(self, channel_obj):
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
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()
900
self.is_updating = False
901
self.parent_id = 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
909
self.expire_time = None
911
self.max_old_items = None
912
self.num_downloaded = None
914
class PlaylistInfo(object):
915
"""Tracks the state of a playlist
917
:param name: playlist name
919
:param is_folder: is this a playlist folder?
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)
926
self.parent_id = None
928
self.parent_id = playlist_obj.folder_id
930
class GuideInfo(object):
931
"""Tracks the state of a channel guide
933
:param name: channel name
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
942
def __init__(self, guide):
943
self.name = guide.get_title()
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())
952
class ItemInfo(object):
953
"""Tracks the state of an item
955
:param name: name of the item
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
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
975
:param expiration_date: datetime object for when the item will expire
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',
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
995
:param is_container_item: whether or not this item is actually a
996
collection of files as opposed to an
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
1009
def __init__(self, item):
1010
self.name = item.get_title()
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()
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()]
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
1056
self.download_info = DownloadInfo(item.downloader)
1057
elif self.state == 'downloading':
1058
self.download_info = PendingDownloadInfo()
1060
self.download_info = None
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)
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
1081
self.up_down_ratio = self.up_total * 1.0 / self.down_total
1083
class DownloadInfo(object):
1084
"""Tracks the download state of an item.
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?
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()
1108
self.reason_failed = u""
1109
self.short_reason_failed = u""
1110
self.eta = downloader.get_eta()
1112
class PendingDownloadInfo(DownloadInfo):
1113
"""DownloadInfo object for pending downloads (downloads queued,
1114
but not started because we've reached some limit)
1117
self.downloaded_size = 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""
1127
class WatchedFolderInfo(object):
1128
"""Tracks the state of a watched folder.
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?
1134
def __init__(self, channel):
1135
self.id = channel.id
1136
self.path = channel.dir
1137
self.visible = channel.visible
1139
class GuideList(FrontendMessage):
1140
"""Sends the frontend the initial list of channel guides
1142
:param default_guide: The Default channel guide
1143
:param guides: list added channel guides
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]
1161
class TabList(FrontendMessage):
1162
"""Sends the frontend the current list of channels and playlists
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.
1167
:param type: ``feed`` or ``playlist``
1168
:param toplevels: the list of ChannelInfo/PlaylistInfo objects
1170
:param folder_children: dict mapping channel folder ids to a list of
1171
ChannelInfo/PlaylistInfo objects for their
1173
:param expanded_folders: set containing ids of the folders that should
1174
be initially expanded.
1176
def __init__(self, type):
1179
self.folder_children = {}
1180
self.expanded_folders = set()
1182
def append(self, info):
1183
self.toplevels.append(info)
1185
self.folder_children[info.id] = []
1187
def append_child(self, parent_id, info):
1188
self.folder_children[parent_id].append(info)
1190
def expand_folder(self, folder_id):
1191
self.expanded_folders.add(folder_id)
1193
class TabsChanged(FrontendMessage):
1194
"""Informs the frontend that the channel list or playlist list has been
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
1205
def __init__(self, type, added, changed, removed, section=None):
1208
self.changed = changed
1209
self.removed = removed
1210
self.section = section
1212
class ItemList(FrontendMessage):
1213
"""Sends the frontend the initial list of items for a feed
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
1219
def __init__(self, type, id, item_infos):
1222
self.items = item_infos
1224
class ItemsChanged(FrontendMessage):
1225
"""Informs the frontend that the items in a feed have changed.
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
1234
def __init__(self, type, id, added, changed, removed):
1238
self.changed = changed
1239
self.removed = removed
1241
class WatchedFolderList(FrontendMessage):
1242
"""Sends the frontend the initial list of watched folders.
1244
:param watched_folders: List of watched folders
1246
def __init__(self, watched_folders):
1247
self.watched_folders = watched_folders
1249
class WatchedFoldersChanged(FrontendMessage):
1250
"""Informs the frontend that the watched folder list has changed.
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
1256
:param removed: list of ids for each watched folder that was removed.
1258
def __init__(self, added, changed, removed):
1260
self.changed = changed
1261
self.removed = removed
1263
class CurrentSearchInfo(FrontendMessage):
1264
"""Informs the frontend of the current search settings.
1266
def __init__(self, engine, text):
1267
self.engine = engine
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.
1274
def __init__(self, count, non_downloading_count):
1276
self.non_downloading_count = non_downloading_count
1278
class PausedCountChanged(FrontendMessage):
1279
"""Informs the frontend that number of paused downloading items
1282
def __init__(self, count):
1285
class NewVideoCountChanged(FrontendMessage):
1286
"""Informs the frontend that number of new videos has changed.
1288
def __init__(self, count):
1291
class NewAudioCountChanged(FrontendMessage):
1292
"""Informs the frontend that number of new videos has changed.
1294
def __init__(self, count):
1297
class UnwatchedCountChanged(FrontendMessage):
1298
"""Informs the frontend that number of unwatched items has changed.
1300
def __init__(self, count):
1303
class VideoConversionTaskInfo(object):
1304
"""Tracks the state of an conversion task.
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
1316
def __init__(self, task):
1318
if task.is_finished():
1319
self.state = 'finished'
1320
self.output_path = task.final_output_path
1322
self.output_path = None
1323
if task.is_failed():
1324
self.state = 'failed'
1325
elif task.is_running():
1326
self.state = "running"
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
1337
class VideoConversionTasksList(FrontendMessage):
1338
"""Send the current list of running and pending conversion tasks to the
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
1346
class VideoConversionsCountChanged(FrontendMessage):
1347
"""Informs the frontend that number of running conversions has changed.
1349
def __init__(self, running_count, other_count):
1350
self.running_count = running_count
1351
self.other_count = other_count
1353
class VideoConversionTaskCreated(FrontendMessage):
1354
"""Informs the frontend that a conversion task has been created.
1356
def __init__(self, task):
1359
class VideoConversionTaskRemoved(FrontendMessage):
1360
"""Informs the frontend that a conversion task has been removed.
1362
def __init__(self, task):
1365
class AllVideoConversionTaskRemoved(FrontendMessage):
1366
"""Informs the frontend that all conversion tasks have been removed.
1370
class VideoConversionTaskChanged(FrontendMessage):
1371
"""Informs the frontend that a conversion task has changed.
1373
This is sent when a conversion task changes state, or when a running task
1374
changes it's progress.
1376
def __init__(self, task):
1379
class MessageToUser(FrontendMessage):
1380
"""Lets the backend send messages directly to the user.
1382
def __init__(self, title, desc):
1386
class PlayMovie(FrontendMessage):
1387
"""Starts playing a specific movie.
1389
def __init__(self, item_infos):
1390
self.item_infos = item_infos
1392
class NotifyUser(FrontendMessage):
1393
"""Sends a notification to the user.
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.
1399
def __init__(self, title, body, notify_type=None):
1402
self.notify_type = notify_type
1404
class SearchComplete(FrontendMessage):
1405
"""Notifies the backend that the search was complete.
1407
def __init__(self, engine, query, result_count):
1408
self.engine = engine
1410
self.result_count = result_count
1412
class CurrentFrontendState(FrontendMessage):
1413
"""Returns the latest data saved with SaveFrontendState.
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
1420
class OpenInExternalBrowser(FrontendMessage):
1421
"""Opens the specified url in an external browser.
1423
def __init__(self, url):
1426
class ProgressDialogStart(FrontendMessage):
1427
def __init__(self, title):
1430
class ProgressDialog(FrontendMessage):
1431
"""Inform the frontend of progress while we perform a long-running task.
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
1437
def __init__(self, description, progress):
1438
self.description = description
1439
self.progress = progress
1441
class ProgressDialogFinished(FrontendMessage):
1444
class FeedlessDownloadStarted(FrontendMessage):
1445
"""Inform the frontend that a new video started downloading because a
1446
subscribe link was clicked.