1 /*
2 * Copyright (C) 2012 W. Michael Petullo.
3 *
4 * Contact: W. Michael Petullo <mike@flyn.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * as published by the Free Software Foundation; version 2.1 of
9 * the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /* See grl-daap-db.c for a description of this database. */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <grilo.h>
28 #include <glib/gi18n-lib.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <glib.h>
32 #include <string.h>
33 #include <libdmapsharing/dmap.h>
34
35 #include "grl-dpap-compat.h"
36 #include "grl-common.h"
37 #include "grl-dpap-db.h"
38
39 #define PHOTOS_ID "photos"
40 #define PHOTOS_NAME _("Photos")
41
42 /* Media IDs start at max and go down. Container IDs start at 1 and go up. */
43 static guint nextid = G_MAXINT; /* NOTE: this should be G_MAXUINT, but iPhoto can't handle it. */
44
45 struct GrlDpapDbPrivate {
46 /* Contains each picture container (tracked with photos hash table) */
47 GrlMedia *photos_container;
48
49 GHashTable *root;
50 GHashTable *photos;
51 };
52
53 enum {
54 PROP_0,
55 PROP_RECORD_FACTORY,
56 };
57
58 static guint
container_hash(gconstpointer a)59 container_hash (gconstpointer a)
60 {
61 return g_str_hash (grl_media_get_id (GRL_MEDIA (a)));
62 }
63
64 static gboolean
container_equal(gconstpointer a,gconstpointer b)65 container_equal (gconstpointer a, gconstpointer b)
66 {
67 return g_str_equal (grl_media_get_id (GRL_MEDIA (a)), grl_media_get_id (GRL_MEDIA (b)));
68 }
69
70 GrlDpapDb *
grl_dpap_db_new(void)71 grl_dpap_db_new (void)
72 {
73 GrlDpapDb *db = g_object_new (TYPE_GRL_DPAP_DB, NULL);
74
75 return db;
76 }
77
78 static DmapRecord *
grl_dpap_db_lookup_by_id(const DmapDb * db,guint id)79 grl_dpap_db_lookup_by_id (const DmapDb *db, guint id)
80 {
81 g_warning ("Not implemented");
82 return NULL;
83 }
84
85 static void
grl_dpap_db_foreach(const DmapDb * db,DmapIdRecordFunc func,gpointer data)86 grl_dpap_db_foreach (const DmapDb *db,
87 DmapIdRecordFunc func,
88 gpointer data)
89 {
90 g_warning ("Not implemented");
91 }
92
93 static gint64
grl_dpap_db_count(const DmapDb * db)94 grl_dpap_db_count (const DmapDb *db)
95 {
96 g_warning ("Not implemented");
97 return 0;
98 }
99
100 static void
set_insert(GHashTable * category,const char * category_name,char * set_name,GrlMedia * media)101 set_insert (GHashTable *category, const char *category_name, char *set_name, GrlMedia *media)
102 {
103 gchar *id = NULL;
104 GrlMedia *container;
105 GHashTable *set;
106
107 id = g_strdup_printf ("%s-%s", category_name, set_name);
108
109 container = grl_media_container_new ();
110 grl_media_set_id (container, id);
111 grl_media_set_title (container, set_name);
112
113 set = g_hash_table_lookup (category, container);
114 if (set == NULL) {
115 set = g_hash_table_new_full (container_hash, container_equal, g_object_unref, NULL);
116 g_hash_table_insert (category, g_object_ref (container), set);
117 }
118
119 g_hash_table_insert (set, g_object_ref (media), NULL);
120
121 g_free (id);
122 g_object_unref (container);
123 }
124
125 guint
grl_dpap_db_add(DmapDb * _db,DmapRecord * _record,GError ** error)126 grl_dpap_db_add (DmapDb *_db, DmapRecord *_record, GError **error)
127 {
128 g_assert (GRL_IS_DPAP_DB (_db));
129 g_assert (DMAP_IS_IMAGE_RECORD (_record));
130
131 GrlDpapDb *db = GRL_DPAP_DB (_db);
132 DmapImageRecord *record = DMAP_IMAGE_RECORD (_record);
133
134 gint height = 0,
135 width = 0,
136 largefilesize = 0,
137 creationdate = 0,
138 rating = 0;
139 GArray *thumbnail = NULL;
140 gchar *id_s = NULL,
141 *filename = NULL,
142 *aspectratio = NULL,
143 *format = NULL,
144 *comments = NULL,
145 *url = NULL;
146 GrlMedia *media;
147
148 g_object_get (record,
149 "large-filesize", &largefilesize,
150 "creation-date", &creationdate,
151 "rating", &rating,
152 "filename", &filename,
153 "aspect-ratio", &aspectratio,
154 "pixel-height", &height,
155 "pixel-width", &width,
156 "format", &format,
157 "comments", &comments,
158 "thumbnail", &thumbnail,
159 "location", &url,
160 NULL);
161
162 id_s = g_strdup_printf ("%u", nextid);
163
164 media = grl_media_image_new ();
165
166 grl_media_set_id (media, id_s);
167
168 if (filename)
169 grl_media_set_title (media, filename);
170
171 if (url) {
172 /* Replace URL's dpap:// with http:// */
173 memcpy (url, "http", 4);
174 grl_media_set_url (media, url);
175 }
176
177 grl_media_set_width (media, width);
178 grl_media_set_height (media, height);
179
180 set_insert (db->priv->photos, PHOTOS_ID, "Unknown", media);
181
182 g_free (id_s);
183 g_object_unref (media);
184 g_free (filename);
185 g_free (aspectratio);
186 g_free (format);
187 g_free (comments);
188 g_free (url);
189 g_array_unref (thumbnail);
190
191 return --nextid;
192 }
193
194 static gboolean
same_media(GrlMedia * a,GrlMedia * b)195 same_media (GrlMedia *a, GrlMedia *b)
196 {
197 return strcmp (grl_media_get_id (a), grl_media_get_id (b)) == 0;
198 }
199
200 void
grl_dpap_db_browse(GrlDpapDb * db,GrlMedia * container,GrlSource * source,guint op_id,guint skip,guint count,GrlSourceResultCb func,gpointer user_data)201 grl_dpap_db_browse (GrlDpapDb *db,
202 GrlMedia *container,
203 GrlSource *source,
204 guint op_id,
205 guint skip,
206 guint count,
207 GrlSourceResultCb func,
208 gpointer user_data)
209 {
210 g_assert (GRL_IS_DPAP_DB (db));
211
212 int i;
213 guint remaining;
214 GHashTable *hash_table;
215 GHashTableIter iter;
216 gpointer key, val;
217
218 const gchar *container_id = grl_media_get_id (container);
219 if (container_id == NULL) {
220 hash_table = db->priv->root;
221 } else if (same_media (container, GRL_MEDIA (db->priv->photos_container))) {
222 hash_table = db->priv->photos;
223 } else {
224 hash_table = g_hash_table_lookup (db->priv->photos, container);
225 }
226
227 /* Should not be NULL; this means the container requested
228 does not exist in the database. */
229 if (hash_table == NULL) {
230 GError *error = g_error_new (GRL_CORE_ERROR,
231 GRL_CORE_ERROR_BROWSE_FAILED,
232 "Invalid container identifier %s",
233 container_id);
234 func (source, op_id, NULL, 0, user_data, error);
235 goto done;
236 }
237
238 remaining = g_hash_table_size (hash_table) - skip;
239 remaining = remaining < count ? remaining : count;
240 g_hash_table_iter_init (&iter, hash_table);
241 for (i = 0; g_hash_table_iter_next (&iter, &key, &val) && i < skip + count; i++) {
242 if (i < skip)
243 continue;
244 if (grl_media_is_container (key))
245 grl_media_set_childcount (key, g_hash_table_size (val));
246 func (source, op_id, GRL_MEDIA (g_object_ref (key)), --remaining, user_data, NULL);
247 }
248 done:
249 return;
250 }
251
252 void
grl_dpap_db_search(GrlDpapDb * db,GrlSource * source,guint op_id,GHRFunc predicate,gpointer pred_user_data,GrlSourceResultCb func,gpointer user_data)253 grl_dpap_db_search (GrlDpapDb *db,
254 GrlSource *source,
255 guint op_id,
256 GHRFunc predicate,
257 gpointer pred_user_data,
258 GrlSourceResultCb func,
259 gpointer user_data)
260 {
261 g_assert (GRL_IS_DPAP_DB (db));
262
263 gint i, j, k;
264 guint remaining = 0;
265 gpointer key1, val1, key2, val2;
266 GHashTable *hash_tables[] = { db->priv->photos };
267
268 /* Use hash table to avoid duplicates */
269 GHashTable *results = NULL;
270 GHashTableIter iter1, iter2;
271
272 results = g_hash_table_new (g_str_hash, g_str_equal);
273
274 /* For photos ... */
275 for (i = 0; i < G_N_ELEMENTS (hash_tables); i++) {
276 g_hash_table_iter_init (&iter1, hash_tables[i]);
277 /* For each album or artist in above... */
278 for (j = 0; g_hash_table_iter_next (&iter1, &key1, &val1); j++) {
279 if (grl_media_is_container (key1)) {
280 g_hash_table_iter_init (&iter2, val1);
281 /* For each media item in above... */
282 for (k = 0; g_hash_table_iter_next (&iter2, &key2, &val2); k++) {
283 const char *id = grl_media_get_id (GRL_MEDIA (key2));
284 /* If the predicate returns true, add to results set. */
285 if (predicate (key2, val2, pred_user_data)
286 && ! g_hash_table_contains (results, id)) {
287 remaining++;
288 g_hash_table_insert (results, (gpointer) id, key2);
289 }
290 }
291 }
292 }
293 }
294
295 /* Process results set. */
296 g_hash_table_iter_init (&iter1, results);
297 for (i = 0; g_hash_table_iter_next (&iter1, &key1, &val1); i++) {
298 func (source, op_id, GRL_MEDIA (g_object_ref (val1)), --remaining, user_data, NULL);
299 }
300 }
301
302 static void
dmap_db_interface_init(gpointer iface,gpointer data)303 dmap_db_interface_init (gpointer iface, gpointer data)
304 {
305 DmapDbInterface *dpap_db = iface;
306
307 g_assert (G_TYPE_FROM_INTERFACE (dpap_db) == DMAP_TYPE_DB);
308
309 dpap_db->add = grl_dpap_db_add_compat;
310 dpap_db->lookup_by_id = grl_dpap_db_lookup_by_id;
311 dpap_db->foreach = grl_dpap_db_foreach;
312 dpap_db->count = grl_dpap_db_count;
313 }
314
G_DEFINE_TYPE_WITH_CODE(GrlDpapDb,grl_dpap_db,G_TYPE_OBJECT,G_ADD_PRIVATE (GrlDpapDb)G_IMPLEMENT_INTERFACE (DMAP_TYPE_DB,dmap_db_interface_init))315 G_DEFINE_TYPE_WITH_CODE (GrlDpapDb, grl_dpap_db, G_TYPE_OBJECT,
316 G_ADD_PRIVATE (GrlDpapDb)
317 G_IMPLEMENT_INTERFACE (DMAP_TYPE_DB, dmap_db_interface_init))
318
319 static GObject*
320 grl_dpap_db_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params)
321 {
322 GObject *object;
323
324 object = G_OBJECT_CLASS (grl_dpap_db_parent_class)->constructor (type, n_construct_params, construct_params);
325
326 return object;
327 }
328
329 static void
grl_dpap_db_init(GrlDpapDb * db)330 grl_dpap_db_init (GrlDpapDb *db)
331 {
332 db->priv = grl_dpap_db_get_instance_private (db);
333
334 db->priv->photos_container = grl_media_container_new ();
335
336 grl_media_set_id (GRL_MEDIA (db->priv->photos_container), PHOTOS_ID);
337 grl_media_set_title (GRL_MEDIA (db->priv->photos_container), PHOTOS_NAME);
338
339 db->priv->root = g_hash_table_new_full (container_hash, container_equal, g_object_unref, (GDestroyNotify) g_hash_table_destroy);
340 db->priv->photos = g_hash_table_new_full (container_hash, container_equal, g_object_unref, (GDestroyNotify) g_hash_table_destroy);
341
342 g_hash_table_insert (db->priv->root, g_object_ref (db->priv->photos_container), db->priv->photos);
343 }
344
345 static void
grl_dpap_db_finalize(GObject * object)346 grl_dpap_db_finalize (GObject *object)
347 {
348 GrlDpapDb *db = GRL_DPAP_DB (object);
349
350 GRL_DEBUG ("Finalizing GrlDpapDb");
351
352 g_object_unref (db->priv->photos_container);
353
354 g_hash_table_destroy (db->priv->photos);
355 }
356
357 static void
grl_dpap_db_class_init(GrlDpapDbClass * klass)358 grl_dpap_db_class_init (GrlDpapDbClass *klass)
359 {
360 GObjectClass *object_class = G_OBJECT_CLASS (klass);
361
362 object_class->finalize = grl_dpap_db_finalize;
363 object_class->constructor = grl_dpap_db_constructor;
364 }
365