Hello all again.

I've implemented Windows service support for MPD.
Also this patch adds console handler that traps "Ctrl+C" and "close window" events and performs clean shutdown. Looks like current implementation does not handle shutdown at all and simply quits when it gets Ctrl+C.

Some notes on implementation:

I've added PIPE_EVENT_SHUTDOWN because calling g_main_loop_quit() do not work when called from another thread.
Main thread was sleeping in g_poll() so I needed some way to wake it up.

By some strange reason call close(event_pipe[0]) in event_pipe_deinit() hangs. In current implementation that code never reached so that was not a problem :-)
I've added a conditional to leave event_pipe[0] open on Win32.

Currently there is no registration/unregistration functionality
Service can be registered with SC tool (sc.exe)
I want to implement registration however I'm not sure whenever this should be a separate program or code inside main MPD program.

Please find patches in the attached files.

/*
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifdef WIN32

#include "config.h"
#include "main.h"
#include "event_pipe.h"

#include <glib.h>

#define WINVER 0x0501
#include <windows.h>

static int service_argc;
static char **service_argv;
static char service_name[] = "";
static BOOL ignore_console_events;
static SERVICE_STATUS_HANDLE service_handle;

static void WINAPI
service_main(DWORD argc, CHAR *argv[]);

static SERVICE_TABLE_ENTRY service_registry[] = {
        {service_name, service_main},
        {NULL, NULL}
};

static void
service_notify_status(DWORD status_code)
{
        SERVICE_STATUS current_status;
        
        current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
        current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
                ? 0
                : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
                
        current_status.dwCurrentState = status_code;
        current_status.dwWin32ExitCode = NO_ERROR;
        current_status.dwCheckPoint = 0;
        current_status.dwWaitHint = 1000;

        SetServiceStatus(service_handle, &current_status);
}

static DWORD WINAPI
service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type,
                   G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context)
{
        switch (control) {
        case SERVICE_CONTROL_SHUTDOWN:
        case SERVICE_CONTROL_STOP:
                event_pipe_emit(PIPE_EVENT_SHUTDOWN);
                return NO_ERROR;
        default:
                return NO_ERROR;
        }
}

static void WINAPI
service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[])
{
        DWORD error_code;
        gchar* error_message;
        
        service_handle =
                RegisterServiceCtrlHandlerEx(service_name,
                                             service_dispatcher, NULL);

        if (service_handle == 0) {
                error_code = GetLastError();
                error_message = g_win32_error_message(error_code);
                g_error("RegisterServiceCtrlHandlerEx() failed: %s",
                        error_message);
        }
        
        service_notify_status(SERVICE_START_PENDING);
        mpd_main(service_argc, service_argv);
        service_notify_status(SERVICE_STOPPED);
}

static BOOL WINAPI
console_handler(DWORD event)
{
        switch (event) {
        case CTRL_C_EVENT:
        case CTRL_CLOSE_EVENT:
                if (!ignore_console_events)
                        event_pipe_emit(PIPE_EVENT_SHUTDOWN);
                return TRUE;
        default:
                return FALSE;
        }
}

int win32_main(int argc, char *argv[])
{
        DWORD error_code;
        gchar* error_message;
        
        service_argc = argc;
        service_argv = argv;
        
        if (StartServiceCtrlDispatcher(service_registry))
                return 0; /* run as service successefully */

        error_code = GetLastError();
        if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
                /* running as console app */
                SetConsoleTitle("Music Player Daemon");
                ignore_console_events = TRUE;
                SetConsoleCtrlHandler(console_handler, TRUE);
                return mpd_main(argc, argv);
        }

        error_message = g_win32_error_message(error_code);
        g_error("StartServiceCtrlDispatcher() failed: %s", error_message);
}

void win32_app_started()
{
        if (service_handle != 0)
                service_notify_status(SERVICE_RUNNING);
        else
                ignore_console_events = FALSE;
}

void win32_app_stopping()
{
        if (service_handle != 0)
                service_notify_status(SERVICE_STOP_PENDING);
        else
                ignore_console_events = TRUE;
}

#endif
diff --git a/Makefile.am b/Makefile.am
index 01abe57..eeeffc9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -278,6 +278,7 @@ src_mpd_SOURCES = \
        src/log.c \
        src/ls.c \
        src/main.c \
