|
MythTV
0.26-pre
|
00001 # Miro - an RSS based video player application 00002 # Copyright (C) 2005-2009 Participatory Culture Foundation 00003 # 00004 # This program is free software; you can redistribute it and/or modify 00005 # it under the terms of the GNU General Public License as published by 00006 # the Free Software Foundation; either version 2 of the License, or 00007 # (at your option) any later version. 00008 # 00009 # This program is distributed in the hope that it will be useful, 00010 # but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 # GNU General Public License for more details. 00013 # 00014 # You should have received a copy of the GNU General Public License 00015 # along with this program; if not, write to the Free Software 00016 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 00017 # 00018 # In addition, as a special exception, the copyright holders give 00019 # permission to link the code of portions of this program with the OpenSSL 00020 # library. 00021 # 00022 # You must obey the GNU General Public License in all respects for all of 00023 # the code used other than OpenSSL. If you modify file(s) with this 00024 # exception, you may extend this exception to your version of the file(s), 00025 # but you are not obligated to do so. If you do not wish to do so, delete 00026 # this exception statement from your version. If you delete this exception 00027 # statement from all source files in the program, then also delete it here. 00028 ########################################################################### 00029 # The original source "...miro/frontends/cli/interpreter.py" 00030 # was modified for the purposes of the MythTV script mirobridge.py 00031 ########################################################################### 00032 00033 import cmd 00034 import threading 00035 import time 00036 import Queue 00037 00038 from miro import app 00039 from miro import dialogs 00040 from miro import eventloop 00041 from miro import folder 00042 from miro import indexes 00043 from miro import util 00044 from miro import views 00045 from miro.frontends.cli import clidialog 00046 from miro.plat import resources 00047 from miro import fileutil 00048 from miro import feed 00049 00050 ## mirobridge.py import additions - All to get feed updates and auto downloads working 00051 import os, sys, subprocess, re, fnmatch, string 00052 import logging 00053 from miro.singleclick import parse_command_line_args 00054 from miro import moviedata 00055 from miro import autodler 00056 from miro import downloader 00057 from miro import iconcache 00058 from miro.clock import clock 00059 from miro import filetypes 00060 00061 00062 def run_in_event_loop(func): 00063 def decorated(*args, **kwargs): 00064 return_hack = [] 00065 event = threading.Event() 00066 def runThenSet(): 00067 try: 00068 return_hack.append(func(*args, **kwargs)) 00069 finally: 00070 event.set() 00071 eventloop.addUrgentCall(runThenSet, 'run in event loop') 00072 event.wait() 00073 if return_hack: 00074 return return_hack[0] 00075 decorated.__doc__ = func.__doc__ 00076 return decorated 00077 00078 class FakeTab: 00079 def __init__(self, tab_type, tabTemplateBase): 00080 self.type = tab_type 00081 self.tabTemplateBase = tabTemplateBase 00082 00083 class MiroInterpreter(cmd.Cmd): 00084 def __init__(self): 00085 cmd.Cmd.__init__(self) 00086 self.quit_flag = False 00087 self.tab = None 00088 self.init_database_objects() 00089 00090 @run_in_event_loop 00091 def init_database_objects(self): 00092 self.channelTabs = util.getSingletonDDBObject(views.channelTabOrder) 00093 self.playlistTabs = util.getSingletonDDBObject(views.playlistTabOrder) 00094 self.tab_changed() 00095 00096 def tab_changed(self): 00097 """Calculate the current prompt. This method access database objects, 00098 so it should only be called from the backend event loop 00099 """ 00100 if self.tab is None: 00101 self.prompt = "> " 00102 self.selection_type = None 00103 elif self.tab.type == 'feed': 00104 if isinstance(self.tab.obj, folder.ChannelFolder): 00105 self.prompt = "channel folder: %s > " % self.tab.obj.get_title() 00106 self.selection_type = 'channel-folder' 00107 else: 00108 self.prompt = "channel: %s > " % self.tab.obj.get_title() 00109 self.selection_type = 'feed' 00110 elif self.tab.type == 'playlist': 00111 self.prompt = "playlist: %s > " % self.tab.obj.get_title() 00112 self.selection_type = 'playlist' 00113 elif (self.tab.type == 'statictab' and 00114 self.tab.tabTemplateBase == 'downloadtab'): 00115 self.prompt = "downloads > " 00116 self.selection_type = 'downloads' 00117 else: 00118 raise ValueError("Unknown tab type") 00119 00120 def postcmd(self, stop, line): 00121 # HACK 00122 # If the last command results in a dialog, give it a little time to 00123 # pop up 00124 time.sleep(0.1) 00125 while True: 00126 try: 00127 dialog = app.cli_events.dialog_queue.get_nowait() 00128 except Queue.Empty: 00129 break 00130 clidialog.handle_dialog(dialog) 00131 00132 return self.quit_flag 00133 00134 def do_help(self, line): 00135 """help -- Lists commands and help.""" 00136 commands = [m for m in dir(self) if m.startswith("do_")] 00137 for mem in commands: 00138 docstring = getattr(self, mem).__doc__ 00139 print " ", docstring 00140 00141 def do_quit(self, line): 00142 """quit -- Quits Miro cli.""" 00143 self.quit_flag = True 00144 00145 @run_in_event_loop 00146 def do_feed(self, line): 00147 """feed <name> -- Selects a feed by name.""" 00148 for tab in self.channelTabs.getView(): 00149 if tab.obj.get_title() == line: 00150 self.tab = tab 00151 self.tab_changed() 00152 return 00153 print "Error: %s not found" % line 00154 00155 @run_in_event_loop 00156 def do_rmfeed(self, line): 00157 """rmfeed <name> -- Deletes a feed.""" 00158 for tab in self.channelTabs.getView(): 00159 if tab.obj.get_title() == line: 00160 tab.obj.remove() 00161 return 00162 print "Error: %s not found" % line 00163 00164 @run_in_event_loop 00165 def complete_feed(self, text, line, begidx, endidx): 00166 return self.handle_tab_complete(text, self.channelTabs.getView()) 00167 00168 @run_in_event_loop 00169 def complete_rmfeed(self, text, line, begidx, endidx): 00170 return self.handle_tab_complete(text, self.channelTabs.getView()) 00171 00172 @run_in_event_loop 00173 def complete_playlist(self, text, line, begidx, endidx): 00174 return self.handle_tab_complete(text, self.playlistTabs.getView()) 00175 00176 def handle_tab_complete(self, text, view): 00177 text = text.lower() 00178 matches = [] 00179 for tab in view: 00180 if tab.obj.get_title().lower().startswith(text): 00181 matches.append(tab.obj.get_title()) 00182 return matches 00183 00184 def handle_item_complete(self, text, view, filterFunc=lambda i: True): 00185 text = text.lower() 00186 matches = [] 00187 for item in view: 00188 if (item.get_title().lower().startswith(text) and 00189 filterFunc(item)): 00190 matches.append(item.get_title()) 00191 return matches 00192 00193 00194 ################################################################################### 00195 # 00196 # Start of mythbridge specific routines 00197 # 00198 ################################################################################### 00199 @run_in_event_loop 00200 def do_mythtv_update_autodownload(self, line): 00201 """Update feeds and auto-download""" 00202 logging.info("Starting auto downloader...") 00203 autodler.start_downloader() 00204 feed.expire_items() 00205 starttime = clock() 00206 logging.timing("Icon clear: %.3f", clock() - starttime) 00207 logging.info("Starting video updates") 00208 moviedata.movieDataUpdater.startThread() 00209 parse_command_line_args() 00210 # autoupdate.check_for_updates() 00211 # Wait a bit before starting the downloader daemon. It can cause a bunch 00212 # of disk/CPU load, so try to avoid it slowing other stuff down. 00213 eventloop.addTimeout(5, downloader.startupDownloader, 00214 "start downloader daemon") 00215 # ditto for feed updates 00216 eventloop.addTimeout(30, feed.start_updates, "start feed updates") 00217 # ditto for clearing stale icon cache files, except it's the very lowest 00218 # priority 00219 eventloop.addTimeout(10, iconcache.clear_orphans, "clear orphans") 00220 00221 def movie_data_program_info(self, movie_path, thumbnail_path): 00222 extractor_path = os.path.join(os.path.split(__file__)[0], "gst_extractor.py") 00223 return ((sys.executable, extractor_path, movie_path, thumbnail_path), None) 00224 00225 @run_in_event_loop 00226 def do_mythtv_check_downloading(self, line): 00227 """Check if any items are being downloaded. Set True or False""" 00228 self.downloading = False 00229 downloadingItems = views.downloadingItems 00230 count = len(downloadingItems) 00231 for item in downloadingItems: 00232 logging.info(u"(%s - %s) video is downloading with (%0.0f%%) complete" % (item.get_channel_title(True).replace(u'/',u'-'), item.get_title().replace(u'/',u'-'), item.download_progress())) 00233 if not count: 00234 logging.info(u"No items downloading") 00235 if count: 00236 self.downloading = True 00237 00238 @run_in_event_loop 00239 def do_mythtv_updatewatched(self, line): 00240 """Process MythTV update watched videos""" 00241 items = views.watchableItems 00242 for video in self.videofiles: 00243 for item in items: 00244 if item.get_filename() == video: 00245 break 00246 else: 00247 logging.info(u"Item for Miro video (%s) not found, skipping" % video) 00248 continue 00249 if self.simulation: 00250 logging.info(u"Simulation: Item (%s - %s) marked as seen and watched" % (item.get_channel_title(True), item.get_title())) 00251 else: 00252 item.markItemSeen(markOtherItems=False) 00253 self.statistics[u'Miro_marked_watch_seen']+=1 00254 logging.info(u"Item (%s - %s) marked as seen and watched" % (item.get_channel_title(True), item.get_title())) 00255 00256 @run_in_event_loop 00257 def do_mythtv_getunwatched(self, line): 00258 """Process MythTV get all un-watched video details""" 00259 if self.verbose: 00260 print 00261 print u"Getting details on un-watched Miro videos" 00262 00263 self.videofiles = [] 00264 if len(views.watchableItems): 00265 if self.verbose: 00266 print u"%-20s %-10s %s" % (u"State", u"Size", u"Name") 00267 print u"-" * 70 00268 for item in views.watchableItems: 00269 # Skip any audio file as MythTV Internal player may abort the MythTV Frontend on a MP3 00270 if not item.isVideo: 00271 continue 00272 state = item.get_state() 00273 if not state == u'newly-downloaded': 00274 continue 00275 # Skip any bittorrent video downloads for legal concerns 00276 if filetypes.is_torrent_filename(item.getURL()): 00277 continue 00278 self.printItems(item) 00279 self.videofiles.append(self._get_item_dict(item)) 00280 if self.verbose: 00281 print 00282 if not len(self.videofiles): 00283 logging.info(u"No un-watched Miro videos") 00284 00285 @run_in_event_loop 00286 def do_mythtv_getwatched(self, line): 00287 """Process MythTV get all watched/saved video details""" 00288 if self.verbose: 00289 print 00290 print u"Getting details on watched/saved Miro videos" 00291 self.videofiles = [] 00292 if len(views.watchableItems): 00293 if self.verbose: 00294 print u"%-20s %-10s %s" % (u"State", u"Size", u"Name") 00295 print "-" * 70 00296 for item in views.watchableItems: 00297 # Skip any audio file as MythTV Internal player may abort the MythTV Frontend on a MP3 00298 if not item.isVideo: 00299 continue 00300 state = item.get_state() 00301 if state == u'newly-downloaded': 00302 continue 00303 # Skip any bittorrent video downloads for legal concerns 00304 if filetypes.is_torrent_filename(item.getURL()): 00305 continue 00306 self.printItems(item) 00307 self.videofiles.append(self._get_item_dict(item)) 00308 if self.verbose: 00309 print 00310 if not len(self.videofiles): 00311 logging.info(u"No watched/saved Miro videos") 00312 00313 def printItems(self, item): 00314 if not self.verbose: 00315 return 00316 state = item.get_state() 00317 if state == u'downloading': 00318 state += u' (%0.0f%%)' % item.download_progress() 00319 print u"%-20s %-10s %s" % (state, item.get_size_for_display(), 00320 item.get_title()) 00321 # end printItems() 00322 00323 @run_in_event_loop 00324 def do_mythtv_item_remove(self, args): 00325 """Removes an item from Miro by file name or Channel and title""" 00326 for item in views.watchableItems: 00327 if isinstance(args, list): 00328 if filter(self.is_not_punct_char, item.get_channel_title(True).lower()) == filter(self.is_not_punct_char, args[0].lower()) and (filter(self.is_not_punct_char, item.get_title().lower())).startswith(filter(self.is_not_punct_char, args[1].lower())): 00329 break 00330 elif filter(self.is_not_punct_char, item.get_filename().lower()) == filter(self.is_not_punct_char, args.lower()): 00331 break 00332 else: 00333 logging.info(u"No item named %s" % args) 00334 return 00335 if item.is_downloaded(): 00336 if self.simulation: 00337 logging.info(u"Simulation: Item (%s - %s) has been removed from Miro" % (item.get_channel_title(True), item.get_title())) 00338 else: 00339 item.expire() 00340 self.statistics[u'Miro_videos_deleted']+=1 00341 logging.info(u'%s has been removed from Miro' % item.get_title()) 00342 else: 00343 logging.info(u'%s is not downloaded' % item.get_title()) 00344 00345 00346 def _get_item_dict(self, item): 00347 """Take an item and convert all elements into a dictionary 00348 return a dictionary of item elements 00349 """ 00350 def compatibleGraphics(filename): 00351 if filename: 00352 (dirName, fileName) = os.path.split(filename) 00353 (fileBaseName, fileExtension)=os.path.splitext(fileName) 00354 if not fileExtension[1:] in [u"png", u"jpg", u"bmp", u"gif"]: 00355 return u'' 00356 else: 00357 return filename 00358 else: 00359 return u'' 00360 00361 def useImageMagick(screenshot): 00362 """ Using ImageMagick's utility 'identify'. Decide whether the screen shot is worth using. 00363 >>> useImageMagick('identify screenshot.jpg') 00364 >>> Example returned information "rose.jpg JPEG 640x480 DirectClass 87kb 0.050u 0:01" 00365 >>> u'' if the screenshot quality is too low 00366 >>> screenshot if the quality is good enough to use 00367 """ 00368 if not self.imagemagick: # If imagemagick is not installed do not bother checking 00369 return u'' 00370 00371 width_height = re.compile(u'''^(.+?)[ ]\[?([0-9]+)x([0-9]+)[^\\/]*$''', re.UNICODE) 00372 p = subprocess.Popen(u'identify "%s"' % (screenshot), shell=True, bufsize=4096, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 00373 00374 response = p.stdout.readline() 00375 if response: 00376 match = width_height.match(response) 00377 if match: 00378 dummy, width, height = match.groups() 00379 width, height = int(width), int(height) 00380 if width >= 320: 00381 return screenshot 00382 return u'' 00383 else: 00384 return u'' 00385 return screenshot 00386 # end useImageMagick() 00387 00388 item_icon_filename = None 00389 channel_icon = None 00390 if item.getFeed(): 00391 channel_icon = fileutil.expand_filename(item.getFeed().iconCache.get_filename()) 00392 00393 if item.iconCache and item.iconCache.filename: 00394 item_icon_filename = item.iconCache.filename 00395 00396 # Conform to maximum length for MythTV database fields title and subtitle 00397 maximum_length = 128 00398 channel_title = item.get_channel_title(True).replace(u'/',u'-') 00399 if channel_title: 00400 if len(channel_title) > maximum_length: 00401 channel_title = channel_title[:maximum_length] 00402 channel_title = channel_title.replace(u'"', u'') # These characters mess with filenames 00403 title = item.get_title().replace(u'/',u'-') 00404 if title: 00405 if len(title) > maximum_length: 00406 title = title[:maximum_length] 00407 title = title.replace(u'"', u'') # These characters mess with filenames 00408 title = title.replace(u"'", u'') # These characters mess with filenames 00409 00410 item_dict = {u'feed_id': item.feed_id, u'parent_id': item.parent_id, u'isContainerItem': item.isContainerItem, u'isVideo': item.isVideo, u'seen': item.seen, u'autoDownloaded': item.autoDownloaded, u'pendingManualDL': item.pendingManualDL, u'downloadedTime': item.downloadedTime, u'watchedTime': item.watchedTime, u'pendingReason': item.pendingReason, u'title': title, u'expired': item.expired, u'keep': item.keep, u'videoFilename': item.get_filename(), u'eligibleForAutoDownload': item.eligibleForAutoDownload, u'duration': item.duration, u'screenshot': item.screenshot, u'resized_screenshots': item.resized_screenshots, u'resumeTime': item.resumeTime, u'channelTitle': channel_title, u'description': item.get_description(), u'size': item._get_size(), u'releasedate': item.get_release_date_obj(), u'length': item.get_duration_value(), u'channel_icon': channel_icon, u'item_icon': item_icon_filename, u'inetref': u'', u'season': 1, u'episode': 1,} 00411 00412 if not item_dict[u'screenshot']: 00413 if item_dict[u'item_icon']: 00414 item_dict[u'screenshot'] = useImageMagick(item_dict[u'item_icon']) 00415 00416 for key in [u'screenshot', u'channel_icon', u'item_icon']: 00417 if item_dict[key]: 00418 item_dict[key] = compatibleGraphics(item_dict[key]) 00419 #if self.verbose: 00420 # if item_dict[key]: 00421 # print "item (%s - %s) %s (%s)" % (channel_title, title, key, item_dict[key]) 00422 # else: 00423 # print "item (%s - %s) does NOT have a %s" % (channel_title, title, key) 00424 00425 #print item_dict 00426 return item_dict 00427 00428 00429 # Two routines used for Channel title search and matching 00430 def is_punct_char(self, char): 00431 '''check if char is punctuation char 00432 return True if char is punctuation 00433 return False if char is not punctuation 00434 ''' 00435 return char in string.punctuation 00436 00437 def is_not_punct_char(self, char): 00438 '''check if char is not punctuation char 00439 return True if char is not punctuation 00440 return False if chaar is punctuation 00441 ''' 00442 return not self.is_punct_char(char) 00443 00444 ################################################################################### 00445 # 00446 # End of mythbridge specific routines 00447 # 00448 ################################################################################### 00449 00450 @run_in_event_loop 00451 def do_feeds(self, line): 00452 """feeds -- Lists all feeds.""" 00453 current_folder = None 00454 for tab in self.channelTabs.getView(): 00455 if isinstance(tab.obj, folder.ChannelFolder): 00456 current_folder = tab.obj 00457 elif tab.obj.getFolder() is not current_folder: 00458 current_folder = None 00459 if current_folder is None: 00460 print tab.obj.get_title() 00461 elif current_folder is tab.obj: 00462 print "[Folder] %s" % tab.obj.get_title() 00463 else: 00464 print " - %s" % tab.obj.get_title() 00465 00466 @run_in_event_loop 00467 def do_play(self, line): 00468 """play <name> -- Plays an item by name in an external player.""" 00469 if self.selection_type is None: 00470 print "Error: No feed/playlist selected" 00471 return 00472 item = self._find_item(line) 00473 if item is None: 00474 print "No item named %r" % line 00475 return 00476 if item.is_downloaded(): 00477 resources.open_file(item.get_video_filename()) 00478 else: 00479 print '%s is not downloaded' % item.get_title() 00480 00481 @run_in_event_loop 00482 def do_playlists(self, line): 00483 """playlists -- Lists all playlists.""" 00484 for tab in self.playlistTabs.getView(): 00485 print tab.obj.get_title() 00486 00487 @run_in_event_loop 00488 def do_playlist(self, line): 00489 """playlist <name> -- Selects a playlist.""" 00490 for tab in self.playlistTabs.getView(): 00491 if tab.obj.get_title() == line: 00492 self.tab = tab 00493 self.tab_changed() 00494 return 00495 print "Error: %s not found" % line 00496 00497 @run_in_event_loop 00498 def do_items(self, line): 00499 """items -- Lists the items in the feed/playlist/tab selected.""" 00500 if self.selection_type is None: 00501 print "Error: No tab/feed/playlist selected" 00502 return 00503 elif self.selection_type == 'feed': 00504 feed = self.tab.obj 00505 view = feed.items.sort(feed.itemSort.sort) 00506 self.printout_item_list(view) 00507 view.unlink() 00508 elif self.selection_type == 'playlist': 00509 playlist = self.tab.obj 00510 self.printout_item_list(playlist.getView()) 00511 elif self.selection_type == 'downloads': 00512 self.printout_item_list(views.downloadingItems, views.pausedItems) 00513 elif self.selection_type == 'channel-folder': 00514 folder = self.tab.obj 00515 allItems = views.items.filterWithIndex( 00516 indexes.itemsByChannelFolder, folder) 00517 allItemsSorted = allItems.sort(folder.itemSort.sort) 00518 self.printout_item_list(allItemsSorted) 00519 allItemsSorted.unlink() 00520 else: 00521 raise ValueError("Unknown tab type") 00522 00523 @run_in_event_loop 00524 def do_downloads(self, line): 00525 """downloads -- Selects the downloads tab.""" 00526 self.tab = FakeTab("statictab", "downloadtab") 00527 self.tab_changed() 00528 00529 def printout_item_list(self, *views): 00530 totalItems = 0 00531 for view in views: 00532 totalItems += len(view) 00533 if totalItems > 0: 00534 print "%-20s %-10s %s" % ("State", "Size", "Name") 00535 print "-" * 70 00536 for view in views: 00537 for item in view: 00538 state = item.get_state() 00539 if state == 'downloading': 00540 state += ' (%0.0f%%)' % item.download_progress() 00541 print "%-20s %-10s %s" % (state, item.get_size_for_display(), 00542 item.get_title()) 00543 print 00544 else: 00545 print "No items" 00546 00547 def _get_item_view(self): 00548 if self.selection_type == 'feed': 00549 feed = self.tab.obj 00550 return feed.items 00551 elif self.selection_type == 'playlist': 00552 playlist = self.tab.obj 00553 return playlist.getView() 00554 elif self.selection_type == 'downloads': 00555 return views.downloadingItems 00556 elif self.selection_type == 'channel-folder': 00557 folder = self.tab.obj 00558 return views.items.filterWithIndex(indexes.itemsByChannelFolder, 00559 folder) 00560 else: 00561 raise ValueError("Unknown selection type") 00562 00563 00564 def _find_item(self, line): 00565 line = line.lower() 00566 for item in self._get_item_view(): 00567 if item.get_title().lower() == line: 00568 return item 00569 00570 @run_in_event_loop 00571 def do_stop(self, line): 00572 """stop <name> -- Stops download by name.""" 00573 if self.selection_type is None: 00574 print "Error: No feed/playlist selected" 00575 return 00576 item = self._find_item(line) 00577 if item is None: 00578 print "No item named %r" % line 00579 return 00580 if item.get_state() in ('downloading', 'paused'): 00581 item.expire() 00582 else: 00583 print '%s is not being downloaded' % item.get_title() 00584 00585 @run_in_event_loop 00586 def complete_stop(self, text, line, begidx, endidx): 00587 return self.handle_item_complete(text, self._get_item_view(), 00588 lambda i: i.get_state() in ('downloading', 'paused')) 00589 00590 @run_in_event_loop 00591 def do_download(self, line): 00592 """download <name> -- Downloads an item by name in the feed/playlist selected.""" 00593 if self.selection_type is None: 00594 print "Error: No feed/playlist selected" 00595 return 00596 item = self._find_item(line) 00597 if item is None: 00598 print "No item named %r" % line 00599 return 00600 if item.get_state() == 'downloading': 00601 print '%s is currently being downloaded' % item.get_title() 00602 elif item.is_downloaded(): 00603 print '%s is already downloaded' % item.get_title() 00604 else: 00605 item.download() 00606 00607 @run_in_event_loop 00608 def complete_download(self, text, line, begidx, endidx): 00609 return self.handle_item_complete(text, self._get_item_view(), 00610 lambda i: i.is_downloadable()) 00611 00612 @run_in_event_loop 00613 def do_pause(self, line): 00614 """pause <name> -- Pauses a download by name.""" 00615 if self.selection_type is None: 00616 print "Error: No feed/playlist selected" 00617 return 00618 item = self._find_item(line) 00619 if item is None: 00620 print "No item named %r" % line 00621 return 00622 if item.get_state() == 'downloading': 00623 item.pause() 00624 else: 00625 print '%s is not being downloaded' % item.get_title() 00626 00627 @run_in_event_loop 00628 def complete_pause(self, text, line, begidx, endidx): 00629 return self.handle_item_complete(text, self._get_item_view(), 00630 lambda i: i.get_state() == 'downloading') 00631 00632 @run_in_event_loop 00633 def do_resume(self, line): 00634 """resume <name> -- Resumes a download by name.""" 00635 if self.selection_type is None: 00636 print "Error: No feed/playlist selected" 00637 return 00638 item = self._find_item(line) 00639 if item is None: 00640 print "No item named %r" % line 00641 return 00642 if item.get_state() == 'paused': 00643 item.resume() 00644 else: 00645 print '%s is not a paused download' % item.get_title() 00646 00647 @run_in_event_loop 00648 def complete_resume(self, text, line, begidx, endidx): 00649 return self.handle_item_complete(text, self._get_item_view(), 00650 lambda i: i.get_state() == 'paused') 00651 00652 @run_in_event_loop 00653 def do_rm(self, line): 00654 """rm <name> -- Removes an item by name in the feed/playlist selected.""" 00655 if self.selection_type is None: 00656 print "Error: No feed/playlist selected" 00657 return 00658 item = self._find_item(line) 00659 if item is None: 00660 print "No item named %r" % line 00661 return 00662 if item.is_downloaded(): 00663 item.expire() 00664 else: 00665 print '%s is not downloaded' % item.get_title() 00666 00667 @run_in_event_loop 00668 def complete_rm(self, text, line, begidx, endidx): 00669 return self.handle_item_complete(text, self._get_item_view(), 00670 lambda i: i.is_downloaded()) 00671 00672 @run_in_event_loop 00673 def do_testdialog(self, line): 00674 """testdialog -- Tests the cli dialog system.""" 00675 d = dialogs.ChoiceDialog("Hello", "I am a test dialog", 00676 dialogs.BUTTON_OK, dialogs.BUTTON_CANCEL) 00677 def callback(dialog): 00678 print "TEST CHOICE: %s" % dialog.choice 00679 d.run(callback) 00680 00681 @run_in_event_loop 00682 def do_dumpdatabase(self, line): 00683 """dumpdatabase -- Dumps the database.""" 00684 from miro import database 00685 print "Dumping database...." 00686 database.defaultDatabase.liveStorage.dumpDatabase(database.defaultDatabase) 00687 print "Done." 00688
1.7.6.1