| /* |
| * |
| * OBEX Client |
| * |
| * Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org> |
| * Copyright (C) 2011-2012 BMW Car IT GmbH. All rights reserved. |
| * |
| * |
| * 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 <stdlib.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include <glib.h> |
| |
| #include "gdbus/gdbus.h" |
| #include "gobex/gobex.h" |
| |
| #include "obexd/src/log.h" |
| #include "dbus.h" |
| #include "transfer.h" |
| #include "session.h" |
| #include "driver.h" |
| #include "transport.h" |
| |
| #define SESSION_INTERFACE "org.bluez.obex.Session1" |
| #define ERROR_INTERFACE "org.bluez.obex.Error" |
| #define SESSION_BASEPATH "/org/bluez/obex/client" |
| |
| #define OBEX_IO_ERROR obex_io_error_quark() |
| #define OBEX_IO_ERROR_FIRST (0xff + 1) |
| |
| enum { |
| OBEX_IO_DISCONNECTED = OBEX_IO_ERROR_FIRST, |
| OBEX_IO_BUSY, |
| }; |
| |
| static guint64 counter = 0; |
| |
| struct callback_data { |
| struct obc_session *session; |
| guint id; |
| session_callback_t func; |
| void *data; |
| }; |
| |
| struct pending_request; |
| typedef int (*session_process_t) (struct pending_request *p, GError **err); |
| typedef void (*destroy_t) (void *data); |
| |
| struct pending_request { |
| guint id; |
| guint req_id; |
| struct obc_session *session; |
| session_process_t process; |
| struct obc_transfer *transfer; |
| session_callback_t func; |
| void *data; |
| destroy_t destroy; |
| }; |
| |
| struct setpath_data { |
| char **remaining; |
| int index; |
| session_callback_t func; |
| void *user_data; |
| }; |
| |
| struct file_data { |
| char *srcname; |
| char *destname; |
| session_callback_t func; |
| void *user_data; |
| }; |
| |
| struct obc_session { |
| guint id; |
| int refcount; |
| char *source; |
| char *destination; |
| uint8_t channel; |
| struct obc_transport *transport; |
| struct obc_driver *driver; |
| char *path; /* Session path */ |
| DBusConnection *conn; |
| GObex *obex; |
| struct pending_request *p; |
| char *owner; /* Session owner */ |
| guint watch; |
| GQueue *queue; |
| guint process_id; |
| char *folder; |
| struct callback_data *callback; |
| }; |
| |
| static GSList *sessions = NULL; |
| |
| static void session_process_queue(struct obc_session *session); |
| static void session_terminate_transfer(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *gerr); |
| static void transfer_complete(struct obc_transfer *transfer, |
| GError *err, void *user_data); |
| |
| static GQuark obex_io_error_quark(void) |
| { |
| return g_quark_from_static_string("obex-io-error-quark"); |
| } |
| |
| struct obc_session *obc_session_ref(struct obc_session *session) |
| { |
| int refs = __sync_add_and_fetch(&session->refcount, 1); |
| |
| DBG("%p: ref=%d", session, refs); |
| |
| return session; |
| } |
| |
| static void session_unregistered(struct obc_session *session) |
| { |
| char *path; |
| |
| if (session->driver && session->driver->remove) |
| session->driver->remove(session); |
| |
| path = session->path; |
| session->path = NULL; |
| |
| g_dbus_unregister_interface(session->conn, path, SESSION_INTERFACE); |
| |
| DBG("Session(%p) unregistered %s", session, path); |
| |
| g_free(path); |
| } |
| |
| static struct pending_request *pending_request_new(struct obc_session *session, |
| session_process_t process, |
| struct obc_transfer *transfer, |
| session_callback_t func, |
| void *data, |
| destroy_t destroy) |
| { |
| struct pending_request *p; |
| static guint id = 0; |
| |
| p = g_new0(struct pending_request, 1); |
| p->id = ++id; |
| p->session = obc_session_ref(session); |
| p->process = process; |
| p->destroy = destroy; |
| p->transfer = transfer; |
| p->func = func; |
| p->data = data; |
| |
| return p; |
| } |
| |
| static void pending_request_free(struct pending_request *p) |
| { |
| if (p->req_id > 0) |
| g_obex_cancel_req(p->session->obex, p->req_id, TRUE); |
| |
| if (p->destroy) |
| p->destroy(p->data); |
| |
| if (p->transfer) |
| obc_transfer_unregister(p->transfer); |
| |
| if (p->session) |
| obc_session_unref(p->session); |
| |
| g_free(p); |
| } |
| |
| static void setpath_data_free(void *process_data) |
| { |
| struct setpath_data *data = process_data; |
| |
| g_strfreev(data->remaining); |
| g_free(data); |
| } |
| |
| static void file_data_free(void *process_data) |
| { |
| struct file_data *data = process_data; |
| |
| g_free(data->srcname); |
| g_free(data->destname); |
| g_free(data); |
| } |
| |
| static void session_free(struct obc_session *session) |
| { |
| DBG("%p", session); |
| |
| if (session->process_id != 0) |
| g_source_remove(session->process_id); |
| |
| if (session->queue) { |
| g_queue_foreach(session->queue, (GFunc) pending_request_free, |
| NULL); |
| g_queue_free(session->queue); |
| } |
| |
| if (session->watch) |
| g_dbus_remove_watch(session->conn, session->watch); |
| |
| if (session->obex) { |
| g_obex_set_disconnect_function(session->obex, NULL, NULL); |
| g_obex_unref(session->obex); |
| } |
| |
| if (session->id > 0 && session->transport != NULL) |
| session->transport->disconnect(session->id); |
| |
| if (session->path) |
| session_unregistered(session); |
| |
| if (session->conn) |
| dbus_connection_unref(session->conn); |
| |
| if (session->p) |
| pending_request_free(session->p); |
| |
| g_free(session->path); |
| g_free(session->owner); |
| g_free(session->source); |
| g_free(session->destination); |
| g_free(session->folder); |
| g_free(session); |
| } |
| |
| static void disconnect_complete(GObex *obex, GError *err, GObexPacket *rsp, |
| void *user_data) |
| { |
| struct obc_session *session = user_data; |
| |
| DBG(""); |
| |
| if (err) |
| error("%s", err->message); |
| |
| /* Disconnect transport */ |
| if (session->id > 0 && session->transport != NULL) { |
| session->transport->disconnect(session->id); |
| session->id = 0; |
| } |
| |
| session_free(session); |
| } |
| |
| void obc_session_unref(struct obc_session *session) |
| { |
| int refs; |
| |
| refs = __sync_sub_and_fetch(&session->refcount, 1); |
| |
| DBG("%p: ref=%d", session, refs); |
| |
| if (refs > 0) |
| return; |
| |
| sessions = g_slist_remove(sessions, session); |
| |
| if (!session->obex) |
| goto disconnect; |
| |
| /* Wait OBEX Disconnect to complete if command succeed otherwise |
| * proceed with transport disconnection since there is nothing else to |
| * be done */ |
| if (g_obex_disconnect(session->obex, disconnect_complete, session, |
| NULL)) |
| return; |
| |
| disconnect: |
| /* Disconnect transport */ |
| if (session->id > 0 && session->transport != NULL) { |
| session->transport->disconnect(session->id); |
| session->id = 0; |
| } |
| |
| session_free(session); |
| } |
| |
| static void callback_destroy(struct callback_data *callback, GError *err) |
| { |
| struct obc_session *session = callback->session; |
| |
| if (callback->id > 0) |
| g_obex_cancel_req(session->obex, callback->id, TRUE); |
| |
| callback->func(session, NULL, err, callback->data); |
| g_free(callback); |
| session->callback = NULL; |
| obc_session_unref(session); |
| } |
| |
| static void connect_cb(GObex *obex, GError *err, GObexPacket *rsp, |
| gpointer user_data) |
| { |
| struct callback_data *callback = user_data; |
| GError *gerr = NULL; |
| uint8_t rsp_code; |
| |
| callback->id = 0; |
| |
| if (err != NULL) { |
| error("connect_cb: %s", err->message); |
| gerr = g_error_copy(err); |
| goto done; |
| } |
| |
| rsp_code = g_obex_packet_get_operation(rsp, NULL); |
| if (rsp_code != G_OBEX_RSP_SUCCESS) |
| gerr = g_error_new(OBEX_IO_ERROR, -EIO, |
| "OBEX Connect failed with 0x%02x", rsp_code); |
| |
| done: |
| callback_destroy(callback, gerr); |
| if (gerr != NULL) |
| g_error_free(gerr); |
| } |
| |
| static void session_disconnected(GObex *obex, GError *err, gpointer user_data) |
| { |
| struct obc_session *session = user_data; |
| |
| if (err) |
| error("%s", err->message); |
| |
| obc_session_shutdown(session); |
| } |
| |
| static void transport_func(GIOChannel *io, GError *err, gpointer user_data) |
| { |
| struct callback_data *callback = user_data; |
| struct obc_session *session = callback->session; |
| struct obc_driver *driver = session->driver; |
| struct obc_transport *transport = session->transport; |
| GObex *obex; |
| GObexApparam *apparam; |
| GObexTransportType type; |
| int tx_mtu = -1; |
| int rx_mtu = -1; |
| |
| DBG(""); |
| |
| if (err != NULL) { |
| error("%s", err->message); |
| goto done; |
| } |
| |
| g_io_channel_set_close_on_unref(io, FALSE); |
| |
| if (transport->getpacketopt && |
| transport->getpacketopt(io, &tx_mtu, &rx_mtu) == 0) |
| type = G_OBEX_TRANSPORT_PACKET; |
| else |
| type = G_OBEX_TRANSPORT_STREAM; |
| |
| obex = g_obex_new(io, type, tx_mtu, rx_mtu); |
| if (obex == NULL) |
| goto done; |
| |
| g_io_channel_set_close_on_unref(io, TRUE); |
| |
| apparam = NULL; |
| |
| if (driver->supported_features) |
| apparam = driver->supported_features(session); |
| |
| if (apparam) { |
| uint8_t buf[1024]; |
| ssize_t len; |
| |
| len = g_obex_apparam_encode(apparam, buf, sizeof(buf)); |
| if (driver->target) |
| callback->id = g_obex_connect(obex, connect_cb, |
| callback, &err, |
| G_OBEX_HDR_TARGET, |
| driver->target, driver->target_len, |
| G_OBEX_HDR_APPARAM, |
| buf, len, |
| G_OBEX_HDR_INVALID); |
| else |
| callback->id = g_obex_connect(obex, connect_cb, |
| callback, &err, |
| G_OBEX_HDR_APPARAM, buf, len, |
| G_OBEX_HDR_INVALID); |
| g_obex_apparam_free(apparam); |
| } else if (driver->target) |
| callback->id = g_obex_connect(obex, connect_cb, callback, &err, |
| G_OBEX_HDR_TARGET, driver->target, driver->target_len, |
| G_OBEX_HDR_INVALID); |
| else |
| callback->id = g_obex_connect(obex, connect_cb, callback, |
| &err, G_OBEX_HDR_INVALID); |
| |
| if (err != NULL) { |
| error("%s", err->message); |
| g_obex_unref(obex); |
| goto done; |
| } |
| |
| session->obex = obex; |
| sessions = g_slist_prepend(sessions, session); |
| |
| g_obex_set_disconnect_function(obex, session_disconnected, session); |
| |
| return; |
| done: |
| callback_destroy(callback, err); |
| } |
| |
| static void owner_disconnected(DBusConnection *connection, void *user_data) |
| { |
| struct obc_session *session = user_data; |
| GError *err; |
| |
| DBG(""); |
| |
| /* |
| * If connection still connecting notify the callback to destroy the |
| * session. |
| */ |
| if (session->callback) { |
| err = g_error_new(OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, |
| "Session closed by user"); |
| callback_destroy(session->callback, err); |
| g_error_free(err); |
| return; |
| } |
| |
| obc_session_shutdown(session); |
| } |
| |
| int obc_session_set_owner(struct obc_session *session, const char *name, |
| GDBusWatchFunction func) |
| { |
| if (session == NULL) |
| return -EINVAL; |
| |
| if (session->watch) |
| g_dbus_remove_watch(session->conn, session->watch); |
| |
| session->watch = g_dbus_add_disconnect_watch(session->conn, name, func, |
| session, NULL); |
| if (session->watch == 0) |
| return -EINVAL; |
| |
| session->owner = g_strdup(name); |
| |
| return 0; |
| } |
| |
| |
| static struct obc_session *session_find(const char *source, |
| const char *destination, |
| const char *service, |
| uint8_t channel, |
| const char *owner) |
| { |
| GSList *l; |
| |
| for (l = sessions; l; l = l->next) { |
| struct obc_session *session = l->data; |
| |
| if (g_strcmp0(session->destination, destination)) |
| continue; |
| |
| if (g_strcmp0(service, session->driver->service)) |
| continue; |
| |
| if (source && g_strcmp0(session->source, source)) |
| continue; |
| |
| if (channel && session->channel != channel) |
| continue; |
| |
| if (g_strcmp0(owner, session->owner)) |
| continue; |
| |
| return session; |
| } |
| |
| return NULL; |
| } |
| |
| static gboolean connection_complete(gpointer data) |
| { |
| struct callback_data *cb = data; |
| |
| cb->func(cb->session, NULL, NULL, cb->data); |
| |
| obc_session_unref(cb->session); |
| |
| g_free(cb); |
| |
| return FALSE; |
| } |
| |
| static int session_connect(struct obc_session *session, |
| session_callback_t function, void *user_data) |
| { |
| struct callback_data *callback; |
| struct obc_transport *transport = session->transport; |
| struct obc_driver *driver = session->driver; |
| |
| callback = g_try_malloc0(sizeof(*callback)); |
| if (callback == NULL) |
| return -ENOMEM; |
| |
| callback->func = function; |
| callback->data = user_data; |
| callback->session = obc_session_ref(session); |
| |
| /* Connection completed */ |
| if (session->obex) { |
| g_idle_add(connection_complete, callback); |
| return 0; |
| } |
| |
| /* Ongoing connection */ |
| if (session->id > 0) { |
| obc_session_unref(callback->session); |
| g_free(callback); |
| return 0; |
| } |
| |
| session->id = transport->connect(session->source, session->destination, |
| driver->uuid, session->channel, |
| transport_func, callback); |
| if (session->id == 0) { |
| obc_session_unref(callback->session); |
| g_free(callback); |
| return -EINVAL; |
| } |
| |
| session->callback = callback; |
| |
| return 0; |
| } |
| |
| struct obc_session *obc_session_create(const char *source, |
| const char *destination, |
| const char *service, |
| uint8_t channel, |
| const char *owner, |
| session_callback_t function, |
| void *user_data) |
| { |
| DBusConnection *conn; |
| struct obc_session *session; |
| struct obc_transport *transport; |
| struct obc_driver *driver; |
| |
| if (destination == NULL) |
| return NULL; |
| |
| session = session_find(source, destination, service, channel, owner); |
| if (session != NULL) |
| goto proceed; |
| |
| /* FIXME: Do proper transport lookup when the API supports it */ |
| transport = obc_transport_find("Bluetooth"); |
| if (transport == NULL) |
| return NULL; |
| |
| driver = obc_driver_find(service); |
| if (driver == NULL) |
| return NULL; |
| |
| conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); |
| if (conn == NULL) |
| return NULL; |
| |
| session = g_try_malloc0(sizeof(*session)); |
| if (session == NULL) |
| return NULL; |
| |
| session->refcount = 1; |
| session->transport = transport; |
| session->driver = driver; |
| session->conn = conn; |
| session->source = g_strdup(source); |
| session->destination = g_strdup(destination); |
| session->channel = channel; |
| session->queue = g_queue_new(); |
| session->folder = g_strdup("/"); |
| |
| if (owner) |
| obc_session_set_owner(session, owner, owner_disconnected); |
| |
| proceed: |
| if (session_connect(session, function, user_data) < 0) { |
| obc_session_unref(session); |
| return NULL; |
| } |
| |
| DBG("session %p transport %s driver %s", session, |
| session->transport->name, session->driver->service); |
| |
| return session; |
| } |
| |
| void obc_session_shutdown(struct obc_session *session) |
| { |
| struct pending_request *p; |
| GError *err; |
| |
| DBG("%p", session); |
| |
| obc_session_ref(session); |
| |
| /* Unregister any pending transfer */ |
| err = g_error_new(OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, |
| "Session closed by user"); |
| |
| if (session->p != NULL && session->p->id != 0) { |
| p = session->p; |
| session->p = NULL; |
| |
| if (p->func) |
| p->func(session, p->transfer, err, p->data); |
| |
| pending_request_free(p); |
| } |
| |
| while ((p = g_queue_pop_head(session->queue))) { |
| if (p->func) |
| p->func(session, p->transfer, err, p->data); |
| |
| pending_request_free(p); |
| } |
| |
| g_error_free(err); |
| |
| /* Unregister interfaces */ |
| if (session->path) |
| session_unregistered(session); |
| |
| obc_session_unref(session); |
| } |
| |
| static void capabilities_complete_callback(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *err, void *user_data) |
| { |
| DBusMessage *message = user_data; |
| char *contents; |
| size_t size; |
| int perr; |
| |
| if (err != NULL) { |
| DBusMessage *error = g_dbus_create_error(message, |
| ERROR_INTERFACE ".Failed", |
| "%s", err->message); |
| g_dbus_send_message(session->conn, error); |
| goto done; |
| } |
| |
| perr = obc_transfer_get_contents(transfer, &contents, &size); |
| if (perr < 0) { |
| DBusMessage *error = g_dbus_create_error(message, |
| ERROR_INTERFACE ".Failed", |
| "Error reading contents: %s", |
| strerror(-perr)); |
| g_dbus_send_message(session->conn, error); |
| goto done; |
| } |
| |
| g_dbus_send_reply(session->conn, message, |
| DBUS_TYPE_STRING, &contents, |
| DBUS_TYPE_INVALID); |
| g_free(contents); |
| |
| done: |
| dbus_message_unref(message); |
| } |
| |
| static DBusMessage *get_capabilities(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| struct obc_session *session = user_data; |
| struct obc_transfer *pull; |
| DBusMessage *reply; |
| GError *gerr = NULL; |
| |
| pull = obc_transfer_get("x-obex/capability", NULL, NULL, &gerr); |
| if (pull == NULL) |
| goto fail; |
| |
| if (!obc_session_queue(session, pull, capabilities_complete_callback, |
| message, &gerr)) |
| goto fail; |
| |
| dbus_message_ref(message); |
| |
| return NULL; |
| |
| fail: |
| reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", |
| gerr->message); |
| g_error_free(gerr); |
| return reply; |
| |
| } |
| |
| static gboolean get_source(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct obc_session *session = data; |
| |
| if (session->source == NULL) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, |
| &session->source); |
| |
| return TRUE; |
| } |
| |
| static gboolean source_exists(const GDBusPropertyTable *property, void *data) |
| { |
| struct obc_session *session = data; |
| |
| return session->source != NULL; |
| } |
| |
| static gboolean get_destination(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct obc_session *session = data; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, |
| &session->destination); |
| |
| return TRUE; |
| } |
| |
| static gboolean get_channel(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct obc_session *session = data; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, |
| &session->channel); |
| |
| return TRUE; |
| } |
| |
| static const GDBusMethodTable session_methods[] = { |
| { GDBUS_ASYNC_METHOD("GetCapabilities", |
| NULL, GDBUS_ARGS({ "capabilities", "s" }), |
| get_capabilities) }, |
| { } |
| }; |
| |
| static gboolean get_target(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct obc_session *session = data; |
| |
| if (session->driver->uuid == NULL) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, |
| &session->driver->uuid); |
| |
| return TRUE; |
| } |
| |
| static gboolean target_exists(const GDBusPropertyTable *property, void *data) |
| { |
| struct obc_session *session = data; |
| |
| return session->driver->uuid != NULL; |
| } |
| |
| static const GDBusPropertyTable session_properties[] = { |
| { "Source", "s", get_source, NULL, source_exists }, |
| { "Destination", "s", get_destination }, |
| { "Channel", "y", get_channel }, |
| { "Target", "s", get_target, NULL, target_exists }, |
| { } |
| }; |
| |
| static gboolean session_process(gpointer data) |
| { |
| struct obc_session *session = data; |
| |
| session->process_id = 0; |
| |
| session_process_queue(session); |
| |
| return FALSE; |
| } |
| |
| static void session_queue(struct pending_request *p) |
| { |
| g_queue_push_tail(p->session->queue, p); |
| |
| if (p->session->process_id == 0) |
| p->session->process_id = g_idle_add(session_process, |
| p->session); |
| } |
| |
| static int session_process_transfer(struct pending_request *p, GError **err) |
| { |
| if (!obc_transfer_start(p->transfer, p->session->obex, err)) |
| return -1; |
| |
| DBG("Tranfer(%p) started", p->transfer); |
| p->session->p = p; |
| return 0; |
| } |
| |
| guint obc_session_queue(struct obc_session *session, |
| struct obc_transfer *transfer, |
| session_callback_t func, void *user_data, |
| GError **err) |
| { |
| struct pending_request *p; |
| |
| if (session->obex == NULL) { |
| obc_transfer_unregister(transfer); |
| g_set_error(err, OBEX_IO_ERROR, -ENOTCONN, |
| "Session not connected"); |
| return 0; |
| } |
| |
| if (!obc_transfer_register(transfer, session->conn, session->path, |
| session->owner, err)) { |
| obc_transfer_unregister(transfer); |
| return 0; |
| } |
| |
| obc_transfer_set_callback(transfer, transfer_complete, session); |
| |
| p = pending_request_new(session, session_process_transfer, transfer, |
| func, user_data, NULL); |
| session_queue(p); |
| return p->id; |
| } |
| |
| static void session_process_queue(struct obc_session *session) |
| { |
| struct pending_request *p; |
| |
| if (session->p != NULL) |
| return; |
| |
| if (session->queue == NULL || g_queue_is_empty(session->queue)) |
| return; |
| |
| obc_session_ref(session); |
| |
| while ((p = g_queue_pop_head(session->queue))) { |
| GError *gerr = NULL; |
| |
| if (p->process(p, &gerr) == 0) |
| break; |
| |
| if (p->func) |
| p->func(session, p->transfer, gerr, p->data); |
| |
| g_clear_error(&gerr); |
| |
| pending_request_free(p); |
| } |
| |
| obc_session_unref(session); |
| } |
| |
| static int pending_transfer_cmptransfer(gconstpointer a, gconstpointer b) |
| { |
| const struct pending_request *p = a; |
| const struct obc_transfer *transfer = b; |
| |
| if (p->transfer == transfer) |
| return 0; |
| |
| return -1; |
| } |
| |
| static void session_terminate_transfer(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *gerr) |
| { |
| struct pending_request *p = session->p; |
| |
| if (p == NULL || p->transfer != transfer) { |
| GList *match; |
| |
| match = g_list_find_custom(session->queue->head, transfer, |
| pending_transfer_cmptransfer); |
| if (match == NULL) |
| return; |
| |
| p = match->data; |
| g_queue_delete_link(session->queue, match); |
| } else |
| session->p = NULL; |
| |
| obc_session_ref(session); |
| |
| if (p->func) |
| p->func(session, p->transfer, gerr, p->data); |
| |
| pending_request_free(p); |
| |
| if (session->p == NULL) |
| session_process_queue(session); |
| |
| obc_session_unref(session); |
| } |
| |
| static void session_notify_complete(struct obc_session *session, |
| struct obc_transfer *transfer) |
| { |
| DBG("Transfer(%p) complete", transfer); |
| |
| session_terminate_transfer(session, transfer, NULL); |
| } |
| |
| static void session_notify_error(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *err) |
| { |
| error("Transfer(%p) Error: %s", transfer, err->message); |
| |
| session_terminate_transfer(session, transfer, err); |
| } |
| |
| static void transfer_complete(struct obc_transfer *transfer, |
| GError *err, void *user_data) |
| { |
| struct obc_session *session = user_data; |
| |
| if (err != 0) |
| goto fail; |
| |
| session_notify_complete(session, transfer); |
| |
| return; |
| |
| fail: |
| session_notify_error(session, transfer, err); |
| } |
| |
| const char *obc_session_register(struct obc_session *session, |
| GDBusDestroyFunction destroy) |
| { |
| if (session->path) |
| return session->path; |
| |
| session->path = g_strdup_printf("%s/session%ju", |
| SESSION_BASEPATH, counter++); |
| |
| if (g_dbus_register_interface(session->conn, session->path, |
| SESSION_INTERFACE, session_methods, |
| NULL, session_properties, session, |
| destroy) == FALSE) |
| goto fail; |
| |
| if (session->driver->probe && session->driver->probe(session) < 0) { |
| g_dbus_unregister_interface(session->conn, session->path, |
| SESSION_INTERFACE); |
| goto fail; |
| } |
| |
| DBG("Session(%p) registered %s", session, session->path); |
| |
| return session->path; |
| |
| fail: |
| g_free(session->path); |
| session->path = NULL; |
| return NULL; |
| } |
| |
| const void *obc_session_get_attribute(struct obc_session *session, |
| int attribute_id) |
| { |
| if (session == NULL || session->id == 0) |
| return NULL; |
| |
| return session->transport->getattribute(session->id, attribute_id); |
| } |
| |
| const char *obc_session_get_owner(struct obc_session *session) |
| { |
| if (session == NULL) |
| return NULL; |
| |
| return session->owner; |
| } |
| |
| const char *obc_session_get_destination(struct obc_session *session) |
| { |
| return session->destination; |
| } |
| |
| const char *obc_session_get_path(struct obc_session *session) |
| { |
| return session->path; |
| } |
| |
| const char *obc_session_get_target(struct obc_session *session) |
| { |
| return session->driver->target; |
| } |
| |
| const char *obc_session_get_folder(struct obc_session *session) |
| { |
| return session->folder; |
| } |
| |
| static void setpath_complete(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *err, void *user_data) |
| { |
| struct pending_request *p = user_data; |
| |
| if (p->func) |
| p->func(session, NULL, err, p->data); |
| |
| if (session->p == p) |
| session->p = NULL; |
| |
| pending_request_free(p); |
| |
| session_process_queue(session); |
| } |
| |
| static void setpath_op_complete(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *err, void *user_data) |
| { |
| struct setpath_data *data = user_data; |
| |
| if (data->func) |
| data->func(session, NULL, err, data->user_data); |
| } |
| |
| static void setpath_set_folder(struct obc_session *session, const char *cur) |
| { |
| char *folder = NULL; |
| const char *delim; |
| |
| delim = strrchr(session->folder, '/'); |
| if (strlen(cur) == 0 || delim == NULL || |
| (strcmp(cur, "..") == 0 && delim == session->folder)) { |
| folder = g_strdup("/"); |
| } else { |
| if (strcmp(cur, "..") == 0) { |
| folder = g_strndup(session->folder, |
| delim - session->folder); |
| } else { |
| if (g_str_has_suffix(session->folder, "/")) |
| folder = g_strconcat(session->folder, |
| cur, NULL); |
| else |
| folder = g_strconcat(session->folder, "/", |
| cur, NULL); |
| } |
| } |
| g_free(session->folder); |
| session->folder = folder; |
| } |
| |
| static void setpath_cb(GObex *obex, GError *err, GObexPacket *rsp, |
| gpointer user_data) |
| { |
| struct pending_request *p = user_data; |
| struct setpath_data *data = p->data; |
| char *next; |
| char *current; |
| guint8 code; |
| |
| p->req_id = 0; |
| |
| if (err != NULL) { |
| setpath_complete(p->session, NULL, err, user_data); |
| return; |
| } |
| |
| code = g_obex_packet_get_operation(rsp, NULL); |
| if (code != G_OBEX_RSP_SUCCESS) { |
| GError *gerr = NULL; |
| g_set_error(&gerr, OBEX_IO_ERROR, code, "%s", |
| g_obex_strerror(code)); |
| setpath_complete(p->session, NULL, gerr, user_data); |
| g_clear_error(&gerr); |
| return; |
| } |
| |
| current = data->remaining[data->index - 1]; |
| setpath_set_folder(p->session, current); |
| |
| /* Ignore empty folder names to avoid resetting the current path */ |
| while ((next = data->remaining[data->index]) && strlen(next) == 0) |
| data->index++; |
| |
| if (next == NULL) { |
| setpath_complete(p->session, NULL, NULL, user_data); |
| return; |
| } |
| |
| data->index++; |
| |
| p->req_id = g_obex_setpath(obex, next, setpath_cb, p, &err); |
| if (err != NULL) { |
| setpath_complete(p->session, NULL, err, user_data); |
| g_error_free(err); |
| } |
| } |
| |
| static int session_process_setpath(struct pending_request *p, GError **err) |
| { |
| struct setpath_data *req = p->data; |
| const char *first = ""; |
| |
| /* Relative path */ |
| if (req->remaining[0][0] != '/') |
| first = req->remaining[req->index]; |
| req->index++; |
| |
| p->req_id = g_obex_setpath(p->session->obex, first, setpath_cb, p, err); |
| if (*err != NULL) |
| return (*err)->code; |
| |
| p->session->p = p; |
| |
| return 0; |
| } |
| |
| guint obc_session_setpath(struct obc_session *session, const char *path, |
| session_callback_t func, void *user_data, |
| GError **err) |
| { |
| struct setpath_data *data; |
| struct pending_request *p; |
| |
| if (session->obex == NULL) { |
| g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, |
| "Session disconnected"); |
| return 0; |
| } |
| |
| data = g_new0(struct setpath_data, 1); |
| data->func = func; |
| data->user_data = user_data; |
| data->remaining = g_strsplit(strlen(path) ? path : "/", "/", 0); |
| |
| if (!data->remaining || !data->remaining[0]) { |
| error("obc_session_setpath: invalid path %s", path); |
| g_set_error(err, OBEX_IO_ERROR, -EINVAL, "Invalid argument"); |
| setpath_data_free(data); |
| return 0; |
| } |
| |
| p = pending_request_new(session, session_process_setpath, NULL, |
| setpath_op_complete, data, setpath_data_free); |
| session_queue(p); |
| return p->id; |
| } |
| |
| static void async_cb(GObex *obex, GError *err, GObexPacket *rsp, |
| gpointer user_data) |
| { |
| struct pending_request *p = user_data; |
| struct obc_session *session = p->session; |
| GError *gerr = NULL; |
| uint8_t code; |
| |
| p->req_id = 0; |
| |
| if (err != NULL) { |
| if (p->func) |
| p->func(p->session, NULL, err, p->data); |
| goto done; |
| } |
| |
| code = g_obex_packet_get_operation(rsp, NULL); |
| if (code != G_OBEX_RSP_SUCCESS) |
| g_set_error(&gerr, OBEX_IO_ERROR, code, "%s", |
| g_obex_strerror(code)); |
| |
| if (p->func) |
| p->func(p->session, NULL, gerr, p->data); |
| |
| if (gerr != NULL) |
| g_clear_error(&gerr); |
| |
| done: |
| pending_request_free(p); |
| session->p = NULL; |
| |
| session_process_queue(session); |
| } |
| |
| static void file_op_complete(struct obc_session *session, |
| struct obc_transfer *transfer, |
| GError *err, void *user_data) |
| { |
| struct file_data *data = user_data; |
| |
| if (data->func) |
| data->func(session, NULL, err, data->user_data); |
| } |
| |
| static int session_process_mkdir(struct pending_request *p, GError **err) |
| { |
| struct file_data *req = p->data; |
| |
| p->req_id = g_obex_mkdir(p->session->obex, req->srcname, async_cb, p, |
| err); |
| if (*err != NULL) |
| return (*err)->code; |
| |
| p->session->p = p; |
| |
| return 0; |
| } |
| |
| guint obc_session_mkdir(struct obc_session *session, const char *folder, |
| session_callback_t func, void *user_data, |
| GError **err) |
| { |
| struct file_data *data; |
| struct pending_request *p; |
| |
| if (session->obex == NULL) { |
| g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, |
| "Session disconnected"); |
| return 0; |
| } |
| |
| data = g_new0(struct file_data, 1); |
| data->srcname = g_strdup(folder); |
| data->func = func; |
| data->user_data = user_data; |
| |
| p = pending_request_new(session, session_process_mkdir, NULL, |
| file_op_complete, data, file_data_free); |
| session_queue(p); |
| return p->id; |
| } |
| |
| static int session_process_copy(struct pending_request *p, GError **err) |
| { |
| struct file_data *req = p->data; |
| |
| p->req_id = g_obex_copy(p->session->obex, req->srcname, req->destname, |
| async_cb, p, err); |
| if (*err != NULL) |
| return (*err)->code; |
| |
| p->session->p = p; |
| |
| return 0; |
| } |
| |
| guint obc_session_copy(struct obc_session *session, const char *srcname, |
| const char *destname, session_callback_t func, |
| void *user_data, GError **err) |
| { |
| struct file_data *data; |
| struct pending_request *p; |
| |
| if (session->obex == NULL) { |
| g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, |
| "Session disconnected"); |
| return 0; |
| } |
| |
| data = g_new0(struct file_data, 1); |
| data->srcname = g_strdup(srcname); |
| data->destname = g_strdup(destname); |
| data->func = func; |
| data->user_data = user_data; |
| |
| p = pending_request_new(session, session_process_copy, NULL, |
| file_op_complete, data, file_data_free); |
| session_queue(p); |
| return p->id; |
| } |
| |
| static int session_process_move(struct pending_request *p, GError **err) |
| { |
| struct file_data *req = p->data; |
| |
| p->req_id = g_obex_move(p->session->obex, req->srcname, req->destname, |
| async_cb, p, err); |
| if (*err != NULL) |
| return (*err)->code; |
| |
| p->session->p = p; |
| |
| return 0; |
| } |
| |
| guint obc_session_move(struct obc_session *session, const char *srcname, |
| const char *destname, session_callback_t func, |
| void *user_data, GError **err) |
| { |
| struct file_data *data; |
| struct pending_request *p; |
| |
| if (session->obex == NULL) { |
| g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, |
| "Session disconnected"); |
| return 0; |
| } |
| |
| data = g_new0(struct file_data, 1); |
| data->srcname = g_strdup(srcname); |
| data->destname = g_strdup(destname); |
| data->func = func; |
| data->user_data = user_data; |
| |
| p = pending_request_new(session, session_process_move, NULL, |
| file_op_complete, data, file_data_free); |
| session_queue(p); |
| return p->id; |
| } |
| |
| static int session_process_delete(struct pending_request *p, GError **err) |
| { |
| struct file_data *req = p->data; |
| |
| p->req_id = g_obex_delete(p->session->obex, req->srcname, async_cb, p, |
| err); |
| if (*err != NULL) |
| return (*err)->code; |
| |
| p->session->p = p; |
| |
| return 0; |
| } |
| |
| guint obc_session_delete(struct obc_session *session, const char *file, |
| session_callback_t func, void *user_data, |
| GError **err) |
| { |
| struct file_data *data; |
| struct pending_request *p; |
| |
| if (session->obex == NULL) { |
| g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, |
| "Session disconnected"); |
| return 0; |
| } |
| |
| data = g_new0(struct file_data, 1); |
| data->srcname = g_strdup(file); |
| data->func = func; |
| data->user_data = user_data; |
| |
| p = pending_request_new(session, session_process_delete, NULL, |
| file_op_complete, data, file_data_free); |
| session_queue(p); |
| return p->id; |
| } |
| |
| void obc_session_cancel(struct obc_session *session, guint id, |
| gboolean remove) |
| { |
| struct pending_request *p = session->p; |
| |
| if (p == NULL || p->id != id) |
| return; |
| |
| if (p->req_id == 0) |
| return; |
| |
| g_obex_cancel_req(session->obex, p->req_id, remove); |
| p->req_id = 0; |
| |
| if (!remove) |
| return; |
| |
| pending_request_free(p); |
| session->p = NULL; |
| |
| session_process_queue(session); |
| } |