+       src/main_win32.c \
        src/event_pipe.c \
        src/daemon.c \
        src/AudioCompress/compress.c \
diff --git a/src/event_pipe.c b/src/event_pipe.c
index af6517c..286256f 100644
--- a/src/event_pipe.c
+++ b/src/event_pipe.c
@@ -116,7 +116,10 @@ void event_pipe_deinit(void)
        g_source_remove(event_pipe_source_id);
        g_io_channel_unref(event_channel);
 
+#ifndef WIN32
+       /* By some strange reason this call hangs on Win32 */
        close(event_pipe[0]);
+#endif
        close(event_pipe[1]);
 }
 
diff --git a/src/event_pipe.h b/src/event_pipe.h
index 6c3d8c1..2bf267d 100644
--- a/src/event_pipe.h
+++ b/src/event_pipe.h
@@ -43,6 +43,9 @@ enum pipe_event {
 
        /** a hardware mixer plugin has detected a change */
        PIPE_EVENT_MIXER,
+       
+       /** shutdown requested */
+       PIPE_EVENT_SHUTDOWN,
 
        PIPE_EVENT_MAX
 };
diff --git a/src/main.c b/src/main.c
index c93a3f6..eca9686 100644
--- a/src/main.c
+++ b/src/main.c
@@ -269,8 +269,26 @@ idle_event_emitted(void)
                client_manager_idle_add(flags);
 }
 
+/**
+ * event_pipe callback function for PIPE_EVENT_SHUTDOWN
+ */
+static void
+shutdown_event_emitted(void)
+{
+       g_main_loop_quit(main_loop);
+}
+
 int main(int argc, char *argv[])
 {
+#ifdef WIN32
+       return win32_main(argc, argv);
+#else
+       return mpd_main(argc, argv);
+#endif
+}
+
+int mpd_main(int argc, char *argv[])
+{
        struct options options;
        clock_t start;
        bool create_db;
@@ -324,6 +342,7 @@ int main(int argc, char *argv[])
 
        event_pipe_init();
        event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
+       event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted);
 
        path_global_init();
        glue_mapper_init();
@@ -391,10 +410,17 @@ int main(int argc, char *argv[])
        /* enable all audio outputs (if not already done by
           playlist_state_restore() */
        pc_update_audio();
+       
+#ifdef WIN32
+       win32_app_started();
+#endif
 
        /* run the main loop */
-
        g_main_loop_run(main_loop);
+       
+#ifdef WIN32
+       win32_app_stopping();
+#endif
 
        /* cleanup */
 
diff --git a/src/main.h b/src/main.h
index c1d3f36..9b9cba0 100644
--- a/src/main.h
+++ b/src/main.h
@@ -28,4 +28,45 @@ extern GMainLoop *main_loop;
 
 extern GCond *main_cond;
 
+/**
+ * A entry point for application.
+ * On non-Windows platforms this is called directly from main()
+ * On Windows platform this is called from win32_main()
+ * after doing some initialization.
+ */
+int mpd_main(int argc, char *argv[]);
+
+#ifdef WIN32
+
+/**
+ * If program is run as windows service performs nessesary initialization
+ * and then calls mpd_main() with specified arguments.
+ * If program is run as a regular application calls mpd_main() immediately.
+ */
+int
+win32_main(int argc, char *argv[]);
+
+/**
+ * When running as a service reports to service control manager
+ * that our service is started.
+ * When running as a console application enables console handler that will
+ * trigger PIPE_EVENT_SHUTDOWN when user closes console window
+ * or presses Ctrl+C.
+ * This function should be called just before entering main loop.
+ */
+void
+win32_app_started(void);
+
+/**
+ * When running as a service reports to service control manager
+ * that our service is about to stop.
+ * When running as a console application enables console handler that will
+ * catch all shutdown requests and ignore them.
+ * This function should be called just after leaving main loop.
+ */
+void
+win32_app_stopping(void);
+
+#endif
+
 #endif
------------------------------------------------------------------------------
Start uncovering the many advantages of virtual appliances
and start using them to simplify application deployment and
accelerate your shift to cloud computing.
http://p.sf.net/sfu/novell-sfdev2dev
_______________________________________________
Musicpd-dev-team mailing list
Musicpd-dev-team@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/musicpd-dev-team

Reply via email to