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