external: Container ID isn't always its path
[profile/ivi/rygel.git] / src / plugins / external / rygel-external-container.vala
1 /*
2  * Copyright (C) 2009 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>.
3  * Copyright (C) 2009,2010 Nokia Corporation.
4  *
5  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
6  *                               <zeeshan.ali@nokia.com>
7  *
8  * This file is part of Rygel.
9  *
10  * Rygel is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * Rygel is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24
25 using GUPnP;
26 using DBus;
27 using Gee;
28 using FreeDesktop;
29
30 /**
31  * Represents an external container.
32  */
33 public class Rygel.External.Container : Rygel.MediaContainer {
34     public MediaContainerProxy actual_container;
35
36     public string host_ip;
37     public string service_name;
38
39     private ItemFactory item_factory;
40     private ArrayList<Container> containers;
41     private Connection connection;
42
43     private bool searchable;
44
45     public Container (string     id,
46                       string     title,
47                       uint       child_count,
48                       bool       searchable,
49                       string     service_name,
50                       string     path,
51                       string     host_ip,
52                       Container? parent = null) {
53         base (id, parent, title, (int) child_count);
54
55         this.service_name = service_name;
56         this.host_ip = host_ip;
57         this.item_factory = new ItemFactory ();
58         this.containers = new ArrayList<Container> ();
59
60         try {
61             this.connection = DBus.Bus.get (DBus.BusType.SESSION);
62         } catch (GLib.Error err) {
63             critical ("Failed to connect to session bus: %s", err.message);
64         }
65
66         // Create proxy to MediaContainer iface
67         this.actual_container = this.connection.get_object (this.service_name,
68                                                             path)
69                                 as MediaContainerProxy;
70
71         this.update_container.begin (true);
72     }
73
74     public override async Gee.List<MediaObject>? get_children (
75                                         uint         offset,
76                                         uint         max_count,
77                                         Cancellable? cancellable)
78                                         throws GLib.Error {
79         string[] filter = {};
80
81         foreach (var object_prop in MediaObjectProxy.PROPERTIES) {
82             filter += object_prop;
83         }
84
85         foreach (var item_prop in MediaItemProxy.PROPERTIES) {
86             filter += item_prop;
87         }
88
89         var children_props = yield this.actual_container.list_children (
90                                         offset,
91                                         max_count,
92                                         filter);
93
94         return yield this.create_media_objects (children_props, this);
95     }
96
97     public override async Gee.List<MediaObject>? search (
98                                         SearchExpression expression,
99                                         uint             offset,
100                                         uint             max_count,
101                                         out uint         total_matches,
102                                         Cancellable?     cancellable)
103                                         throws GLib.Error {
104         if (!this.searchable) {
105             // Backend doesn't implement search :(
106             return yield base.search (expression,
107                                       offset,
108                                       max_count,
109                                       out total_matches,
110                                       cancellable);
111         }
112
113         string[] filter = {};
114         foreach (var object_prop in MediaObjectProxy.PROPERTIES) {
115             filter += object_prop;
116         }
117
118         foreach (var container_prop in MediaContainerProxy.PROPERTIES) {
119             filter += container_prop;
120         }
121
122         foreach (var item_prop in MediaItemProxy.PROPERTIES) {
123             filter += item_prop;
124         }
125
126         var ext_expression = this.translate_expression (expression);
127         var result = yield this.actual_container.search_objects (
128                                         ext_expression.to_string (),
129                                         offset,
130                                         max_count,
131                                         filter);
132         total_matches = result.length;
133
134         return yield this.create_media_objects (result);
135     }
136
137     public override async MediaObject? find_object (string       id,
138                                                     Cancellable? cancellable)
139                                                     throws GLib.Error {
140         MediaObject media_object = null;
141
142         // Create proxy to MediaObject iface
143         var actual_object = this.connection.get_object (this.service_name, id)
144                             as MediaObjectProxy;
145
146         if (actual_object.object_type == "container") {
147             media_object = this.find_container_by_id (id);
148
149             if (media_object == null) {
150                 // Not a child container, lets search in child containers then
151                 foreach (var container in this.containers) {
152                     media_object = yield container.find_object (id,
153                                                                 cancellable);
154
155                     if (media_object != null) {
156                         break;
157                     }
158                 }
159             }
160         } else {
161             var parent_container = new DummyContainer
162                                         ((string) actual_object.parent,
163                                          "LaLaLa",
164                                          0,
165                                          null);
166
167             var props_iface = this.connection.get_object (this.service_name, id)
168                               as Properties;
169
170             var props = yield props_iface.get_all (MediaItemProxy.IFACE);
171
172             // Its an item then
173             media_object = yield this.item_factory.create (
174                                         id,
175                                         actual_object.object_type,
176                                         actual_object.display_name,
177                                         props,
178                                         this.service_name,
179                                         this.host_ip,
180                                         parent_container);
181         }
182
183         return media_object;
184     }
185
186     private async Gee.List<MediaObject> create_media_objects (
187                                         HashTable<string,Value?>[] all_props,
188                                         MediaContainer?            parent
189                                         = null) throws GLib.Error {
190         var media_objects = new ArrayList <MediaObject> ();
191
192         foreach (var props in all_props) {
193             var id = props.lookup ("Path").get_string ();
194             var type = props.lookup ("Type").get_string ();
195
196             MediaContainer parent_container;
197             if (parent != null) {
198                 parent_container = parent;
199             } else {
200                 var parent_id = props.lookup ("Parent").get_string ();
201
202                 parent_container = new DummyContainer (parent_id,
203                                                        "LaLaLa",
204                                                        0,
205                                                        null);
206             }
207
208             MediaObject media_object = null;
209             if (type == "container") {
210                 media_object = this.find_container_by_id (id);
211             }
212
213             if (media_object == null) {
214                 var title = props.lookup ("DisplayName").get_string ();
215
216                 if (type == "container") {
217                     var child_count = props.lookup ("ChildCount").get_uint ();
218
219                     media_object = new DummyContainer (id,
220                                                        title,
221                                                        child_count,
222                                                        parent_container);
223                 } else {
224                     // Its an item then
225                     media_object = yield this.item_factory.create (
226                                         id,
227                                         type,
228                                         title,
229                                         props,
230                                         this.service_name,
231                                         this.host_ip,
232                                         parent_container);
233                 }
234             }
235
236             media_objects.add (media_object);
237         }
238
239         return media_objects;
240     }
241
242     private async void refresh_child_containers () throws GLib.Error {
243         string[] filter = {};
244
245         foreach (var object_prop in MediaObjectProxy.PROPERTIES) {
246             filter += object_prop;
247         }
248
249         foreach (var container_prop in MediaContainerProxy.PROPERTIES) {
250             filter += container_prop;
251         }
252
253         var children_props = yield this.actual_container.list_containers (
254                                         0,
255                                         0,
256                                         filter);
257         this.containers.clear ();
258
259         foreach (var props in children_props) {
260             var path = props.lookup ("Path").get_string ();
261             var title = props.lookup ("DisplayName").get_string ();
262             var child_count = props.lookup ("ChildCount").get_uint ();
263             var searchable = props.lookup ("Searchable").get_boolean ();
264
265             var container = new Container (path,
266                                            title,
267                                            child_count,
268                                            searchable,
269                                            this.service_name,
270                                            path,
271                                            this.host_ip,
272                                            this);
273             this.containers.add (container);
274         }
275     }
276
277     private async void update_container (bool connect_signal = false) {
278         try {
279             // Update our information about the container
280             yield this.refresh_child_containers ();
281         } catch (GLib.Error err) {
282             warning ("Failed to update information about container '%s': %s",
283                      this.actual_container.get_path (),
284                      err.message);
285         }
286
287         // and signal the clients
288         this.updated ();
289
290         if (connect_signal) {
291             this.actual_container.updated.connect (this.on_updated);
292         }
293     }
294
295     private void on_updated (MediaContainerProxy actual_container) {
296         this.update_container.begin ();
297     }
298
299     private MediaContainer find_container_by_id (string id) {
300         MediaContainer target = null;
301
302         foreach (var container in this.containers) {
303             if (container.id == id) {
304                 target = container;
305
306                 break;
307             }
308         }
309
310         return target;
311     }
312
313     private SearchExpression translate_expression (
314                                         SearchExpression upnp_expression) {
315         if (upnp_expression is RelationalExpression) {
316             var expression = upnp_expression as RelationalExpression;
317             var ext_expression = new RelationalExpression ();
318             ext_expression.op = expression.op;
319             ext_expression.operand1 = this.translate_property (
320                                         expression.operand1);
321             ext_expression.operand2 = expression.operand2;
322
323             return ext_expression;
324         } else {
325             var expression = upnp_expression as LogicalExpression;
326             var ext_expression = new LogicalExpression ();
327
328             ext_expression.op = expression.op;
329             ext_expression.operand1 = this.translate_expression (
330                                         expression.operand1);
331             ext_expression.operand2 = this.translate_expression (
332                                         expression.operand2);
333
334             return ext_expression;
335         }
336     }
337
338     public string translate_property (string property) {
339         switch (property) {
340         case "@id":
341             return "Path";
342         case "@parentID":
343             return "Parent";
344         case "dc:title":
345             return "DisplayName";
346         case "dc:creator":
347         case "upnp:artist":
348         case "upnp:author":
349             return "Artist";
350         case "upnp:album":
351             return "Album";
352         default:
353             return property;
354         }
355     }
356 }
357