build,core,plugins: Port to GDBus and GVariant
[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 Gee;
27 using FreeDesktop;
28
29 /**
30  * Represents an external container.
31  */
32 public class Rygel.External.Container : Rygel.MediaContainer {
33     public MediaContainerProxy actual_container;
34
35     public string host_ip;
36     public string service_name;
37
38     private ItemFactory item_factory;
39     private ArrayList<Container> containers;
40
41     private bool searchable;
42
43     public Container (string     id,
44                       string     title,
45                       uint       child_count,
46                       bool       searchable,
47                       string     service_name,
48                       string     path,
49                       string     host_ip,
50                       Container? parent = null) throws IOError {
51         base (id, parent, title, (int) child_count);
52
53         this.service_name = service_name;
54         this.host_ip = host_ip;
55         this.item_factory = new ItemFactory ();
56         this.containers = new ArrayList<Container> ();
57
58         // Create proxy to MediaContainer iface
59         this.actual_container = Bus.get_proxy_sync (BusType.SESSION,
60                                                     this.service_name,
61                                                     path);
62
63         this.update_container.begin (true);
64     }
65
66     public override async MediaObjects? get_children (uint         offset,
67                                                       uint         max_count,
68                                                       Cancellable? cancellable)
69                                                       throws GLib.Error {
70         string[] filter = {};
71
72         foreach (var object_prop in MediaObjectProxy.PROPERTIES) {
73             filter += object_prop;
74         }
75
76         foreach (var item_prop in MediaItemProxy.PROPERTIES) {
77             filter += item_prop;
78         }
79
80         var children_props = yield this.actual_container.list_children (
81                                         offset,
82                                         max_count,
83                                         filter);
84
85         return yield this.create_media_objects (children_props, this);
86     }
87
88     public override async MediaObjects? search (SearchExpression? expression,
89                                                 uint              offset,
90                                                 uint              max_count,
91                                                 out uint          total_matches,
92                                                 Cancellable?      cancellable)
93                                                 throws GLib.Error {
94         if (expression == null || !this.searchable) {
95             // Either its wildcard or backend doesn't implement search :(
96             return yield base.search (expression,
97                                       offset,
98                                       max_count,
99                                       out total_matches,
100                                       cancellable);
101         }
102
103         string[] filter = {};
104         foreach (var object_prop in MediaObjectProxy.PROPERTIES) {
105             filter += object_prop;
106         }
107
108         foreach (var container_prop in MediaContainerProxy.PROPERTIES) {
109             filter += container_prop;
110         }
111
112         foreach (var item_prop in MediaItemProxy.PROPERTIES) {
113             filter += item_prop;
114         }
115
116         var ext_expression = this.translate_expression (expression);
117         var result = yield this.actual_container.search_objects (
118                                         ext_expression.to_string (),
119                                         offset,
120                                         max_count,
121                                         filter);
122         total_matches = result.length;
123
124         return yield this.create_media_objects (result);
125     }
126
127     public override async MediaObject? find_object (string       id,
128                                                     Cancellable? cancellable)
129                                                     throws GLib.Error {
130         MediaObject media_object = null;
131
132         // Create proxy to MediaObject iface
133         MediaObjectProxy actual_object = Bus.get_proxy_sync (BusType.SESSION,
134                                                              this.service_name,
135                                                              id);
136
137         if (actual_object.object_type == "container") {
138             media_object = this.find_container_by_id (id);
139
140             if (media_object == null) {
141                 // Not a child container, lets search in child containers then
142                 foreach (var container in this.containers) {
143                     media_object = yield container.find_object (id,
144                                                                 cancellable);
145
146                     if (media_object != null) {
147                         break;
148                     }
149                 }
150             }
151         } else {
152             var parent_container = new DummyContainer
153                                         ((string) actual_object.parent,
154                                          "LaLaLa",
155                                          0,
156                                          null);
157
158             Properties props_iface = Bus.get_proxy_sync (BusType.SESSION,
159                                                          this.service_name,
160                                                          id);
161
162             var props = yield props_iface.get_all (MediaItemProxy.IFACE);
163
164             // Its an item then
165             media_object = yield this.item_factory.create (
166                                         id,
167                                         actual_object.object_type,
168                                         actual_object.display_name,
169                                         props,
170                                         this.service_name,
171                                         this.host_ip,
172                                         parent_container);
173         }
174
175         return media_object;
176     }
177
178     private async MediaObjects create_media_objects (
179                                         HashTable<string,Variant>[] all_props,
180                                         MediaContainer?             parent
181                                         = null) throws GLib.Error {
182         var media_objects = new MediaObjects ();
183
184         foreach (var props in all_props) {
185             var id = (string) props.lookup ("Path");
186             var type = (string) props.lookup ("Type");
187
188             MediaContainer parent_container;
189             if (parent != null) {
190                 parent_container = parent;
191             } else {
192                 var parent_id = (string) props.lookup ("Parent");
193
194                 parent_container = new DummyContainer (parent_id,
195                                                        "LaLaLa",
196                                                        0,
197                                                        null);
198             }
199
200             MediaObject media_object = null;
201             if (type == "container") {
202                 media_object = this.find_container_by_id (id);
203             }
204
205             if (media_object == null) {
206                 var title = (string) props.lookup ("DisplayName");
207
208                 if (type == "container") {
209                     var child_count = (uint) props.lookup ("ChildCount");
210
211                     media_object = new DummyContainer (id,
212                                                        title,
213                                                        child_count,
214                                                        parent_container);
215                 } else {
216                     // Its an item then
217                     media_object = yield this.item_factory.create (
218                                         id,
219                                         type,
220                                         title,
221                                         props,
222                                         this.service_name,
223                                         this.host_ip,
224                                         parent_container);
225                 }
226             }
227
228             media_objects.add (media_object);
229         }
230
231         return media_objects;
232     }
233
234     private async void refresh_child_containers () throws GLib.Error {
235         string[] filter = {};
236
237         foreach (var object_prop in MediaObjectProxy.PROPERTIES) {
238             filter += object_prop;
239         }
240
241         foreach (var container_prop in MediaContainerProxy.PROPERTIES) {
242             filter += container_prop;
243         }
244
245         var children_props = yield this.actual_container.list_containers (
246                                         0,
247                                         0,
248                                         filter);
249         this.containers.clear ();
250
251         foreach (var props in children_props) {
252             var path = (string) props.lookup ("Path");
253             var title = (string) props.lookup ("DisplayName");
254             var child_count = (uint) props.lookup ("ChildCount");
255             var searchable = (bool) props.lookup ("Searchable");
256
257             var container = new Container (path,
258                                            title,
259                                            child_count,
260                                            searchable,
261                                            this.service_name,
262                                            path,
263                                            this.host_ip,
264                                            this);
265             this.containers.add (container);
266         }
267     }
268
269     private async void update_container (bool connect_signal = false) {
270         try {
271             // Update our information about the container
272             yield this.refresh_child_containers ();
273         } catch (GLib.Error err) {
274             warning ("Failed to update information about container '%s': %s",
275                      this.actual_container.get_object_path (),
276                      err.message);
277         }
278
279         // and signal the clients
280         this.updated ();
281
282         if (connect_signal) {
283             this.actual_container.updated.connect (this.on_updated);
284         }
285     }
286
287     private void on_updated (MediaContainerProxy actual_container) {
288         this.update_container.begin ();
289     }
290
291     private MediaContainer find_container_by_id (string id) {
292         MediaContainer target = null;
293
294         foreach (var container in this.containers) {
295             if (container.id == id) {
296                 target = container;
297
298                 break;
299             }
300         }
301
302         return target;
303     }
304
305     private SearchExpression translate_expression (
306                                         SearchExpression upnp_expression) {
307         if (upnp_expression is RelationalExpression) {
308             var expression = upnp_expression as RelationalExpression;
309             var ext_expression = new RelationalExpression ();
310             ext_expression.op = expression.op;
311             ext_expression.operand1 = this.translate_property (
312                                         expression.operand1);
313             ext_expression.operand2 = expression.operand2;
314
315             return ext_expression;
316         } else {
317             var expression = upnp_expression as LogicalExpression;
318             var ext_expression = new LogicalExpression ();
319
320             ext_expression.op = expression.op;
321             ext_expression.operand1 = this.translate_expression (
322                                         expression.operand1);
323             ext_expression.operand2 = this.translate_expression (
324                                         expression.operand2);
325
326             return ext_expression;
327         }
328     }
329
330     public string translate_property (string property) {
331         switch (property) {
332         case "@id":
333             return "Path";
334         case "@parentID":
335             return "Parent";
336         case "dc:title":
337             return "DisplayName";
338         case "dc:creator":
339         case "upnp:artist":
340         case "upnp:author":
341             return "Artist";
342         case "upnp:album":
343             return "Album";
344         default:
345             return property;
346         }
347     }
348 }
349