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:
parent
9120989a88
commit
1cfe027fdb
|
@ -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
|
||||
|
|
122
gtk/conf.c
122
gtk/conf.c
|
@ -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;
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 */
|
57
gtk/main.c
57
gtk/main.c
|
@ -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};
|
||||
|
|
46
gtk/util.c
46
gtk/util.c
|
@ -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;
|
||||
|
|
15
gtk/util.h
15
gtk/util.h
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue