blob: 765f08d422747884fc47feb9cf285ff6d5d23a04 [file] [log] [blame]
/*
*
* OBEX Server
*
* Copyright (C) 2010-2011 Nokia Corporation
*
*
* 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 <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "obexd/src/log.h"
#include "messages.h"
static char *root_folder = NULL;
struct session {
char *cwd;
char *cwd_absolute;
void *request;
};
struct folder_listing_data {
struct session *session;
const char *name;
uint16_t max;
uint16_t offset;
messages_folder_listing_cb callback;
void *user_data;
};
/* NOTE: Neither IrOBEX nor MAP specs says that folder listing needs to
* be sorted (in IrOBEX examples it is not). However existing implementations
* seem to follow the fig. 3-2 from MAP specification v1.0, and I've seen a
* test suite requiring folder listing to be in that order.
*/
static int folder_names_cmp(gconstpointer a, gconstpointer b,
gpointer user_data)
{
static const char *order[] = {
"inbox", "outbox", "sent", "deleted", "draft", NULL
};
struct session *session = user_data;
int ia, ib;
if (g_strcmp0(session->cwd, "telecom/msg") == 0) {
for (ia = 0; order[ia]; ia++) {
if (g_strcmp0(a, order[ia]) == 0)
break;
}
for (ib = 0; order[ib]; ib++) {
if (g_strcmp0(b, order[ib]) == 0)
break;
}
if (ia != ib)
return ia - ib;
}
return g_strcmp0(a, b);
}
static char *get_next_subdir(DIR *dp, char *path)
{
struct dirent *ep;
char *abs, *name;
for (;;) {
if ((ep = readdir(dp)) == NULL)
return NULL;
if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
continue;
abs = g_build_filename(path, ep->d_name, NULL);
if (g_file_test(abs, G_FILE_TEST_IS_DIR)) {
g_free(abs);
break;
}
g_free(abs);
}
name = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL);
if (name == NULL) {
DBG("g_filename_to_utf8(): invalid filename");
return NULL;
}
return name;
}
static ssize_t get_subdirs(struct folder_listing_data *fld, GSList **list)
{
DIR *dp;
char *path, *name;
size_t n;
path = g_build_filename(fld->session->cwd_absolute, fld->name, NULL);
dp = opendir(path);
if (dp == NULL) {
int err = -errno;
DBG("opendir(): %d, %s", -err, strerror(-err));
g_free(path);
return err;
}
n = 0;
while ((name = get_next_subdir(dp, path)) != NULL) {
n++;
if (fld->max > 0)
*list = g_slist_prepend(*list, name);
}
closedir(dp);
g_free(path);
*list = g_slist_sort_with_data(*list, folder_names_cmp, fld->session);
return n;
}
static void return_folder_listing(struct folder_listing_data *fld, GSList *list)
{
struct session *session = fld->session;
GSList *cur;
uint16_t num = 0;
uint16_t offs = 0;
/* XXX: This isn't really documented for MAP. I need to take a look how
* other implementations choose to deal with parent folder.
*/
if (session->cwd[0] != 0 && fld->offset == 0) {
num++;
fld->callback(session, -EAGAIN, 0, "..", fld->user_data);
} else {
offs++;
}
for (cur = list; offs < fld->offset; offs++) {
cur = cur->next;
if (cur == NULL)
break;
}
for (; cur != NULL && num < fld->max; cur = cur->next, num++)
fld->callback(session, -EAGAIN, 0, cur->data, fld->user_data);
fld->callback(session, 0, 0, NULL, fld->user_data);
}
static gboolean get_folder_listing(void *d)
{
struct folder_listing_data *fld = d;
ssize_t n;
GSList *list = NULL;
n = get_subdirs(fld, &list);
if (n < 0) {
fld->callback(fld->session, n, 0, NULL, fld->user_data);
return FALSE;
}
if (fld->max == 0) {
fld->callback(fld->session, 0, n, NULL, fld->user_data);
return FALSE;
}
return_folder_listing(fld, list);
g_slist_free_full(list, g_free);
return FALSE;
}
int messages_init(void)
{
char *tmp;
if (root_folder)
return 0;
tmp = getenv("MAP_ROOT");
if (tmp) {
root_folder = g_strdup(tmp);
return 0;
}
tmp = getenv("HOME");
if (!tmp)
return -ENOENT;
root_folder = g_build_filename(tmp, "map-messages", NULL);
return 0;
}
void messages_exit(void)
{
g_free(root_folder);
root_folder = NULL;
}
int messages_connect(void **s)
{
struct session *session;
session = g_new0(struct session, 1);
session->cwd = g_strdup("");
session->cwd_absolute = g_strdup(root_folder);
*s = session;
return 0;
}
void messages_disconnect(void *s)
{
struct session *session = s;
g_free(session->cwd);
g_free(session->cwd_absolute);
g_free(session);
}
int messages_set_notification_registration(void *session,
void (*send_event)(void *session,
const struct messages_event *event, void *user_data),
void *user_data)
{
return -ENOSYS;
}
int messages_set_folder(void *s, const char *name, gboolean cdup)
{
struct session *session = s;
char *newrel = NULL;
char *newabs;
char *tmp;
if (name && (strchr(name, '/') || strcmp(name, "..") == 0))
return -EBADR;
if (cdup) {
if (session->cwd[0] == 0)
return -ENOENT;
newrel = g_path_get_dirname(session->cwd);
/* We use empty string for indication of the root directory */
if (newrel[0] == '.' && newrel[1] == 0)
newrel[0] = 0;
}
tmp = newrel;
if (!cdup && (!name || name[0] == 0))
newrel = g_strdup("");
else
newrel = g_build_filename(newrel ? newrel : session->cwd, name,
NULL);
g_free(tmp);
newabs = g_build_filename(root_folder, newrel, NULL);
if (!g_file_test(newabs, G_FILE_TEST_IS_DIR)) {
g_free(newrel);
g_free(newabs);
return -ENOENT;
}
g_free(session->cwd);
session->cwd = newrel;
g_free(session->cwd_absolute);
session->cwd_absolute = newabs;
return 0;
}
int messages_get_folder_listing(void *s, const char *name, uint16_t max,
uint16_t offset,
messages_folder_listing_cb callback,
void *user_data)
{
struct session *session = s;
struct folder_listing_data *fld;
fld = g_new0(struct folder_listing_data, 1);
fld->session = session;
fld->name = name;
fld->max = max;
fld->offset = offset;
fld->callback = callback;
fld->user_data = user_data;
session->request = fld;
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, get_folder_listing,
fld, g_free);
return 0;
}
int messages_get_messages_listing(void *session, const char *name,
uint16_t max, uint16_t offset,
uint8_t subject_len,
const struct messages_filter *filter,
messages_get_messages_listing_cb callback,
void *user_data)
{
return -ENOSYS;
}
int messages_get_message(void *session, const char *handle,
unsigned long flags,
messages_get_message_cb callback,
void *user_data)
{
return -ENOSYS;
}
int messages_update_inbox(void *session, messages_status_cb callback,
void *user_data)
{
return -ENOSYS;
}
int messages_set_read(void *session, const char *handle, uint8_t value,
messages_status_cb callback, void *user_data)
{
return -ENOSYS;
}
int messages_set_delete(void *session, const char *handle, uint8_t value,
messages_status_cb callback, void *user_data)
{
return -ENOSYS;
}
void messages_abort(void *s)
{
struct session *session = s;
if (session->request) {
g_idle_remove_by_data(session->request);
session->request = NULL;
}
}