summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorRegis Merlino <regis.merlino@intel.com>2013-02-22 15:52:55 +0100
committerMark Ryan <mark.d.ryan@intel.com>2013-02-25 16:23:27 +0100
commit3f332f2c5d8d991a0ea657086b0cff2dfd787206 (patch)
treeed4539b12422e667600db9743a181b52f9294fbe /test
parentb01091c46738d801903c00d1e072fa4e51ad9804 (diff)
downloaddleyna-server-3f332f2c5d8d991a0ea657086b0cff2dfd787206.tar.gz
[Init] Add initial source code
Signed-off-by: Regis Merlino <regis.merlino@intel.com>
Diffstat (limited to 'test')
-rw-r--r--test/dbus/dms-info.c395
-rw-r--r--test/dbus/download_sync_controller.py504
-rwxr-xr-xtest/dbus/dsc.sh1
-rw-r--r--test/dbus/lost_client_test.py79
-rwxr-xr-xtest/dbus/mc.sh1
-rw-r--r--test/dbus/mediaconsole.py213
-rw-r--r--test/dbus/monitor_contents_changes.py58
-rwxr-xr-xtest/dbus/monitor_last_change.py48
-rwxr-xr-xtest/dbus/monitor_upload_update.py46
-rw-r--r--test/dbus/stress_test.py64
10 files changed, 1409 insertions, 0 deletions
diff --git a/test/dbus/dms-info.c b/test/dbus/dms-info.c
new file mode 100644
index 0000000..8b9b075
--- /dev/null
+++ b/test/dbus/dms-info.c
@@ -0,0 +1,395 @@
+/*
+ * dms-info
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ ******************************************************************************/
+
+#include <string.h>
+#include <stdio.h>
+#include <sys/signalfd.h>
+#include <signal.h>
+#include <stdbool.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#define DMS_INFO_SERVICE "com.intel.dleyna-server"
+#define DMS_INFO_MANAGER_IF "com.intel.dLeynaServer.Manager"
+#define DMS_INFO_MANAGER_OBJ "/com/intel/dLeynaServer"
+#define DMS_INFO_GET_SERVERS "GetServers"
+#define DMS_INFO_GET_ALL "GetAll"
+#define DMS_INFO_PROPERTIES_IF "org.freedesktop.DBus.Properties"
+
+typedef struct dms_info_t_ dms_info_t;
+struct dms_info_t_
+{
+ guint sig_id;
+ GMainLoop *main_loop;
+ GDBusProxy *manager_proxy;
+ GCancellable *cancellable;
+ GHashTable *dmss;
+ unsigned int async;
+};
+
+typedef struct dms_server_data_t_ dms_server_data_t;
+struct dms_server_data_t_ {
+ GCancellable *cancellable;
+ GDBusProxy *proxy;
+};
+
+static dms_server_data_t *prv_dms_server_data_new(GDBusProxy *proxy)
+{
+ dms_server_data_t *data = g_new(dms_server_data_t, 1);
+ data->proxy = proxy;
+ data->cancellable = g_cancellable_new();
+ return data;
+}
+
+static void prv_dms_server_data_delete(gpointer user_data)
+{
+ dms_server_data_t *data = user_data;
+ g_object_unref(data->cancellable);
+ g_object_unref(data->proxy);
+ g_free(data);
+}
+
+static void prv_dms_info_free(dms_info_t *info)
+{
+ if (info->manager_proxy)
+ g_object_unref(info->manager_proxy);
+
+ if (info->sig_id)
+ (void) g_source_remove(info->sig_id);
+
+ if (info->main_loop)
+ g_main_loop_unref(info->main_loop);
+
+ if (info->cancellable)
+ g_object_unref(info->cancellable);
+
+ if (info->dmss)
+ g_hash_table_unref(info->dmss);
+}
+
+static void prv_dump_container_props(GVariant *props)
+{
+ GVariantIter iter;
+ GVariant *dictionary;
+ gchar *key;
+ GVariant *value;
+ gchar *formatted_value;
+
+ dictionary = g_variant_get_child_value(props, 0);
+ (void) g_variant_iter_init(&iter, dictionary);
+
+ printf("\n");
+ while (g_variant_iter_next(&iter, "{&sv}", &key, &value)) {
+ formatted_value = g_variant_print(value, FALSE);
+ printf("%s: %s\n", key, formatted_value);
+ g_free(formatted_value);
+ g_variant_unref(value);
+ }
+}
+
+static void prv_get_props_cb(GObject *source_object, GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *variant;
+ dms_info_t *info = user_data;
+ dms_server_data_t *data;
+ const gchar *obj_path;
+
+ obj_path = g_dbus_proxy_get_object_path((GDBusProxy *) source_object);
+ data = g_hash_table_lookup(info->dmss, obj_path);
+
+ --info->async;
+
+ if (g_cancellable_is_cancelled(data->cancellable)) {
+ if (info->async == 0)
+ g_main_loop_quit(info->main_loop);
+ printf("Get Properties cancelled.\n");
+ } else {
+ variant = g_dbus_proxy_call_finish(info->manager_proxy, res,
+ NULL);
+ if (!variant) {
+ printf("Get Properties failed.\n");
+ } else {
+ prv_dump_container_props(variant);
+ g_variant_unref(variant);
+ }
+ }
+}
+
+static void prv_get_container_props(dms_info_t *info, const gchar *container)
+{
+ dms_server_data_t *data;
+ GDBusProxy *proxy;
+ GDBusProxyFlags flags;
+
+ if (!g_hash_table_lookup_extended(info->dmss, container, NULL, NULL)) {
+ printf("Container Object Found: %s\n", container);
+
+ /* We'll create these proxies synchronously. The server
+ is already started and we don't want to retrieve any
+ properties so it should be fast. Okay, we do want
+ to retrieve properties but we want to do so for all
+ interfaces and not just org.gnome.UPnP.MediaContainer2.
+ To do this we will need to call GetAll("").
+ */
+
+ flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
+ proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION,
+ flags, NULL,
+ DMS_INFO_SERVICE,
+ container,
+ DMS_INFO_PROPERTIES_IF,
+ NULL, NULL);
+ if (!proxy) {
+ printf("Unable to create Container Proxy for %s\n",
+ container);
+ } else {
+ data = prv_dms_server_data_new(proxy);
+ g_hash_table_insert(info->dmss, g_strdup(container),
+ data);
+ ++info->async;
+ g_dbus_proxy_call(proxy, DMS_INFO_GET_ALL,
+ g_variant_new("(s)", ""),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ data->cancellable,
+ prv_get_props_cb,
+ info);
+ }
+ }
+}
+
+static void prv_get_root_folders(dms_info_t *info, GVariant *servers)
+{
+ GVariantIter iter;
+ GVariant *array;
+ const gchar *container;
+ gsize count;
+
+ /* Results always seem to be packed inside a tuple. We need
+ to extract our array from the tuple */
+
+ array = g_variant_get_child_value(servers, 0);
+ count = g_variant_iter_init(&iter, array);
+
+ printf("Found %"G_GSIZE_FORMAT" DMS Root Containers\n", count);
+
+ while (g_variant_iter_next(&iter, "o", &container))
+ prv_get_container_props(info, container);
+}
+
+static void prv_get_servers_cb(GObject *source_object, GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *variant;
+ dms_info_t *info = user_data;
+
+ --info->async;
+
+ if (g_cancellable_is_cancelled(info->cancellable)) {
+ if (info->async == 0)
+ g_main_loop_quit(info->main_loop);
+ printf("Get Servers cancelled\n");
+ } else {
+ variant = g_dbus_proxy_call_finish(info->manager_proxy, res,
+ NULL);
+ if (!variant) {
+ printf("Get Servers failed.\n");
+ } else {
+ prv_get_root_folders(info, variant);
+ g_variant_unref(variant);
+ }
+ }
+}
+
+static void prv_on_signal(GDBusProxy *proxy, gchar *sender_name,
+ gchar *signal_name, GVariant *parameters,
+ gpointer user_data)
+{
+ gchar *container;
+ dms_info_t *info = user_data;
+
+ if (!strcmp(signal_name, "FoundServer")) {
+ g_variant_get(parameters, "(&o)", &container);
+ if (!g_hash_table_lookup_extended(info->dmss, container, NULL,
+ NULL)) {
+ printf("\nFound DMS %s\n", container);
+ prv_get_container_props(info, container);
+ }
+ } else if (!strcmp(signal_name, "LostServer")) {
+ g_variant_get(parameters, "(&o)", &container);
+ printf("\nLost DMS %s\n", container);
+ (void) g_hash_table_remove(info->dmss, container);
+ }
+}
+
+static void prv_manager_proxy_created(GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ dms_info_t *info = user_data;
+ GDBusProxy *proxy;
+
+ --info->async;
+
+ if (g_cancellable_is_cancelled(info->cancellable)) {
+ printf("Manager proxy creation cancelled.\n");
+ if (info->async == 0)
+ g_main_loop_quit(info->main_loop);
+ } else {
+ proxy = g_dbus_proxy_new_finish(result, NULL);
+ if (!proxy) {
+ printf("Unable to create manager proxy.\n");
+ } else {
+ info->manager_proxy = proxy;
+
+ /* Set up signals to be notified when servers
+ appear and dissapear */
+
+ g_signal_connect(proxy, "g-signal",
+ G_CALLBACK (prv_on_signal), info);
+
+ /* Now we need to retrieve a list of the DMSes on
+ the network. This involves IPC so we will do
+ this asynchronously.*/
+
+ ++info->async;
+ g_cancellable_reset(info->cancellable);
+ g_dbus_proxy_call(proxy, DMS_INFO_GET_SERVERS,
+ NULL, G_DBUS_CALL_FLAGS_NONE, -1,
+ info->cancellable, prv_get_servers_cb,
+ info);
+ }
+ }
+}
+
+static gboolean prv_quit_handler(GIOChannel *source, GIOCondition condition,
+ gpointer user_data)
+{
+ dms_info_t *info = user_data;
+ GHashTableIter iter;
+ gpointer value;
+ dms_server_data_t *data;
+
+ /* We cannot quit straight away if asynchronous calls our outstanding.
+ First we need to cancel them. Each time one is cancel info->async
+ will drop by 1. Once it reaches 0 the callback function associated
+ with the command being cancelled will quit the mainloop. */
+
+ if (info->async == 0) {
+ g_main_loop_quit(info->main_loop);
+ } else {
+ g_cancellable_cancel(info->cancellable);
+ g_hash_table_iter_init(&iter, info->dmss);
+ while (g_hash_table_iter_next(&iter, NULL, &value)) {
+ data = value;
+ g_cancellable_cancel(data->cancellable);
+ }
+ }
+
+ info->sig_id = 0;
+
+ return FALSE;
+}
+
+static bool prv_init_signal_handler(sigset_t mask, dms_info_t *info)
+{
+ bool retval = false;
+ int fd = -1;
+ GIOChannel *channel = NULL;
+
+ fd = signalfd(-1, &mask, SFD_NONBLOCK);
+ if (fd == -1)
+ goto on_error;
+
+ channel = g_io_channel_unix_new(fd);
+ g_io_channel_set_close_on_unref(channel, TRUE);
+
+ if (g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL) !=
+ G_IO_STATUS_NORMAL)
+ goto on_error;
+
+ if (g_io_channel_set_encoding(channel, NULL, NULL) !=
+ G_IO_STATUS_NORMAL)
+ goto on_error;
+
+ info->sig_id = g_io_add_watch(channel, G_IO_IN | G_IO_PRI,
+ prv_quit_handler,
+ info);
+
+ retval = true;
+
+on_error:
+
+ if (channel)
+ g_io_channel_unref(channel);
+
+ return retval;
+}
+
+int main(int argc, char *argv[])
+{
+ dms_info_t info;
+ sigset_t mask;
+
+ memset(&info, 0, sizeof(info));
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGTERM);
+ sigaddset(&mask, SIGINT);
+
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ goto on_error;
+
+ g_type_init();
+
+ /* Create proxy for com.intel.dLeynaServer.Manager. The Manager
+ object has no properties. We will create the proxy asynchronously
+ and use G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES to ensure that
+ gio does not contact the server to retrieve remote properties. Creating
+ the proxy will force dleyna-media-service be to launched if it is not
+ already running. */
+
+ info.cancellable = g_cancellable_new();
+ info.async = 1;
+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL, DMS_INFO_SERVICE, DMS_INFO_MANAGER_OBJ,
+ DMS_INFO_MANAGER_IF, info.cancellable,
+ prv_manager_proxy_created, &info);
+
+ info.dmss = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+ prv_dms_server_data_delete);
+
+ info.main_loop = g_main_loop_new(NULL, FALSE);
+
+ if (!prv_init_signal_handler(mask, &info))
+ goto on_error;
+
+ g_main_loop_run(info.main_loop);
+
+on_error:
+
+ prv_dms_info_free(&info);
+
+ return 0;
+}
diff --git a/test/dbus/download_sync_controller.py b/test/dbus/download_sync_controller.py
new file mode 100644
index 0000000..521cab4
--- /dev/null
+++ b/test/dbus/download_sync_controller.py
@@ -0,0 +1,504 @@
+# download_sync_controller
+#
+# Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU Lesser General Public License,
+# version 2.1, as published by the Free Software Foundation.
+#
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+#
+# Regis Merlino <regis.merlino@intel.com>
+#
+
+import ConfigParser
+import os
+import shutil
+import urllib
+import dbus
+
+from mediaconsole import UPNP, Container, Device
+
+class _DscUpnp(UPNP):
+ def __init__(self):
+ UPNP.__init__(self)
+
+ def get_servers(self):
+ return self._manager.GetServers()
+
+class _DscContainer(Container):
+ def __init__(self, path):
+ Container.__init__(self, path)
+ self.__path = path
+
+ def find_containers(self):
+ return self._containerIF.SearchObjectsEx(
+ 'Type derivedfrom "container"',
+ 0, 0,
+ ['DisplayName', 'Path', 'Type'], '')[0]
+ def find_updates(self, upd_id):
+ return self._containerIF.SearchObjectsEx(
+ 'ObjectUpdateID > "{0}"'.format(upd_id),
+ 0, 0,
+ ['DisplayName', 'Path', 'RefPath', 'URLs',
+ 'Type', 'Parent'], '')[0]
+
+ def find_children(self):
+ return self._containerIF.ListChildrenEx(0, 0,
+ ['DisplayName', 'Path', 'RefPath',
+ 'URLs', 'Parent', 'Type'], '')
+
+class DscError(Exception):
+ """A Download Sync Controller error."""
+ def __init__(self, message):
+ """
+ message: description of the error
+
+ """
+ Exception.__init__(self, message)
+ self.message = message
+
+ def __str__(self):
+ return 'DscError: ' + self.message
+
+class _DscDownloader(object):
+ def __init__(self, url, path):
+ self.url = url
+ self.path = path
+
+ def download(self):
+ urllib.urlretrieve(self.url, self.path)
+
+class _DscStore(object):
+ SYNC_SECTION = 'sync_info'
+ SYNC_OPTION = 'sync_contents'
+ CUR_ID_OPTION = 'current_id'
+ MEDIA_SECTION = 'media_info'
+
+ NAME_SUFFIX = '-name'
+
+ ITEM_NEW = 1
+ ITEM_UPDATE = 2
+ CONTAINER_NEW = 3
+
+ def __init__(self, root_path, server_id):
+ self.__root_path = root_path + '/' + server_id
+ self.__config_path = self.__root_path + '/' + 'tracking.conf'
+
+ self.__config = ConfigParser.ConfigParser()
+ self.__cur_id = 0
+ self.__sync = False
+
+ def initialize(self, sync):
+ if not os.path.exists(self.__root_path):
+ os.makedirs(self.__root_path)
+
+ self.__config.read(self.__config_path)
+
+ if not self.__config.has_section(_DscStore.SYNC_SECTION):
+ self.__config.add_section(_DscStore.SYNC_SECTION)
+ self.__config.set(_DscStore.SYNC_SECTION,
+ _DscStore.CUR_ID_OPTION, '0')
+ if sync:
+ self.__config.set(_DscStore.SYNC_SECTION,
+ _DscStore.SYNC_OPTION, 'yes')
+ else:
+ self.__config.set(_DscStore.SYNC_SECTION,
+ _DscStore.SYNC_OPTION, 'no')
+
+
+ if not self.__config.has_section(_DscStore.MEDIA_SECTION):
+ self.__config.add_section(_DscStore.MEDIA_SECTION)
+
+ self.__cur_id = self.__config.getint(_DscStore.SYNC_SECTION,
+ _DscStore.CUR_ID_OPTION)
+
+ self.__sync = self.__config.getboolean(_DscStore.SYNC_SECTION,
+ _DscStore.SYNC_OPTION)
+
+ def __write_config(self):
+ with open(self.__config_path, 'wb') as configfile:
+ self.__config.write(configfile)
+
+ def __id_from_path(self, path):
+ return os.path.basename(path)
+
+ def __orig_id(self, media_object):
+ try:
+ return self.__id_from_path(media_object['RefPath'])
+ except KeyError:
+ return self.__id_from_path(media_object['Path'])
+
+ def __removed_items(self, local_ids, remote_items):
+ for local_id in local_ids:
+ found = False
+
+ for remote in remote_items:
+ remote_id = self.__id_from_path(remote['Path'])
+ if local_id.endswith(_DscStore.NAME_SUFFIX) or \
+ local_id == remote_id:
+ found = True
+
+ if not found:
+ yield local_id
+
+ def __sync_item(self, obj, obj_id, parent_id, status, write_conf):
+ orig = self.__orig_id(obj)
+
+ if status == _DscStore.ITEM_UPDATE:
+ old_path = self.__config.get(_DscStore.MEDIA_SECTION, orig)
+ new_path = self.__create_path_for_name(obj['DisplayName'])
+ print u'\tMedia "{0}" updated'.format(obj['DisplayName'])
+ print u'\t\tto "{0}"'.format(new_path)
+ self.__config.set(_DscStore.MEDIA_SECTION, orig, new_path)
+ os.rename(old_path, new_path)
+ elif status == _DscStore.ITEM_NEW:
+ print u'\tNew media "{0}" tracked'.format(obj['DisplayName'])
+ self.__config.set(parent_id, obj_id, orig)
+ self.__config.set(parent_id, obj_id + _DscStore.NAME_SUFFIX,
+ obj['DisplayName'])
+ if not self.__config.has_option(_DscStore.MEDIA_SECTION, orig) and \
+ self.__sync:
+ local_path = self.__create_path_for_name(obj['DisplayName'])
+ self.__config.set(_DscStore.MEDIA_SECTION, orig, local_path)
+ print u'\tDownloading contents from "{0}"'.format(obj['URLs'][0])
+ print u'\t\tinto "{0}"...'.format(local_path)
+ downloader = _DscDownloader(obj['URLs'][0], local_path)
+ downloader.download()
+ else:
+ pass
+
+ if write_conf:
+ self.__write_config()
+
+ def __create_path_for_name(self, file_name):
+ new_path = self.__root_path + '/' + str(self.__cur_id) + '-' + file_name
+
+ self.__cur_id += 1
+ self.__config.set(_DscStore.SYNC_SECTION, _DscStore.CUR_ID_OPTION,
+ str(self.__cur_id))
+
+ return new_path
+
+ def remove(self):
+ if os.path.exists(self.__root_path):
+ shutil.rmtree(self.__root_path)
+
+ def sync_container(self, container, items):
+ print u'Syncing container "{0}"...'.format(container['DisplayName'])
+
+ container_id = self.__id_from_path(container['Path'])
+ if not self.__config.has_section(container_id):
+ self.__config.add_section(container_id)
+
+ for remote in items:
+ remote_id = self.__id_from_path(remote['Path'])
+ if not self.__config.has_option(container_id, remote_id):
+ if remote['Type'] == 'container':
+ status = _DscStore.CONTAINER_NEW
+ else:
+ status = _DscStore.ITEM_NEW
+ self.__sync_item(remote, remote_id, container_id, status, False)
+
+ for local in self.__removed_items(
+ self.__config.options(container_id), items):
+ if self.__config.has_section(local):
+ print u'\tRemoved a container'
+ self.__config.remove_option(container_id, local)
+ self.__config.remove_section(local)
+ else:
+ orig = self.__config.get(container_id, local)
+ name = self.__config.get(container_id,
+ local + _DscStore.NAME_SUFFIX)
+ print u'\tRemoved media "{0}"'.format(name)
+ self.__config.remove_option(container_id, local)
+ self.__config.remove_option(container_id,
+ local + _DscStore.NAME_SUFFIX)
+ if local == orig:
+ orig_name = self.__config.get(_DscStore.MEDIA_SECTION, orig)
+ self.__config.remove_option(_DscStore.MEDIA_SECTION, orig)
+ if self.__sync:
+ print u'\tRemoved local downloaded contents "{0}"' \
+ .format(orig_name)
+ if os.path.exists(orig_name):
+ os.remove(orig_name)
+
+ self.__write_config()
+
+ def sync_item(self, obj):
+ print u'Syncing item "{0}"...'.format(obj['DisplayName'])
+ obj_id = self.__id_from_path(obj['Path'])
+ parent_id = self.__id_from_path(obj['Parent'])
+ if self.__config.has_option(parent_id, obj_id):
+ status = _DscStore.ITEM_UPDATE
+ else:
+ status = _DscStore.ITEM_NEW
+
+ self.__sync_item(obj, obj_id, parent_id, status, True)
+
+class DscController(object):
+ """A Download Sync Controller.
+
+ The Download Sync Controller receive changes in the content or metadata
+ stored on media servers (DMS/M-DMS) and apply those changes to
+ the local storage.
+ Media servers must expose the 'content-synchronization' capability to
+ be tracked by this controller.
+
+ The three main methods are servers(), track() and sync().
+ * servers() lists the media servers available on the network
+ * track() is used to add a media server to the list of servers that are
+ to be synchronized.
+ * sync() launches the servers synchronisation to a local storage
+
+ Sample usage:
+ >>> controller.servers()
+ >>> controller.track('/com/intel/dLeynaServer/server/0')
+ >>> controller.sync()
+
+ """
+ CONFIG_PATH = os.environ['HOME'] + '/.config/download-sync-controller.conf'
+ SUID_OPTION = 'system_update_id'
+ SRT_OPTION = 'service_reset_token'
+ SYNC_OPTION = 'sync_contents'
+ DATA_PATH_SECTION = '__data_path__'
+ DATA_PATH_OPTION = 'path'
+
+ def __init__(self, rel_path = None):
+ """
+ rel_path: if provided, contains the relative local storage path,
+ from the user's HOME directory.
+ If not provided, the local storage path will be
+ '$HOME/download-sync-controller'
+
+ """
+ self.__upnp = _DscUpnp()
+
+ self.__config = ConfigParser.ConfigParser()
+ self.__config.read(DscController.CONFIG_PATH)
+
+ if rel_path:
+ self.__set_data_path(rel_path)
+ elif not self.__config.has_section(DscController.DATA_PATH_SECTION):
+ self.__set_data_path('download-sync-controller')
+
+ self.__store_path = self.__config.get(DscController.DATA_PATH_SECTION,
+ DscController.DATA_PATH_OPTION)
+
+ def __write_config(self):
+ with open(DscController.CONFIG_PATH, 'wb') as configfile:
+ self.__config.write(configfile)
+
+ def __set_data_path(self, rel_path):
+ data_path = os.environ['HOME'] + '/' + rel_path
+
+ if not self.__config.has_section(DscController.DATA_PATH_SECTION):
+ self.__config.add_section(DscController.DATA_PATH_SECTION)
+
+ self.__config.set(DscController.DATA_PATH_SECTION,
+ DscController.DATA_PATH_OPTION, data_path)
+
+ self.__write_config()
+
+ def __need_sync(self, servers):
+ for item in servers:
+ device = Device(item)
+ uuid = device.get_prop('UDN')
+ new_srt = device.get_prop('ServiceResetToken')
+ new_id = device.get_prop('SystemUpdateID')
+
+ if self.__config.has_section(uuid):
+ cur_id = self.__config.getint(uuid, DscController.SUID_OPTION)
+ cur_srt = self.__config.get(uuid, DscController.SRT_OPTION)
+ if cur_id == -1 or cur_srt != new_srt:
+ print
+ print u'Server {0} needs *full* sync:'.format(uuid)
+ yield item, uuid, 0, new_id, True
+ elif cur_id < new_id:
+ print
+ print u'Server {0} needs sync:'.format(uuid)
+ yield item, uuid, cur_id, new_id, False
+
+ def __check_trackable(self, server):
+ try:
+ try:
+ srt = server.get_prop('ServiceResetToken')
+ except:
+ raise DscError("'ServiceResetToken' variable not supported")
+
+ try:
+ dlna_caps = server.get_prop('DLNACaps')
+ if not 'content-synchronization' in dlna_caps:
+ raise
+ except:
+ raise DscError("'content-synchronization' cap not supported")
+
+ try:
+ search_caps = server.get_prop('SearchCaps')
+ if not [x for x in search_caps if 'ObjectUpdateID' in x]:
+ raise
+ if not [x for x in search_caps if 'ContainerUpdateID' in x]:
+ raise
+ except:
+ raise DscError("'objectUpdateID' search cap not supported")
+
+ return srt
+ except DscError as err:
+ print err
+ return None
+
+ def track(self, server_path, track = True, sync_contents = True):
+ """Adds or removes a media server to/from the controller's list.
+
+ server_path: d-bus path for the media server
+ track: when 'True', adds a server to the list
+ when 'False' removes a server from the list
+ sync_contents: when 'True', downloads media contents to the local
+ storage upon synchronization.
+
+ """
+ server = Device(server_path)
+ server_uuid = server.get_prop('UDN')
+
+ if track and not self.__config.has_section(server_uuid):
+ srt = self.__check_trackable(server)
+ if srt != None:
+ self.__config.add_section(server_uuid)
+
+ self.__config.set(server_uuid, DscController.SUID_OPTION, '-1')
+ self.__config.set(server_uuid, DscController.SRT_OPTION, srt)
+ if sync_contents:
+ self.__config.set(server_uuid, DscController.SYNC_OPTION,
+ 'yes')
+ else:
+ self.__config.set(server_uuid, DscController.SYNC_OPTION,
+ 'no')
+
+ self.__write_config()
+ else:
+ print u"Sorry, the server {0} has no such capability and " \
+ "will not be tracked.".format(server_path)
+
+ elif not track and self.__config.has_section(server_uuid):
+ self.__config.remove_section(server_uuid)
+ self.__write_config()
+
+ store = _DscStore(self.__store_path, server_uuid)
+ store.remove()
+
+ def track_reset(self, server_path, sync_contents = True):
+ """Removes local contents and meta data for a media server.
+
+ The next synchronization will be a *full* synchronization.
+
+ server_path: d-bus path for the media server
+ sync_contents: when 'True', downloads media contents to the local
+ storage upon synchronization.
+
+ """
+ self.track(server_path, False, sync_contents)
+ self.track(server_path, True, sync_contents)
+
+ def servers(self):
+ """Displays media servers available on the network.
+
+ Displays media servers information as well as the tracked status.
+
+ """
+ print u'Running servers:'
+
+ for item in self.__upnp.get_servers():
+ try:
+ server = Container(item)
+ try:
+ folder_name = server.get_prop('FriendlyName')
+ except Exception:
+ folder_name = server.get_prop('DisplayName')
+ device = Device(item)
+ dev_uuid = device.get_prop('UDN')
+ dev_path = device.get_prop('Path')
+
+ print u'{0:<25} Tracked({2}) {3} {1}'.format(folder_name,
+ dev_path,
+ self.__config.has_option(dev_uuid,
+ DscController.SUID_OPTION),
+ dev_uuid)
+
+ except dbus.exceptions.DBusException as err:
+ print u'Cannot retrieve properties for ' + item
+ print str(err).strip()[:-1]
+
+ def tracked_servers(self):
+ """Displays the list of servers currently tracked by the controller."""
+ print u'Tracked servers:'
+
+ for name in self.__config.sections():
+ if name != DscController.DATA_PATH_SECTION:
+ print u'{0:<30}'.format(name)
+
+ def sync(self):
+ """Performs a synchronization for all the tracked media servers.
+
+ Displays some progress information during the process.
+
+ """
+ print u'Syncing...'
+
+ for item, uuid, cur, new, full_sync in \
+ self.__need_sync(self.__upnp.get_servers()):
+ sync = self.__config.getboolean(uuid, DscController.SYNC_OPTION)
+
+ if full_sync:
+ print u'Resetting local contents for server {0}'.format(uuid)
+
+ self.track_reset(item)
+
+ objects = _DscContainer(item).find_containers()
+ else:
+ objects = _DscContainer(item).find_updates(cur)
+
+ store = _DscStore(self.__store_path, uuid)
+ store.initialize(sync)
+
+ for obj in objects:
+ if obj['Type'] == 'container':
+ children = _DscContainer(obj['Path']).find_children()
+ store.sync_container(obj, children)
+ else:
+ store.sync_item(obj)
+
+ self.__config.set(uuid, DscController.SUID_OPTION, str(new))
+ self.__write_config()
+
+ print
+ print u'Done.'
+
+ def reset(self):
+ """Removes local contents and meta data for all the tracked servers.
+
+ After the call, the list of tracked servers will be empty.
+
+ """
+ for name in self.__config.sections():
+ if name != DscController.DATA_PATH_SECTION:
+ self.__config.remove_section(name)
+
+ store = _DscStore(self.__store_path, name)
+ store.remove()
+
+ self.__write_config()
+
+
+if __name__ == '__main__':
+ controller = DscController()
+ controller.servers()
+ print
+ print u'"controller" instance is ready for use.'
+ print u'Type "help(DscController)" for more details and usage samples.'
diff --git a/test/dbus/dsc.sh b/test/dbus/dsc.sh
new file mode 100755
index 0000000..e8e2f8d
--- /dev/null
+++ b/test/dbus/dsc.sh
@@ -0,0 +1 @@
+python -i -m download_sync_controller
diff --git a/test/dbus/lost_client_test.py b/test/dbus/lost_client_test.py
new file mode 100644
index 0000000..d488601
--- /dev/null
+++ b/test/dbus/lost_client_test.py
@@ -0,0 +1,79 @@
+# test_lost_client
+#
+# Copyright (C) 2012 Intel Corporation. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU Lesser General Public License,
+# version 2.1, as published by the Free Software Foundation.
+#
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+#
+# Regis Merlino <regis.merlino@intel.com>
+#
+
+import gobject
+import dbus
+import dbus.mainloop.glib
+
+def handle_browse_reply(objects):
+ print "Total Items: " + str(len(objects))
+ print
+ loop.quit()
+
+def handle_error(e):
+ print "An error occured"
+ loop.quit()
+
+def make_async_call():
+ root.ListChildrenEx(0,
+ 5,
+ ["DisplayName", "Type"],
+ "-DisplayName",
+ reply_handler=handle_browse_reply,
+ error_handler=handle_error)
+ root.ListChildrenEx(0,
+ 5,
+ ["DisplayName", "Type"],
+ "-DisplayName",
+ reply_handler=handle_browse_reply,
+ error_handler=handle_error)
+ root.ListChildrenEx(0,
+ 5,
+ ["DisplayName", "Type"],
+ "-DisplayName",
+ reply_handler=handle_browse_reply,
+ error_handler=handle_error)
+ root.ListChildrenEx(0,
+ 5,
+ ["DisplayName", "Type"],
+ "-DisplayName",
+ reply_handler=handle_browse_reply,
+ error_handler=handle_error)
+ root.ListChildrenEx(0,
+ 5,
+ ["DisplayName", "Type"],
+ "-DisplayName",
+ reply_handler=handle_browse_reply,
+ error_handler=handle_error)
+ # Test: force quit - this should cancel the search on server side
+ loop.quit()
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+bus = dbus.SessionBus()
+root = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server',
+ '/com/intel/dLeynaServer/server/0'),
+ 'org.gnome.UPnP.MediaContainer2')
+
+gobject.timeout_add(1000, make_async_call)
+
+loop = gobject.MainLoop()
+loop.run()
diff --git a/test/dbus/mc.sh b/test/dbus/mc.sh
new file mode 100755
index 0000000..4ba0611
--- /dev/null
+++ b/test/dbus/mc.sh
@@ -0,0 +1 @@
+python -i -m mediaconsole
diff --git a/test/dbus/mediaconsole.py b/test/dbus/mediaconsole.py
new file mode 100644
index 0000000..b0a0057
--- /dev/null
+++ b/test/dbus/mediaconsole.py
@@ -0,0 +1,213 @@
+# mediaconsole
+#
+# Copyright (C) 2012 Intel Corporation. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU Lesser General Public License,
+# version 2.1, as published by the Free Software Foundation.
+#
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+#
+# Mark Ryan <mark.d.ryan@intel.com>
+#
+
+import dbus
+import sys
+import json
+
+def print_properties(props):
+ print json.dumps(props, indent=4, sort_keys=True)
+
+class MediaObject(object):
+
+ def __init__(self, path):
+ bus = dbus.SessionBus()
+ self._propsIF = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server', path),
+ 'org.freedesktop.DBus.Properties')
+ self.__objIF = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server', path),
+ 'org.gnome.UPnP.MediaObject2')
+
+ def get_props(self, iface = ""):
+ return self._propsIF.GetAll(iface)
+
+ def get_prop(self, prop_name, iface = ""):
+ return self._propsIF.Get(iface, prop_name)
+
+ def print_prop(self, prop_name, iface = ""):
+ print_properties(self._propsIF.Get(iface, prop_name))
+
+ def print_props(self, iface = ""):
+ print_properties(self._propsIF.GetAll(iface))
+
+ def print_dms_id(self):
+ path = self._propsIF.Get("", "Path")
+ dms_id = path[path.rfind("/") + 1:]
+ i = 0
+ while i+1 < len(dms_id):
+ num = dms_id[i] + dms_id[i+1]
+ sys.stdout.write(unichr(int(num, 16)))
+ i = i + 2
+ print
+
+ def delete(self):
+ return self.__objIF.Delete()
+
+ def update(self, to_add_update, to_delete):
+ return self.__objIF.Update(to_add_update, to_delete)
+
+class Item(MediaObject):
+ def __init__(self, path):
+ MediaObject.__init__(self, path)
+ bus = dbus.SessionBus()
+ self._itemIF = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server', path),
+ 'org.gnome.UPnP.MediaItem2')
+
+ def print_compatible_resource(self, protocol_info, fltr):
+ print_properties(self._itemIF.GetCompatibleResource(protocol_info,
+ fltr))
+
+class Container(MediaObject):
+
+ def __init__(self, path):
+ MediaObject.__init__(self, path)
+ bus = dbus.SessionBus()
+ self._containerIF = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server', path),
+ 'org.gnome.UPnP.MediaContainer2')
+
+ def list_children(self, offset, count, fltr, sort=""):
+ objects = self._containerIF.ListChildrenEx(offset, count, fltr, sort)
+ for item in objects:
+ print_properties(item)
+ print ""
+
+ def list_containers(self, offset, count, fltr, sort=""):
+ objects = self._containerIF.ListContainersEx(offset, count, fltr, sort)
+ for item in objects:
+ print_properties(item)
+ print ""
+
+ def list_items(self, offset, count, fltr, sort=""):
+ objects = self._containerIF.ListItemsEx(offset, count, fltr, sort)
+ for item in objects:
+ print_properties(item)
+ print ""
+
+ def search(self, query, offset, count, fltr, sort=""):
+ objects, total = self._containerIF.SearchObjectsEx(query, offset,
+ count, fltr, sort)
+ print "Total Items: " + str(total)
+ print
+ for item in objects:
+ print_properties(item)
+ print ""
+
+ def tree(self, level=0):
+ objects = self._containerIF.ListChildren(
+ 0, 0, ["DisplayName", "Path", "Type"])
+ for props in objects:
+ print (" " * (level * 4) + props["DisplayName"] +
+ " : (" + props["Path"]+ ")")
+ if props["Type"] == "container":
+ Container(props["Path"]).tree(level + 1)
+
+ def upload(self, name, file_path):
+ (tid, path) = self._containerIF.Upload(name, file_path)
+ print "Transfer ID: " + str(tid)
+ print u"Path: " + path
+
+ def create_container(self, name, type, child_types):
+ path = self._containerIF.CreateContainer(name, type, child_types)
+ print u"New container path: " + path
+
+ def create_playlist(self, title, items, creator="", genre="", desc=""):
+ (tid, path) = self._containerIF.CreatePlaylist(title, creator, genre,
+ desc, items)
+ print "Transfer ID: " + str(tid)
+ print u"Path: " + path
+
+class Device(Container):
+
+ def __init__(self, path):
+ Container.__init__(self, path)
+ bus = dbus.SessionBus()
+ self._deviceIF = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server', path),
+ 'com.intel.dLeynaServer.MediaDevice')
+
+ def upload_to_any(self, name, file_path):
+ (tid, path) = self._deviceIF.UploadToAnyContainer(name, file_path)
+ print "Transfer ID: " + str(tid)
+ print u"Path: " + path
+
+ def create_container_in_any(self, name, type, child_types):
+ path = self._deviceIF.CreateContainerInAnyContainer(name, type,
+ child_types)
+ print u"New container path: " + path
+
+ def get_upload_status(self, id):
+ (status, length, total) = self._deviceIF.GetUploadStatus(id)
+ print "Status: " + status
+ print "Length: " + str(length)
+ print "Total: " + str(total)
+
+ def get_upload_ids(self):
+ upload_ids = self._deviceIF.GetUploadIDs()
+ print_properties(upload_ids)
+
+ def cancel_upload(self, id):
+ self._deviceIF.CancelUpload(id)
+
+ def cancel(self):
+ return self._deviceIF.Cancel()
+
+ def create_playlist_in_any(self, title, items, creator="", genre="", desc=""):
+ (tid, path) = self._deviceIF.CreatePlaylistInAnyContainer(title,
+ creator,
+ genre,
+ desc,
+ items)
+ print "Transfer ID: " + str(tid)
+ print u"Path: " + path
+
+
+class UPNP(object):
+
+ def __init__(self):
+ bus = dbus.SessionBus()
+ self._manager = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server',
+ '/com/intel/dLeynaServer'),
+ 'com.intel.dLeynaServer.Manager')
+
+ def servers(self):
+ for i in self._manager.GetServers():
+ try:
+ server = Container(i)
+ try:
+ folderName = server.get_prop("FriendlyName");
+ except Exception:
+ folderName = server.get_prop("DisplayName");
+ print u'{0:<30}{1:<30}'.format(folderName , i)
+ except dbus.exceptions.DBusException, err:
+ print u"Cannot retrieve properties for " + i
+ print str(err).strip()[:-1]
+
+ def version(self):
+ print self._manager.GetVersion()
+
+ def set_protocol_info(self, protocol_info):
+ self._manager.SetProtocolInfo(protocol_info)
+
+ def prefer_local_addresses(self, prefer):
+ self._manager.PreferLocalAddresses(prefer)
diff --git a/test/dbus/monitor_contents_changes.py b/test/dbus/monitor_contents_changes.py
new file mode 100644
index 0000000..e9b0b58
--- /dev/null
+++ b/test/dbus/monitor_contents_changes.py
@@ -0,0 +1,58 @@
+# monitor_contents_changes
+#
+# Copyright (C) 2012 Intel Corporation. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU Lesser General Public License,
+# version 2.1, as published by the Free Software Foundation.
+#
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+#
+# Regis Merlino <regis.merlino@intel.com>
+#
+
+import gobject
+import dbus
+import dbus.mainloop.glib
+import json
+
+def print_properties(props):
+ print json.dumps(props, indent=4, sort_keys=True)
+
+def properties_changed(iface, changed, invalidated, path):
+ print "PropertiesChanged signal from {%s} [%s]" % (iface, path)
+ print "Changed:"
+ print_properties(changed)
+ print "Invalidated:"
+ print_properties(invalidated)
+
+def container_update(id_list, path, interface):
+ iface = interface[interface.rfind(".") + 1:]
+ print "ContainerUpdateIDs signal from {%s} [%s]" % (iface, path)
+ for (id_path, sys_id) in id_list:
+ print "-->\t %s : %u" % (id_path, sys_id)
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SessionBus()
+
+ bus.add_signal_receiver(properties_changed,
+ bus_name="com.intel.dleyna-server",
+ signal_name = "PropertiesChanged",
+ path_keyword="path")
+ bus.add_signal_receiver(container_update,
+ bus_name="com.intel.dleyna-server",
+ signal_name = "ContainerUpdateIDs",
+ path_keyword="path",
+ interface_keyword="interface")
+
+ mainloop = gobject.MainLoop()
+ mainloop.run()
diff --git a/test/dbus/monitor_last_change.py b/test/dbus/monitor_last_change.py
new file mode 100755
index 0000000..d831ff2
--- /dev/null
+++ b/test/dbus/monitor_last_change.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+# monitor_last_change
+#
+# Copyright (C) 2012 Intel Corporation. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU Lesser General Public License,
+# version 2.1, as published by the Free Software Foundation.
+#
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+#
+# Regis Merlino <regis.merlino@intel.com>
+# Mark Ryan <mark.d.ryan@intel.com>
+#
+
+import gobject
+import dbus
+import dbus.mainloop.glib
+import json
+
+def print_properties(props):
+ print json.dumps(props, indent=4, sort_keys=True)
+
+def last_change(state_event, path):
+ print "LastChange signal from [%s]" % path
+ print "State Event:"
+ print_properties(state_event)
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SessionBus()
+
+ bus.add_signal_receiver(last_change,
+ bus_name="com.intel.dleyna-server",
+ signal_name = "LastChange",
+ path_keyword="path")
+
+ mainloop = gobject.MainLoop()
+ mainloop.run()
diff --git a/test/dbus/monitor_upload_update.py b/test/dbus/monitor_upload_update.py
new file mode 100755
index 0000000..cf5bea8
--- /dev/null
+++ b/test/dbus/monitor_upload_update.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+# monitor_upload_update
+#
+# Copyright (C) 2012 Intel Corporation. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU Lesser General Public License,
+# version 2.1, as published by the Free Software Foundation.
+#
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+#
+# Regis Merlino <regis.merlino@intel.com>
+# Mark Ryan <mark.d.ryan@intel.com>
+#
+
+import gobject
+import dbus
+import dbus.mainloop.glib
+
+def upload_update(upload_id, status, uploaded, to_upload, path):
+ print "UploadUpdate signal from [%s]" % (path)
+ print "Upload ID: " + str(upload_id)
+ print "Status: " + status
+ print "Uploaded: " + str(uploaded)
+ print "To Upload: " + str(to_upload)
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SessionBus()
+
+ bus.add_signal_receiver(upload_update,
+ bus_name="com.intel.dleyna-server",
+ signal_name = "UploadUpdate",
+ path_keyword="path")
+
+ mainloop = gobject.MainLoop()
+ mainloop.run()
diff --git a/test/dbus/stress_test.py b/test/dbus/stress_test.py
new file mode 100644
index 0000000..782cfa5
--- /dev/null
+++ b/test/dbus/stress_test.py
@@ -0,0 +1,64 @@
+# stress-test
+#
+# Copyright (C) 2012 Intel Corporation. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU Lesser General Public License,
+# version 2.1, as published by the Free Software Foundation.
+#
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+#
+# Regis Merlino <regis.merlino@intel.com>
+#
+
+import gobject
+import dbus
+import dbus.mainloop.glib
+
+def handle_browse_reply(objects):
+ print "Total Items: " + str(len(objects))
+
+def handle_error(err):
+ if err.get_dbus_name() == 'com.intel.dleyna.Cancelled':
+ print "Cancelled..."
+ else:
+ print "An error occured"
+ print err
+ loop.quit()
+
+def make_async_calls():
+ i = 0
+ while i < 5:
+ root.ListChildrenEx(0,
+ 5,
+ ["DisplayName", "Type"],
+ "-DisplayName",
+ reply_handler=handle_browse_reply,
+ error_handler=handle_error)
+ i += 1
+ device.Cancel()
+ gobject.timeout_add(1000, make_async_calls)
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+bus = dbus.SessionBus()
+root = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server',
+ '/com/intel/dLeynaServer/server/0'),
+ 'org.gnome.UPnP.MediaContainer2')
+device = dbus.Interface(bus.get_object(
+ 'com.intel.dleyna-server',
+ '/com/intel/dLeynaServer/server/0'),
+ 'com.intel.dLeynaServer.MediaDevice')
+
+gobject.timeout_add(1000, make_async_calls)
+
+loop = gobject.MainLoop()
+loop.run()