Logo Search packages:      
Sourcecode: obex-data-server version File versions

ods-session.c

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*-
 *
 * Copyright (C) 2007-2008 Tadas Dailyda <tadas@dailyda.com>
 *
 * Licensed under the GNU General Public License Version 2
 *
 * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <utime.h>

#include <glib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>

#include <bluetooth/bluetooth.h>
#include <openobex/obex.h>
#include <openobex/obex_const.h>

#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>

#include "ods-common.h"
#include "ods-error.h"
#include "ods-manager.h"
#include "ods-marshal.h"
#include "ods-obex.h"
#include "ods-session.h"
#include "ods-session-dbus-glue.h"


static void     ods_session_class_init    (OdsSessionClass *klass);
static void     ods_session_init          (OdsSession     *session);
static void     ods_session_finalize      (GObject          *object);
static gint       ods_session_connect_internal (OdsSession *session, 
                                                                              GError **error);

#define ODS_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ODS_TYPE_SESSION, OdsSessionPrivate))

#define ODS_SESSION_LOCK(session) g_message ("LOCK"); g_static_mutex_lock (&(session)->priv->mutex)
#define ODS_SESSION_UNLOCK(session) g_message ("UNLOCK"); g_static_mutex_unlock (&(session)->priv->mutex)

struct OdsSessionPrivate
{
      /* constructor properties */
      gint                          fd; /* rfcomm device */
      guint                         service;
      gchar                         *owner; /* D-Bus client, who initiated this session */
      /* state variables */
      OdsSessionState               state; /* ODS_SESSION_STATE_NOT_CONNECTED by default */
      /* OBEX connection */
      OdsObexContext                *obex_context;
      guint                         io_watch;
      /* other */
      GStaticMutex                  mutex;
      DBusGMethodInvocation   *dbus_context; /* D-Bus context for async methods */
      gchar                         *dbus_path; /* D-Bus path for this object */
      gchar                         *current_path; /* Current path on remote device */
      gchar                         *new_path; /* Temporarily stored new path on remote device */
      
      
};

enum {
      CANCELLED,
      CONNECTED,
      DISCONNECTED,
      CLOSED,
      TRANSFER_STARTED,
      TRANSFER_PROGRESS,
      TRANSFER_COMPLETED,
      ERROR_OCCURRED,
      LAST_SIGNAL
};

static guint      signals [LAST_SIGNAL] = { 0, };
/* for numbering established sessions */
static guint      iterator = 0;

G_DEFINE_TYPE (OdsSession, ods_session, G_TYPE_OBJECT)

/* This is to be called from mainloop, not directly. It's to ensure
 * that we don't finalize this object from within itself. */
static gboolean
emit_disconnected (OdsSession *session)
{
      g_signal_emit (session, signals [DISCONNECTED], 0);
      return FALSE;
}

static gboolean
obex_io_callback (GIOChannel *io_channel, GIOCondition cond, gpointer data)
{
      obex_t            *obex_handle;
      OdsSession  *session;
      GError            *error = NULL;
      gboolean    ret = TRUE;

      obex_handle = (obex_t *) data;
      session = ODS_SESSION (OBEX_GetUserData (obex_handle));
      
      g_message ("io callback");
      if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
            g_source_remove (session->priv->io_watch);
            g_idle_add ((GSourceFunc) emit_disconnected, session);
            /* CMD_DISCONNECT was sent but did not receive proper reply,
             * don't emit ErrorOccurred */
            if (session->priv->state == ODS_SESSION_STATE_NOT_CONNECTED)
                  return FALSE;
            g_set_error (&error, ODS_ERROR, ODS_ERROR_LINK_ERROR, "Connection error");
            /* cleanup transfer data and set state to NOT_CONNECTED */
            /* If it was GET operation, remove incomplete file */
            if (session->priv->obex_context->obex_cmd == OBEX_CMD_GET && 
                        session->priv->obex_context->stream_fd >= 0)
                  g_unlink (session->priv->obex_context->local);
            ods_obex_transfer_close (session->priv->obex_context);
            session->priv->state = ODS_SESSION_STATE_NOT_CONNECTED;
            /* Return D-Bus context, unlock mutex */
            if (session->priv->dbus_context) {
                  dbus_g_method_return_error (session->priv->dbus_context, error);
                  session->priv->dbus_context = NULL;
                  ODS_SESSION_UNLOCK (session);
            }
            ret = FALSE;
      } else if (OBEX_HandleInput (obex_handle, 1) < 0) {
            g_set_error (&error, ODS_ERROR, ODS_ERROR_BAD_DATA, 
                                    "Could not parse incoming data");         
      }
      
      if (error) {
            gchar *error_name;
            /* Get D-Bus name for error */
            error_name = ods_error_get_dbus_name (error);
            /* emit ErrorOccurred signal */
            g_signal_emit (session, signals [ERROR_OCCURRED], 0,
                                    error_name, error->message);
            g_free (error_name);
            g_clear_error (&error);
      }

      return ret;
}

