mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-12-15 06:55:30 +08:00
902f089da6
This will allow setting the callback before the transfer is started, particularly to report queued transfer cancellations.
676 lines
15 KiB
C
676 lines
15 KiB
C
/*
|
|
*
|
|
* OBEX Client
|
|
*
|
|
* Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.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 St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <inttypes.h>
|
|
|
|
#include <glib.h>
|
|
#include <gdbus.h>
|
|
#include <gobex.h>
|
|
|
|
#include "log.h"
|
|
#include "transfer.h"
|
|
|
|
#define TRANSFER_INTERFACE "org.openobex.Transfer"
|
|
#define TRANSFER_BASEPATH "/org/openobex"
|
|
|
|
#define DEFAULT_BUFFER_SIZE 4096
|
|
|
|
#define OBC_TRANSFER_ERROR obc_transfer_error_quark()
|
|
|
|
static guint64 counter = 0;
|
|
|
|
struct transfer_callback {
|
|
transfer_callback_t func;
|
|
void *data;
|
|
};
|
|
|
|
struct obc_transfer {
|
|
GObex *obex;
|
|
struct obc_transfer_params *params;
|
|
struct transfer_callback *callback;
|
|
DBusConnection *conn;
|
|
char *agent; /* Transfer agent */
|
|
char *path; /* Transfer path */
|
|
gchar *filename; /* Transfer file location */
|
|
char *name; /* Transfer object name */
|
|
char *type; /* Transfer object type */
|
|
int fd;
|
|
guint xfer;
|
|
char *buffer;
|
|
size_t buffer_len;
|
|
int filled;
|
|
gint64 size;
|
|
gint64 transferred;
|
|
int err;
|
|
};
|
|
|
|
static GQuark obc_transfer_error_quark(void)
|
|
{
|
|
return g_quark_from_static_string("obc-transfer-error-quark");
|
|
}
|
|
|
|
static void append_entry(DBusMessageIter *dict,
|
|
const char *key, int type, void *val)
|
|
{
|
|
DBusMessageIter entry, value;
|
|
const char *signature;
|
|
|
|
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
|
|
NULL, &entry);
|
|
|
|
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
|
|
|
|
switch (type) {
|
|
case DBUS_TYPE_STRING:
|
|
signature = DBUS_TYPE_STRING_AS_STRING;
|
|
break;
|
|
case DBUS_TYPE_BYTE:
|
|
signature = DBUS_TYPE_BYTE_AS_STRING;
|
|
break;
|
|
case DBUS_TYPE_UINT64:
|
|
signature = DBUS_TYPE_UINT64_AS_STRING;
|
|
break;
|
|
default:
|
|
signature = DBUS_TYPE_VARIANT_AS_STRING;
|
|
break;
|
|
}
|
|
|
|
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
|
|
signature, &value);
|
|
dbus_message_iter_append_basic(&value, type, val);
|
|
dbus_message_iter_close_container(&entry, &value);
|
|
|
|
dbus_message_iter_close_container(dict, &entry);
|
|
}
|
|
|
|
static DBusMessage *obc_transfer_get_properties(DBusConnection *connection,
|
|
DBusMessage *message, void *user_data)
|
|
{
|
|
struct obc_transfer *transfer = user_data;
|
|
DBusMessage *reply;
|
|
DBusMessageIter iter, dict;
|
|
|
|
reply = dbus_message_new_method_return(message);
|
|
if (!reply)
|
|
return NULL;
|
|
|
|
dbus_message_iter_init_append(reply, &iter);
|
|
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
|
|
|
|
append_entry(&dict, "Name", DBUS_TYPE_STRING, &transfer->name);
|
|
append_entry(&dict, "Size", DBUS_TYPE_UINT64, &transfer->size);
|
|
append_entry(&dict, "Filename", DBUS_TYPE_STRING, &transfer->filename);
|
|
|
|
dbus_message_iter_close_container(&iter, &dict);
|
|
|
|
return reply;
|
|
}
|
|
|
|
static void obc_transfer_abort(struct obc_transfer *transfer)
|
|
{
|
|
struct transfer_callback *callback = transfer->callback;
|
|
|
|
if (transfer->xfer == 0)
|
|
return;
|
|
|
|
g_obex_cancel_transfer(transfer->xfer);
|
|
transfer->xfer = 0;
|
|
|
|
if (callback) {
|
|
GError *err;
|
|
|
|
err = g_error_new(OBC_TRANSFER_ERROR, -ECANCELED, "%s",
|
|
strerror(ECANCELED));
|
|
callback->func(transfer, transfer->transferred, err,
|
|
callback->data);
|
|
g_error_free(err);
|
|
}
|
|
}
|
|
|
|
static DBusMessage *obc_transfer_cancel(DBusConnection *connection,
|
|
DBusMessage *message, void *user_data)
|
|
{
|
|
struct obc_transfer *transfer = user_data;
|
|
const gchar *sender;
|
|
DBusMessage *reply;
|
|
|
|
sender = dbus_message_get_sender(message);
|
|
if (g_strcmp0(transfer->agent, sender) != 0)
|
|
return g_dbus_create_error(message,
|
|
"org.openobex.Error.NotAuthorized",
|
|
"Not Authorized");
|
|
|
|
reply = dbus_message_new_method_return(message);
|
|
if (!reply)
|
|
return NULL;
|
|
|
|
obc_transfer_abort(transfer);
|
|
|
|
return reply;
|
|
}
|
|
|
|
static GDBusMethodTable obc_transfer_methods[] = {
|
|
{ "GetProperties", "", "a{sv}", obc_transfer_get_properties },
|
|
{ "Cancel", "", "", obc_transfer_cancel },
|
|
{ }
|
|
};
|
|
|
|
static void obc_transfer_free(struct obc_transfer *transfer)
|
|
{
|
|
DBG("%p", transfer);
|
|
|
|
if (transfer->xfer)
|
|
g_obex_cancel_transfer(transfer->xfer);
|
|
|
|
if (transfer->fd > 0)
|
|
close(transfer->fd);
|
|
|
|
if (transfer->params != NULL) {
|
|
g_free(transfer->params->data);
|
|
g_free(transfer->params);
|
|
}
|
|
|
|
if (transfer->conn)
|
|
dbus_connection_unref(transfer->conn);
|
|
|
|
if (transfer->obex)
|
|
g_obex_unref(transfer->obex);
|
|
|
|
g_free(transfer->callback);
|
|
g_free(transfer->agent);
|
|
g_free(transfer->filename);
|
|
g_free(transfer->name);
|
|
g_free(transfer->type);
|
|
g_free(transfer->path);
|
|
g_free(transfer->buffer);
|
|
g_free(transfer);
|
|
}
|
|
|
|
struct obc_transfer *obc_transfer_register(DBusConnection *conn,
|
|
GObex *obex,
|
|
const char *agent,
|
|
const char *filename,
|
|
const char *name,
|
|
const char *type,
|
|
struct obc_transfer_params *params)
|
|
{
|
|
struct obc_transfer *transfer;
|
|
|
|
transfer = g_new0(struct obc_transfer, 1);
|
|
transfer->obex = g_obex_ref(obex);
|
|
transfer->agent = g_strdup(agent);
|
|
transfer->filename = g_strdup(filename);
|
|
transfer->name = g_strdup(name);
|
|
transfer->type = g_strdup(type);
|
|
transfer->params = params;
|
|
|
|
/* for OBEX specific mime types we don't need to register a transfer */
|
|
if (type != NULL &&
|
|
(strncmp(type, "x-obex/", 7) == 0 ||
|
|
strncmp(type, "x-bt/", 5) == 0))
|
|
goto done;
|
|
|
|
transfer->path = g_strdup_printf("%s/transfer%ju",
|
|
TRANSFER_BASEPATH, counter++);
|
|
|
|
transfer->conn = dbus_bus_get(DBUS_BUS_SESSION, NULL);
|
|
if (transfer->conn == NULL) {
|
|
obc_transfer_free(transfer);
|
|
return NULL;
|
|
}
|
|
|
|
if (g_dbus_register_interface(transfer->conn, transfer->path,
|
|
TRANSFER_INTERFACE,
|
|
obc_transfer_methods, NULL, NULL,
|
|
transfer, NULL) == FALSE) {
|
|
obc_transfer_free(transfer);
|
|
return NULL;
|
|
}
|
|
|
|
done:
|
|
DBG("%p registered %s", transfer, transfer->path);
|
|
|
|
return transfer;
|
|
}
|
|
|
|
void obc_transfer_unregister(struct obc_transfer *transfer)
|
|
{
|
|
if (transfer->path) {
|
|
g_dbus_unregister_interface(transfer->conn,
|
|
transfer->path, TRANSFER_INTERFACE);
|
|
}
|
|
|
|
DBG("%p unregistered %s", transfer, transfer->path);
|
|
|
|
obc_transfer_free(transfer);
|
|
}
|
|
|
|
static void obc_transfer_read(struct obc_transfer *transfer,
|
|
const void *buf, gsize len)
|
|
{
|
|
gsize bsize;
|
|
|
|
/* copy all buffered data */
|
|
bsize = transfer->buffer_len - transfer->filled;
|
|
|
|
if (bsize < len) {
|
|
transfer->buffer_len += len - bsize;
|
|
transfer->buffer = g_realloc(transfer->buffer,
|
|
transfer->buffer_len);
|
|
}
|
|
|
|
memcpy(transfer->buffer + transfer->filled, buf, len);
|
|
|
|
transfer->filled += len;
|
|
transfer->transferred += len;
|
|
}
|
|
|
|
static void get_buf_xfer_complete(GObex *obex, GError *err, gpointer user_data)
|
|
{
|
|
struct obc_transfer *transfer = user_data;
|
|
struct transfer_callback *callback = transfer->callback;
|
|
gsize bsize;
|
|
|
|
transfer->xfer = 0;
|
|
|
|
if (err) {
|
|
transfer->err = err->code;
|
|
goto done;
|
|
}
|
|
|
|
if (transfer->filled > 0 &&
|
|
transfer->buffer[transfer->filled - 1] == '\0')
|
|
goto done;
|
|
|
|
bsize = transfer->buffer_len - transfer->filled;
|
|
if (bsize < 1) {
|
|
transfer->buffer_len += 1;
|
|
transfer->buffer = g_realloc(transfer->buffer,
|
|
transfer->buffer_len);
|
|
}
|
|
|
|
transfer->buffer[transfer->filled] = '\0';
|
|
transfer->size = strlen(transfer->buffer);
|
|
|
|
done:
|
|
if (callback)
|
|
callback->func(transfer, transfer->size, err, callback->data);
|
|
}
|
|
|
|
static void get_buf_xfer_progress(GObex *obex, GError *err, GObexPacket *rsp,
|
|
gpointer user_data)
|
|
{
|
|
struct obc_transfer *transfer = user_data;
|
|
struct transfer_callback *callback = transfer->callback;
|
|
GObexPacket *req;
|
|
GObexHeader *hdr;
|
|
const guint8 *buf;
|
|
gsize len;
|
|
guint8 rspcode;
|
|
gboolean final;
|
|
|
|
if (err != NULL) {
|
|
get_buf_xfer_complete(obex, err, transfer);
|
|
return;
|
|
}
|
|
|
|
rspcode = g_obex_packet_get_operation(rsp, &final);
|
|
if (rspcode != G_OBEX_RSP_SUCCESS && rspcode != G_OBEX_RSP_CONTINUE) {
|
|
err = g_error_new(OBC_TRANSFER_ERROR, rspcode,
|
|
"Transfer failed (0x%02x)", rspcode);
|
|
get_buf_xfer_complete(obex, err, transfer);
|
|
g_error_free(err);
|
|
return;
|
|
}
|
|
|
|
hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_APPARAM);
|
|
if (hdr) {
|
|
g_obex_header_get_bytes(hdr, &buf, &len);
|
|
if (len != 0) {
|
|
if (transfer->params == NULL)
|
|
transfer->params =
|
|
g_new0(struct obc_transfer_params, 1);
|
|
else
|
|
g_free(transfer->params->data);
|
|
|
|
transfer->params->data = g_memdup(buf, len);
|
|
transfer->params->size = len;
|
|
}
|
|
}
|
|
|
|
hdr = g_obex_packet_get_body(rsp);
|
|
if (hdr) {
|
|
g_obex_header_get_bytes(hdr, &buf, &len);
|
|
if (len != 0)
|
|
obc_transfer_read(transfer, buf, len);
|
|
}
|
|
|
|
if (rspcode == G_OBEX_RSP_SUCCESS) {
|
|
get_buf_xfer_complete(obex, err, transfer);
|
|
return;
|
|
}
|
|
|
|
if (!g_obex_srm_active(obex)) {
|
|
req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, G_OBEX_HDR_INVALID);
|
|
|
|
transfer->xfer = g_obex_send_req(obex, req, -1,
|
|
get_buf_xfer_progress,
|
|
transfer, &err);
|
|
}
|
|
|
|
if (callback)
|
|
callback->func(transfer, transfer->transferred, err,
|
|
callback->data);
|
|
}
|
|
|
|
static void xfer_complete(GObex *obex, GError *err, gpointer user_data)
|
|
{
|
|
struct obc_transfer *transfer = user_data;
|
|
struct transfer_callback *callback = transfer->callback;
|
|
|
|
transfer->xfer = 0;
|
|
|
|
if (err) {
|
|
transfer->err = err->code;
|
|
goto done;
|
|
}
|
|
|
|
transfer->size = transfer->transferred;
|
|
|
|
done:
|
|
if (callback)
|
|
callback->func(transfer, transfer->size, err, callback->data);
|
|
}
|
|
|
|
static gboolean get_xfer_progress(const void *buf, gsize len,
|
|
gpointer user_data)
|
|
{
|
|
struct obc_transfer *transfer = user_data;
|
|
struct transfer_callback *callback = transfer->callback;
|
|
|
|
obc_transfer_read(transfer, buf, len);
|
|
|
|
if (transfer->fd > 0) {
|
|
gint w;
|
|
|
|
w = write(transfer->fd, transfer->buffer, transfer->filled);
|
|
if (w < 0) {
|
|
transfer->err = -errno;
|
|
return FALSE;
|
|
}
|
|
|
|
transfer->filled -= w;
|
|
}
|
|
|
|
if (callback)
|
|
callback->func(transfer, transfer->transferred, NULL,
|
|
callback->data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gssize put_buf_xfer_progress(void *buf, gsize len, gpointer user_data)
|
|
{
|
|
struct obc_transfer *transfer = user_data;
|
|
struct transfer_callback *callback = transfer->callback;
|
|
gsize size;
|
|
|
|
if (transfer->transferred == transfer->size)
|
|
return 0;
|
|
|
|
size = transfer->size - transfer->transferred;
|
|
size = len > size ? len : size;
|
|
if (size == 0)
|
|
return 0;
|
|
|
|
memcpy(buf, transfer->buffer + transfer->transferred, size);
|
|
|
|
transfer->transferred += size;
|
|
|
|
if (callback)
|
|
callback->func(transfer, transfer->transferred, NULL,
|
|
callback->data);
|
|
|
|
return size;
|
|
}
|
|
|
|
static gssize put_xfer_progress(void *buf, gsize len, gpointer user_data)
|
|
{
|
|
struct obc_transfer *transfer = user_data;
|
|
struct transfer_callback *callback = transfer->callback;
|
|
gssize size;
|
|
|
|
size = read(transfer->fd, buf, len);
|
|
if (size <= 0) {
|
|
transfer->err = -errno;
|
|
return size;
|
|
}
|
|
|
|
if (callback)
|
|
callback->func(transfer, transfer->transferred, NULL,
|
|
callback->data);
|
|
|
|
transfer->transferred += size;
|
|
|
|
return size;
|
|
}
|
|
|
|
gboolean obc_transfer_set_callback(struct obc_transfer *transfer,
|
|
transfer_callback_t func,
|
|
void *user_data)
|
|
{
|
|
struct transfer_callback *callback;
|
|
|
|
if (transfer->callback != NULL)
|
|
return FALSE;
|
|
|
|
callback = g_new0(struct transfer_callback, 1);
|
|
callback->func = func;
|
|
callback->data = user_data;
|
|
|
|
transfer->callback = callback;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int obc_transfer_get(struct obc_transfer *transfer)
|
|
{
|
|
GError *err = NULL;
|
|
GObexPacket *req;
|
|
GObexDataConsumer data_cb;
|
|
GObexFunc complete_cb;
|
|
GObexResponseFunc rsp_cb = NULL;
|
|
|
|
if (transfer->xfer != 0)
|
|
return -EALREADY;
|
|
|
|
if (transfer->type != NULL &&
|
|
(strncmp(transfer->type, "x-obex/", 7) == 0 ||
|
|
strncmp(transfer->type, "x-bt/", 5) == 0)) {
|
|
rsp_cb = get_buf_xfer_progress;
|
|
} else {
|
|
int fd = open(transfer->name ? : transfer->filename,
|
|
O_WRONLY | O_CREAT, 0600);
|
|
|
|
if (fd < 0) {
|
|
error("open(): %s(%d)", strerror(errno), errno);
|
|
return -errno;
|
|
}
|
|
transfer->fd = fd;
|
|
data_cb = get_xfer_progress;
|
|
complete_cb = xfer_complete;
|
|
}
|
|
|
|
req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, G_OBEX_HDR_INVALID);
|
|
|
|
if (transfer->filename != NULL)
|
|
g_obex_packet_add_unicode(req, G_OBEX_HDR_NAME,
|
|
transfer->filename);
|
|
|
|
if (transfer->type != NULL)
|
|
g_obex_packet_add_bytes(req, G_OBEX_HDR_TYPE, transfer->type,
|
|
strlen(transfer->type) + 1);
|
|
|
|
if (transfer->params != NULL)
|
|
g_obex_packet_add_bytes(req, G_OBEX_HDR_APPARAM,
|
|
transfer->params->data,
|
|
transfer->params->size);
|
|
|
|
if (rsp_cb)
|
|
transfer->xfer = g_obex_send_req(transfer->obex, req, -1,
|
|
rsp_cb, transfer, &err);
|
|
else
|
|
transfer->xfer = g_obex_get_req_pkt(transfer->obex, req,
|
|
data_cb, complete_cb, transfer,
|
|
&err);
|
|
|
|
if (transfer->xfer == 0)
|
|
return -ENOTCONN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int obc_transfer_put(struct obc_transfer *transfer)
|
|
{
|
|
GError *err = NULL;
|
|
GObexPacket *req;
|
|
GObexDataProducer data_cb;
|
|
|
|
if (transfer->xfer != 0)
|
|
return -EALREADY;
|
|
|
|
if (transfer->buffer) {
|
|
data_cb = put_buf_xfer_progress;
|
|
goto done;
|
|
}
|
|
|
|
data_cb = put_xfer_progress;
|
|
|
|
done:
|
|
req = g_obex_packet_new(G_OBEX_OP_PUT, FALSE, G_OBEX_HDR_INVALID);
|
|
|
|
if (transfer->name != NULL)
|
|
g_obex_packet_add_unicode(req, G_OBEX_HDR_NAME,
|
|
transfer->name);
|
|
|
|
if (transfer->type != NULL)
|
|
g_obex_packet_add_bytes(req, G_OBEX_HDR_TYPE, transfer->type,
|
|
strlen(transfer->type) + 1);
|
|
|
|
if (transfer->size < UINT32_MAX)
|
|
g_obex_packet_add_uint32(req, G_OBEX_HDR_LENGTH, transfer->size);
|
|
|
|
if (transfer->params != NULL)
|
|
g_obex_packet_add_bytes(req, G_OBEX_HDR_APPARAM,
|
|
transfer->params->data,
|
|
transfer->params->size);
|
|
|
|
transfer->xfer = g_obex_put_req_pkt(transfer->obex, req, data_cb,
|
|
xfer_complete, transfer,
|
|
&err);
|
|
if (transfer->xfer == 0)
|
|
return -ENOTCONN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int obc_transfer_get_params(struct obc_transfer *transfer,
|
|
struct obc_transfer_params *params)
|
|
{
|
|
params->data = transfer->params->data;
|
|
params->size = transfer->params->size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void obc_transfer_clear_buffer(struct obc_transfer *transfer)
|
|
{
|
|
transfer->filled = 0;
|
|
}
|
|
|
|
const char *obc_transfer_get_buffer(struct obc_transfer *transfer, size_t *size)
|
|
{
|
|
if (size)
|
|
*size = transfer->filled;
|
|
|
|
return transfer->buffer;
|
|
}
|
|
|
|
void obc_transfer_set_buffer(struct obc_transfer *transfer, char *buffer)
|
|
{
|
|
transfer->size = strlen(buffer);
|
|
transfer->buffer = buffer;
|
|
}
|
|
|
|
void obc_transfer_set_name(struct obc_transfer *transfer, const char *name)
|
|
{
|
|
g_free(transfer->name);
|
|
transfer->name = g_strdup(name);
|
|
}
|
|
|
|
const char *obc_transfer_get_path(struct obc_transfer *transfer)
|
|
{
|
|
return transfer->path;
|
|
}
|
|
|
|
gint64 obc_transfer_get_size(struct obc_transfer *transfer)
|
|
{
|
|
return transfer->size;
|
|
}
|
|
|
|
int obc_transfer_set_file(struct obc_transfer *transfer)
|
|
{
|
|
int fd;
|
|
struct stat st;
|
|
|
|
fd = open(transfer->filename, O_RDONLY);
|
|
if (fd < 0) {
|
|
error("open(): %s(%d)", strerror(errno), errno);
|
|
return -errno;
|
|
}
|
|
|
|
if (fstat(fd, &st) < 0) {
|
|
error("fstat(): %s(%d)", strerror(errno), errno);
|
|
close(fd);
|
|
return -errno;
|
|
}
|
|
|
|
transfer->fd = fd;
|
|
transfer->size = st.st_size;
|
|
|
|
return 0;
|
|
}
|