build,core,plugins: Port to GDBus and GVariant
[profile/ivi/rygel.git] / src / plugins / tracker / rygel-tracker-metadata-values.vala
1 /*
2  * Copyright (C) 2008 Zeeshan Ali <zeenix@gmail.com>.
3  * Copyright (C) 2008 Nokia Corporation.
4  *
5  * Author: Zeeshan Ali <zeenix@gmail.com>
6  *
7  * This file is part of Rygel.
8  *
9  * Rygel is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Rygel is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  */
23
24 using GUPnP;
25 using Gee;
26
27 /**
28  * Container listing possible values of a particuler Tracker metadata key.
29  */
30 public abstract class Rygel.Tracker.MetadataValues : Rygel.SimpleContainer {
31     /* class-wide constants */
32     private const string TRACKER_SERVICE = "org.freedesktop.Tracker1";
33     private const string RESOURCES_PATH = "/org/freedesktop/Tracker1/Resources";
34     private const string ITEM_VARIABLE = "?item";
35
36     private ItemFactory item_factory;
37
38     // In tracker 0.7, we might don't get values of keys in place so you need a
39     // chain of keys to reach to final destination. For instances:
40     // nmm:Performer -> nmm:artistName
41     public string[] key_chain;
42
43     private string child_class;
44
45     private ResourcesIface resources;
46     private ResourcesClassIface resources_class;
47
48     public MetadataValues (string         id,
49                            MediaContainer parent,
50                            string         title,
51                            ItemFactory    item_factory,
52                            string[]       key_chain,
53                            string?        child_class = null) {
54         base (id, parent, title);
55
56         this.item_factory = item_factory;
57         this.key_chain = key_chain;
58         this.child_class = child_class;
59
60         try {
61             this.create_proxies ();
62         } catch (IOError error) {
63             critical (_("Failed to connect to session bus: %s"), error.message);
64
65             return;
66         }
67
68         this.fetch_metadata_values.begin ();
69
70         this.hook_to_changes ();
71     }
72
73     private async void fetch_metadata_values () {
74         // First thing, clear the existing hierarchy, if any
75         this.clear ();
76
77         int i;
78         var triplets = new QueryTriplets ();
79
80         // All variables used in the query
81         var num_keys = this.key_chain.length - 1;
82         var variables = new string[num_keys];
83         for (i = 0; i < num_keys; i++) {
84             variables[i] = "?" + key_chain[i].replace (":", "_");
85
86             string subject;
87             if (i == 0) {
88                 subject = ITEM_VARIABLE;
89             } else {
90                 subject = variables[i - 1];
91             }
92
93             triplets.add (new QueryTriplet (subject,
94                                             this.key_chain[i],
95                                             variables[i]));
96         }
97
98         triplets.insert (0, new QueryTriplet (ITEM_VARIABLE,
99                                               "a",
100                                               this.item_factory.category));
101
102         // Variables to select from query
103         var selected = new ArrayList<string> ();
104         // Last variable is the only thing we are interested in the result
105         var last_variable = variables[num_keys - 1];
106         selected.add ("DISTINCT " + last_variable);
107
108         var query = new SelectionQuery (selected,
109                                         triplets,
110                                         null,
111                                         last_variable);
112
113         try {
114             yield query.execute (this.resources);
115         } catch (IOError error) {
116             critical (_("Error getting all values for '%s': %s"),
117                       string.joinv (" -> ", this.key_chain),
118                       error.message);
119
120             return;
121         }
122
123         /* Iterate through all the values */
124         for (i = 0; i < query.result.length[0]; i++) {
125             string value = query.result[i, 0];
126
127             if (value == "") {
128                 continue;
129             }
130
131             var title = this.create_title_for_value (value);
132             var id = this.create_id_for_title (title);
133             if (!this.is_child_id_unique (id)) {
134                 continue;
135             }
136
137             // The child container can use the same triplets we used in our
138             // query.
139             var child_triplets = new QueryTriplets.clone (triplets);
140
141             // However we constrain the object of our last triplet.
142             var filters = new ArrayList<string> ();
143             var filter = this.create_filter (child_triplets.last ().obj, value);
144             filters.add (filter);
145
146             var container = new SearchContainer (id,
147                                                  this,
148                                                  title,
149                                                  this.item_factory,
150                                                  child_triplets,
151                                                  filters);
152             if (this.child_class != null) {
153                 container.upnp_class = child_class;
154             }
155
156             this.add_child (container);
157         }
158
159         this.updated ();
160     }
161
162     public override async MediaObject? find_object (string       id,
163                                                     Cancellable? cancellable)
164                                                     throws GLib.Error {
165         if (this.is_our_child (id)) {
166             return yield base.find_object (id, cancellable);
167         } else {
168             return null;
169         }
170     }
171
172     protected virtual string create_id_for_title (string title) {
173         return this.id + ":" + title;
174     }
175
176     protected virtual string create_title_for_value (string value) {
177         return value;
178     }
179
180     protected virtual string create_filter (string variable, string value) {
181         return variable + " = \"" + value + "\"";
182     }
183
184     private bool is_our_child (string id) {
185         return id.has_prefix (this.id + ":");
186     }
187
188     private void create_proxies () throws IOError {
189         this.resources = Bus.get_proxy_sync (BusType.SESSION,
190                                              TRACKER_SERVICE,
191                                              RESOURCES_PATH);
192         this.resources_class = Bus.get_proxy_sync (
193                                         BusType.SESSION,
194                                         TRACKER_SERVICE,
195                                         this.item_factory.resources_class_path);
196     }
197
198     private void hook_to_changes () {
199         // For any changes in subjects, just recreate hierarchy
200         this.resources_class.subjects_added.connect ((subjects) => {
201             this.fetch_metadata_values.begin ();
202         });
203         this.resources_class.subjects_removed.connect ((subjects) => {
204             this.fetch_metadata_values.begin ();
205         });
206         this.resources_class.subjects_changed.connect ((before, after) => {
207             this.fetch_metadata_values.begin ();
208         });
209     }
210
211     private bool is_child_id_unique (string child_id) {
212         var unique = true;
213
214         foreach (var child in this.children) {
215             if (child.id == child_id) {
216                 unique = false;
217
218                 break;
219             }
220         }
221
222         return unique;
223     }
224 }
225