static void
obex_transfer_done (OdsSession *session, gint response)
{
      GError      *error = NULL;
      gchar *error_name;
      
      session->priv->state = ODS_SESSION_STATE_OPEN;
      if (response != OBEX_RSP_SUCCESS) {
            /* get GError corresponding to OBEX response code */
            ods_error_obexrsp2gerror (response, &error);
            /* Get D-Bus name for error */
            error_name = ods_error_get_dbus_name (error);
            /* emit ErrorOccurred Signal */
            g_signal_emit (session, signals [ERROR_OCCURRED], 0,
                                    error_name, error->message);
            g_free (error_name);
            g_clear_error (&error);
            return;
      }
      if (session->priv->obex_context->report_progress) {
            /* emit signals */
            g_signal_emit (session, signals [TRANSFER_PROGRESS], 0,
                                    session->priv->obex_context->target_size);
            g_signal_emit (session, signals [TRANSFER_COMPLETED], 0);
      }
}

static void
obex_transfer_data_exchange_done (OdsSession *session, gint ret)
{
      GError                  *error = NULL;
      gchar             *error_name;
      OdsObexContext    *obex_context;
      
      obex_context = session->priv->obex_context;
      if (ret < 0) {
            ods_error_err2gerror (ret, &error);
            /* Get D-Bus name for error */
            error_name = ods_error_get_dbus_name (error);
            /* emit ErrorOccurred Signal */
            g_signal_emit (session, signals [ERROR_OCCURRED], 0,
                                    error_name, error->message);
            g_free (error_name);
            g_clear_error (&error);
            /* Reset state */
            session->priv->state = ODS_SESSION_STATE_OPEN;
      } else if (obex_context->report_progress &&
                        !obex_context->transfer_started_signal_emitted) {
            g_signal_emit (session, signals [TRANSFER_STARTED], 0,
                                    obex_context->remote,
                                    obex_context->local,
                                    obex_context->target_size);
            obex_context->transfer_started_signal_emitted = TRUE;
      }
}
 
