class TorrentviewTestCase(unittest.TestCase): def setUp(self): # NOQA pass def tearDown(self): # NOQA pass def test_torrentview_columns(self): self.mainwindow = MainWindow() self.torrentview = TorrentView() self.torrentdetails = TorrentDetails() self.menubar = MenuBar() default_column_index = ['filter', 'torrent_id', 'dirty', '#', 'Name', 'Size', 'Downloaded', 'Uploaded', 'Progress', 'Seeders', 'Peers', 'Seeders/Peers', 'Down Speed', 'Up Speed', 'Down Limit', 'Up Limit', 'ETA', 'Ratio', 'Avail', 'Added', 'Tracker', 'Save Path'] default_liststore_columns = [bool, str, bool, int, str, str, gobject.TYPE_UINT64, gobject.TYPE_UINT64, gobject.TYPE_UINT64, float, str, int, int, int, int, float, float, float, float, float, int, float, float, float, str, str, str] self.assertEquals(self.torrentview.column_index, default_column_index) self.assertEquals(self.torrentview.liststore_columns, default_liststore_columns) self.assertEquals(self.torrentview.columns["Save Path"].column_indices, [26]) # Add a text column test_col = "Test column" self.torrentview.add_text_column(test_col, status_field=["label"]) self.assertEquals(len(self.torrentview.liststore_columns), 28) self.assertEquals(len(self.torrentview.column_index), 23) self.assertEquals(self.torrentview.column_index[-1], test_col) self.assertEquals(self.torrentview.columns[test_col].column_indices, [27]) # Add a second text column test_col2 = "Test column2" self.torrentview.add_text_column(test_col2, status_field=["label2"]) self.assertEquals(len(self.torrentview.liststore_columns), 29) self.assertEquals(len(self.torrentview.column_index), 24) self.assertEquals(self.torrentview.column_index[-1], test_col2) self.assertEquals(self.torrentview.columns[test_col2].column_indices, [28]) # Remove column self.torrentview.remove_column(test_col) self.assertEquals(len(self.torrentview.liststore_columns), 28) self.assertEquals(len(self.torrentview.column_index), 23) self.assertEquals(self.torrentview.column_index[-1], test_col2) self.assertEquals(self.torrentview.columns[test_col2].column_indices, [27]) # Add a column with multiple column types test_col3 = "Test column3" self.torrentview.add_progress_column(test_col3, status_field=["progress", "label3"], col_types=[float, str]) self.assertEquals(len(self.torrentview.liststore_columns), 30) self.assertEquals(len(self.torrentview.column_index), 24) self.assertEquals(self.torrentview.column_index[-1], test_col3) self.assertEquals(self.torrentview.columns[test_col3].column_indices, [28, 29]) # Remove multiple column-types column self.torrentview.remove_column(test_col3) self.assertEquals(len(self.torrentview.liststore_columns), 28) self.assertEquals(len(self.torrentview.column_index), 23) self.assertEquals(self.torrentview.column_index[-1], test_col2) self.assertEquals(self.torrentview.columns[test_col2].column_indices, [27])
def set_up(self): if libs_available is False: raise unittest.SkipTest('GTKUI dependencies not available') common.set_tmp_config_dir() # MainWindow loads this config file, so lets make sure it contains the defaults ConfigManager('gtkui.conf', defaults=DEFAULT_PREFS) self.mainwindow = MainWindow() self.torrentview = TorrentView() self.torrentdetails = TorrentDetails() self.menubar = MenuBar()
def test_write(self): """ writing to a file-like object; need this for webui. Not strictly a unit test, but tests if calls do not fail... """ from deluge.ui.gtkui.gtkui import DEFAULT_PREFS from deluge.ui.gtkui.preferences import Preferences from deluge.ui.gtkui.mainwindow import MainWindow from deluge.configmanager import ConfigManager from deluge.ui.gtkui.pluginmanager import PluginManager from deluge.ui.gtkui.torrentdetails import TorrentDetails from deluge.ui.gtkui.torrentview import TorrentView from deluge.plugins.Stats.deluge.plugins.stats import graph, gtkui ConfigManager('gtkui.conf', defaults=DEFAULT_PREFS) self.plugins = PluginManager() MainWindow() TorrentView() TorrentDetails() Preferences() class FakeFile(object): def __init__(self): self.data = [] def write(self, data): self.data.append(data) stats_gtkui = gtkui.GtkUI('test_stats') stats_gtkui.enable() yield stats_gtkui.graphs_tab.update() g = stats_gtkui.graphs_tab.graph g.add_stat('download_rate', color=graph.green) g.add_stat('upload_rate', color=graph.blue) g.set_left_axis(formatter=fspeed, min=10240) surface = g.draw(900, 150) file_like = FakeFile() surface.write_to_png(file_like) data = b''.join(file_like.data) with open('file_like.png', 'wb') as _file: _file.write(data)
class TorrentviewTestCase(BaseTestCase): default_column_index = ['filter', 'torrent_id', 'dirty', '#', 'Name', 'Size', 'Downloaded', 'Uploaded', 'Remaining', 'Progress', 'Seeds', 'Peers', 'Seeds:Peers', 'Down Speed', 'Up Speed', 'Down Limit', 'Up Limit', 'ETA', 'Ratio', 'Avail', 'Added', 'Completed', 'Complete Seen', 'Tracker', 'Download Folder', 'Owner', 'Shared'] default_liststore_columns = [bool, str, bool, int, str, str, # Name TYPE_UINT64, TYPE_UINT64, TYPE_UINT64, TYPE_UINT64, float, str, # Progress int, int, int, int, float, # Seeds, Peers int, int, float, float, int, float, float, # ETA, Ratio, Avail int, int, int, str, str, # Tracker str, str, bool] # shared def set_up(self): if libs_available is False: raise unittest.SkipTest('GTKUI dependencies not available') common.set_tmp_config_dir() # MainWindow loads this config file, so lets make sure it contains the defaults ConfigManager('gtkui.conf', defaults=DEFAULT_PREFS) self.mainwindow = MainWindow() self.torrentview = TorrentView() self.torrentdetails = TorrentDetails() self.menubar = MenuBar() def tear_down(self): return component.shutdown() def test_torrentview_columns(self): self.assertEqual(self.torrentview.column_index, TorrentviewTestCase.default_column_index) self.assertEqual(self.torrentview.liststore_columns, TorrentviewTestCase.default_liststore_columns) self.assertEqual(self.torrentview.columns['Download Folder'].column_indices, [29]) def test_add_column(self): # Add a text column test_col = 'Test column' self.torrentview.add_text_column(test_col, status_field=['label']) self.assertEqual(len(self.torrentview.liststore_columns), len(TorrentviewTestCase.default_liststore_columns) + 1) self.assertEqual(len(self.torrentview.column_index), len(TorrentviewTestCase.default_column_index) + 1) self.assertEqual(self.torrentview.column_index[-1], test_col) self.assertEqual(self.torrentview.columns[test_col].column_indices, [32]) def test_add_columns(self): # Add a text column test_col = 'Test column' self.torrentview.add_text_column(test_col, status_field=['label']) # Add a second text column test_col2 = 'Test column2' self.torrentview.add_text_column(test_col2, status_field=['label2']) self.assertEqual(len(self.torrentview.liststore_columns), len(TorrentviewTestCase.default_liststore_columns) + 2) self.assertEqual(len(self.torrentview.column_index), len(TorrentviewTestCase.default_column_index) + 2) # test_col self.assertEqual(self.torrentview.column_index[-2], test_col) self.assertEqual(self.torrentview.columns[test_col].column_indices, [32]) # test_col2 self.assertEqual(self.torrentview.column_index[-1], test_col2) self.assertEqual(self.torrentview.columns[test_col2].column_indices, [33]) def test_remove_column(self): # Add and remove text column test_col = 'Test column' self.torrentview.add_text_column(test_col, status_field=['label']) self.torrentview.remove_column(test_col) self.assertEqual(len(self.torrentview.liststore_columns), len(TorrentviewTestCase.default_liststore_columns)) self.assertEqual(len(self.torrentview.column_index), len(TorrentviewTestCase.default_column_index)) self.assertEqual(self.torrentview.column_index[-1], TorrentviewTestCase.default_column_index[-1]) self.assertEqual(self.torrentview.columns[TorrentviewTestCase.default_column_index[-1]].column_indices, [31]) def test_remove_columns(self): # Add two columns test_col = 'Test column' self.torrentview.add_text_column(test_col, status_field=['label']) test_col2 = 'Test column2' self.torrentview.add_text_column(test_col2, status_field=['label2']) # Remove test_col self.torrentview.remove_column(test_col) self.assertEqual(len(self.torrentview.liststore_columns), len(TorrentviewTestCase.default_liststore_columns) + 1) self.assertEqual(len(self.torrentview.column_index), len(TorrentviewTestCase.default_column_index) + 1) self.assertEqual(self.torrentview.column_index[-1], test_col2) self.assertEqual(self.torrentview.columns[test_col2].column_indices, [32]) # Remove test_col2 self.torrentview.remove_column(test_col2) self.assertEqual(len(self.torrentview.liststore_columns), len(TorrentviewTestCase.default_liststore_columns)) self.assertEqual(len(self.torrentview.column_index), len(TorrentviewTestCase.default_column_index)) self.assertEqual(self.torrentview.column_index[-1], TorrentviewTestCase.default_column_index[-1]) self.assertEqual(self.torrentview.columns[TorrentviewTestCase.default_column_index[-1]].column_indices, [31]) def test_add_remove_column_multiple_types(self): # Add a column with multiple column types test_col3 = 'Test column3' self.torrentview.add_progress_column(test_col3, status_field=['progress', 'label3'], col_types=[float, str]) self.assertEqual(len(self.torrentview.liststore_columns), len(TorrentviewTestCase.default_liststore_columns) + 2) self.assertEqual(len(self.torrentview.column_index), len(TorrentviewTestCase.default_column_index) + 1) self.assertEqual(self.torrentview.column_index[-1], test_col3) self.assertEqual(self.torrentview.columns[test_col3].column_indices, [32, 33]) # Remove multiple column-types column self.torrentview.remove_column(test_col3) self.assertEqual(len(self.torrentview.liststore_columns), len(TorrentviewTestCase.default_liststore_columns)) self.assertEqual(len(self.torrentview.column_index), len(TorrentviewTestCase.default_column_index)) self.assertEqual(self.torrentview.column_index[-1], TorrentviewTestCase.default_column_index[-1]) self.assertEqual(self.torrentview.columns[TorrentviewTestCase.default_column_index[-1]].column_indices, [31])
def test_torrentview_columns(self): self.mainwindow = MainWindow() self.torrentview = TorrentView() self.torrentdetails = TorrentDetails() self.menubar = MenuBar() default_column_index = [ 'filter', 'torrent_id', 'dirty', '#', 'Name', 'Size', 'Downloaded', 'Uploaded', 'Progress', 'Seeders', 'Peers', 'Seeders/Peers', 'Down Speed', 'Up Speed', 'Down Limit', 'Up Limit', 'ETA', 'Ratio', 'Avail', 'Added', 'Tracker', 'Save Path' ] default_liststore_columns = [ bool, str, bool, int, str, str, gobject.TYPE_UINT64, gobject.TYPE_UINT64, gobject.TYPE_UINT64, float, str, int, int, int, int, float, float, float, float, float, int, float, float, float, str, str, str ] self.assertEquals(self.torrentview.column_index, default_column_index) self.assertEquals(self.torrentview.liststore_columns, default_liststore_columns) self.assertEquals(self.torrentview.columns["Save Path"].column_indices, [26]) # Add a text column test_col = "Test column" self.torrentview.add_text_column(test_col, status_field=["label"]) self.assertEquals(len(self.torrentview.liststore_columns), 28) self.assertEquals(len(self.torrentview.column_index), 23) self.assertEquals(self.torrentview.column_index[-1], test_col) self.assertEquals(self.torrentview.columns[test_col].column_indices, [27]) # Add a second text column test_col2 = "Test column2" self.torrentview.add_text_column(test_col2, status_field=["label2"]) self.assertEquals(len(self.torrentview.liststore_columns), 29) self.assertEquals(len(self.torrentview.column_index), 24) self.assertEquals(self.torrentview.column_index[-1], test_col2) self.assertEquals(self.torrentview.columns[test_col2].column_indices, [28]) # Remove column self.torrentview.remove_column(test_col) self.assertEquals(len(self.torrentview.liststore_columns), 28) self.assertEquals(len(self.torrentview.column_index), 23) self.assertEquals(self.torrentview.column_index[-1], test_col2) self.assertEquals(self.torrentview.columns[test_col2].column_indices, [27]) # Add a column with multiple column types test_col3 = "Test column3" self.torrentview.add_progress_column( test_col3, status_field=["progress", "label3"], col_types=[float, str]) self.assertEquals(len(self.torrentview.liststore_columns), 30) self.assertEquals(len(self.torrentview.column_index), 24) self.assertEquals(self.torrentview.column_index[-1], test_col3) self.assertEquals(self.torrentview.columns[test_col3].column_indices, [28, 29]) # Remove multiple column-types column self.torrentview.remove_column(test_col3) self.assertEquals(len(self.torrentview.liststore_columns), 28) self.assertEquals(len(self.torrentview.column_index), 23) self.assertEquals(self.torrentview.column_index[-1], test_col2) self.assertEquals(self.torrentview.columns[test_col2].column_indices, [27])
def __init__(self, args): # Setup gtkbuilder/glade translation setup_translations(setup_gettext=False, setup_pygtk=True) # Setup signals def on_die(*args): log.debug('OS signal "die" caught with args: %s', args) reactor.stop() if windows_check(): from win32api import SetConsoleCtrlHandler SetConsoleCtrlHandler(on_die, True) log.debug('Win32 "die" handler registered') elif osx_check() and WINDOWING == 'quartz': import gtkosx_application self.osxapp = gtkosx_application.gtkosx_application_get() self.osxapp.connect('NSApplicationWillTerminate', on_die) log.debug('OSX quartz "die" handler registered') # Set process name again to fix gtk issue setproctitle(getproctitle()) # Attempt to register a magnet URI handler with gconf, but do not overwrite # if already set by another program. associate_magnet_links(False) # Make sure gtkui.conf has at least the defaults set self.config = ConfigManager('gtkui.conf', DEFAULT_PREFS) # Make sure the gtkui state folder has been created if not os.path.exists(os.path.join(get_config_dir(), 'gtkui_state')): os.makedirs(os.path.join(get_config_dir(), 'gtkui_state')) # Set language if self.config['language'] is not None: set_language(self.config['language']) # Start the IPC Interface before anything else.. Just in case we are # already running. self.queuedtorrents = QueuedTorrents() self.ipcinterface = IPCInterface(args.torrents) # Initialize gdk threading threads_init() # We make sure that the UI components start once we get a core URI client.set_disconnect_callback(self.__on_disconnect) self.trackericons = TrackerIcons() self.sessionproxy = SessionProxy() # Initialize various components of the gtkui self.mainwindow = MainWindow() self.menubar = MenuBar() self.toolbar = ToolBar() self.torrentview = TorrentView() self.torrentdetails = TorrentDetails() self.sidebar = SideBar() self.filtertreeview = FilterTreeView() self.preferences = Preferences() self.systemtray = SystemTray() self.statusbar = StatusBar() self.addtorrentdialog = AddTorrentDialog() if osx_check() and WINDOWING == 'quartz': def nsapp_open_file(osxapp, filename): # Ignore command name which is raised at app launch (python opening main script). if filename == sys.argv[0]: return True process_args([filename]) self.osxapp.connect('NSApplicationOpenFile', nsapp_open_file) from deluge.ui.gtkui.menubar_osx import menubar_osx menubar_osx(self, self.osxapp) self.osxapp.ready() # Initalize the plugins self.plugins = PluginManager() # Show the connection manager self.connectionmanager = ConnectionManager() # Setup RPC stats logging # daemon_bps: time, bytes_sent, bytes_recv self.daemon_bps = (0, 0, 0) self.rpc_stats = LoopingCall(self.log_rpc_stats) self.closing = False # Twisted catches signals to terminate, so have it call a pre_shutdown method. reactor.addSystemEventTrigger('before', 'gtkui_close', self.close) def gtkui_sigint_handler(num, frame): log.debug('SIGINT signal caught, firing event: gtkui_close') reactor.callLater(0, reactor.fireSystemEvent, 'gtkui_close') signal.signal(signal.SIGINT, gtkui_sigint_handler)
class GtkUI(object): def __init__(self, args): # Setup gtkbuilder/glade translation setup_translations(setup_gettext=False, setup_pygtk=True) # Setup signals def on_die(*args): log.debug('OS signal "die" caught with args: %s', args) reactor.stop() if windows_check(): from win32api import SetConsoleCtrlHandler SetConsoleCtrlHandler(on_die, True) log.debug('Win32 "die" handler registered') elif osx_check() and WINDOWING == 'quartz': import gtkosx_application self.osxapp = gtkosx_application.gtkosx_application_get() self.osxapp.connect('NSApplicationWillTerminate', on_die) log.debug('OSX quartz "die" handler registered') # Set process name again to fix gtk issue setproctitle(getproctitle()) # Attempt to register a magnet URI handler with gconf, but do not overwrite # if already set by another program. associate_magnet_links(False) # Make sure gtkui.conf has at least the defaults set self.config = ConfigManager('gtkui.conf', DEFAULT_PREFS) # Make sure the gtkui state folder has been created if not os.path.exists(os.path.join(get_config_dir(), 'gtkui_state')): os.makedirs(os.path.join(get_config_dir(), 'gtkui_state')) # Set language if self.config['language'] is not None: set_language(self.config['language']) # Start the IPC Interface before anything else.. Just in case we are # already running. self.queuedtorrents = QueuedTorrents() self.ipcinterface = IPCInterface(args.torrents) # Initialize gdk threading threads_init() # We make sure that the UI components start once we get a core URI client.set_disconnect_callback(self.__on_disconnect) self.trackericons = TrackerIcons() self.sessionproxy = SessionProxy() # Initialize various components of the gtkui self.mainwindow = MainWindow() self.menubar = MenuBar() self.toolbar = ToolBar() self.torrentview = TorrentView() self.torrentdetails = TorrentDetails() self.sidebar = SideBar() self.filtertreeview = FilterTreeView() self.preferences = Preferences() self.systemtray = SystemTray() self.statusbar = StatusBar() self.addtorrentdialog = AddTorrentDialog() if osx_check() and WINDOWING == 'quartz': def nsapp_open_file(osxapp, filename): # Ignore command name which is raised at app launch (python opening main script). if filename == sys.argv[0]: return True process_args([filename]) self.osxapp.connect('NSApplicationOpenFile', nsapp_open_file) from deluge.ui.gtkui.menubar_osx import menubar_osx menubar_osx(self, self.osxapp) self.osxapp.ready() # Initalize the plugins self.plugins = PluginManager() # Show the connection manager self.connectionmanager = ConnectionManager() # Setup RPC stats logging # daemon_bps: time, bytes_sent, bytes_recv self.daemon_bps = (0, 0, 0) self.rpc_stats = LoopingCall(self.log_rpc_stats) self.closing = False # Twisted catches signals to terminate, so have it call a pre_shutdown method. reactor.addSystemEventTrigger('before', 'gtkui_close', self.close) def gtkui_sigint_handler(num, frame): log.debug('SIGINT signal caught, firing event: gtkui_close') reactor.callLater(0, reactor.fireSystemEvent, 'gtkui_close') signal.signal(signal.SIGINT, gtkui_sigint_handler) def start(self): reactor.callWhenRunning(self._on_reactor_start) # Initialize gdk threading threads_enter() reactor.run() # Reactor no longer running so async callbacks (Deferreds) cannot be # processed after this point. threads_leave() def shutdown(self, *args, **kwargs): log.debug('GTKUI shutting down...') # Shutdown all components if client.is_standalone: return component.shutdown() @defer.inlineCallbacks def close(self): if self.closing: return self.closing = True # Make sure the config is saved. self.config.save() # Ensure columns state is saved self.torrentview.save_state() # Shut down components yield self.shutdown() # The gtk modal dialogs (e.g. Preferences) can prevent the application # quitting, so force exiting by destroying MainWindow. Must be done here # to avoid hanging when quitting with SIGINT (CTRL-C). self.mainwindow.window.destroy() reactor.stop() # Restart the application after closing if MainWindow restart attribute set. if component.get('MainWindow').restart: os.execv(sys.argv[0], sys.argv) def log_rpc_stats(self): """Log RPC statistics for thinclient mode.""" if not client.connected(): return t = time.time() recv = client.get_bytes_recv() sent = client.get_bytes_sent() delta_time = t - self.daemon_bps[0] delta_sent = sent - self.daemon_bps[1] delta_recv = recv - self.daemon_bps[2] self.daemon_bps = (t, sent, recv) sent_rate = fspeed(delta_sent / delta_time) recv_rate = fspeed(delta_recv / delta_time) log.debug('RPC: Sent %s (%s) Recv %s (%s)', fsize(sent), sent_rate, fsize(recv), recv_rate) def _on_reactor_start(self): log.debug('_on_reactor_start') self.mainwindow.first_show() if not self.config['standalone']: return self._start_thinclient() err_msg = '' try: client.start_standalone() except DaemonRunningError: err_msg = _('A Deluge daemon (deluged) is already running.\n' 'To use Standalone mode, stop local daemon and restart Deluge.') except ImportError as ex: if 'No module named libtorrent' in ex.message: err_msg = _('Only Thin Client mode is available because libtorrent is not installed.\n' 'To use Standalone mode, please install libtorrent package.') else: log.exception(ex) err_msg = _('Only Thin Client mode is available due to unknown Import Error.\n' 'To use Standalone mode, please see logs for error details.') except Exception as ex: log.exception(ex) err_msg = _('Only Thin Client mode is available due to unknown Import Error.\n' 'To use Standalone mode, please see logs for error details.') else: component.start() return def on_dialog_response(response): """User response to switching mode dialog.""" if response == RESPONSE_YES: # Turning off standalone self.config['standalone'] = False self._start_thinclient() else: # User want keep Standalone Mode so just quit. self.mainwindow.quit() # An error occurred so ask user to switch from Standalone to Thin Client mode. err_msg += '\n\n' + _('Continue in Thin Client mode?') d = YesNoDialog(_('Change User Interface Mode'), err_msg).run() d.addCallback(on_dialog_response) def _start_thinclient(self): """Start the gtkui in thinclient mode""" if log.isEnabledFor(logging.DEBUG): self.rpc_stats.start(10) # Check to see if we need to start the localhost daemon if self.config['autostart_localhost']: def on_localhost_status(status_info, port): if status_info[1] == 'Offline': log.debug('Autostarting localhost: %s', host_config[0:3]) self.connectionmanager.start_daemon(port, get_config_dir()) for host_config in self.connectionmanager.hostlist.config['hosts']: if host_config[1] in LOCALHOST: d = self.connectionmanager.hostlist.get_host_status(host_config[0]) d.addCallback(on_localhost_status, host_config[2]) break # Autoconnect to a host if self.config['autoconnect']: for host_config in self.connectionmanager.hostlist.config['hosts']: host_id, host, port, user, __ = host_config if host_id == self.config['autoconnect_host_id']: log.debug('Trying to connect to %s@%s:%s', user, host, port) self.connectionmanager._connect(host_id, try_counter=6) break if self.config['show_connection_manager_on_start']: # Dialog is blocking so call last. self.connectionmanager.show() def __on_disconnect(self): """ Called when disconnected from the daemon. We basically just stop all the components here. """ self.daemon_bps = (0, 0, 0) component.stop()
class GtkUI(object): def __init__(self, args): # Setup gtkbuilder/glade translation setup_translations(setup_gettext=False, setup_pygtk=True) # Setup signals def on_die(*args): log.debug('OS signal "die" caught with args: %s', args) reactor.stop() if windows_check(): from win32api import SetConsoleCtrlHandler SetConsoleCtrlHandler(on_die, True) log.debug('Win32 "die" handler registered') elif osx_check() and WINDOWING == 'quartz': import gtkosx_application self.osxapp = gtkosx_application.gtkosx_application_get() self.osxapp.connect('NSApplicationWillTerminate', on_die) log.debug('OSX quartz "die" handler registered') # Set process name again to fix gtk issue setproctitle(getproctitle()) # Attempt to register a magnet URI handler with gconf, but do not overwrite # if already set by another program. associate_magnet_links(False) # Make sure gtkui.conf has at least the defaults set self.config = ConfigManager('gtkui.conf', DEFAULT_PREFS) # Make sure the gtkui state folder has been created if not os.path.exists(os.path.join(get_config_dir(), 'gtkui_state')): os.makedirs(os.path.join(get_config_dir(), 'gtkui_state')) # Set language if self.config['language'] is not None: set_language(self.config['language']) # Start the IPC Interface before anything else.. Just in case we are # already running. self.queuedtorrents = QueuedTorrents() self.ipcinterface = IPCInterface(args.torrents) # Initialize gdk threading threads_init() # We make sure that the UI components start once we get a core URI client.set_disconnect_callback(self.__on_disconnect) self.trackericons = TrackerIcons() self.sessionproxy = SessionProxy() # Initialize various components of the gtkui self.mainwindow = MainWindow() self.menubar = MenuBar() self.toolbar = ToolBar() self.torrentview = TorrentView() self.torrentdetails = TorrentDetails() self.sidebar = SideBar() self.filtertreeview = FilterTreeView() self.preferences = Preferences() self.systemtray = SystemTray() self.statusbar = StatusBar() self.addtorrentdialog = AddTorrentDialog() if osx_check() and WINDOWING == 'quartz': def nsapp_open_file(osxapp, filename): # Ignore command name which is raised at app launch (python opening main script). if filename == sys.argv[0]: return True process_args([filename]) self.osxapp.connect('NSApplicationOpenFile', nsapp_open_file) from deluge.ui.gtkui.menubar_osx import menubar_osx menubar_osx(self, self.osxapp) self.osxapp.ready() # Initalize the plugins self.plugins = PluginManager() # Show the connection manager self.connectionmanager = ConnectionManager() # Setup RPC stats logging # daemon_bps: time, bytes_sent, bytes_recv self.daemon_bps = (0, 0, 0) self.rpc_stats = LoopingCall(self.log_rpc_stats) self.closing = False # Twisted catches signals to terminate, so have it call a pre_shutdown method. reactor.addSystemEventTrigger('before', 'gtkui_close', self.close) def gtkui_sigint_handler(num, frame): log.debug('SIGINT signal caught, firing event: gtkui_close') reactor.callLater(0, reactor.fireSystemEvent, 'gtkui_close') signal.signal(signal.SIGINT, gtkui_sigint_handler) def start(self): reactor.callWhenRunning(self._on_reactor_start) # Initialize gdk threading threads_enter() reactor.run() # Reactor no longer running so async callbacks (Deferreds) cannot be # processed after this point. threads_leave() def shutdown(self, *args, **kwargs): log.debug('GTKUI shutting down...') # Shutdown all components if client.is_standalone: return component.shutdown() @defer.inlineCallbacks def close(self): if self.closing: return self.closing = True # Make sure the config is saved. self.config.save() # Ensure columns state is saved self.torrentview.save_state() # Shut down components yield self.shutdown() # The gtk modal dialogs (e.g. Preferences) can prevent the application # quitting, so force exiting by destroying MainWindow. Must be done here # to avoid hanging when quitting with SIGINT (CTRL-C). self.mainwindow.window.destroy() reactor.stop() # Restart the application after closing if MainWindow restart attribute set. if component.get('MainWindow').restart: os.execv(sys.argv[0], sys.argv) def log_rpc_stats(self): """Log RPC statistics for thinclient mode.""" if not client.connected(): return t = time.time() recv = client.get_bytes_recv() sent = client.get_bytes_sent() delta_time = t - self.daemon_bps[0] delta_sent = sent - self.daemon_bps[1] delta_recv = recv - self.daemon_bps[2] self.daemon_bps = (t, sent, recv) sent_rate = fspeed(delta_sent / delta_time) recv_rate = fspeed(delta_recv / delta_time) log.debug( 'RPC: Sent %s (%s) Recv %s (%s)', fsize(sent), sent_rate, fsize(recv), recv_rate, ) def _on_reactor_start(self): log.debug('_on_reactor_start') self.mainwindow.first_show() if not self.config['standalone']: return self._start_thinclient() err_msg = '' try: client.start_standalone() except DaemonRunningError: err_msg = _( 'A Deluge daemon (deluged) is already running.\n' 'To use Standalone mode, stop local daemon and restart Deluge.' ) except ImportError as ex: if 'No module named libtorrent' in ex.message: err_msg = _( 'Only Thin Client mode is available because libtorrent is not installed.\n' 'To use Standalone mode, please install libtorrent package.' ) else: log.exception(ex) err_msg = _( 'Only Thin Client mode is available due to unknown Import Error.\n' 'To use Standalone mode, please see logs for error details.' ) except Exception as ex: log.exception(ex) err_msg = _( 'Only Thin Client mode is available due to unknown Import Error.\n' 'To use Standalone mode, please see logs for error details.' ) else: component.start() return def on_dialog_response(response): """User response to switching mode dialog.""" if response == RESPONSE_YES: # Turning off standalone self.config['standalone'] = False self._start_thinclient() else: # User want keep Standalone Mode so just quit. self.mainwindow.quit() # An error occurred so ask user to switch from Standalone to Thin Client mode. err_msg += '\n\n' + _('Continue in Thin Client mode?') d = YesNoDialog(_('Change User Interface Mode'), err_msg).run() d.addCallback(on_dialog_response) def _start_thinclient(self): """Start the gtkui in thinclient mode""" if log.isEnabledFor(logging.DEBUG): self.rpc_stats.start(10) # Check to see if we need to start the localhost daemon if self.config['autostart_localhost']: def on_localhost_status(status_info, port): if status_info[1] == 'Offline': log.debug('Autostarting localhost: %s', host_config[0:3]) self.connectionmanager.start_daemon(port, get_config_dir()) for host_config in self.connectionmanager.hostlist.config['hosts']: if host_config[1] in LOCALHOST: d = self.connectionmanager.hostlist.get_host_status(host_config[0]) d.addCallback(on_localhost_status, host_config[2]) break # Autoconnect to a host if self.config['autoconnect']: for host_config in self.connectionmanager.hostlist.config['hosts']: host_id, host, port, user, __ = host_config if host_id == self.config['autoconnect_host_id']: log.debug('Trying to connect to %s@%s:%s', user, host, port) self.connectionmanager._connect(host_id, try_counter=6) break if self.config['show_connection_manager_on_start']: # Dialog is blocking so call last. self.connectionmanager.show() def __on_disconnect(self): """ Called when disconnected from the daemon. We basically just stop all the components here. """ self.daemon_bps = (0, 0, 0) component.stop()