diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index 6b2b5d203..d7c36fd34 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -1,7 +1,7 @@ # # daemon.py # -# Copyright (C) 2007 Andrew Resch +# Copyright (C) 2007-2009 Andrew Resch # # Deluge is free software. # @@ -31,8 +31,8 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. # -# +import os import gettext import locale import pkg_resources @@ -44,9 +44,50 @@ import deluge.configmanager import deluge.common from deluge.core.rpcserver import RPCServer, export from deluge.log import LOG as log +import deluge.error class Daemon(object): def __init__(self, options=None, args=None, classic=False): + # Check for another running instance of the daemon + if os.path.isfile(deluge.configmanager.get_config_dir("deluged.pid")): + # Get the PID and the port of the supposedly running daemon + (pid, port) = open(deluge.configmanager.get_config_dir("deluged.pid")).read().strip().split(";") + pid = int(pid) + port = int(port) + + def process_running(pid): + if deluge.common.windows_check(): + # Do some fancy WMI junk to see if the PID exists in Windows + from win32com.client import GetObject + def get_proclist(): + WMI = GetObject('winmgmts:') + processes = WMI.InstancesOf('Win32_Process') + return [process.Properties_('ProcessID').Value for process in processes] + return pid in get_proclist() + else: + # We can just use os.kill on UNIX to test if the process is running + try: + os.kill(pid, 0) + except OSError: + return False + else: + return True + + if process_running(pid): + # Ok, so a process is running with this PID, let's make doubly-sure + # it's a deluged process by trying to open a socket to it's port. + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect(("127.0.0.1", port)) + except socket.error: + # Can't connect, so it must not be a deluged process.. + pass + else: + # This is a deluged! + s.close() + raise deluge.error.DaemonRunningError("There is a deluge daemon running with this config directory!") + # Initialize gettext try: locale.setlocale(locale.LC_ALL, '') @@ -77,8 +118,6 @@ class Daemon(object): SetConsoleCtrlHandler(win_handler) version = deluge.common.get_version() - if deluge.common.get_revision(): - version = version + "r" + deluge.common.get_revision() log.info("Deluge daemon %s", version) log.debug("options: %s", options) @@ -108,6 +147,11 @@ class Daemon(object): # Make sure we start the PreferencesManager first component.start("PreferencesManager") + # Write out a pid file all the time, we use this to see if a deluged is running + # We also include the running port number to do an additional test + open(deluge.configmanager.get_config_dir("deluged.pid"), "wb").write( + "%s;%s\n" % (os.getpid(), options.port if options.port else 58846)) + if not classic: component.start() try: @@ -117,6 +161,12 @@ class Daemon(object): @export() def shutdown(self, *args, **kwargs): + try: + os.remove(deluge.configmanager.get_config_dir("deluged.pid")) + except Exception, e: + log.exception(e) + log.error("Error removing deluged.pid!") + component.shutdown() try: reactor.stop() diff --git a/deluge/error.py b/deluge/error.py index 1e4da0b22..1e4d3da3a 100644 --- a/deluge/error.py +++ b/deluge/error.py @@ -43,3 +43,5 @@ class DelugeError(Exception): class NoCoreError(DelugeError): pass +class DaemonRunningError(DelugeError): + pass diff --git a/deluge/ui/gtkui/dialogs.py b/deluge/ui/gtkui/dialogs.py new file mode 100644 index 000000000..276e88b1d --- /dev/null +++ b/deluge/ui/gtkui/dialogs.py @@ -0,0 +1,85 @@ +# +# dialogs.py +# +# Copyright (C) 2009 Andrew Resch +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# + +import gtk +import deluge.component as component + +class YesNoDialog(gtk.Dialog): + """ + Displays a dialog asking the user to select Yes or No to a question. + + When run(), it will return either a gtk.RESPONSE_YES or a gtk.RESPONSE_NO. + + """ + def __init__(self, header, text, parent=None): + """ + :param header: str, the header portion of the dialog, try to keep it short and to the point + :param text: str, the body of the dialog, this can be longer with a more + thorough explanation of the question + :param parent: gtkWindow, the parent window, if None it will default to the + MainWindow + """ + super(YesNoDialog, self).__init__( + title="", + parent=parent if parent else component.get("MainWindow").window, + flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, + buttons=(gtk.STOCK_YES, gtk.RESPONSE_YES, gtk.STOCK_NO, gtk.RESPONSE_NO)) + + # XXX: All of this stuff should be moved to a base dialog class.. + self.set_border_width(5) + self.set_default_size(200, 100) + hbox = gtk.HBox(spacing=5) + image = gtk.Image() + image.set_from_stock(gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG) + image.set_alignment(0.5, 0.0) + hbox.pack_start(image, False, False) + vbox = gtk.VBox(spacing=5) + label = gtk.Label("" + header + "") + label.set_use_markup(True) + label.set_alignment(0.0, 0.5) + label.set_line_wrap(True) + vbox.pack_start(label, False, False) + tlabel = gtk.Label(text) + tlabel.set_use_markup(True) + tlabel.set_line_wrap(True) + tlabel.set_alignment(0.0, 0.5) + vbox.pack_start(tlabel, False, False) + hbox.pack_start(vbox, False, False) + self.vbox.pack_start(hbox, False, False) + self.vbox.show_all() + + def run(self): + response = super(YesNoDialog, self).run() + self.destroy() + return response diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 16b9217b7..d516a566e 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -1,7 +1,7 @@ # # gtkui.py # -# Copyright (C) 2007 Andrew Resch +# Copyright (C) 2007-2009 Andrew Resch # # Deluge is free software. # @@ -62,11 +62,13 @@ from connectionmanager import ConnectionManager from pluginmanager import PluginManager from ipcinterface import IPCInterface from deluge.ui.tracker_icons import TrackerIcons - from queuedtorrents import QueuedTorrents from addtorrentdialog import AddTorrentDialog +import dialogs + import deluge.configmanager import deluge.common +import deluge.error DEFAULT_PREFS = { "classic_mode": True, @@ -232,9 +234,24 @@ class GtkUI: def _on_reactor_start(self): log.debug("_on_reactor_start") if self.config["classic_mode"]: - client.start_classic_mode() - component.start() - return + try: + client.start_classic_mode() + except deluge.error.DaemonRunningError: + response = dialogs.YesNoDialog( + "Turn off Classic Mode?", + "It appears that a Deluge daemon process (deluged) is already running.\n\n\ +You will either need to stop the daemon or turn off Classic Mode to continue.").run() + + self.started_in_classic = False + if response != gtk.RESPONSE_YES: + # The user does not want to turn Classic Mode off, so just quit + reactor.stop() + return + # Turning off classic_mode + self.config["classic_mode"] = False + else: + component.start() + return # Autoconnect to a host if self.config["autoconnect"]: @@ -250,7 +267,6 @@ class GtkUI: self.connectionmanager.show() - def __on_disconnect(self): """ Called when disconnected from the daemon. We basically just stop all