static void
obex_request_done (OdsSession *session, obex_object_t *object, int command,
                              int response)
{
      GError                  *error = NULL;
      OdsObexContext    *obex_context;
      
      g_message ("obex_request_done: command %d, response %s", command,
                        OBEX_ResponseToString (response));
      
      obex_context = session->priv->obex_context;
      
      switch (command) {
            case OBEX_CMD_CONNECT:
                  if (response == OBEX_RSP_SUCCESS) {
                        ods_obex_connect_done (obex_context, object);
                        /* update state */
                        session->priv->state = ODS_SESSION_STATE_OPEN;
                        g_signal_emit (session, signals [CONNECTED], 0);
                  } else {
                        gchar *error_name;
                        
                        g_set_error (&error, ODS_ERROR, ODS_ERROR_CONNECTION_REFUSED, 
                                                "Remote device refused connection");
                        /* Get D-Bus name for error */
                        error_name = ods_error_get_dbus_name (error);
                        /* emit ErrorOccurred signal */
                        g_signal_emit (session, signals [ERROR_OCCURRED], 0,
                                                error_name, error->message);
                        g_free (error_name);
                        g_clear_error (&error);
                  }
                  break;
            case OBEX_CMD_DISCONNECT:
                  g_source_remove (session->priv->io_watch);
                  g_idle_add ((GSourceFunc) emit_disconnected, session);
                  break;
            case OBEX_CMD_SETPATH:
                  /* check response code here */
                  if (response == OBEX_RSP_NOT_FOUND) {
                        g_set_error (&error, ODS_ERROR, ODS_ERROR_NOT_FOUND, 
                                                "Path not found");
                        if (session->priv->dbus_context) {
                              dbus_g_method_return_error (session->priv->dbus_context,
                                                                        error);
                              session->priv->dbus_context = NULL;
                        }
                        g_clear_error (&error);
                  } else if (response == OBEX_RSP_SUCCESS) {
                        gchar *temp;
                        if (!strcmp (session->priv->new_path, ""))
                              temp = g_strdup ("/");
                        else if (!strcmp (session->priv->new_path, "..")) {
                              gchar *temp2;
                              /* get rid of trailing "/" */
                              session->priv->current_path[
                                                strlen (session->priv->current_path)-1] = 0;
                              temp2 = g_path_get_dirname (session->priv->current_path);
                              /* add trailing "/" */
                              temp = g_strdup_printf ("%s/", temp2);
                              g_free (temp2);
                        } else {
                              gchar *first_element;
                              /* make sure we don't leave "/" in the beginning */
                              if (session->priv->current_path[0] == '/')
                                    first_element = "";
                              else
                                    first_element = session->priv->current_path;
                              temp = g_strdup_printf ("%s%s/", first_element,
                                                                        session->priv->new_path);
                        }
                        g_free (session->priv->current_path);
                        session->priv->current_path = temp;
                        if (session->priv->dbus_context) {
                              dbus_g_method_return (session->priv->dbus_context);
                              session->priv->dbus_context = NULL;
                        }
                  } else {/* some other response code, must be error */
                        /* get GError corresponding to OBEX response code */
                        ods_error_obexrsp2gerror (response, &error);
                        dbus_g_method_return_error (session->priv->dbus_context, error);
                        session->priv->dbus_context = NULL;
                        g_clear_error (&error);
                  }
                  ODS_SESSION_UNLOCK (session);
                  break;
            case OBEX_CMD_ABORT:
                  break;
            case OBEX_CMD_PUT:
                  if (!obex_context->report_progress) {
                        /* DeleteRemoteFile was executed */
                        session->priv->state = ODS_SESSION_STATE_OPEN;
                        if (session->priv->dbus_context) {
                              if (response != OBEX_RSP_SUCCESS) {
                                    /* get GError corresponding to OBEX response code */
                                    ods_error_obexrsp2gerror (response, &error);
                                    dbus_g_method_return_error (session->priv->dbus_context,
                                                                              error);
                                    session->priv->dbus_context = NULL;
                                    g_clear_error (&error);
                              } else {
                                    dbus_g_method_return (session->priv->dbus_context);
                                    session->priv->dbus_context = NULL;
                              }
                              ODS_SESSION_UNLOCK (session);
                        }
                  } else {
                        /* for normal transfers */
                        obex_transfer_done (session, response);
                  }
                  ods_obex_transfer_close (obex_context);
                  break;
            case OBEX_CMD_GET:
                  if (!obex_context->report_progress) {
                        /* RetrieveFolderListing or GetCapability was executed */
                        session->priv->state = ODS_SESSION_STATE_OPEN;
                        if (session->priv->dbus_context) {
                              if (response != OBEX_RSP_SUCCESS) {
                                    /* get GError corresponding to OBEX response code */
                                    ods_error_obexrsp2gerror (response, &error);
                                    dbus_g_method_return_error (session->priv->dbus_context,
                                                                              error);
                                    session->priv->dbus_context = NULL;
                                    g_clear_error (&error);
                              } else {
                                    gchar *buf;
                                    buf = ods_obex_get_buffer_as_string (obex_context);
                                    dbus_g_method_return (session->priv->dbus_context, 
                                                                        g_strdup (buf));
                                    session->priv->dbus_context = NULL;
                              }
                              ODS_SESSION_UNLOCK (session);
                        }
                  } else {
                        obex_transfer_done (session, response);
                        /* change modification time for received file */
                        if (obex_context->local) {
                              g_warning ("MODTIME: %d", (gint)obex_context->modtime);
                              if (obex_context->modtime != -1) {
                                    struct utimbuf ubuf;
                                    ubuf.actime = time (NULL);
                                    ubuf.modtime = obex_context->modtime;
                                    if (utime (obex_context->local, &ubuf) < 0)
                                          g_warning ("Invalid modification time");
                              }
                        }
                  }
                  ods_obex_transfer_close (obex_context);
                  break;
            case OBEX_CMD_SESSION:
                  break;
      }
}

static void
obex_event (obex_t *handle, obex_object_t *object, int mode, int event, 
                  int command, int response)
{
      OdsSession        *session;
      OdsObexContext    *obex_context;
      gint              ret;
      
      session = ODS_SESSION (OBEX_GetUserData (handle));
      obex_context = session->priv->obex_context;
      g_message ("event: %d", event);
      switch (event) {
            case OBEX_EV_PROGRESS:
                  if (obex_context->report_progress) {
                        g_signal_emit (session, signals [TRANSFER_PROGRESS], 0,
                                                obex_context->counter);
                        g_warning ("PROGRESS: %" G_GUINT64_FORMAT, obex_context->counter);
                  }
                  break;
            case OBEX_EV_REQHINT:
                  OBEX_ObjectSetRsp (object, OBEX_RSP_NOT_IMPLEMENTED, response);
                  break;
            case OBEX_EV_REQ:
                  OBEX_ObjectSetRsp (object, OBEX_RSP_NOT_IMPLEMENTED, response);
                  break;
            case OBEX_EV_REQDONE:
                  obex_request_done (session, object, command, response);
                  break;
            case OBEX_EV_PARSEERR:
            case OBEX_EV_ACCEPTHINT:
                  break;
            case OBEX_EV_LINKERR:
                  /* we will get LINKERR when Cancel was called, but device didn't
                   * send OBEX_RSP_SUCCESS response (might be OBEX_RSP_BAD_REQUEST).
                   * When link error really happens, it is handled in io_callback */
                  g_warning ("EV_LINKERR");
            case OBEX_EV_ABORT:
                  g_message ("EV_ABORT");
                  /* Cleanup transfer data and reset state */
                  /* If it was GET operation, remove incomplete file */
                  if (obex_context->obex_cmd == OBEX_CMD_GET &&
                              obex_context->stream_fd >= 0)
                        g_unlink (obex_context->local);
                  ods_obex_transfer_close (obex_context);
                  session->priv->state = ODS_SESSION_STATE_OPEN;
                  
                  /* Emit Cancelled signal */
                  g_signal_emit (session, signals [CANCELLED], 0);
                  
                  /* In case this was trigerred by Cancel method */
                  if (session->priv->dbus_context) {
                        dbus_g_method_return (session->priv->dbus_context);
                        session->priv->dbus_context = NULL;
                        ODS_SESSION_UNLOCK (session);
                  }
                  break;
            case OBEX_EV_STREAMEMPTY:
                  ret = ods_obex_writestream (obex_context, object);
                  obex_transfer_data_exchange_done (session, ret);
                  break;
            case OBEX_EV_STREAMAVAIL:
                  ret = ods_obex_readstream (obex_context, object);
                  obex_transfer_data_exchange_done (session, ret);
                  break;
            case OBEX_EV_UNEXPECTED:
            case OBEX_EV_REQCHECK:
                  break;
      }
}

