/*
* Copyright 2008 Evenflow, Inc.
*
* caja-dropbox-hooks.c
* Implements connection handling and C interface for the Dropbox hook socket.
*
* This file is part of caja-dropbox.
*
* caja-dropbox 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 3 of the License, or
* (at your option) any later version.
*
* caja-dropbox 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 caja-dropbox. If not, see .
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "g-util.h"
#include "async-io-coroutine.h"
#include "dropbox-client-util.h"
#include "caja-dropbox-hooks.h"
typedef struct {
DropboxUpdateHook hook;
gpointer ud;
} HookData;
static gboolean
try_to_connect(CajaDropboxHookserv *hookserv);
static gboolean
handle_hook_server_input(GIOChannel *chan,
GIOCondition cond,
CajaDropboxHookserv *hookserv) {
/*debug_enter(); */
/* we have some sweet macros defined that allow us to write this
async event handler like a microthread yeahh, watch out for context */
CRBEGIN(hookserv->hhsi.line);
while (1) {
hookserv->hhsi.command_args =
g_hash_table_new_full((GHashFunc) g_str_hash,
(GEqualFunc) g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_strfreev);
hookserv->hhsi.numargs = 0;
/* read the command name */
{
gchar *line;
CRREADLINE(hookserv->hhsi.line, chan, line);
hookserv->hhsi.command_name = dropbox_client_util_desanitize(line);
g_free(line);
}
/*debug("got a hook name: %s", hookserv->hhsi.command_name); */
/* now read each arg line (until a certain limit) until we receive "done" */
while (1) {
gchar *line;
/* if too many arguments, this connection seems malicious */
if (hookserv->hhsi.numargs >= 20) {
CRHALT;
}
CRREADLINE(hookserv->hhsi.line, chan, line);
if (strcmp("done", line) == 0) {
g_free(line);
break;
}
else {
gboolean parse_result;
parse_result =
dropbox_client_util_command_parse_arg(line,
hookserv->hhsi.command_args);
g_free(line);
if (FALSE == parse_result) {
debug("bad parse");
CRHALT;
}
}
hookserv->hhsi.numargs += 1;
}
{
HookData *hd;
hd = (HookData *)
g_hash_table_lookup(hookserv->dispatch_table,
hookserv->hhsi.command_name);
if (hd != NULL) {
(hd->hook)(hookserv->hhsi.command_args, hd->ud);
}
}
g_free(hookserv->hhsi.command_name);
g_hash_table_unref(hookserv->hhsi.command_args);
hookserv->hhsi.command_name = NULL;
hookserv->hhsi.command_args = NULL;
}
CREND;
}
static void
watch_killer(CajaDropboxHookserv *hookserv) {
debug("hook client disconnected");
hookserv->connected = FALSE;
g_hook_list_invoke(&(hookserv->ondisconnect_hooklist), FALSE);
/* we basically just have to free the memory allocated in the
handle_hook_server_init ctx */
if (hookserv->hhsi.command_name != NULL) {
g_free(hookserv->hhsi.command_name);
hookserv->hhsi.command_name = NULL;
}
if (hookserv->hhsi.command_args != NULL) {
g_hash_table_unref(hookserv->hhsi.command_args);
hookserv->hhsi.command_args = NULL;
}
g_io_channel_unref(hookserv->chan);
hookserv->chan = NULL;
hookserv->event_source = 0;
hookserv->socket = 0;
/* lol we also have to start a new connection */
try_to_connect(hookserv);
}
static gboolean
try_to_connect(CajaDropboxHookserv *hookserv) {
/* create socket */
hookserv->socket = socket(PF_UNIX, SOCK_STREAM, 0);
/* set native non-blocking, for connect timeout */
{
unsigned int flags;
if ((flags = fcntl(hookserv->socket, F_GETFL, 0)) < 0) {
goto FAIL_CLEANUP;
}
if (fcntl(hookserv->socket, F_SETFL, flags | O_NONBLOCK) < 0) {
goto FAIL_CLEANUP;
}
}
/* connect to server, might fail of course */
{
struct sockaddr_un addr;
socklen_t addr_len;
/* intialize address structure */
addr.sun_family = AF_UNIX;
g_snprintf(addr.sun_path,
sizeof(addr.sun_path),
"%s/.dropbox/iface_socket",
g_get_home_dir());
addr_len = sizeof(addr) - sizeof(addr.sun_path) + strlen(addr.sun_path);
/* if there was an error we have to try again later */
if (connect(hookserv->socket, (struct sockaddr *) &addr, addr_len) < 0) {
if (errno == EINPROGRESS) {
fd_set writers;
struct timeval tv = {1, 0};
FD_ZERO(&writers);
FD_SET(hookserv->socket, &writers);
/* if nothing was ready after 3 seconds, fail out homie */
if (select(hookserv->socket+1, NULL, &writers, NULL, &tv) == 0) {
goto FAIL_CLEANUP;
}
if (connect(hookserv->socket, (struct sockaddr *) &addr, addr_len) < 0) {
debug("couldn't connect to hook server after 1 second");
goto FAIL_CLEANUP;
}
}
else {
goto FAIL_CLEANUP;
}
}
}
/* lol sometimes i write funny codez */
if (FALSE) {
FAIL_CLEANUP:
close(hookserv->socket);
g_timeout_add_seconds(1, (GSourceFunc) try_to_connect, hookserv);
return FALSE;
}
/* great we connected!, let's create the channel and wait on it */
hookserv->chan = g_io_channel_unix_new(hookserv->socket);
g_io_channel_set_line_term(hookserv->chan, "\n", -1);
g_io_channel_set_close_on_unref(hookserv->chan, TRUE);
/*debug("create channel"); */
/* Set non-blocking ;) (again just in case) */
{
GIOFlags flags;
GIOStatus iostat;
flags = g_io_channel_get_flags(hookserv->chan);
iostat = g_io_channel_set_flags(hookserv->chan, flags | G_IO_FLAG_NONBLOCK,
NULL);
if (iostat == G_IO_STATUS_ERROR) {
g_io_channel_unref(hookserv->chan);
g_timeout_add_seconds(1, (GSourceFunc) try_to_connect, hookserv);
return FALSE;
}
}
/*debug("set non blocking"); */
/* this is fun, async io watcher */
hookserv->hhsi.line = 0;
hookserv->hhsi.command_args = NULL;
hookserv->hhsi.command_name = NULL;
hookserv->event_source =
g_io_add_watch_full(hookserv->chan, G_PRIORITY_DEFAULT,
G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) handle_hook_server_input, hookserv,
(GDestroyNotify) watch_killer);
debug("hook client connected");
hookserv->connected = TRUE;
g_hook_list_invoke(&(hookserv->onconnect_hooklist), FALSE);
/*debug("added watch");*/
return FALSE;
}
/* should only be called in glib main loop */
/* returns a gboolean because it is a GSourceFunc */
gboolean caja_dropbox_hooks_force_reconnect(CajaDropboxHookserv *hookserv) {
debug_enter();
if (hookserv->connected == FALSE) {
return FALSE;
}
debug("forcing hook to reconnect");
g_assert(hookserv->event_source >= 0);
if (hookserv->event_source > 0) {
g_source_remove(hookserv->event_source);
}
else if (hookserv->event_source == 0) {
debug("event source was zero!!!!!");
}
return FALSE;
}
gboolean
caja_dropbox_hooks_is_connected(CajaDropboxHookserv *hookserv) {
return hookserv->connected;
}
void
caja_dropbox_hooks_setup(CajaDropboxHookserv *hookserv) {
hookserv->dispatch_table = g_hash_table_new_full((GHashFunc) g_str_hash,
(GEqualFunc) g_str_equal,
g_free, g_free);
hookserv->connected = FALSE;
g_hook_list_init(&(hookserv->ondisconnect_hooklist), sizeof(GHook));
g_hook_list_init(&(hookserv->onconnect_hooklist), sizeof(GHook));
}
void
caja_dropbox_hooks_add_on_disconnect_hook(CajaDropboxHookserv *hookserv,
DropboxHookClientConnectHook dhcch,
gpointer ud) {
GHook *newhook;
newhook = g_hook_alloc(&(hookserv->ondisconnect_hooklist));
newhook->func = dhcch;
newhook->data = ud;
g_hook_append(&(hookserv->ondisconnect_hooklist), newhook);
}
void
caja_dropbox_hooks_add_on_connect_hook(CajaDropboxHookserv *hookserv,
DropboxHookClientConnectHook dhcch,
gpointer ud) {
GHook *newhook;
newhook = g_hook_alloc(&(hookserv->onconnect_hooklist));
newhook->func = dhcch;
newhook->data = ud;
g_hook_append(&(hookserv->onconnect_hooklist), newhook);
}
void caja_dropbox_hooks_add(CajaDropboxHookserv *ndhs,
const gchar *hook_name,
DropboxUpdateHook hook, gpointer ud) {
HookData *hd;
hd = g_new(HookData, 1);
hd->hook = hook;
hd->ud = ud;
g_hash_table_insert(ndhs->dispatch_table, g_strdup(hook_name), hd);
}
void
caja_dropbox_hooks_start(CajaDropboxHookserv *hookserv) {
try_to_connect(hookserv);
}