Add IPC code for another process to communicate with a running

transmission-gtk instance.
Try to add any filenames found on the command-line, using IPC if
  transmission-gtk is already running.
Some minor code cleanups.
Remove lockfile on a normal exit, justfor the sake of being tidy.
This commit is contained in:
Josh Elsasser 2006-04-25 07:26:27 +00:00
parent 9120989a88
commit 1cfe027fdb
11 changed files with 1087 additions and 52 deletions

View File

@ -1,7 +1,7 @@
include ../Makefile.config
include ../Makefile.common
SRCS = conf.c dialogs.c main.c trcellrenderertorrent.c util.c
SRCS = conf.c dialogs.c io.c ipc.c main.c trcellrenderertorrent.c util.c
OBJS = $(SRCS:%.c=%.o)
CFLAGS += $(CFLAGS_GTK) -I../libtransmission

View File

@ -30,12 +30,13 @@
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <glib/gi18n.h>
#include "conf.h"
@ -44,6 +45,7 @@
#define CONF_SUBDIR "gtk"
#define FILE_LOCK "lock"
#define FILE_SOCKET "socket"
#define FILE_PREFS "prefs"
#define FILE_PREFS_TMP "prefs.tmp"
#define FILE_STATE "state"
@ -57,26 +59,33 @@
static int
lockfile(const char *file, char **errstr);
static void
cf_removelocks(void);
static gboolean
writefile_traverse(gpointer key, gpointer value, gpointer data);
static char *
getstateval(struct cf_torrentstate *state, char *line);
static char *confdir = NULL;
static char *old_confdir = NULL;
static GTree *prefs = NULL;
static char *gl_confdir = NULL;
static char *gl_old_confdir = NULL;
static GTree *gl_prefs = NULL;
static char *gl_lockpath = NULL;
static char *gl_old_lockpath = NULL;
/* errstr may be NULL, this might be called before GTK is initialized */
static int
lockfile(const char *file, char **errstr) {
int fd, savederr;
struct flock lk;
*errstr = NULL;
if(NULL != errstr)
*errstr = NULL;
if(0 > (fd = open(file, O_RDWR | O_CREAT, 0666))) {
savederr = errno;
*errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
file, strerror(errno));
if(NULL != errstr)
*errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
file, strerror(errno));
errno = savederr;
return -1;
}
@ -88,12 +97,14 @@ lockfile(const char *file, char **errstr) {
lk.l_whence = SEEK_SET;
if(-1 == fcntl(fd, F_SETLK, &lk)) {
savederr = errno;
if(EAGAIN == errno)
*errstr = g_strdup_printf(_("Another copy of %s is already running."),
g_get_application_name());
else
*errstr = g_strdup_printf(_("Failed to lock the file %s:\n%s"),
file, strerror(errno));
if(NULL != errstr) {
if(EAGAIN == errno)
*errstr = g_strdup_printf(_("Another copy of %s is already running."),
g_get_application_name());
else
*errstr = g_strdup_printf(_("Failed to lock the file %s:\n%s"),
file, strerror(errno));
}
close(fd);
errno = savederr;
return -1;
@ -102,38 +113,67 @@ lockfile(const char *file, char **errstr) {
return fd;
}
/* errstr may be NULL, this might be called before GTK is initialized */
gboolean
cf_init(const char *dir, char **errstr) {
*errstr = NULL;
old_confdir = g_strdup(dir);
confdir = g_build_filename(dir, CONF_SUBDIR, NULL);
if(NULL != errstr)
*errstr = NULL;
gl_old_confdir = g_strdup(dir);
gl_confdir = g_build_filename(dir, CONF_SUBDIR, NULL);
if(mkdir_p(confdir, 0777))
if(mkdir_p(gl_confdir, 0777))
return TRUE;
*errstr = g_strdup_printf(_("Failed to create the directory %s:\n%s"),
confdir, strerror(errno));
if(NULL != errstr)
*errstr = g_strdup_printf(_("Failed to create the directory %s:\n%s"),
gl_confdir, strerror(errno));
return FALSE;
}
/* errstr may be NULL, this might be called before GTK is initialized */
gboolean
cf_lock(char **errstr) {
char *path = g_build_filename(old_confdir, OLD_FILE_LOCK, NULL);
char *path = g_build_filename(gl_old_confdir, OLD_FILE_LOCK, NULL);
int fd = lockfile(path, errstr);
if(0 <= fd) {
if(0 > fd)
g_free(path);
path = g_build_filename(confdir, FILE_LOCK, NULL);
else {
gl_old_lockpath = path;
path = g_build_filename(gl_confdir, FILE_LOCK, NULL);
fd = lockfile(path, errstr);
if(0 > fd)
g_free(path);
else
gl_lockpath = path;
}
g_free(path);
g_atexit(cf_removelocks);
return 0 <= fd;
}
static void
cf_removelocks(void) {
if(NULL != gl_lockpath) {
unlink(gl_lockpath);
g_free(gl_lockpath);
}
if(NULL != gl_old_lockpath) {
unlink(gl_old_lockpath);
g_free(gl_old_lockpath);
}
}
char *
cf_sockname(void) {
return g_build_filename(gl_confdir, FILE_SOCKET, NULL);
}
gboolean
cf_loadprefs(char **errstr) {
char *path = g_build_filename(confdir, FILE_PREFS, NULL);
char *path = g_build_filename(gl_confdir, FILE_PREFS, NULL);
char *oldpath;
GIOChannel *io;
GError *err;
@ -143,10 +183,10 @@ cf_loadprefs(char **errstr) {
*errstr = NULL;
if(NULL != prefs)
g_tree_destroy(prefs);
if(NULL != gl_prefs)
g_tree_destroy(gl_prefs);
prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
gl_prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
g_free, g_free);
err = NULL;
@ -158,7 +198,7 @@ cf_loadprefs(char **errstr) {
else {
g_error_free(err);
err = NULL;
oldpath = g_build_filename(old_confdir, OLD_FILE_PREFS, NULL);
oldpath = g_build_filename(gl_old_confdir, OLD_FILE_PREFS, NULL);
io = g_io_channel_new_file(oldpath, "r", &err);
g_free(oldpath);
}
@ -181,7 +221,7 @@ cf_loadprefs(char **errstr) {
NULL != (sep = strchr(line, PREF_SEP_KEYVAL)) && sep > line) {
*sep = '\0';
line[termpos] = '\0';
g_tree_insert(prefs, g_strcompress(line), g_strcompress(sep + 1));
g_tree_insert(gl_prefs, g_strcompress(line), g_strcompress(sep + 1));
}
g_free(line);
}
@ -205,16 +245,16 @@ cf_loadprefs(char **errstr) {
const char *
cf_getpref(const char *name) {
assert(NULL != prefs);
assert(NULL != gl_prefs);
return g_tree_lookup(prefs, name);
return g_tree_lookup(gl_prefs, name);
}
void
cf_setpref(const char *name, const char *value) {
assert(NULL != prefs);
assert(NULL != gl_prefs);
g_tree_insert(prefs, g_strdup(name), g_strdup(value));
g_tree_insert(gl_prefs, g_strdup(name), g_strdup(value));
}
struct writeinfo {
@ -224,13 +264,13 @@ struct writeinfo {
gboolean
cf_saveprefs(char **errstr) {
char *file = g_build_filename(confdir, FILE_PREFS, NULL);
char *tmpfile = g_build_filename(confdir, FILE_PREFS_TMP, NULL);
char *file = g_build_filename(gl_confdir, FILE_PREFS, NULL);
char *tmpfile = g_build_filename(gl_confdir, FILE_PREFS_TMP, NULL);
GIOChannel *io = NULL;
struct writeinfo info;
int fd;
assert(NULL != prefs);
assert(NULL != gl_prefs);
assert(NULL != errstr);
*errstr = NULL;
@ -254,7 +294,7 @@ cf_saveprefs(char **errstr) {
info.io = io;
info.err = NULL;
g_tree_foreach(prefs, writefile_traverse, &info);
g_tree_foreach(gl_prefs, writefile_traverse, &info);
if(NULL != info.err ||
G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &info.err)) {
*errstr = g_strdup_printf(_("Error while writing to the file %s:\n%s"),
@ -312,7 +352,7 @@ writefile_traverse(gpointer key, gpointer value, gpointer data) {
GList *
cf_loadstate(char **errstr) {
char *path = g_build_filename(confdir, FILE_STATE, NULL);
char *path = g_build_filename(gl_confdir, FILE_STATE, NULL);
char *oldpath;
GIOChannel *io;
GError *err;
@ -331,7 +371,7 @@ cf_loadstate(char **errstr) {
else {
g_error_free(err);
err = NULL;
oldpath = g_build_filename(old_confdir, OLD_FILE_STATE, NULL);
oldpath = g_build_filename(gl_old_confdir, OLD_FILE_STATE, NULL);
io = g_io_channel_new_file(oldpath, "r", &err);
g_free(oldpath);
}
@ -432,8 +472,8 @@ getstateval(struct cf_torrentstate *state, char *line) {
gboolean
cf_savestate(GList *torrents, char **errstr) {
char *file = g_build_filename(confdir, FILE_STATE, NULL);
char *tmpfile = g_build_filename(confdir, FILE_STATE_TMP, NULL);
char *file = g_build_filename(gl_confdir, FILE_STATE, NULL);
char *tmpfile = g_build_filename(gl_confdir, FILE_STATE_TMP, NULL);
GIOChannel *io = NULL;
GError *err;
int fd;

View File

@ -39,6 +39,8 @@ gboolean
cf_init(const char *confdir, char **errstr);
gboolean
cf_lock(char **errstr);
char *
cf_sockname(void);
gboolean
cf_loadprefs(char **errstr);
const char *

View File

@ -28,6 +28,7 @@
#define TG_PREFS_H
#include "transmission.h"
#include "util.h"
/* macros for names of prefs we use */
#define PREF_PORT "listening-port"
@ -43,9 +44,6 @@
#define DEF_UPLIMIT 20
#define DEF_USEUPLIMIT TRUE
typedef gboolean (*add_torrent_func_t)(void *, const char *, const char *, gboolean, GList **);
typedef void (*torrents_added_func_t)(void *);
void
makeprefwindow(GtkWindow *parent, tr_handle_t *tr, gboolean *opened);

354
gtk/io.c Normal file
View File

@ -0,0 +1,354 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <glib.h>
#include "io.h"
#include "util.h"
#define IO_BLOCKSIZE (1024)
struct iosource {
GSource source;
GPollFD infd;
GPollFD outfd;
ioidfunc_t sent;
iodatafunc_t received;
ionewfunc_t accepted;
iofunc_t closed;
void *cbdata;
char *inbuf;
unsigned int inused;
unsigned int inmax;
GList *outbufs;
unsigned int lastid;
};
struct iooutbuf {
char *data;
unsigned int len;
unsigned int off;
unsigned int id;
};
static gboolean
nonblock(int fd);
static struct iosource *
newsource(void);
static void
freeoutbuf(struct iooutbuf *buf);
static gboolean
io_prepare(GSource *source, gint *timeout_);
static gboolean
io_check(GSource *source);
static gboolean
io_dispatch(GSource *source, GSourceFunc callback, gpointer gdata);
static void
io_finalize(GSource *source);
static void
io_accept(struct iosource *io);
static void
io_read(struct iosource *io);
static void
io_write(struct iosource *io);
static void
io_disconnect(struct iosource *io, int err);
static GSourceFuncs sourcefuncs = {
io_prepare,
io_check,
io_dispatch,
io_finalize,
NULL,
NULL
};
GSource *
io_new(int fd, ioidfunc_t sent, iodatafunc_t received,
iofunc_t closed, void *cbdata) {
struct iosource *io;
if(!nonblock(fd))
return NULL;
io = newsource();
io->sent = sent;
io->received = received;
io->closed = closed;
io->cbdata = cbdata;
io->infd.fd = fd;
io->infd.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
io->infd.revents = 0;
io->outfd.fd = fd;
io->outfd.events = G_IO_OUT | G_IO_ERR;
io->outfd.revents = 0;
g_source_add_poll((GSource*)io, &io->infd);
g_source_attach((GSource*)io, NULL);
return (GSource*)io;
}
GSource *
io_new_listening(int fd, socklen_t len, ionewfunc_t accepted,
iofunc_t closed, void *cbdata) {
struct iosource *io;
g_assert(NULL != accepted);
if(!nonblock(fd))
return NULL;
io = newsource();
io->accepted = accepted;
io->closed = closed;
io->cbdata = cbdata;
io->infd.fd = fd;
io->infd.events = G_IO_IN | G_IO_ERR;
io->infd.revents = 0;
io->inbuf = g_new(char, len);
io->inmax = len;
g_source_add_poll((GSource*)io, &io->infd);
g_source_attach((GSource*)io, NULL);
return (GSource*)io;
}
static gboolean
nonblock(int fd) {
int flags;
if(0 > (flags = fcntl(fd, F_GETFL)) ||
0 > fcntl(fd, F_SETFL, flags | O_NONBLOCK))
return FALSE;
return TRUE;
}
static struct iosource *
newsource(void) {
GSource *source = g_source_new(&sourcefuncs, sizeof(struct iosource));
struct iosource *io = (struct iosource*)source;
io->sent = NULL;
io->received = NULL;
io->accepted = NULL;
io->closed = NULL;
io->cbdata = NULL;
bzero(&io->infd, sizeof(io->infd));
io->infd.fd = -1;
bzero(&io->outfd, sizeof(io->outfd));
io->outfd.fd = -1;
io->inbuf = NULL;
io->inused = 0;
io->inmax = 0;
io->outbufs = NULL;
io->lastid = 0;
return io;
}
unsigned int
io_send(GSource *source, const char *data, unsigned int len) {
char *new = g_new(char, len);
memcpy(new, data, len);
return io_send_keepdata(source, new, len);
}
unsigned int
io_send_keepdata(GSource *source, char *data, unsigned int len) {
struct iosource *io = (struct iosource*)source;
struct iooutbuf *buf = g_new(struct iooutbuf, 1);
buf->data = data;
buf->len = len;
buf->off = 0;
io->lastid++;
buf->id = io->lastid;
if(NULL != io->outbufs)
io->outbufs = g_list_append(io->outbufs, buf);
else {
io->outbufs = g_list_append(io->outbufs, buf);
g_source_add_poll(source, &io->outfd);
}
return io->lastid;
}
static void
freeoutbuf(struct iooutbuf *buf) {
if(NULL != buf->data)
g_free(buf->data);
g_free(buf);
}
static gboolean
io_prepare(GSource *source SHUTUP, gint *timeout_) {
*timeout_ = -1;
return FALSE;
}
static gboolean
io_check(GSource *source) {
struct iosource *io = (struct iosource*)source;
if(io->infd.revents)
return TRUE;
if(NULL != io->outbufs && io->outfd.revents)
return TRUE;
else
return FALSE;
}
static gboolean
io_dispatch(GSource *source, GSourceFunc callback SHUTUP,
gpointer gdata SHUTUP) {
struct iosource *io = (struct iosource*)source;
if(io->infd.revents & (G_IO_ERR | G_IO_HUP) ||
io->outfd.revents & G_IO_ERR)
io_disconnect(io, 0 /* XXX how do I get errors here? */ );
else if(io->infd.revents & G_IO_IN)
(NULL == io->accepted ? io_read : io_accept)(io);
else if(io->outfd.revents & G_IO_OUT)
io_write(io);
else
return FALSE;
return TRUE;
}
static void
io_finalize(GSource *source SHUTUP) {
struct iosource *io = (struct iosource*)source;
if(NULL != io->outbufs) {
g_list_foreach(io->outbufs, (GFunc)freeoutbuf, NULL);
g_list_free(io->outbufs);
}
if(NULL != io->inbuf)
g_free(io->inbuf);
}
static void
io_biggify(char **buf, unsigned int used, unsigned int *max) {
if(used + IO_BLOCKSIZE > *max) {
*max += IO_BLOCKSIZE;
*buf = g_renew(char, *buf, *max);
}
}
static void
io_accept(struct iosource *io) {
int fd;
socklen_t len;
if(0 > (fd = accept(io->infd.fd, (struct sockaddr*)io->inbuf, &len))) {
if(EAGAIN == errno || ECONNABORTED == errno || EWOULDBLOCK == errno)
return;
io_disconnect(io, errno);
}
io->accepted((GSource*)io, fd, (struct sockaddr*)io->inbuf, len, io->cbdata);
}
static void
io_read(struct iosource *io) {
ssize_t res = 0;
gboolean newdata = FALSE;
unsigned int used;
int err = 0;
g_source_ref((GSource*)io);
do {
if(!newdata && 0 < res)
newdata = TRUE;
io->inused += res;
io_biggify(&io->inbuf, io->inused, &io->inmax);
errno = 0;
res = read(io->infd.fd, io->inbuf + io->inused, io->inmax - io->inused);
if(0 > res)
err = errno;
} while(0 < res);
if(NULL == io->received)
io->inused = 0;
else if(newdata) {
used = io->received((GSource*)io, io->inbuf, io->inused, io->cbdata);
if(used > io->inused)
used = io->inused;
if(0 < used) {
if(used < io->inused)
memmove(io->inbuf, io->inbuf + used, io->inused - used);
io->inused -= used;
}
}
if(0 != err && EAGAIN != err)
io_disconnect(io, err);
else if(0 == res)
io_disconnect(io, 0);
g_source_unref((GSource*)io);
}
static void
io_write(struct iosource *io) {
struct iooutbuf *buf;
ssize_t res = 1;
int err = 0;
g_source_ref((GSource*)io);
while(NULL != io->outbufs && 0 == err) {
buf = io->outbufs->data;
while(buf->off < buf->len && 0 < res) {
errno = 0;
res = write(io->outfd.fd, buf->data + buf->off, buf->len - buf->off);
if(0 > res)
err = errno;
else
buf->off += res;
}
if(buf->off >= buf->len) {
io->outbufs = g_list_remove(io->outbufs, buf);
if(NULL == io->outbufs)
g_source_remove_poll((GSource*)io, &io->outfd);
if(NULL != io->sent)
io->sent((GSource*)io, buf->id, io->cbdata);
freeoutbuf(buf);
}
}
if(0 != err && EAGAIN != err)
io_disconnect(io, err);
g_source_unref((GSource*)io);
}
static void
io_disconnect(struct iosource *io, int err) {
if(NULL != io->closed) {
errno = err;
io->closed((GSource*)io, io->cbdata);
}
if(NULL != io->outbufs)
g_source_remove_poll((GSource*)io, &io->outfd);
g_source_remove_poll((GSource*)io, &io->infd);
g_source_remove(g_source_get_id((GSource*)io));
g_source_unref((GSource*)io);
}

49
gtk/io.h Normal file
View File

@ -0,0 +1,49 @@
/*
Copyright (c) 2006 Joshua Elsasser. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TG_IO_H
#define TG_IO_H
typedef void (*iofunc_t)(GSource*, void*);
typedef void (*ioidfunc_t)(GSource*, unsigned int, void*);
typedef unsigned int (*iodatafunc_t)(GSource*, char*, unsigned int, void*);
typedef void (*ionewfunc_t)(GSource*, int, struct sockaddr*, socklen_t, void*);
GSource *
io_new(int fd, ioidfunc_t sent, iodatafunc_t received,
iofunc_t closed, void *cbdata);
GSource *
io_new_listening(int fd, socklen_t len, ionewfunc_t accepted,
iofunc_t closed, void *cbdata);
unsigned int
io_send(GSource *source, const char *data, unsigned int len);
unsigned int
io_send_keepdata(GSource *source, char *data, unsigned int len);
#endif /* TG_IO_H */

449
gtk/ipc.c Normal file
View File

@ -0,0 +1,449 @@
/*
Copyright (c) 2006 Joshua Elsasser. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include "bencode.h"
#include "conf.h"
#include "io.h"
#include "ipc.h"
#include "util.h"
#define PROTOVERS 1 /* IPC protocol version */
/* int, IPC protocol version */
#define MSG_VERSION ("version")
/* list of strings, full paths to torrent files to load */
#define MSG_ADDFILES ("addfiles")
enum contype { CON_SERV, CON_ADDFILE };
struct constate_serv {
void *wind;
add_torrent_func_t addfunc;
torrents_added_func_t donefunc;
void *cbdata;
};
struct constate_addfile {
GMainLoop *loop;
GList *files;
gboolean *succeeded;
unsigned int addid;
};
struct constate;
typedef void (*handler_func_t)(struct constate*, const char*, benc_val_t *);
struct handlerdef {char *name; handler_func_t func;};
struct constate {
GSource *source;
int fd;
const struct handlerdef *funcs;
enum contype type;
union {
struct constate_serv serv;
struct constate_addfile addfile;
} u;
};
void
ipc_socket_setup(void *parent, add_torrent_func_t addfunc,
torrents_added_func_t donefunc, void *cbdata);
gboolean
ipc_sendfiles_blocking(GList *files);
static void
serv_bind(struct constate *con);
static void
rmsock(void);
static gboolean
client_connect(char *path, struct constate *con);
static void
srv_io_accept(GSource *source, int fd, struct sockaddr *sa, socklen_t len,
void *vdata);
static int
send_msg(struct constate *con, const char *name, benc_val_t *val);
static int
send_msg_int(struct constate *con, const char *name, int num);
static unsigned int
all_io_received(GSource *source, char *data, unsigned int len, void *vdata);
static void
destroycon(struct constate *con);
static void
all_io_closed(GSource *source, void *vdata);
static void
srv_addfile(struct constate *con, const char *name, benc_val_t *val);
static void
afc_version(struct constate *con, const char *name, benc_val_t *val);
static void
afc_io_sent(GSource *source, unsigned int id, void *vdata);
static const struct handlerdef gl_funcs_serv[] = {
{MSG_ADDFILES, srv_addfile},
{NULL, NULL}
};
static const struct handlerdef gl_funcs_addfile[] = {
{MSG_VERSION, afc_version},
{NULL, NULL}
};
/* this is only used on the server */
static char *gl_sockpath = NULL;
void
ipc_socket_setup(void *parent, add_torrent_func_t addfunc,
torrents_added_func_t donefunc, void *cbdata) {
struct constate *con;
con = g_new0(struct constate, 1);
con->source = NULL;
con->fd = -1;
con->funcs = gl_funcs_serv;
con->type = CON_SERV;
con->u.serv.wind = parent;
con->u.serv.addfunc = addfunc;
con->u.serv.donefunc = donefunc;
con->u.serv.cbdata = cbdata;
serv_bind(con);
}
gboolean
ipc_sendfiles_blocking(GList *files) {
struct constate *con;
char *path;
gboolean ret = FALSE;
con = g_new0(struct constate, 1);
con->source = NULL;
con->fd = -1;
con->funcs = gl_funcs_addfile;
con->type = CON_ADDFILE;
con->u.addfile.loop = g_main_loop_new(NULL, TRUE);
con->u.addfile.files = files;
con->u.addfile.succeeded = &ret;
con->u.addfile.addid = 0;
path = cf_sockname();
if(!client_connect(path, con)) {
g_free(path);
destroycon(con);
return FALSE;
}
g_main_loop_run(con->u.addfile.loop);
return ret;
}
/* open a local socket for clients connections */
static void
serv_bind(struct constate *con) {
struct sockaddr_un sa;
rmsock();
gl_sockpath = cf_sockname();
if(0 > (con->fd = socket(AF_LOCAL, SOCK_STREAM, 0)))
goto fail;
bzero(&sa, sizeof(sa));
sa.sun_family = AF_LOCAL;
strncpy(sa.sun_path, gl_sockpath, sizeof(sa.sun_path) - 1);
/* unlink any existing socket file before trying to create ours */
unlink(gl_sockpath);
if(0 > bind(con->fd, (struct sockaddr *)&sa, SUN_LEN(&sa))) {
/* bind may fail if there was already a socket, so try twice */
unlink(gl_sockpath);
if(0 > bind(con->fd, (struct sockaddr *)&sa, SUN_LEN(&sa)))
goto fail;
}
if(0 > listen(con->fd, 5))
goto fail;
con->source = io_new_listening(con->fd, sizeof(struct sockaddr_un),
srv_io_accept, all_io_closed, con);
g_atexit(rmsock);
return;
fail:
errmsg(con->u.serv.wind, _("Failed to set up socket:\n%s"),
strerror(errno));
if(0 <= con->fd)
close(con->fd);
con->fd = -1;
rmsock();
}
static void
rmsock(void) {
if(NULL != gl_sockpath) {
unlink(gl_sockpath);
g_free(gl_sockpath);
}
}
static gboolean
client_connect(char *path, struct constate *con) {
struct sockaddr_un addr;
if(0 > (con->fd = socket(AF_UNIX, SOCK_STREAM, 0))) {
fprintf(stderr, _("failed to create socket: %s\n"), strerror(errno));
return FALSE;
}
bzero(&addr, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if(0 > connect(con->fd, (struct sockaddr*)&addr, SUN_LEN(&addr))) {
fprintf(stderr, _("failed to connect to %s: %s\n"), path, strerror(errno));
return FALSE;
}
con->source = io_new(con->fd, afc_io_sent, all_io_received,
all_io_closed, con);
return TRUE;
}
static void
srv_io_accept(GSource *source SHUTUP, int fd, struct sockaddr *sa SHUTUP,
socklen_t len SHUTUP, void *vdata) {
struct constate *con = vdata;
struct constate *newcon;
newcon = g_new(struct constate, 1);
memcpy(newcon, con, sizeof(*newcon));
newcon->fd = fd;
newcon->source = io_new(fd, NULL, all_io_received, all_io_closed, newcon);
if(NULL != newcon->source)
send_msg_int(newcon, MSG_VERSION, PROTOVERS);
else {
g_free(newcon);
close(fd);
}
}
static int
send_msg(struct constate *con, const char *name, benc_val_t *val) {
char *buf;
size_t used, total;
benc_val_t dict;
char stupid;
/* construct a dictionary value */
/* XXX I shouldn't be constructing benc_val_t's by hand */
bzero(&dict, sizeof(dict));
dict.type = TYPE_DICT;
dict.val.l.alloc = 2;
dict.val.l.count = 2;
dict.val.l.vals = g_new0(benc_val_t, 2);
dict.val.l.vals[0].type = TYPE_STR;
dict.val.l.vals[0].val.s.i = strlen(name);
dict.val.l.vals[0].val.s.s = (char*)name;
dict.val.l.vals[1] = *val;
/* bencode the dictionary, starting at offset 8 in the buffer */
buf = malloc(9);
g_assert(NULL != buf);
total = 9;
used = 8;
if(tr_bencSave(&dict, &buf, &used, &total)) {
g_assert_not_reached();
}
g_free(dict.val.l.vals);
/* write the bencoded data length into the first 8 bytes of the buffer */
stupid = buf[8];
snprintf(buf, 9, "%08X", used - 8);
buf[8] = stupid;
/* send the data */
return io_send_keepdata(con->source, buf, used);
}
static int
send_msg_int(struct constate *con, const char *name, int num) {
benc_val_t val;
bzero(&val, sizeof(val));
val.type = TYPE_INT;
val.val.i = num;
return send_msg(con, name, &val);
}
static unsigned int
all_io_received(GSource *source, char *data, unsigned int len, void *vdata) {
struct constate *con = vdata;
size_t size;
char stupid;
char *end;
benc_val_t msgs;
int ii, jj;
if(9 > len)
return 0;
stupid = data[8];
data[8] = '\0';
size = strtoul(data, NULL, 16);
data[8] = stupid;
if(size + 8 > len)
return 0;
if(!tr_bencLoad(data + 8, size, &msgs, &end) && TYPE_DICT == msgs.type) {
for(ii = 0; msgs.val.l.count > ii + 1; ii += 2)
if(TYPE_STR == msgs.val.l.vals[ii].type)
for(jj = 0; NULL != con->funcs[jj].name; jj++)
if(0 == strcmp(msgs.val.l.vals[ii].val.s.s, con->funcs[jj].name)) {
con->funcs[jj].func(con, msgs.val.l.vals[ii].val.s.s,
msgs.val.l.vals + ii + 1);
break;
}
tr_bencFree(&msgs);
}
return size + 8 +
all_io_received(source, data + size + 8, len - size - 8, con);
}
static void
destroycon(struct constate *con) {
con->source = NULL;
if(0 <= con->fd)
close(con->fd);
con->fd = -1;
switch(con->type) {
case CON_SERV:
break;
case CON_ADDFILE:
freestrlist(con->u.addfile.files);
g_main_loop_quit(con->u.addfile.loop);
break;
}
}
static void
all_io_closed(GSource *source SHUTUP, void *vdata) {
struct constate *con = vdata;
destroycon(con);
}
static void
srv_addfile(struct constate *con, const char *name SHUTUP, benc_val_t *val) {
struct constate_serv *srv = &con->u.serv;
GList *errs = NULL;
char *str;
int ii;
gboolean added;
benc_val_t *file;
if(TYPE_LIST == val->type) {
added = FALSE;
for(ii = 0; ii < val->val.l.count; ii++) {
file = &val->val.l.vals[ii];
if(TYPE_STR == file->type && g_utf8_validate(file->val.s.s, -1, NULL)) {
/* XXX somehow escape invalid utf-8 */
added = TRUE;
srv->addfunc(srv->cbdata, file->val.s.s, NULL, FALSE, &errs);
}
}
if(NULL != errs) {
str = joinstrlist(errs, "\n");
errmsg(srv->wind, ngettext("Failed to load the torrent file %s",
"Failed to load the torrent files:\n%s",
g_list_length(errs)), str);
freestrlist(errs);
g_free(str);
}
if(added)
srv->donefunc(srv->cbdata);
}
}
static void
afc_version(struct constate *con, const char *name SHUTUP, benc_val_t *val) {
struct constate_addfile *afc = &con->u.addfile;
GList *file;
benc_val_t list, *str;
if(TYPE_INT != val->type || PROTOVERS != val->val.i) {
fprintf(stderr, _("bad IPC protocol version\n"));
destroycon(con);
} else {
/* XXX handle getting a non-version tag, invalid data,
or nothing (read timeout) */
bzero(&list, sizeof(list));
list.type = TYPE_LIST;
list.val.l.alloc = g_list_length(afc->files);
list.val.l.vals = g_new0(benc_val_t, list.val.l.alloc);
for(file = afc->files; NULL != file; file = file->next) {
str = list.val.l.vals + list.val.l.count;
str->type = TYPE_STR;
str->val.s.i = strlen(file->data);
str->val.s.s = file->data;
list.val.l.count++;
}
g_list_free(afc->files);
afc->files = NULL;
afc->addid = send_msg(con, MSG_ADDFILES, &list);
tr_bencFree(&list);
}
}
static void
afc_io_sent(GSource *source SHUTUP, unsigned int id, void *vdata) {
struct constate_addfile *afc = &((struct constate*)vdata)->u.addfile;
if(0 < id && afc->addid == id) {
*(afc->succeeded) = TRUE;
destroycon(vdata);
}
}

39
gtk/ipc.h Normal file
View File

@ -0,0 +1,39 @@
/*
Copyright (c) 2006 Joshua Elsasser. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TG_IPC_H
#define TG_IPC_H
#include "util.h"
void
ipc_socket_setup(void *wind, add_torrent_func_t addfunc,
torrents_added_func_t donefunc, void *cbdata);
gboolean
ipc_sendfiles_blocking(GList *files);
#endif /* TG_IPC_H */

View File

@ -42,6 +42,7 @@
#include "conf.h"
#include "dialogs.h"
#include "ipc.h"
#include "transmission.h"
#include "trcellrenderertorrent.h"
#include "util.h"
@ -69,6 +70,9 @@ struct pieces {
char p[120];
};
GList *
readargs(int argc, char **argv);
void
maketypes(void);
gpointer
@ -77,7 +81,7 @@ void
tr_pieces_free(gpointer);
void
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved);
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved, GList *args);
GtkWidget *
makewind_toolbar(struct cbdata *data);
GtkWidget *
@ -141,6 +145,8 @@ makeidlist(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
void
maketorrentlist(tr_torrent_t *tor, void *data);
void
safepipe(void);
void
setupsighandlers(void);
void
fatalsig(int sig);
@ -192,6 +198,17 @@ main(int argc, char **argv) {
GList *saved;
const char *pref;
long intval;
GList *argfiles;
gboolean didinit, didlock;
safepipe();
argfiles = readargs(argc, argv);
didinit = cf_init(tr_getPrefsDirectory(), NULL);
didlock = FALSE;
if(NULL != argfiles && didinit && !(didlock = cf_lock(NULL)))
return !ipc_sendfiles_blocking(argfiles);
setupsighandlers();
@ -212,8 +229,8 @@ main(int argc, char **argv) {
"}\n"
"widget \"TransmissionDialog\" style \"transmission-standard\"\n");
if(cf_init(tr_getPrefsDirectory(), &err)) {
if(cf_lock(&err)) {
if(didinit || cf_init(tr_getPrefsDirectory(), &err)) {
if(didlock || cf_lock(&err)) {
/* create main window now so any error dialogs can be it's children */
mainwind = gtk_window_new(GTK_WINDOW_TOPLEVEL);
preferr = NULL;
@ -238,7 +255,7 @@ main(int argc, char **argv) {
tr_setBindPort(tr, intval);
maketypes();
makewind(mainwind, tr, saved);
makewind(mainwind, tr, saved, argfiles);
if(NULL != preferr)
gtk_widget_show_all(preferr);
@ -255,11 +272,27 @@ main(int argc, char **argv) {
g_free(err);
}
if(NULL != argfiles)
freestrlist(argfiles);
gtk_main();
return 0;
}
GList *
readargs(int argc, char **argv) {
while(0 < --argc) {
argv++;
if(0 == strcmp("--", *argv))
return checkfilenames(argc - 1, argv + 1);
else if('-' != argv[0][0])
return checkfilenames(argc, argv);
}
return NULL;
}
void
maketypes(void) {
tr_type_pieces = g_boxed_type_register_static(
@ -277,7 +310,7 @@ tr_pieces_free(gpointer data) {
}
void
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved, GList *args) {
GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
GtkWidget *status = gtk_statusbar_new();
@ -326,6 +359,9 @@ makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
}
g_list_free(saved);
for(ii = g_list_first(args); NULL != ii; ii = ii->next)
addtorrent(data, ii->data, NULL, FALSE, &loaderrs);
if(NULL != loaderrs) {
str = joinstrlist(loaderrs, "\n");
errmsg(GTK_WINDOW(wind), ngettext("Failed to load the torrent file %s",
@ -353,6 +389,8 @@ makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
MIN(height, req.width * 8 / 5) : MAX(height, req.width * 5 / 8)));
gtk_widget_show(wind);
ipc_socket_setup(GTK_WINDOW(wind), addtorrent, addedtorrents, data);
}
GtkWidget *
@ -1192,6 +1230,15 @@ maketorrentlist(tr_torrent_t *tor, void *data) {
*list = g_list_append(*list, tor);
}
void
safepipe(void) {
struct sigaction sa;
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
}
void
setupsighandlers(void) {
int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2};

View File

@ -30,6 +30,7 @@
#include <signal.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
@ -93,8 +94,7 @@ ratiostr(guint64 down, guint64 up) {
ratio = (double)up / (double)down;
return g_strdup_printf("%.*f", (10.0 > ratio ? 2 : (100.0 > ratio ? 1 : 0)),
ratio);
return g_strdup_printf("%.*f", BESTDECIMAL(ratio), ratio);
}
gboolean
@ -146,6 +146,17 @@ joinstrlist(GList *list, char *sep) {
return ret;
}
void
freestrlist(GList *list) {
GList *ii;
if(NULL != list) {
for(ii = g_list_first(list); NULL != ii; ii = ii->next)
g_free(ii->data);
g_list_free(list);
}
}
char *
urldecode(const char *str, int len) {
int ii, jj;
@ -183,6 +194,37 @@ urldecode(const char *str, int len) {
return ret;
}
GList *
checkfilenames(int argc, char **argv) {
char *pwd = g_get_current_dir();
int ii, cd;
char *dirstr, *filestr;
GList *ret = NULL;
for(ii = 0; ii < argc; ii++) {
dirstr = g_path_get_dirname(argv[ii]);
if(!g_path_is_absolute(argv[ii])) {
filestr = g_build_filename(pwd, dirstr, NULL);
g_free(dirstr);
dirstr = filestr;
}
cd = chdir(dirstr);
g_free(dirstr);
if(0 > cd)
continue;
dirstr = g_get_current_dir();
filestr = g_path_get_basename(argv[ii]);
ret = g_list_append(ret, g_build_filename(dirstr, filestr, NULL));
g_free(dirstr);
g_free(filestr);
}
chdir(pwd);
g_free(pwd);
return ret;
}
GtkWidget *
errmsg(GtkWindow *wind, const char *format, ...) {
GtkWidget *dialog;

View File

@ -37,6 +37,9 @@
#define SHUTUP
#endif
typedef gboolean (*add_torrent_func_t)(void *, const char *, const char *, gboolean, GList **);
typedef void (*torrents_added_func_t)(void *);
/* return number of items in array */
#define ALEN(a) (sizeof(a) / sizeof((a)[0]))
@ -66,10 +69,20 @@ mkdir_p(const char *name, mode_t mode);
char *
joinstrlist(GList *list, char *sep);
/* free a GList of strings */
void
freestrlist(GList *list);
/* decodes a string that has been urlencoded */
char *
urldecode(const char *str, int len);
/* return a list of cleaned-up paths, with invalid directories removed */
GList *
checkfilenames(int argc, char **argv);
#ifdef GTK_MAJOR_VERSION
/* if wind is NULL then you must call gtk_widget_show on the returned widget */
GtkWidget *
@ -91,4 +104,6 @@ GtkWidget *
verrmsg(GtkWindow *wind, callbackfunc_t func, void *data,
const char *format, va_list ap);
#endif /* GTK_MAJOR_VERSION */
#endif /* TG_UTIL_H */