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.
35
from miro import dialogs
36
from miro import eventloop
38
from miro import folder
41
from miro.frontends.cli import clidialog
42
from miro.plat import resources
44
def run_in_event_loop(func):
45
def decorated(*args, **kwargs):
47
event = threading.Event()
50
return_hack.append(func(*args, **kwargs))
53
eventloop.add_urgent_call(runThenSet, 'run in event loop')
57
decorated.__doc__ = func.__doc__
61
def __init__(self, tab_type, tab_template_base):
63
self.tab_template_base = tab_template_base
65
class MiroInterpreter(cmd.Cmd):
67
cmd.Cmd.__init__(self)
68
self.quit_flag = False
70
self.init_database_objects()
73
print "Type \"help\" for help."
76
def init_database_objects(self):
77
self.video_feed_tabs = tabs.TabOrder.video_feed_order()
78
self.audio_feed_tabs = tabs.TabOrder.audio_feed_order()
79
self.playlist_tabs = tabs.TabOrder.playlist_order()
82
def tab_changed(self):
83
"""Calculate the current prompt. This method access database objects,
84
so it should only be called from the backend event loop
88
self.selection_type = None
90
elif self.tab.type == 'feed':
91
if isinstance(self.tab, folder.ChannelFolder):
92
self.prompt = "channel folder: %s > " % self.tab.get_title()
93
self.selection_type = 'channel-folder'
95
self.prompt = "channel: %s > " % self.tab.get_title()
96
self.selection_type = 'feed'
98
elif self.tab.type == 'playlist':
99
self.prompt = "playlist: %s > " % self.tab.get_title()
100
self.selection_type = 'playlist'
102
elif (self.tab.type == 'statictab' and
103
self.tab.tab_template_base == 'downloadtab'):
104
self.prompt = "downloads > "
105
self.selection_type = 'downloads'
107
raise ValueError("Unknown tab type")
109
def postcmd(self, stop, line):
111
# If the last command results in a dialog, give it a little time to
116
dialog = app.cli_events.dialog_queue.get_nowait()
119
clidialog.handle_dialog(dialog)
121
return self.quit_flag
123
def do_help(self, line):
124
"""help -- Lists commands and help."""
125
commands = [m for m in dir(self) if m.startswith("do_")]
127
docstring = getattr(self, mem).__doc__
128
print " ", docstring.strip()
130
def do_quit(self, line):
131
"""quit -- Quits Miro cli."""
132
self.quit_flag = True
135
def do_feed(self, line):
136
"""feed <name> -- Selects a feed by name."""
137
for tab in self.video_feed_tabs.get_all_tabs():
138
if tab.get_title() == line:
140
self.tab.type = "feed"
143
for tab in self.audio_feed_tabs.get_all_tabs():
144
if tab.get_title() == line:
146
self.tab.type = "feed"
149
print "Error: %s not found." % line
152
def do_rmfeed(self, line):
153
"""rmfeed <name> -- Deletes a feed."""
154
for tab in self.video_feed_tabs.get_all_tabs():
155
if tab.get_title() == line:
158
for tab in self.audio_feed_tabs.get_all_tabs():
159
if tab.get_title() == line:
162
print "Error: %s not found." % line
165
def complete_feed(self, text, line, begidx, endidx):
166
return self.handle_tab_complete(text, list(self.video_feed_tabs.get_all_tabs()) + list(self.audio_feed_tabs.get_all_tabs()))
169
def complete_rmfeed(self, text, line, begidx, endidx):
170
return self.handle_tab_complete(text, list(self.video_feed_tabs.get_all_tabs()) + list(self.audio_feed_tabs.get_all_tabs()))
173
def complete_playlist(self, text, line, begidx, endidx):
174
return self.handle_tab_complete(text, self.playlist_tabs.get_all_tabs())
176
def handle_tab_complete(self, text, view_items):
179
for tab in view_items:
180
if tab.get_title().lower().startswith(text):
181
matches.append(tab.get_title())
184
def handle_item_complete(self, text, view, filterFunc=lambda i: True):
188
if (item.get_title().lower().startswith(text) and
190
matches.append(item.get_title())
193
def _print_feeds(self, feeds):
194
current_folder = None
196
if isinstance(tab, folder.ChannelFolder):
198
elif tab.get_folder() is not current_folder:
199
current_folder = None
200
if current_folder is None:
201
print " * " + tab.get_title()
202
elif current_folder is tab:
203
print " * [Folder] %s" % tab.get_title()
205
print " * - %s" % tab.get_title()
208
def do_feeds(self, line):
209
"""feeds -- Lists all feeds."""
211
self._print_feeds(self.video_feed_tabs.get_all_tabs())
213
self._print_feeds(self.audio_feed_tabs.get_all_tabs())
216
def do_play(self, line):
217
"""play <name> -- Plays an item by name in an external player."""
218
if self.selection_type is None:
219
print "Error: No feed/playlist selected."
221
item = self._find_item(line)
223
print "No item named %r" % line
225
if item.is_downloaded():
226
resources.open_file(item.get_video_filename())
228
print '%s is not downloaded' % item.get_title()
231
def do_playlists(self, line):
232
"""playlists -- Lists all playlists."""
233
for tab in self.playlistTabs.getView():
234
print tab.obj.get_title()
237
def do_playlist(self, line):
238
"""playlist <name> -- Selects a playlist."""
239
for tab in self.playlistTabs.getView():
240
if tab.obj.get_title() == line:
244
print "Error: %s not found." % line
247
def do_items(self, line):
248
"""items -- Lists the items in the feed/playlist/tab selected."""
249
if self.selection_type is None:
250
print "Error: No tab/feed/playlist selected."
252
elif self.selection_type == 'feed':
255
self.printout_item_list(view)
256
elif self.selection_type == 'playlist':
257
playlist = self.tab.obj
258
self.printout_item_list(playlist.getView())
259
elif self.selection_type == 'downloads':
260
self.printout_item_list(item.Item.downloading_view(),
261
item.Item.paused_view())
262
elif self.selection_type == 'channel-folder':
263
folder = self.tab.obj
264
allItems = views.items.filterWithIndex(
265
indexes.itemsByChannelFolder, folder)
266
allItemsSorted = allItems.sort(folder.itemSort.sort)
267
self.printout_item_list(allItemsSorted)
268
allItemsSorted.unlink()
270
raise ValueError("Unknown tab type")
273
def do_downloads(self, line):
274
"""downloads -- Selects the downloads tab."""
275
self.tab = FakeTab("statictab", "downloadtab")
278
def printout_item_list(self, *views):
281
totalItems += view.count()
283
print "%-20s %-10s %s" % ("State", "Size", "Name")
287
state = item.get_state()
288
if state == 'downloading':
289
state += ' (%0.0f%%)' % item.download_progress()
290
print "%-20s %-10s %s" % (state, item.get_size_for_display(),
296
def _get_item_view(self):
297
if self.selection_type == 'feed':
298
return item.Item.visible_feed_view(self.tab.id)
299
elif self.selection_type == 'playlist':
300
return item.Item.playlist_view(self.tab.id)
301
elif self.selection_type == 'downloads':
302
return item.Item.downloading_view()
303
elif self.selection_type == 'channel-folder':
305
return item.Item.visible_folder_view(folder.id)
307
raise ValueError("Unknown selection type")
309
def _find_item(self, line):
311
for item in self._get_item_view():
312
if item.get_title().lower() == line:
316
def do_stop(self, line):
317
"""stop <name> -- Stops download by name."""
318
if self.selection_type is None:
319
print "Error: No feed/playlist selected."
321
item = self._find_item(line)
323
print "No item named %r" % line
325
if item.get_state() in ('downloading', 'paused'):
328
print '%s is not being downloaded' % item.get_title()
331
def complete_stop(self, text, line, begidx, endidx):
332
return self.handle_item_complete(text, self._get_item_view(),
333
lambda i: i.get_state() in ('downloading', 'paused'))
336
def do_download(self, line):
337
"""download <name> -- Downloads an item by name in the feed/playlist selected."""
338
if self.selection_type is None:
339
print "Error: No feed/playlist selected."
341
item = self._find_item(line)
343
print "No item named %r" % line
345
if item.get_state() == 'downloading':
346
print '%s is currently being downloaded' % item.get_title()
347
elif item.is_downloaded():
348
print '%s is already downloaded' % item.get_title()
353
def complete_download(self, text, line, begidx, endidx):
354
return self.handle_item_complete(text, self._get_item_view(),
355
lambda i: i.is_downloadable())
358
def do_pause(self, line):
359
"""pause <name> -- Pauses a download by name."""
360
if self.selection_type is None:
361
print "Error: No feed/playlist selected."
363
item = self._find_item(line)
365
print "No item named %r" % line
367
if item.get_state() == 'downloading':
370
print '%s is not being downloaded' % item.get_title()
373
def complete_pause(self, text, line, begidx, endidx):
374
return self.handle_item_complete(text, self._get_item_view(),
375
lambda i: i.get_state() == 'downloading')
378
def do_resume(self, line):
379
"""resume <name> -- Resumes a download by name."""
380
if self.selection_type is None:
381
print "Error: No feed/playlist selected."
383
item = self._find_item(line)
385
print "No item named %r" % line
387
if item.get_state() == 'paused':
390
print '%s is not a paused download' % item.get_title()
393
def complete_resume(self, text, line, begidx, endidx):
394
return self.handle_item_complete(text, self._get_item_view(),
395
lambda i: i.get_state() == 'paused')
398
def do_rm(self, line):
399
"""rm <name> -- Removes an item by name in the feed/playlist selected."""
400
if self.selection_type is None:
401
print "Error: No feed/playlist selected."
403
item = self._find_item(line)
405
print "No item named %r" % line
407
if item.is_downloaded():
410
print '%s is not downloaded' % item.get_title()
413
def complete_rm(self, text, line, begidx, endidx):
414
return self.handle_item_complete(text, self._get_item_view(),
415
lambda i: i.is_downloaded())
418
def do_testdialog(self, line):
419
"""testdialog -- Tests the cli dialog system."""
420
d = dialogs.ChoiceDialog("Hello", "I am a test dialog",
421
dialogs.BUTTON_OK, dialogs.BUTTON_CANCEL)
422
def callback(dialog):
423
print "TEST CHOICE: %s" % dialog.choice
427
def do_dumpdatabase(self, line):
428
"""dumpdatabase -- Dumps the database."""
429
from miro import database
430
print "Dumping database...."
431
database.defaultDatabase.liveStorage.dumpDatabase(database.defaultDatabase)