00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
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 item
00042 from miro import folder
00043 from miro import util
00044 from miro import tabs
00045 from miro.frontends.cli import clidialog
00046 from miro.plat import resources
00047
00048
00049 import os, sys, subprocess, re, fnmatch, string
00050 import logging
00051 from miro import moviedata
00052 from miro import commandline
00053 from miro import autodler
00054 from miro import downloader
00055 from miro import iconcache
00056 from miro.clock import clock
00057 from miro import feed
00058 from miro.commandline import parse_command_line_args
00059 from miro import fileutil
00060 from miro import autoupdate
00061 from miro import startup
00062 from miro import filetypes
00063 from miro import messages
00064
00065 def run_in_event_loop(func):
00066 def decorated(*args, **kwargs):
00067 return_hack = []
00068 event = threading.Event()
00069 def runThenSet():
00070 try:
00071 return_hack.append(func(*args, **kwargs))
00072 finally:
00073 event.set()
00074 eventloop.addUrgentCall(runThenSet, 'run in event loop')
00075 event.wait()
00076 if return_hack:
00077 return return_hack[0]
00078 decorated.__doc__ = func.__doc__
00079 return decorated
00080
00081 class FakeTab:
00082 def __init__(self, tab_type, tab_template_base):
00083 self.type = tab_type
00084 self.tab_template_base = tab_template_base
00085
00086 class MiroInterpreter(cmd.Cmd):
00087 def __init__(self):
00088 cmd.Cmd.__init__(self)
00089 self.quit_flag = False
00090 self.tab = None
00091 self.init_database_objects()
00092
00093 @run_in_event_loop
00094 def init_database_objects(self):
00095 self.video_feed_tabs = tabs.TabOrder.video_feed_order()
00096 self.audio_feed_tabs = tabs.TabOrder.audio_feed_order()
00097 self.playlist_tabs = tabs.TabOrder.playlist_order()
00098 self.tab_changed()
00099
00100 def tab_changed(self):
00101 """Calculate the current prompt. This method access database objects,
00102 so it should only be called from the backend event loop
00103 """
00104 if self.tab is None:
00105 self.prompt = "> "
00106 self.selection_type = None
00107
00108 elif self.tab.type == 'feed':
00109 if isinstance(self.tab, folder.ChannelFolder):
00110 self.prompt = "channel folder: %s > " % self.tab.get_title()
00111 self.selection_type = 'channel-folder'
00112 else:
00113 self.prompt = "channel: %s > " % self.tab.get_title()
00114 self.selection_type = 'feed'
00115
00116 elif self.tab.type == 'playlist':
00117 self.prompt = "playlist: %s > " % self.tab.get_title()
00118 self.selection_type = 'playlist'
00119
00120 elif (self.tab.type == 'statictab' and
00121 self.tab.tab_template_base == 'downloadtab'):
00122 self.prompt = "downloads > "
00123 self.selection_type = 'downloads'
00124 else:
00125 raise ValueError("Unknown tab type")
00126
00127 def postcmd(self, stop, line):
00128
00129
00130
00131 time.sleep(0.1)
00132 while True:
00133 try:
00134 dialog = app.cli_events.dialog_queue.get_nowait()
00135 except Queue.Empty:
00136 break
00137 clidialog.handle_dialog(dialog)
00138
00139 return self.quit_flag
00140
00141 def do_help(self, line):
00142 """help -- Lists commands and help."""
00143 commands = [m for m in dir(self) if m.startswith("do_")]
00144 for mem in commands:
00145 docstring = getattr(self, mem).__doc__
00146 print " ", docstring
00147
00148 def do_quit(self, line):
00149 """quit -- Quits Miro cli."""
00150 self.quit_flag = True
00151
00152 @run_in_event_loop
00153 def do_feed(self, line):
00154 """feed <name> -- Selects a feed by name."""
00155 for tab in self.video_feed_tabs.get_all_tabs():
00156 if tab.get_title() == line:
00157 self.tab = tab
00158 self.tab.type = "feed"
00159 self.tab_changed()
00160 return
00161 for tab in self.audio_feed_tabs.get_all_tabs():
00162 if tab.get_title() == line:
00163 self.tab = tab
00164 self.tab.type = "feed"
00165 self.tab_changed()
00166 return
00167 print "Error: %s not found" % line
00168
00169 @run_in_event_loop
00170 def do_rmfeed(self, line):
00171 """rmfeed <name> -- Deletes a feed."""
00172 for tab in self.video_feed_tabs.get_all_tabs():
00173 if tab.get_title() == line:
00174 tab.remove()
00175 return
00176 for tab in self.audio_feed_tabs.get_all_tabs():
00177 if tab.get_title() == line:
00178 tab.remove()
00179 return
00180 print "Error: %s not found" % line
00181
00182 @run_in_event_loop
00183 def complete_feed(self, text, line, begidx, endidx):
00184 return self.handle_tab_complete(text, list(self.video_feed_tabs.get_all_tabs()) + list(self.audio_feed_tabs.get_all_tabs()))
00185
00186 @run_in_event_loop
00187 def complete_rmfeed(self, text, line, begidx, endidx):
00188 return self.handle_tab_complete(text, list(self.video_feed_tabs.get_all_tabs()) + list(self.audio_feed_tabs.get_all_tabs()))
00189
00190 @run_in_event_loop
00191 def complete_playlist(self, text, line, begidx, endidx):
00192 return self.handle_tab_complete(text, self.playlist_tabs.get_all_tabs())
00193
00194 def handle_tab_complete(self, text, view_items):
00195 text = text.lower()
00196 matches = []
00197 for tab in view_items:
00198 if tab.get_title().lower().startswith(text):
00199 matches.append(tab.get_title())
00200 return matches
00201
00202 def handle_item_complete(self, text, view, filterFunc=lambda i: True):
00203 text = text.lower()
00204 matches = []
00205 for item in view:
00206 if (item.get_title().lower().startswith(text) and
00207 filterFunc(item)):
00208 matches.append(item.get_title())
00209 return matches
00210
00211
00212
00213
00214
00215
00216
00217 @run_in_event_loop
00218 def do_mythtv_update_autodownload(self, line):
00219 """Update feeds and auto-download"""
00220 logging.info("Starting auto downloader...")
00221 autodler.start_downloader()
00222 feed.expire_items()
00223 logging.info("Starting video data updates")
00224
00225 moviedata.movie_data_updater.start_thread()
00226 commandline.startup()
00227
00228
00229
00230
00231 eventloop.addTimeout(5, downloader.startup_downloader,
00232 "start downloader daemon")
00233
00234 eventloop.addTimeout(30, feed.start_updates, "start feed updates")
00235
00236
00237 eventloop.addTimeout(10, startup.clear_icon_cache_orphans, "clear orphans")
00238
00239
00240 def movie_data_program_info(self, movie_path, thumbnail_path):
00241 extractor_path = os.path.join(os.path.split(__file__)[0], "gst_extractor.py")
00242 return ((sys.executable, extractor_path, movie_path, thumbnail_path), None)
00243
00244 @run_in_event_loop
00245 def do_mythtv_check_downloading(self, line):
00246 """Check if any items are being downloaded. Set True or False"""
00247 self.downloading = False
00248
00249 downloadingItems = item.Item.only_downloading_view()
00250 count = downloadingItems.count()
00251 for it in downloadingItems:
00252 logging.info(u"(%s - %s) video is downloading with (%0.0f%%) complete" % (it.get_channel_title(True).replace(u'/',u'-'), it.get_title().replace(u'/',u'-'), it.download_progress()))
00253 if not count:
00254 logging.info(u"No items downloading")
00255 if count:
00256 self.downloading = True
00257
00258 @run_in_event_loop
00259 def do_mythtv_import_opml(self, filename):
00260 """Import an OPML file"""
00261 try:
00262 messages.ImportFeeds(filename).send_to_backend()
00263 logging.info(u"Import of OPML file (%s) sent to Miro" % (filename))
00264 except Exception, e:
00265 logging.info(u"Import of OPML file (%s) failed, error (%s)" % (filename, e))
00266
00267 @run_in_event_loop
00268 def do_mythtv_updatewatched(self, line):
00269 """Process MythTV update watched videos"""
00270
00271 items = item.Item.downloaded_view()
00272 for video in self.videofiles:
00273 for it in items:
00274 if it.get_filename() == video:
00275 break
00276 else:
00277 logging.info(u"Item for Miro video (%s) not found, skipping" % video)
00278 continue
00279 if self.simulation:
00280 logging.info(u"Simulation: Item (%s - %s) marked as seen and watched" % (it.get_channel_title(True), it.get_title()))
00281 else:
00282 it.mark_item_seen(markOtherItems=True)
00283 self.statistics[u'Miro_marked_watch_seen']+=1
00284 logging.info(u"Item (%s - %s) marked as seen and watched" % (it.get_channel_title(True), it.get_title()))
00285
00286 @run_in_event_loop
00287 def do_mythtv_getunwatched(self, line):
00288 """Process MythTV get all un-watched video details"""
00289 if self.verbose:
00290 print
00291 print u"Getting details on un-watched Miro videos"
00292 self.videofiles = []
00293 if item.Item.newly_downloaded_view().count():
00294 if self.verbose:
00295 print u"%-20s %-10s %s" % (u"State", u"Size", u"Name")
00296 print u"-" * 70
00297 for it in item.Item.newly_downloaded_view():
00298
00299
00300
00301 state = it.get_state()
00302 if not state == u'newly-downloaded':
00303 continue
00304
00305 if hasattr(it.get_parent(), u'url'):
00306 if filetypes.is_torrent_filename(it.get_parent().url):
00307 continue
00308 self.printItems(it)
00309 self.videofiles.append(self._get_item_dict(it))
00310 if self.verbose:
00311 print
00312 if not len(self.videofiles):
00313 logging.info(u"No un-watched Miro videos")
00314
00315 @run_in_event_loop
00316 def do_mythtv_getwatched(self, line):
00317 """Process MythTV get all watched/saved video details"""
00318 if self.verbose:
00319 print
00320 print u"Getting details on watched/saved Miro videos"
00321 self.videofiles = []
00322 if item.Item.downloaded_view().count():
00323 if self.verbose:
00324 print u"%-20s %-10s %s" % (u"State", u"Size", u"Name")
00325 print u"-" * 70
00326 for it in item.Item.downloaded_view():
00327 state = it.get_state()
00328 if state == u'newly-downloaded':
00329 continue
00330
00331 if hasattr(it.get_parent(), u'url'):
00332 if filetypes.is_torrent_filename(it.get_parent().url):
00333 continue
00334 self.printItems(it)
00335 self.videofiles.append(self._get_item_dict(it))
00336 if self.verbose:
00337 print
00338 if not len(self.videofiles):
00339 logging.info(u"No watched/saved Miro videos")
00340
00341 def printItems(self, it):
00342 if not self.verbose:
00343 return
00344 state = it.get_state()
00345 if state == u'downloading':
00346 state += u' (%0.0f%%)' % it.download_progress()
00347 print u"%-20s %-10s %s" % (state, it.get_size_for_display(),
00348 it.get_title())
00349
00350
00351 @run_in_event_loop
00352 def do_mythtv_item_remove(self, args):
00353 """Removes an item from Miro by file name or Channel and title"""
00354 for it in item.Item.downloaded_view():
00355 if isinstance(args, list):
00356 if not args[0] or not args[1]:
00357 continue
00358 if filter(self.is_not_punct_char, it.get_channel_title(True).lower()) == filter(self.is_not_punct_char, args[0].lower()) and (filter(self.is_not_punct_char, it.get_title().lower())).startswith(filter(self.is_not_punct_char, args[1].lower())):
00359 break
00360 elif args:
00361 if filter(self.is_not_punct_char, it.get_filename().lower()) == filter(self.is_not_punct_char, args.lower()):
00362 break
00363 else:
00364 logging.info(u"No item named %s" % args)
00365 return
00366 if it.is_downloaded():
00367 if self.simulation:
00368 logging.info(u"Simulation: Item (%s - %s) has been removed from Miro" % (it.get_channel_title(True), it.get_title()))
00369 else:
00370 it.expire()
00371 self.statistics[u'Miro_videos_deleted']+=1
00372 logging.info(u'%s has been removed from Miro' % it.get_title())
00373 else:
00374 logging.info(u'%s is not downloaded' % it.get_title())
00375
00376 def _get_item_dict(self, it):
00377 """Take an item and convert all elements into a dictionary
00378 return a dictionary of item elements
00379 """
00380 def compatibleGraphics(filename):
00381 if filename:
00382 (dirName, fileName) = os.path.split(filename)
00383 (fileBaseName, fileExtension)=os.path.splitext(fileName)
00384 if not fileExtension[1:] in [u"png", u"jpg", u"bmp", u"gif"]:
00385 return u''
00386 else:
00387 return filename
00388 else:
00389 return u''
00390
00391 def useImageMagick(screenshot):
00392 """ Using ImageMagick's utility 'identify'. Decide whether the screen shot is worth using.
00393 >>> useImageMagick('identify screenshot.jpg')
00394 >>> Example returned information "rose.jpg JPEG 640x480 DirectClass 87kb 0.050u 0:01"
00395 >>> u'' if the screenshot quality is too low
00396 >>> screenshot if the quality is good enough to use
00397 """
00398 if not self.imagemagick:
00399 return u''
00400
00401 width_height = re.compile(u'''^(.+?)[ ]\[?([0-9]+)x([0-9]+)[^\\/]*$''', re.UNICODE)
00402 p = subprocess.Popen(u'identify "%s"' % (screenshot), shell=True, bufsize=4096, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
00403
00404 response = p.stdout.readline()
00405 if response:
00406 match = width_height.match(response)
00407 if match:
00408 dummy, width, height = match.groups()
00409 width, height = int(width), int(height)
00410 if width >= 320:
00411 return screenshot
00412 return u''
00413 else:
00414 return u''
00415 return screenshot
00416
00417
00418 item_icon_filename = it.icon_cache.filename
00419 channel_icon = it.get_feed().icon_cache.get_filename()
00420
00421
00422 maximum_length = 128
00423 channel_title = it.get_channel_title(True).replace(u'/',u'-')
00424 if channel_title:
00425 if len(channel_title) > maximum_length:
00426 channel_title = channel_title[:maximum_length]
00427 channel_title = channel_title.replace(u'"', u'')
00428 title = it.get_title().replace(u'/',u'-')
00429 if title:
00430 if len(title) > maximum_length:
00431 title = title[:maximum_length]
00432 title = title.replace(u'"', u'')
00433 title = title.replace(u"'", u'')
00434
00435 item_dict = {u'feed_id': it.feed_id, u'parent_id': it.parent_id, u'isContainerItem': it.isContainerItem, u'seen': it.seen, u'autoDownloaded': it.autoDownloaded, u'pendingManualDL': it.pendingManualDL, u'downloadedTime': it.downloadedTime, u'watchedTime': it.watchedTime, u'pendingReason': it.pendingReason, u'title': title, u'expired': it.expired, u'keep': it.keep, u'videoFilename': it.get_filename(), u'eligibleForAutoDownload': it.eligibleForAutoDownload, u'duration': it.duration, u'screenshot': it.screenshot, u'resumeTime': it.resumeTime, u'channelTitle': channel_title, u'description': it.get_description(), u'size': it._get_size(), u'releasedate': it.get_release_date_obj(), u'length': it.get_duration_value(), u'channel_icon': channel_icon, u'item_icon': item_icon_filename, u'inetref': u'', u'season': 1, u'episode': 1,}
00436
00437 if not item_dict[u'screenshot']:
00438 if item_dict[u'item_icon']:
00439 item_dict[u'screenshot'] = useImageMagick(item_dict[u'item_icon'])
00440
00441 for key in [u'screenshot', u'channel_icon', u'item_icon']:
00442 if item_dict[key]:
00443 item_dict[key] = compatibleGraphics(item_dict[key])
00444
00445
00446
00447
00448
00449
00450
00451 return item_dict
00452
00453
00454 def is_punct_char(self, char):
00455 '''check if char is punctuation char
00456 return True if char is punctuation
00457 return False if char is not punctuation
00458 '''
00459 return char in string.punctuation
00460
00461 def is_not_punct_char(self, char):
00462 '''check if char is not punctuation char
00463 return True if char is not punctuation
00464 return False if chaar is punctuation
00465 '''
00466 return not self.is_punct_char(char)
00467
00468
00469
00470
00471
00472
00473
00474
00475 def _print_feeds(self, feeds):
00476 current_folder = None
00477 for tab in feeds:
00478 if isinstance(tab, folder.ChannelFolder):
00479 current_folder = tab
00480 elif tab.get_folder() is not current_folder:
00481 current_folder = None
00482 if current_folder is None:
00483 print " * " + tab.get_title()
00484 elif current_folder is tab:
00485 print " * [Folder] %s" % tab.get_title()
00486 else:
00487 print " * - %s" % tab.get_title()
00488
00489 @run_in_event_loop
00490 def do_feeds(self, line):
00491 """feeds -- Lists all feeds."""
00492 print "VIDEO FEEDS"
00493 self._print_feeds(self.video_feed_tabs.get_all_tabs())
00494 print "AUDIO FEEDS"
00495 self._print_feeds(self.audio_feed_tabs.get_all_tabs())
00496
00497 @run_in_event_loop
00498 def do_play(self, line):
00499 """play <name> -- Plays an item by name in an external player."""
00500 if self.selection_type is None:
00501 print "Error: No feed/playlist selected"
00502 return
00503 item = self._find_item(line)
00504 if item is None:
00505 print "No item named %r" % line
00506 return
00507 if item.is_downloaded():
00508 resources.open_file(item.get_video_filename())
00509 else:
00510 print '%s is not downloaded' % item.get_title()
00511
00512 @run_in_event_loop
00513 def do_playlists(self, line):
00514 """playlists -- Lists all playlists."""
00515 for tab in self.playlistTabs.getView():
00516 print tab.obj.get_title()
00517
00518 @run_in_event_loop
00519 def do_playlist(self, line):
00520 """playlist <name> -- Selects a playlist."""
00521 for tab in self.playlistTabs.getView():
00522 if tab.obj.get_title() == line:
00523 self.tab = tab
00524 self.tab_changed()
00525 return
00526 print "Error: %s not found" % line
00527
00528 @run_in_event_loop
00529 def do_items(self, line):
00530 """items -- Lists the items in the feed/playlist/tab selected."""
00531 if self.selection_type is None:
00532 print "Error: No tab/feed/playlist selected"
00533 return
00534 elif self.selection_type == 'feed':
00535 feed = self.tab
00536 view = feed.items
00537 self.printout_item_list(view)
00538 elif self.selection_type == 'playlist':
00539 playlist = self.tab.obj
00540 self.printout_item_list(playlist.getView())
00541 elif self.selection_type == 'downloads':
00542 self.printout_item_list(item.Item.downloading_view(),
00543 item.Item.paused_view())
00544 elif self.selection_type == 'channel-folder':
00545 folder = self.tab.obj
00546 allItems = views.items.filterWithIndex(
00547 indexes.itemsByChannelFolder, folder)
00548 allItemsSorted = allItems.sort(folder.itemSort.sort)
00549 self.printout_item_list(allItemsSorted)
00550 allItemsSorted.unlink()
00551 else:
00552 raise ValueError("Unknown tab type")
00553
00554 @run_in_event_loop
00555 def do_downloads(self, line):
00556 """downloads -- Selects the downloads tab."""
00557 self.tab = FakeTab("statictab", "downloadtab")
00558 self.tab_changed()
00559
00560 def printout_item_list(self, *views):
00561 totalItems = 0
00562 for view in views:
00563 totalItems += view.count()
00564 if totalItems > 0:
00565 print "%-20s %-10s %s" % ("State", "Size", "Name")
00566 print "-" * 70
00567 for view in views:
00568 for item in view:
00569 state = item.get_state()
00570 if state == 'downloading':
00571 state += ' (%0.0f%%)' % item.download_progress()
00572 print "%-20s %-10s %s" % (state, item.get_size_for_display(),
00573 item.get_title())
00574 print
00575 else:
00576 print "No items"
00577
00578 def _get_item_view(self):
00579 if self.selection_type == 'feed':
00580 return item.Item.visible_feed_view(self.tab.id)
00581 elif self.selection_type == 'playlist':
00582 return item.Item.playlist_view(self.tab.id)
00583 elif self.selection_type == 'downloads':
00584 return item.Item.downloading_view()
00585 elif self.selection_type == 'channel-folder':
00586 folder = self.tab
00587 return item.Item.visible_folder_view(folder.id)
00588 else:
00589 raise ValueError("Unknown selection type")
00590
00591 def _find_item(self, line):
00592 line = line.lower()
00593 for item in self._get_item_view():
00594 if item.get_title().lower() == line:
00595 return item
00596
00597 @run_in_event_loop
00598 def do_stop(self, line):
00599 """stop <name> -- Stops download by name."""
00600 if self.selection_type is None:
00601 print "Error: No feed/playlist selected"
00602 return
00603 item = self._find_item(line)
00604 if item is None:
00605 print "No item named %r" % line
00606 return
00607 if item.get_state() in ('downloading', 'paused'):
00608 item.expire()
00609 else:
00610 print '%s is not being downloaded' % item.get_title()
00611
00612 @run_in_event_loop
00613 def complete_stop(self, text, line, begidx, endidx):
00614 return self.handle_item_complete(text, self._get_item_view(),
00615 lambda i: i.get_state() in ('downloading', 'paused'))
00616
00617 @run_in_event_loop
00618 def do_download(self, line):
00619 """download <name> -- Downloads an item by name in the feed/playlist selected."""
00620 if self.selection_type is None:
00621 print "Error: No feed/playlist selected"
00622 return
00623 item = self._find_item(line)
00624 if item is None:
00625 print "No item named %r" % line
00626 return
00627 if item.get_state() == 'downloading':
00628 print '%s is currently being downloaded' % item.get_title()
00629 elif item.is_downloaded():
00630 print '%s is already downloaded' % item.get_title()
00631 else:
00632 item.download()
00633
00634 @run_in_event_loop
00635 def complete_download(self, text, line, begidx, endidx):
00636 return self.handle_item_complete(text, self._get_item_view(),
00637 lambda i: i.is_downloadable())
00638
00639 @run_in_event_loop
00640 def do_pause(self, line):
00641 """pause <name> -- Pauses a download by name."""
00642 if self.selection_type is None:
00643 print "Error: No feed/playlist selected"
00644 return
00645 item = self._find_item(line)
00646 if item is None:
00647 print "No item named %r" % line
00648 return
00649 if item.get_state() == 'downloading':
00650 item.pause()
00651 else:
00652 print '%s is not being downloaded' % item.get_title()
00653
00654 @run_in_event_loop
00655 def complete_pause(self, text, line, begidx, endidx):
00656 return self.handle_item_complete(text, self._get_item_view(),
00657 lambda i: i.get_state() == 'downloading')
00658
00659 @run_in_event_loop
00660 def do_resume(self, line):
00661 """resume <name> -- Resumes a download by name."""
00662 if self.selection_type is None:
00663 print "Error: No feed/playlist selected"
00664 return
00665 item = self._find_item(line)
00666 if item is None:
00667 print "No item named %r" % line
00668 return
00669 if item.get_state() == 'paused':
00670 item.resume()
00671 else:
00672 print '%s is not a paused download' % item.get_title()
00673
00674 @run_in_event_loop
00675 def complete_resume(self, text, line, begidx, endidx):
00676 return self.handle_item_complete(text, self._get_item_view(),
00677 lambda i: i.get_state() == 'paused')
00678
00679 @run_in_event_loop
00680 def do_rm(self, line):
00681 """rm <name> -- Removes an item by name in the feed/playlist selected."""
00682 if self.selection_type is None:
00683 print "Error: No feed/playlist selected"
00684 return
00685 item = self._find_item(line)
00686 if item is None:
00687 print "No item named %r" % line
00688 return
00689 if item.is_downloaded():
00690 item.expire()
00691 else:
00692 print '%s is not downloaded' % item.get_title()
00693
00694 @run_in_event_loop
00695 def complete_rm(self, text, line, begidx, endidx):
00696 return self.handle_item_complete(text, self._get_item_view(),
00697 lambda i: i.is_downloaded())
00698
00699 @run_in_event_loop
00700 def do_testdialog(self, line):
00701 """testdialog -- Tests the cli dialog system."""
00702 d = dialogs.ChoiceDialog("Hello", "I am a test dialog",
00703 dialogs.BUTTON_OK, dialogs.BUTTON_CANCEL)
00704 def callback(dialog):
00705 print "TEST CHOICE: %s" % dialog.choice
00706 d.run(callback)
00707
00708 @run_in_event_loop
00709 def do_dumpdatabase(self, line):
00710 """dumpdatabase -- Dumps the database."""
00711 from miro import database
00712 print "Dumping database...."
00713 database.defaultDatabase.liveStorage.dumpDatabase(database.defaultDatabase)
00714 print "Done."