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