static gboolean
ods_session_setup_transport (OdsSession *session)
{
      OdsObexContext *obex_context;
      GIOChannel *chan;
      gint ret;
      GError *error = NULL;
      
      
      obex_context = session->priv->obex_context;
      
      /* call OBEX_Init, setup FD Transport here */
      obex_context->obex_handle = OBEX_Init (OBEX_TRANS_FD, obex_event, 0);
      if (obex_context->obex_handle == NULL) {
            /* error (out of memory) */
            g_warning ("error out of memory");
            return FALSE;
      }
      OBEX_SetUserData (obex_context->obex_handle, session);

      OBEX_SetTransportMTU (obex_context->obex_handle, 
                                          ODS_OBEX_RX_MTU, 
                                          ODS_OBEX_TX_MTU);
      
      ret = FdOBEX_TransportSetup (obex_context->obex_handle, 
                                                      session->priv->fd, 
                                                      session->priv->fd, 
                                                      0);
      if (ret < 0) {
            OBEX_Cleanup (obex_context->obex_handle);
            /* error (transport error or smth) */
            g_warning ("error transport setup fail");
            return FALSE;
      }

      chan = g_io_channel_unix_new (session->priv->fd);
      session->priv->io_watch = g_io_add_watch(chan,
                  G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
                  obex_io_callback, obex_context->obex_handle);
      g_io_channel_unref(chan);

      /* connect automatically */
      if (ods_session_connect_internal (session, &error) == -1) {
            /* error (error) */
            g_clear_error (&error);
            g_warning ("error connect fail");
            return FALSE;
      }
      return TRUE;
}

static gboolean
ods_session_check_state (OdsSession *session, 
                                                DBusGMethodInvocation *context)
{
      GError *error = NULL;
      
      /* check if connected */
      if (session->priv->state == ODS_SESSION_STATE_NOT_CONNECTED) {
            g_set_error (&error, ODS_ERROR, ODS_ERROR_NOT_CONNECTED,
                                    "Not connected");
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            return FALSE;
      }
      /* check if busy */
      if (session->priv->state == ODS_SESSION_STATE_BUSY) {
            g_set_error (&error, ODS_ERROR, ODS_ERROR_BUSY,
                                    "Another operation in progress");
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            return FALSE;
      }
      
      return TRUE;
}

static void
ods_session_set_property (GObject      *object,
                        guint         property_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
      OdsSession *self = (OdsSession *) object;

      switch (property_id) {
            case ODS_SESSION_FD:
                  self->priv->fd = g_value_get_int (value);
                  if (self->priv->fd >= 0)
                        ods_session_setup_transport (self);/* ignoring errors */
                  break;
            case ODS_SESSION_SERVICE:
                  self->priv->service = g_value_get_int (value);
                  break;
            case ODS_SESSION_OWNER:
                  self->priv->owner = g_value_dup_string (value);
                  break;
            default:
                  /* We don't have any other property... */
                  G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
                  break;
      }
}

static void
ods_session_get_property (GObject      *object,
                        guint         property_id,
                        GValue       *value,
                        GParamSpec   *pspec)
{
      OdsSession *self = (OdsSession *) object;

      switch (property_id) {
            case ODS_SESSION_FD:
                  g_value_set_int (value, self->priv->fd);
                  break;
            case ODS_SESSION_SERVICE:
                  g_value_set_int (value, self->priv->service);
                  break;
            case ODS_SESSION_OWNER:
                  g_value_set_string (value, self->priv->owner);
                  break;
            case ODS_SESSION_DBUS_PATH:
                  g_value_set_string (value, self->priv->dbus_path);
                  break;
            default:
                  /* We don't have any other property... */
                  G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
                  break;
      }
}

/**
 * ods_session_class_init:
 * @klass: The OdsSessionClass
 **/
