Import the initial Gallery 2 code
[g2-sharing] / src / gallery2.c
1 /*
2  * This file is part of sharing-plugin-gallery2
3  *
4  * Copyright (C) 2009 Heikki Kallasjoki. All rights reserved.
5  * Copyright (C) 2008-2009 Nokia Corporation. All rights reserved.
6  *
7  * This code is licensed under a MIT-style license, that can be
8  * found in the file called "COPYING" in the root directory.
9  *
10  */
11
12 #include <stdio.h>
13 #include <string.h>
14 #include <glib.h>
15 #include <osso-log.h>
16 #include <sharing-http.h>
17 #include "gallery2.h"
18
19 /**
20  * gallery2_login:
21  * @con: Connection to use
22  * @urlbase: Base URL to the Gallery 2 site
23  * @username: User name
24  * @password: Password
25  * @cookies: Output parameter for any cookies set.
26  * @auth: Output paremeter for the authentication token.
27  *
28  * Logs in to the Gallery 2 service.
29  *
30  * Returns: Validation result.
31  */
32 SharingPluginInterfaceAccountValidateResult
33 gallery2_login (ConIcConnection* con,
34                 const gchar* urlbase, const gchar* username, const gchar* password,
35                 GHashTable** cookies, gchar** auth)
36 {
37         SharingPluginInterfaceAccountValidateResult ret = SHARING_ACCOUNT_VALIDATE_SUCCESS;
38
39         SharingHTTP* http = sharing_http_new ();
40
41         *cookies = 0;
42         *auth = 0;
43
44         /* Do the login request */
45
46         gchar* url = g_strdup_printf("%s/main.php?g2_controller=remote:GalleryRemote&"
47                         "g2_form[cmd]=login&g2_form[protocol_version]=2.0&"
48                         "g2_form[uname]=%s&g2_form[password]=%s",
49                         urlbase, username, password);
50
51         sharing_http_set_connection (http, con);
52         SharingHTTPRunResponse res = sharing_http_run (http, url);
53
54         g_free (url);
55         url = 0;
56
57         /* Parse the response */
58
59         if (res == SHARING_HTTP_RUNRES_SUCCESS && sharing_http_get_res_code (http) == 200)
60         {
61                 /* Split response into lines */
62
63                 gchar** lines = 0;
64
65                 {
66                         gsize content_len = 0;
67                         const gchar* content = sharing_http_get_res_content (http, &content_len);
68                         gchar* c = g_strndup (content, content_len); /* force \0 termination */
69                         lines = g_strsplit_set (c, "\r\n", 0);
70                         g_free (c);
71                 }
72
73                 /* Process the lines */
74
75                 *cookies = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
76
77                 gboolean in_body = FALSE;
78
79                 for (gchar** p = lines; *p; p++)
80                 {
81                         gchar* line = *p;
82
83                         if (!in_body)
84                         {
85                                 if (g_ascii_strncasecmp (line, "Set-Cookie:", 11) == 0)
86                                 {
87                                         /* Extract the key=value part of the cookie */
88
89                                         gchar** data = 0;
90
91                                         {
92                                                 gchar* c = g_strchug (line+11); /* start of cookie data */
93
94                                                 char* end = strchr (c, ';');
95                                                 if (end) *end = 0;
96
97                                                 data = g_strsplit (c, "=", 2);
98                                         }
99
100                                         if (!data[0] || !data[1])
101                                         {
102                                                 /* Bad Set-Cookie: header, ignore */
103                                                 g_strfreev (data);
104                                                 continue;
105                                         }
106
107                                         /* Insert into our table */
108
109                                         g_hash_table_replace (*cookies, data[0], data[1]);
110                                         g_free (data); /* not g_strfreev! strings still used in "cookies" */
111
112                                         continue;
113                                 }
114
115                                 if (g_str_has_prefix (line, "#__GR2PROTO__"))
116                                 {
117                                         in_body = TRUE;
118                                         continue;
119                                 }
120                         }
121                         else
122                         {
123                                 /* Split key=value into fields */
124
125                                 gchar* value = strchr (line, '=');
126                                 if (!value) continue;
127                                 *value = 0;
128                                 value++;
129
130                                 /* Process the necessary parts */
131
132                                 if (strcmp (line, "status") == 0 && strcmp (value, "0") != 0)
133                                 {
134                                         ULOG_ERR_L ("Gallery 2 login auth failed\n");
135                                         ret = SHARING_ACCOUNT_VALIDATE_FAILED;
136                                         break;
137                                 }
138
139                                 if (strcmp (line, "auth_token") == 0)
140                                 {
141                                         *auth = g_strdup (value);
142                                         continue;
143                                 }
144                         }
145                 }
146
147                 g_strfreev (lines);
148         }
149         else
150         {
151                 ULOG_ERR_L ("Gallery 2 login connection failed\n");
152                 ret = SHARING_ACCOUNT_VALIDATE_ERROR_CONNECTION;
153         }
154
155         if (ret != SHARING_ACCOUNT_VALIDATE_SUCCESS)
156         {
157                 if (*cookies) g_hash_table_unref (*cookies);
158                 if (*auth) g_free (auth);
159                 *cookies = 0;
160                 *auth = 0;
161         }
162
163         sharing_http_unref (http);
164         return ret;
165 }
166
167 /**
168  * gallery2_lookup_album:
169  * @con: Connection to use
170  * @urlbase: Base URL to the Gallery 2 site
171  * @albumpath: Slash-separated path to album
172  * @album: Output parameter to hold the album ID
173  * @cookies: Cookies from gallery2_login.
174  * @auth: Authentication token from gallery2_login.
175  *
176  * Retrieves the album id based on an album path.
177  */
178 SharingPluginInterfaceAccountValidateResult
179 gallery2_lookup_album (ConIcConnection* con,
180                 const gchar* urlbase, const gchar* albumpath, gchar** album,
181                 GHashTable* cookies, gchar* auth)
182 {
183         SharingPluginInterfaceAccountValidateResult ret = SHARING_ACCOUNT_VALIDATE_ERROR_UNKNOWN;
184
185         SharingHTTP* http = sharing_http_new ();
186
187         /* Prepare and send the request */
188
189         gchar* url = g_strdup_printf("%s/main.php?g2_controller=remote:GalleryRemote%s%s&"
190                         "g2_form[cmd]=fetch-albums-prune&g2_form[protocol_version]=2.2&g2_form[no_perms]=yes",
191                         urlbase,
192                         auth ? "&g2_authToken=" : "", auth ? auth : "");
193
194         if (cookies)
195         {
196                 GHashTableIter iter;
197                 gpointer key, value;
198                 g_hash_table_iter_init (&iter, cookies);
199                 while (g_hash_table_iter_next (&iter, &key, &value))
200                 {
201                         gchar* hdr = g_strdup_printf("Cookie: %s=%s", (gchar*)key, (gchar*)value);
202                         sharing_http_add_req_header_line (http, hdr);
203                         g_free (hdr);
204                 }
205         }
206
207         sharing_http_set_connection (http, con);
208         SharingHTTPRunResponse res = sharing_http_run (http, url);
209
210         g_free (url);
211         url = 0;
212
213         /* Parse the response into an album map. */
214
215         GHashTable* album_names = 0;  /* map string (display-name) -> GSList [ string (url-name) ] */
216         GHashTable* album_parent = 0; /* map string (url-name) -> string (url-name) */
217         gchar* album_root = 0;        /* root album url-name */
218         gboolean valid = FALSE;       /* true if the maps are usable */
219         char** lines = 0;             /* raw data (response lines) */
220
221         if (res == SHARING_HTTP_RUNRES_SUCCESS && sharing_http_get_res_code (http) == 200)
222         {
223                 {
224                         gsize content_len = 0;
225                         const gchar* content = sharing_http_get_res_body (http, &content_len);
226                         gchar* c = g_strndup (content, content_len); /* force \0 termination */
227                         lines = g_strsplit_set (c, "\r\n", 0);
228                         g_free (c);
229                 }
230
231                 gboolean in_body = FALSE;
232                 gchar* current_ref_num = 0;
233                 gchar* current_url_name = 0;
234
235                 for (gchar** p = lines; *p; p++)
236                 {
237                         gchar* line = *p;
238
239                         if (!in_body)
240                         {
241                                 if (g_str_has_prefix (line, "#__GR2PROTO__"))
242                                 {
243                                         album_names = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_slist_free);
244                                         album_parent = g_hash_table_new (g_str_hash, g_str_equal);
245                                         in_body = TRUE;
246                                 }
247                                 continue;
248                         }
249
250                         gchar* value = strchr (line, '=');
251                         if (!value) continue;
252                         *value = 0;
253                         value++;
254
255                         if (strcmp (line, "status") == 0)
256                         {
257                                 if (strcmp (value, "0") == 0)
258                                         valid = TRUE;
259                                 continue;
260                         }
261                         else if (g_str_has_prefix (line, "album.name."))
262                         {
263                                 current_ref_num = line + 11;
264                                 current_url_name = value;
265                         }
266                         else if (g_str_has_prefix (line, "album.title."))
267                         {
268                                 if (!current_ref_num || strcmp (current_ref_num, line + 12) != 0)
269                                         continue;
270                                 GSList* others = g_hash_table_lookup (album_names, value);
271                                 if (others) g_hash_table_steal (album_names, value);
272                                 g_hash_table_insert (album_names, value, g_slist_prepend (others, current_url_name));
273                         }
274                         else if (g_str_has_prefix (line, "album.parent."))
275                         {
276                                 if (!current_ref_num || strcmp (current_ref_num, line + 13) != 0)
277                                         continue;
278                                 g_hash_table_insert (album_parent, current_url_name, value);
279                                 if (strcmp (value, "0") == 0)
280                                         album_root = current_url_name;
281                         }
282                 }
283         }
284         else
285                 ret = SHARING_ACCOUNT_VALIDATE_ERROR_CONNECTION;
286
287         sharing_http_unref (http);
288
289         /* Find the album we are interested in. */
290
291         *album = 0;
292
293         if (album_names && album_parent && album_root && valid)
294         {
295                 gchar* current_album = album_root;
296                 gboolean seen_parents = FALSE; /* for the root special case */
297                 gboolean found_final = FALSE;  /* to make sure the last real component is found */
298
299                 gchar** components = g_strsplit (albumpath, "/", 0);
300
301                 for (gchar** p = components; *p; p++)
302                 {
303                         if (!**p) continue; /* ignore empty path components */
304                         found_final = FALSE; /* this component needs to be found */
305                         GSList* candidates = g_hash_table_lookup (album_names, *p);
306                         /* bad case: no candidates at all */
307                         if (!candidates) break;
308                         /* special case 1: if only one candidate and no unseen parents, choose that */
309                         if (!seen_parents && !g_slist_next (candidates))
310                         {
311                                 found_final = TRUE;
312                                 current_album = candidates->data;
313                                 continue;
314                         }
315                         /* general case: find a candidate with an acceptable parent */
316                         while (candidates)
317                         {
318                                 gchar* parent = g_hash_table_lookup (album_parent, candidates->data);
319                                 /* suitable parents: current_album, or (if no specified parents) null or 0 (explicit root case) */
320                                 if ((parent && strcmp (parent, current_album) == 0)
321                                                 || (!seen_parents && (!parent || strcmp (parent, "0") == 0)))
322                                 {
323                                         found_final = TRUE;
324                                         current_album = candidates->data;
325                                         break;
326                                 }
327                                 candidates = g_slist_next (candidates); /* try next */
328                         }
329                 }
330
331                 g_strfreev (components);
332
333                 if (found_final)
334                 {
335                         *album = g_strdup(current_album);
336                         ret = SHARING_ACCOUNT_VALIDATE_SUCCESS;
337                 }
338         }
339
340         if (album_names) g_hash_table_unref (album_names);
341         if (album_parent) g_hash_table_unref (album_parent);
342         g_strfreev (lines);
343
344         return ret;
345 }
346
347 /* gallery2_send callback helper declarations */
348
349 struct gallery2_send_record
350 {
351         SharingTransfer* transfer;
352         gdouble progress_start;
353         gdouble progress_end;
354         guint64 media_bytes;
355         gboolean* dms;
356 };
357
358 gboolean gallery2_send_callback (SharingHTTP* http, guint64 bytes_sent, gpointer user_data);
359
360 /**
361  * gallery2_send:
362  * @con: Connection to use
363  * @transfer: Sharing transfer object
364  * @progress_start: Initial state of progress meter
365  * @progress_end: Desired final state of progress meter
366  * @dms: Dead man's switch
367  * @media: Media item to send
368  * @urlbase: Base URL to the Gallery 2 site
369  * @album: Album ID from gallery2_lookup_album.
370  * @cookies: Cookies from gallery2_login.
371  * @auth: Authentication token from gallery2_login.
372  *
373  * Sends a media item to a Gallery 2 service.
374  *
375  * Returns: Send result.
376  */
377 SharingPluginInterfaceSendResult
378 gallery2_send (ConIcConnection* con,
379                 SharingTransfer* transfer, gdouble progress_start, gdouble progress_end, gboolean *dms,
380                 SharingEntryMedia* media,
381                 const gchar* urlbase, const gchar* album, GHashTable* cookies, gchar* auth)
382 {
383         struct gallery2_send_record rec = {
384                 .transfer = transfer,
385                 .progress_start = progress_start,
386                 .progress_end = progress_end,
387                 .media_bytes = 0,
388                 .dms = dms
389         };
390
391         SharingPluginInterfaceSendResult ret = SHARING_SEND_SUCCESS;
392
393         SharingHTTP* http = sharing_http_new ();
394
395         /* Prepare and send the request */
396
397         /* gchar* album = "1652"; */ /* TODO: get from UI/login */
398
399         gchar* media_title = sharing_entry_media_get_title (media);
400         gchar* media_mime = sharing_entry_media_get_mime (media);
401         gchar* media_filename = sharing_entry_media_get_filename (media);
402
403         const gchar* desc = sharing_entry_media_get_desc (media);
404
405         gchar* url = g_strdup_printf("%s/main.php?g2_controller=remote:GalleryRemote%s%s&"
406                         "g2_form[cmd]=add-item&g2_form[protocol_version]=2.0&"
407                         "g2_form[set_albumName]=%s&g2_form[caption]=%s"
408                         "%s%s%s%s",
409                         urlbase,
410                         auth ? "&g2_authToken=" : "", auth ? auth : "",
411                         album, media_title,
412                         desc ? "&g2_form[extrafield.Summary]=" : "", desc ? desc : "",
413                         desc ? "&g2_form[extrafield.Description]=" : "", desc ? desc : "");
414
415         if (cookies)
416         {
417                 GHashTableIter iter;
418                 gpointer key, value;
419                 g_hash_table_iter_init (&iter, cookies);
420                 while (g_hash_table_iter_next (&iter, &key, &value))
421                 {
422                         gchar* hdr = g_strdup_printf("Cookie: %s=%s", (gchar*)key, (gchar*)value);
423                         sharing_http_add_req_header_line (http, hdr);
424                         g_free (hdr);
425                 }
426         }
427
428         sharing_http_add_req_multipart_file_with_filename (http,
429                 "g2_userfile",
430                 sharing_entry_media_get_localpath (media),
431                 media_mime ? media_mime : "image/jpeg",
432                 media_filename ? media_filename : "unknown.jpg");
433
434         g_free (media_title);
435         g_free (media_mime);
436         g_free (media_filename);
437
438         media_title = media_mime = media_filename = 0;
439
440         rec.media_bytes = sharing_entry_media_get_size (media);
441         sharing_http_set_progress_callback (http, gallery2_send_callback, &rec);
442
443         *dms = FALSE;
444         sharing_http_set_connection (http, con);
445         SharingHTTPRunResponse res = sharing_http_run (http, url);
446         *dms = FALSE;
447
448         g_free (url);
449         url = 0;
450
451         /* Parse the response */
452
453         if (res == SHARING_HTTP_RUNRES_SUCCESS && sharing_http_get_res_code (http) == 200)
454         {
455                 gchar** lines = 0;
456
457                 {
458                         gsize content_len = 0;
459                         const gchar* content = sharing_http_get_res_body (http, &content_len);
460                         gchar* c = g_strndup (content, content_len); /* force \0 termination */
461                         lines = g_strsplit_set (c, "\r\n", 0);
462                         g_free (c);
463                 }
464
465                 gboolean in_body = FALSE;
466                 ret = SHARING_SEND_ERROR_UNKNOWN;
467
468                 for (gchar** p = lines; *p; p++)
469                 {
470                         gchar* line = *p;
471
472                         if (!in_body)
473                         {
474                                 if (g_str_has_prefix (line, "#__GR2PROTO__"))
475                                         in_body = TRUE;
476                                 continue;
477                         }
478
479                         gchar* value = strchr (line, '=');
480                         if (!value) continue;
481                         *value = 0;
482                         value++;
483
484                         if (strcmp (line, "status") == 0)
485                         {
486                                 if (strcmp (value, "0") == 0)
487                                         ret = SHARING_SEND_SUCCESS;
488                                 break;
489                         }
490                 }
491
492                 g_strfreev (lines);
493         }
494         else if (res == SHARING_HTTP_RUNRES_CANCELLED)
495                 ret = SHARING_SEND_CANCELLED;
496         else
497                 ret = SHARING_SEND_ERROR_CONNECTION;
498
499         sharing_http_unref (http);
500
501         *dms = FALSE;
502         sharing_transfer_set_progress (transfer, progress_end);
503
504         return ret;
505 }
506
507 /* gallery2_send callback implementation */
508
509 gboolean gallery2_send_callback (SharingHTTP* http, guint64 bytes_sent, gpointer user_data)
510 {
511         struct gallery2_send_record* rec = user_data;
512
513         if (!sharing_transfer_continue (rec->transfer))
514                 return FALSE;
515
516         *rec->dms = FALSE;
517
518         gdouble progress = (rec->progress_start + rec->progress_end) / 2.0;
519
520         if (rec->media_bytes)
521         {
522                 if (bytes_sent >= rec->media_bytes)
523                         progress = rec->progress_end;
524                 else
525                         progress = rec->progress_start + (bytes_sent / (gdouble)rec->media_bytes) * (rec->progress_end - rec->progress_start);
526         }
527
528         sharing_transfer_set_progress (rec->transfer, progress);
529
530         return TRUE;
531 }