diff options
author | Regis Merlino <regis.merlino@intel.com> | 2013-02-22 15:52:55 +0100 |
---|---|---|
committer | Mark Ryan <mark.d.ryan@intel.com> | 2013-02-25 16:23:27 +0100 |
commit | 3f332f2c5d8d991a0ea657086b0cff2dfd787206 (patch) | |
tree | ed4539b12422e667600db9743a181b52f9294fbe /test | |
parent | b01091c46738d801903c00d1e072fa4e51ad9804 (diff) | |
download | dleyna-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.c | 395 | ||||
-rw-r--r-- | test/dbus/download_sync_controller.py | 504 | ||||
-rwxr-xr-x | test/dbus/dsc.sh | 1 | ||||
-rw-r--r-- | test/dbus/lost_client_test.py | 79 | ||||
-rwxr-xr-x | test/dbus/mc.sh | 1 | ||||
-rw-r--r-- | test/dbus/mediaconsole.py | 213 | ||||
-rw-r--r-- | test/dbus/monitor_contents_changes.py | 58 | ||||
-rwxr-xr-x | test/dbus/monitor_last_change.py | 48 | ||||
-rwxr-xr-x | test/dbus/monitor_upload_update.py | 46 | ||||
-rw-r--r-- | test/dbus/stress_test.py | 64 |
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() |