static void
ods_session_class_init (OdsSessionClass *klass)
{
      GObjectClass *object_class = G_OBJECT_CLASS (klass);
      
      object_class->finalize = ods_session_finalize;
      
      object_class->set_property = ods_session_set_property;
      object_class->get_property = ods_session_get_property;

      g_object_class_install_property (object_class,
                                                      ODS_SESSION_FD,
                                                      g_param_spec_int ("fd",
                                                            "", "",
                                                            -1, G_MAXINT, /* min, max values */
                                                            0 /* default value */,
                                                            G_PARAM_READWRITE));
                                                            
      g_object_class_install_property (object_class,
                                                      ODS_SESSION_SERVICE,
                                                      g_param_spec_int ("service",
                                                            "", "",
                                                            0, G_MAXINT, /* min, max values */
                                                            0 /* default value */,
                                                            G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
      
      g_object_class_install_property (object_class,
                                                      ODS_SESSION_OWNER,
                                                      g_param_spec_string ("owner",
                                                            "", "",
                                                            "" /* default value */,
                                                            G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
                                                            
      g_object_class_install_property (object_class,
                                                      ODS_SESSION_DBUS_PATH,
                                                      g_param_spec_string ("dbus-path",
                                                            "", "",
                                                            "" /* default value */,
                                                            G_PARAM_READABLE));
      
      signals [CANCELLED] =
            g_signal_new ("cancelled",
                        G_TYPE_FROM_CLASS (object_class), 
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (OdsSessionClass, cancelled),
                        NULL, 
                        NULL, 
                        g_cclosure_marshal_VOID__VOID,
                        G_TYPE_NONE, 0);
      signals [CONNECTED] =
            g_signal_new ("connected",
                        G_TYPE_FROM_CLASS (object_class), 
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (OdsSessionClass, connected),
                        NULL, 
                        NULL, 
                        g_cclosure_marshal_VOID__VOID,
                        G_TYPE_NONE, 0);
      signals [DISCONNECTED] =
            g_signal_new ("disconnected",
                        G_TYPE_FROM_CLASS (object_class), 
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (OdsSessionClass, disconnected),
                        NULL, 
                        NULL, 
                        g_cclosure_marshal_VOID__VOID,
                        G_TYPE_NONE, 0);
      signals [CLOSED] =
            g_signal_new ("closed",
                        G_TYPE_FROM_CLASS (object_class), 
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (OdsSessionClass, closed),
                        NULL, 
                        NULL, 
                        g_cclosure_marshal_VOID__VOID,
                        G_TYPE_NONE, 0);
      signals [TRANSFER_STARTED] =
            g_signal_new ("transfer-started",
                        G_TYPE_FROM_CLASS (object_class), 
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (OdsSessionClass, transfer_started),
                        NULL, 
                        NULL,
                        ods_marshal_VOID__STRING_STRING_UINT64,
                        G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64);
      signals [TRANSFER_PROGRESS] =
            g_signal_new ("transfer-progress",
                        G_TYPE_FROM_CLASS (object_class), 
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (OdsSessionClass, transfer_progress),
                        NULL, 
                        NULL, 
                        ods_marshal_VOID__UINT64,
                        G_TYPE_NONE, 1, G_TYPE_UINT64);
      signals [TRANSFER_COMPLETED] =
            g_signal_new ("transfer-completed",
                        G_TYPE_FROM_CLASS (object_class), 
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (OdsSessionClass, transfer_completed),
                        NULL, 
                        NULL, 
                        g_cclosure_marshal_VOID__VOID,
                        G_TYPE_NONE, 0);
      signals [ERROR_OCCURRED] =
            g_signal_new ("error-occurred",
                        G_TYPE_FROM_CLASS (object_class), 
                        G_SIGNAL_RUN_LAST,
                        G_STRUCT_OFFSET (OdsSessionClass, error_occurred),
                        NULL, 
                        NULL,
                        ods_marshal_VOID__STRING_STRING,
                        G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
      
      g_type_class_add_private (klass, sizeof (OdsSessionPrivate));
      
      GError *error = NULL;

      /* Init the DBus connection, per-klass */
      klass->connection = dbus_g_bus_get (ODS_DBUS_BUS, &error);
      if (klass->connection == NULL)
      {
            g_warning("Unable to connect to dbus: %s", error->message);
            g_clear_error (&error);
            return;
      }

      /* &dbus_glib_ods_session_object_info is provided in the 
       * dbus/ods-session-dbus-glue.h file */
      dbus_g_object_type_install_info (ODS_TYPE_SESSION, &dbus_glib_ods_session_object_info);
}

/**
 * ods_session_init:
 * @session: This class instance
 **/
static void
ods_session_init (OdsSession *session)
{
      OdsSessionClass *klass = ODS_SESSION_GET_CLASS (session);
      session->priv = ODS_SESSION_GET_PRIVATE (session);
      
      session->priv->state = ODS_SESSION_STATE_NOT_CONNECTED;
      session->priv->current_path = g_strdup ("/");
      
      session->priv->obex_context = ods_obex_context_new ();
      
      /* figure out DBus object path for this instance */
      session->priv->dbus_path = (gchar *)g_malloc0 (
                                                            ODS_SESSION_DBUS_PATH_MAX_LENGTH);
      g_sprintf (session->priv->dbus_path, 
                        ODS_SESSION_DBUS_PATH_PATTERN, 
                        iterator);
      iterator++;
      
      /* create mutex */
      g_static_mutex_init (&session->priv->mutex);
      
      dbus_g_connection_register_g_object (klass->connection, 
                                          session->priv->dbus_path, 
                                          G_OBJECT (session));
}

/**
 * ods_session_finalize:
 * @object: The object to finalize
 *
 * Finalize the session
 **/
static void
ods_session_finalize (GObject *object)
{
      OdsSession *session;

      g_return_if_fail (object != NULL);
      g_return_if_fail (ODS_IS_SESSION (object));

      session = ODS_SESSION (object);

      g_return_if_fail (session->priv != NULL);

      /* close connection, free obex_context */
      g_message ("closing connection");
      shutdown (session->priv->fd, SHUT_RDWR);
      close (session->priv->fd);
      OBEX_Cleanup (session->priv->obex_context->obex_handle);
      g_free (session->priv->obex_context);
      /* free other private variables */
      g_free (session->priv->owner);
      g_free (session->priv->dbus_path);
      if (session->priv->new_path)
            g_free (session->priv->new_path);
      g_free (session->priv->current_path);
      g_static_mutex_free (&session->priv->mutex);

      G_OBJECT_CLASS (ods_session_parent_class)->finalize (object);
}

/**
 * ods_session_new:
 *
 * Return value: a new OdsSession object.
 **/
OdsSession *
ods_session_new (gint fd, gint service,   const gchar *owner)
{
      OdsSession *session;
      session = g_object_new (ODS_TYPE_SESSION, 
                                          "fd", fd,
                                          "service", service,
                                          "owner", owner,
                                          NULL);
      return ODS_SESSION (session);
}

static gint
ods_session_connect_internal (OdsSession *session, GError **error)
{
      guchar      *uuid = NULL;
      guint uuid_length = 0;
      gint  ret;
      
      if (ods_session_is_connected (session)) {
            /* emit CONNECTED signal now */
            g_signal_emit (session, signals[CONNECTED], 0);
            return 1;
      }
      
      switch (session->priv->service) {
            case ODS_SERVICE_FTP:
                  uuid = (guchar *) OBEX_FTP_UUID;
                  uuid_length = OBEX_FTP_UUID_LEN;
                  break;
            case ODS_SERVICE_PBAP:
                  uuid = (guchar *) OBEX_PBAP_UUID;
                  uuid_length = OBEX_PBAP_UUID_LEN;
                  break;
      }
      /* send obex connect command */
      ret = ods_obex_connect (session->priv->obex_context, uuid, 
                                                      uuid_length);
      if (ret < 0) {
            ods_error_err2gerror (ret, error);
            return -1;
      }
      return 0;
}

gboolean
ods_session_connect     (OdsSession *session, DBusGMethodInvocation *context)
{
      gint  ret;
      GError      *error = NULL;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      
      ret = ods_session_connect_internal (session, &error);
      if (ret == -1) {
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            ODS_SESSION_UNLOCK (session);
      } else {
            dbus_g_method_return (context);
            ODS_SESSION_UNLOCK (session);
      }

      return TRUE;
}

gint
ods_session_disconnect_internal (OdsSession *session, GError **error)
{
      gint ret;
      
      if (session->priv->state == ODS_SESSION_STATE_NOT_CONNECTED) {
            /* emit DISCONNECTED signal now */
            g_idle_add ((GSourceFunc) emit_disconnected, session);
            g_source_remove(session->priv->io_watch);
            return 1;
      }

      /* actually disconnect */
      session->priv->state = ODS_SESSION_STATE_NOT_CONNECTED;
      ret = ods_obex_disconnect (session->priv->obex_context);
      if (ret < 0) {
            /* emit DISCONNECTED signal now and set state to NOT_CONNECTED
             * in this case disconnection will happen when socket is closed */
            g_idle_add ((GSourceFunc) emit_disconnected, session);
            
            ods_error_err2gerror (ret, error);
            g_source_remove(session->priv->io_watch);
            return -1;
      }

      return 0;
}

gboolean
ods_session_disconnect (OdsSession *session, DBusGMethodInvocation *context)
{
      gint  ret;
      GError      *error = NULL;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (session->priv->state == ODS_SESSION_STATE_BUSY) {
            g_set_error (&error, ODS_ERROR, ODS_ERROR_BUSY,
                                    "Operations in progress need to be cancelled first");
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      
      /* actually disconnect */
      ret = ods_session_disconnect_internal (session, &error);
      if (ret == -1) {
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            ODS_SESSION_UNLOCK (session);
      } else {
            dbus_g_method_return (context);
            ODS_SESSION_UNLOCK (session);
      }

      return TRUE;
}

gboolean
ods_session_close (OdsSession *session, DBusGMethodInvocation *context)
{
      GError *error = NULL;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (session->priv->state != ODS_SESSION_STATE_NOT_CONNECTED) {
            g_set_error (&error, ODS_ERROR, ODS_ERROR_FAILED,
                                    "Need to disconnect first");
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      /* emit CLOSED signal; manager will finalize this object */
      ODS_SESSION_UNLOCK (session);
      g_signal_emit (session, signals[CLOSED], 0);
      dbus_g_method_return (context);
      return TRUE;
}

gboolean
ods_session_is_connected (OdsSession *session)
{
      return (session->priv->state != ODS_SESSION_STATE_NOT_CONNECTED);
}

static gboolean
ods_session_setpath (OdsSession *session, const gchar *path, gboolean create,
                                    DBusGMethodInvocation *context)
{
      gint              ret;
      GError                  *error = NULL;
      OdsObexContext    *obex_context = session->priv->obex_context;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (!ods_session_check_state (session, context)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      /* validate path */
      if (strchr (path, '/')) {
            g_set_error (&error, ODS_ERROR,     ODS_ERROR_INVALID_ARGUMENTS, "Invalid character in path ('/')");
            dbus_g_method_return_error (context, error);
            return FALSE;
      }
      
      /* set dbus context */
      g_assert (!session->priv->dbus_context);
      session->priv->dbus_context = context;
      /* copy new path to temporary variable */
      if (session->priv->new_path)
            g_free (session->priv->new_path);
      session->priv->new_path = g_strdup (path);
      
      /* change the folder */
      ret = ods_obex_setpath (obex_context, path, create);
      if (ret < 0) {
            ods_error_err2gerror(ret, &error);
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            /* reset state */
            session->priv->dbus_context = NULL;
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      return TRUE;
}

gboolean
ods_session_change_current_folder (OdsSession *session, const gchar *path, 
                                                      DBusGMethodInvocation *context)
{
      return ods_session_setpath (session, path, FALSE, context);
}

gboolean
ods_session_change_current_folder_backward (OdsSession *session,
                                                                  DBusGMethodInvocation *context)
{
      return ods_session_setpath (session, "..", FALSE, context);
}

gboolean
ods_session_change_current_folder_to_root (OdsSession *session,
                                                                  DBusGMethodInvocation *context)
{
      return ods_session_setpath (session, "", FALSE, context);
}

gchar *
ods_session_get_current_path (OdsSession *session)
{
      return g_strdup (session->priv->current_path);
}

gboolean
ods_session_copy_remote_file (OdsSession *session, 
                                                const gchar *remote_filename,
                                                const gchar *local_path,
                                                DBusGMethodInvocation *context)
{
      gint  ret;
      GError      *error = NULL;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (!ods_session_check_state (session, context)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      /* validate remote_filename */
      if (*remote_filename == '\0' || strchr (remote_filename, '/')) {
            g_set_error (&error, ODS_ERROR,     ODS_ERROR_INVALID_ARGUMENTS, 
                                    "Invalid remote filename");
            dbus_g_method_return_error (context, error);
            return FALSE;
      }
      /* validate local path */
      if (*local_path == '\0') {
            g_set_error (&error, ODS_ERROR,     ODS_ERROR_INVALID_ARGUMENTS, 
                                    "Invalid local path");
            dbus_g_method_return_error (context, error);
            return FALSE;
      }
      
      session->priv->state = ODS_SESSION_STATE_BUSY;
      ret = ods_obex_get (session->priv->obex_context, local_path, 
                                                remote_filename, NULL);
      if (ret < 0) {
            ods_error_err2gerror (ret, &error);
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            /* reset state */
            session->priv->state = ODS_SESSION_STATE_OPEN;
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      
      /* return immediately, user will get transfer progress and completion signals */
      dbus_g_method_return (context);
      ODS_SESSION_UNLOCK (session);
      return TRUE;
}

gboolean
ods_session_create_folder (OdsSession *session, 
                                                const gchar *folder_name, 
                                                DBusGMethodInvocation *context)
{
      return ods_session_setpath (session, folder_name, TRUE, context);
}

static gboolean
ods_session_get_by_type (OdsSession *session, DBusGMethodInvocation *context,
                                          const gchar *type)
{
      gint  ret;
      GError      *error = NULL;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (!ods_session_check_state (session, context)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      /* set dbus context */
      g_assert (!session->priv->dbus_context);
      session->priv->dbus_context = context;
      
      session->priv->state = ODS_SESSION_STATE_BUSY;
      ret = ods_obex_get (session->priv->obex_context, NULL, NULL, type);
      if (ret < 0) {
            ods_error_err2gerror (ret, &error);
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            session->priv->dbus_context = NULL;
            /* reset state */
            session->priv->state = ODS_SESSION_STATE_OPEN;
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      return TRUE;
}

gboolean
ods_session_retrieve_folder_listing (OdsSession *session, 
                                                            DBusGMethodInvocation *context)
{
      return ods_session_get_by_type (session, context, LST_TYPE);
}

gboolean
ods_session_get_capability (OdsSession *session, DBusGMethodInvocation *context)
{
      return ods_session_get_by_type (session, context, CAP_TYPE);
}

gboolean
ods_session_send_file (OdsSession *session, 
                                          const gchar *local_path,
                                          DBusGMethodInvocation *context)
{
      gint  ret;
      gchar *basename;
      GError      *error = NULL;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (!ods_session_check_state (session, context)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (!g_file_test (local_path, G_FILE_TEST_IS_REGULAR)) {
            g_set_error (&error, ODS_ERROR,     ODS_ERROR_INVALID_ARGUMENTS, 
                                    "Invalid local path");
            dbus_g_method_return_error (context, error);
            return FALSE;
      }
      
      basename = g_path_get_basename (local_path);
      session->priv->state = ODS_SESSION_STATE_BUSY;
      ret = ods_obex_put (session->priv->obex_context, local_path, 
                                                basename, NULL);
      g_free (basename);
      if (ret < 0) {
            ods_error_err2gerror (ret, &error);
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            /* reset state */
            session->priv->state = ODS_SESSION_STATE_OPEN;
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      
      /* return immediately, user will get transfer progress and completion signals */
      dbus_g_method_return (context);
      ODS_SESSION_UNLOCK (session);
      return TRUE;
}

gboolean
ods_session_delete_remote_file (OdsSession *session,
                                                const gchar *remote_filename, 
                                                DBusGMethodInvocation *context)
{
      gint  ret;
      GError      *error = NULL;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (!ods_session_check_state (session, context)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      if (*remote_filename == '\0' || strchr (remote_filename, '/')) {
            g_set_error (&error, ODS_ERROR,     ODS_ERROR_INVALID_ARGUMENTS,
                                    "Invalid remote filename");
            dbus_g_method_return_error (context, error);
            return FALSE;
      }
      
      /* set dbus context */
      g_assert (!session->priv->dbus_context);
      session->priv->dbus_context = context;
      session->priv->state = ODS_SESSION_STATE_BUSY;
      ret = ods_obex_put (session->priv->obex_context, NULL,
                                                remote_filename, NULL);
      if (ret < 0) {
            ods_error_err2gerror (ret, &error);
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            session->priv->dbus_context = NULL;
            /* reset state */
            session->priv->state = ODS_SESSION_STATE_OPEN;
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      return TRUE;
}

GHashTable *
ods_session_get_transfer_info (OdsSession *session)
{
      GHashTable *info;
      
      gchar *time_str = (gchar *)g_malloc (17);
      
      info = g_hash_table_new ((GHashFunc)g_str_hash, (GEqualFunc)g_str_equal);
      g_hash_table_insert (info, "LocalPath", 
                                          g_strdup (session->priv->obex_context->local));
      g_hash_table_insert (info, "RemoteFilename",
                                          g_strdup (session->priv->obex_context->remote));
      g_hash_table_insert (info, "Size",
                                          g_strdup_printf ("%" G_GUINT64_FORMAT, 
                                                session->priv->obex_context->target_size));
      if (session->priv->obex_context->modtime != -1)
            ods_make_iso8601 (session->priv->obex_context->modtime, time_str, 
                                          sizeof (time_str));
      else
            time_str = "";
      g_hash_table_insert (info, "Time", time_str);
      return info;
}

gboolean
ods_session_is_busy (OdsSession *session)
{
      /* check for any operation (except transfers) */
      if (!g_static_mutex_trylock (&session->priv->mutex))
            return TRUE;
      else
            g_static_mutex_unlock (&session->priv->mutex);
      /* check for transfers */
      return (session->priv->state == ODS_SESSION_STATE_BUSY);
}

gint
ods_session_cancel_internal (OdsSession *session)
{
      if (session->priv->state != ODS_SESSION_STATE_BUSY) {
            /* emit CANCELLED signal now */
            g_signal_emit (session, signals[CANCELLED], 0);
            return 1;
      }
      /* Send CMD_ABORT; cleanup will be done in obex_event */
      return OBEX_CancelRequest (session->priv->obex_context->obex_handle, TRUE);
}

gboolean
ods_session_cancel (OdsSession *session, DBusGMethodInvocation *context)
{
      GError *error = NULL;
      
      ODS_SESSION_LOCK (session);
      /* do checks */
      if (!ods_check_caller (context, session->priv->owner)) {
            ODS_SESSION_UNLOCK (session);
            return FALSE;
      }
      
      if (ods_session_cancel_internal (session) == -1) {
            g_set_error (&error, ODS_ERROR, ODS_ERROR_OUT_OF_MEMORY, "Out of memory");
            dbus_g_method_return_error (context, error);
            g_clear_error (&error);
            ODS_SESSION_UNLOCK (session);
      } else {
            /* set dbus context */
            g_assert (!session->priv->dbus_context);
            session->priv->dbus_context = context;
            /* will return at obex_event{EV_ABORT} */
      }
      return TRUE;
}

Generated by  Doxygen 1.6.0   Back to index