Rename package to sharing-twitter-multi (maemo- prefix is unnecessary)
[maemo-sharing-twitter-multi:maemo-sharing-twitter-multi.git] / src / twitmulti.c
1 /*
2  * This file is part of sharing-twitter-multi
3  *
4  * Copyright (C) 2010 Igalia, S.L.
5  * Authors: Alberto Garcia <agarcia@igalia.com>
6  *
7  * This library is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * version 3 as published by the Free Software Foundation.
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this software. If not, see <http://www.gnu.org/licenses/>
17  */
18
19 #include "twitmulti.h"
20 #include "util.h"
21
22 #include <hildon-mime.h>
23 #include <sharing-http.h>
24 #include <libxml/parser.h>
25
26 #define TWITPIC_API_KEY                 "1f9ce28260728df0a40cafe3506a9407"
27 #define MOBYPICTURE_API_KEY             "L5RL7tAoAAsgZqKP"
28
29 static gchar *
30 parse_server_response                   (const gchar       *response,
31                                          gsize              size,
32                                          TwitterPicService  service)
33 {
34   xmlDoc *doc;
35   const gchar *rootid, *urlid;
36   gchar *url = NULL;
37
38   g_return_val_if_fail (response != NULL, NULL);
39
40   switch (service)
41     {
42     case SERVICE_TWITPIC:
43     case SERVICE_IMGLY:
44     case SERVICE_POSTEROUS:
45       rootid = "image";
46       urlid  = "url";
47       break;
48     case SERVICE_MOBYPICTURE:
49     case SERVICE_TWITGOO:
50       rootid = "rsp";
51       urlid  = "mediaurl";
52       break;
53     }
54
55   doc = xmlParseMemory (response, size);
56   if (doc != NULL)
57     {
58       xmlNode *iter, *root = NULL;
59
60       for (iter = xmlDocGetRootElement (doc); iter && !root; iter = iter->next)
61         if (xmlStrEqual (iter->name, (xmlChar *) rootid))
62           root = iter;
63
64       if (root != NULL)
65         for (iter = root->xmlChildrenNode; iter && !url; iter = iter->next)
66           if (xmlStrEqual (iter->name, (xmlChar *) urlid))
67             {
68               xmlChar *s = xmlNodeGetContent (iter);
69               url = g_strdup ((gchar *) s);
70               xmlFree (s);
71             }
72
73       xmlFreeDoc (doc);
74     }
75
76   return url;
77 }
78
79 static void
80 open_auth_url_cb                        (const gchar *url,
81                                          gpointer     data)
82 {
83   if (url)
84     hildon_uri_open (url, NULL, NULL);
85   gtk_dialog_response (GTK_DIALOG (data), url ? GTK_RESPONSE_YES : GTK_RESPONSE_NO);
86 }
87
88 static gboolean
89 open_auth_url                           (SharingAccount  *account,
90                                          GtkWindow       *parent,
91                                          ConIcConnection *con)
92 {
93   GtkWidget *d;
94   gboolean response;
95
96   d = gtk_dialog_new ();
97   gtk_window_set_title (GTK_WINDOW (d), "Opening web browser ...");
98   gtk_window_set_transient_for (GTK_WINDOW (d), parent);
99   hildon_gtk_window_set_progress_indicator (GTK_WINDOW (d), TRUE);
100   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (d)->vbox),
101                      gtk_label_new ("Opening web browser, please wait ..."));
102   gtk_widget_show_all (d);
103
104   twitter_get_auth_url (account, con, open_auth_url_cb, d);
105
106   do
107     {
108       response = gtk_dialog_run (GTK_DIALOG (d));
109     }
110   while (response != GTK_RESPONSE_YES && response != GTK_RESPONSE_NO);
111
112   gtk_widget_destroy (d);
113
114   return (response == GTK_RESPONSE_YES);
115 }
116
117 static void
118 register_account_clicked                (GtkWidget *button,
119                                          gpointer   data)
120 {
121   gtk_dialog_response (GTK_DIALOG (gtk_widget_get_toplevel (button)),
122                        GTK_RESPONSE_ACCEPT);
123 }
124
125 static void
126 text_changed_cb                         (GObject    *entry,
127                                          GParamSpec *pspec,
128                                          gpointer    data)
129 {
130   const gchar *text = gtk_entry_get_text (GTK_ENTRY (entry));
131   while (*text != '\0' && g_ascii_isspace (*text))
132     text++;
133   gtk_dialog_set_response_sensitive (GTK_DIALOG (data), GTK_RESPONSE_ACCEPT,
134                                      *text != '\0');
135 }
136
137 static gboolean
138 twitmulti_account_enter_pin             (SharingAccount *account,
139                                          GtkWindow      *parent)
140 {
141   gint response;
142   gchar *pin = NULL;
143   GtkWidget *d, *label, *entry, *hbox;
144
145   g_return_val_if_fail (account != NULL, FALSE);
146
147   d = gtk_dialog_new ();
148   hbox = gtk_hbox_new (FALSE, 0);
149   gtk_window_set_title (GTK_WINDOW (d), "Account setup - Twitter PIN number");
150   gtk_window_set_transient_for (GTK_WINDOW (d), parent);
151   gtk_dialog_add_button (GTK_DIALOG (d), GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
152   gtk_dialog_set_response_sensitive (GTK_DIALOG (d), GTK_RESPONSE_ACCEPT, FALSE);
153
154   label = gtk_label_new ("Enter PIN number:");
155   entry = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH);
156   gtk_entry_set_width_chars (GTK_ENTRY (entry), 10);
157
158   g_signal_connect (entry, "notify::text", G_CALLBACK (text_changed_cb), d);
159
160   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (d)->vbox), hbox);
161   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
162   gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
163
164   gtk_widget_show_all (d);
165   response = gtk_dialog_run (GTK_DIALOG (d));
166
167   if (response == GTK_RESPONSE_ACCEPT)
168     pin = g_strstrip (g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))));
169
170   twitter_account_set_pin (account, pin);
171
172   g_free (pin);
173   gtk_widget_destroy (d);
174
175   return (response == GTK_RESPONSE_ACCEPT);
176 }
177
178 gboolean
179 twitmulti_account_setup                 (SharingAccount *account,
180                                          GtkWindow      *parent)
181 {
182   gint response;
183   gboolean success = FALSE;
184   GtkWidget *d, *label, *button;
185   GtkContainer *vbox;
186
187   d = gtk_dialog_new ();
188   vbox = GTK_CONTAINER (GTK_DIALOG (d)->vbox);
189   gtk_window_set_title (GTK_WINDOW (d), "Account setup - Twitter");
190   gtk_window_set_transient_for (GTK_WINDOW (d), parent);
191
192   label = gtk_label_new ("You need to register a Twitter account using the web page.\n"
193                          "If you don't have one, you'll be offered the option to do it.\n"
194                          "You'll be given a PIN number to continue this setup.");
195   button = gtk_button_new_with_label ("Register Twitter account");
196
197   hildon_gtk_widget_set_theme_size (button, HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH);
198
199   gtk_container_add (vbox, label);
200   gtk_container_add (vbox, button);
201
202   g_signal_connect (button, "clicked", G_CALLBACK (register_account_clicked), account);
203
204   gtk_widget_show_all (d);
205   response = gtk_dialog_run (GTK_DIALOG (d));
206   gtk_widget_destroy (d);
207
208   if (response == GTK_RESPONSE_ACCEPT)
209     {
210       ConIcConnection *con = con_ic_connection_new ();
211       if (open_auth_url (account, parent, con))
212         success = twitmulti_account_enter_pin (account, parent);
213       g_object_unref (con);
214     }
215
216   return success;
217 }
218
219
220 gboolean
221 twitmulti_account_validate              (SharingAccount *account,
222                                          gboolean       *dead_mans_switch)
223 {
224   *dead_mans_switch = FALSE;
225   return twitter_account_validate (account);
226 }
227
228 typedef struct {
229   SharingTransfer *transfer;
230   gboolean        *dead_mans_switch;
231   guint64          size;
232 } UploadProgressData;
233
234 static gboolean
235 upload_progress_cb                      (SharingHTTP *http,
236                                          guint64      bytes_sent,
237                                          gpointer     user_data)
238 {
239   UploadProgressData *data = user_data;
240   gdouble progress = 0.0;
241   *(data->dead_mans_switch) = FALSE;
242   if (data->size > 0)
243     progress = (gdouble) bytes_sent / data->size;
244   sharing_transfer_set_progress (data->transfer, CLAMP (progress, 0.0, 1.0));
245   return TRUE;
246 }
247
248 SharingPluginInterfaceSendResult
249 twitmulti_share_file                    (SharingTransfer *transfer,
250                                          ConIcConnection *con,
251                                          gboolean        *dead_mans_switch)
252 {
253   SharingPluginInterfaceSendResult retval;
254   SharingEntry *entry;
255   const GSList *l;
256   const gchar *servicename;
257   gboolean post_to_twitter = TRUE;
258   TwitterPicService service = SERVICE_TWITPIC;
259
260   retval = SHARING_SEND_SUCCESS;
261   *dead_mans_switch = FALSE;
262   sharing_transfer_set_progress (transfer, 0.0);
263
264   entry = sharing_transfer_get_entry (transfer);
265   l = sharing_entry_get_media (entry);
266
267   servicename = sharing_entry_get_option (entry, "service");
268   if (servicename)
269     {
270       if (g_str_equal (servicename, "twitgoo"))
271         service = SERVICE_TWITGOO;
272       else if (g_str_equal (servicename, "mobypicture"))
273         service = SERVICE_MOBYPICTURE;
274       else if (g_str_equal (servicename, "imgly"))
275         service = SERVICE_IMGLY;
276       else if (g_str_equal (servicename, "posterous"))
277         service = SERVICE_POSTEROUS;
278     }
279
280   if (g_strcmp0 (sharing_entry_get_option (entry, "posttotwitter"), "no") == 0)
281     post_to_twitter = FALSE;
282
283   for (; l != NULL && retval == SHARING_SEND_SUCCESS; l = l->next)
284     {
285       const gchar *path;
286       gchar *mime;
287       SharingAccount *account;
288       SharingEntryMedia *media = l->data;
289
290       g_return_val_if_fail (media != NULL, SHARING_SEND_ERROR_UNKNOWN);
291
292       path = sharing_entry_media_get_localpath (media);
293       mime = sharing_entry_media_get_mime (media);
294       account = sharing_entry_get_account (entry);
295
296       if (path && mime && !sharing_entry_media_get_sent (media) &&
297           twitter_account_validate (account))
298         {
299           const gchar *posturl;
300           gchar *hdr, *title;
301           SharingHTTP *http = sharing_http_new ();
302           SharingHTTPRunResponse httpret;
303           UploadProgressData data;
304
305           data.transfer = transfer;
306           data.dead_mans_switch = dead_mans_switch;
307           data.size = sharing_entry_media_get_size (media);
308
309           title = sharing_entry_media_get_title (media);
310
311           /* If the title field is empty, use the description instead */
312           if (!title)
313             title = g_strdup (sharing_entry_media_get_desc (media));
314
315           if (title)
316             g_strstrip (title);
317           else
318             title = g_strdup ("Photo: ");
319
320           sharing_http_set_progress_callback (http, upload_progress_cb, &data);
321           sharing_http_add_req_multipart_file (http, "media", path, mime);
322           sharing_http_add_req_multipart_data (http, "message", title, -1, "text/plain");
323
324           switch (service)
325             {
326             case SERVICE_TWITPIC:
327               sharing_http_add_req_multipart_data (http, "key", TWITPIC_API_KEY, -1, "text/plain");
328               posturl = "http://api.twitpic.com/2/upload.xml";
329               break;
330             case SERVICE_MOBYPICTURE:
331               sharing_http_add_req_multipart_data (http, "key", MOBYPICTURE_API_KEY, -1, "text/plain");
332               posturl = "https://api.mobypicture.com/2.0/upload.xml";
333               break;
334             case SERVICE_TWITGOO:
335               posturl = "http://twitgoo.com/api/upload";
336               break;
337             case SERVICE_IMGLY:
338               posturl = "http://img.ly/api/2/upload.xml";
339               break;
340             case SERVICE_POSTEROUS:
341               posturl = "https://posterous.com/api2/upload.xml";
342               break;
343             default:
344               g_return_val_if_reached (SHARING_SEND_ERROR_UNKNOWN);
345             }
346
347           hdr = twitter_get_verify_credentials_header (account);
348           sharing_http_add_req_header_line (http, hdr);
349           g_free (hdr);
350
351           sharing_http_add_req_header_line (http, "X-Auth-Service-Provider: " TWITTER_VERIFY_CREDENTIALS_URL);
352           httpret = sharing_http_run (http, posturl);
353
354           switch (httpret)
355             {
356             case SHARING_HTTP_RUNRES_SUCCESS:
357               {
358                 gsize len;
359                 const gchar *body;
360                 gchar *img_url;
361
362                 body = sharing_http_get_res_body (http, &len);
363                 img_url = parse_server_response (body, len, service);
364
365                 if (img_url != NULL)
366                   {
367                     gchar *tweet = g_strconcat (title, " ", img_url, NULL);
368
369                     if (!post_to_twitter || twitter_update_status (tweet, account))
370                       sharing_entry_media_set_sent (media, TRUE);
371
372                     g_free (img_url);
373                     g_free (tweet);
374                   }
375                 else
376                   {
377                     retval = SHARING_SEND_ERROR_UNKNOWN;
378                   }
379               }
380               break;
381             case SHARING_HTTP_RUNRES_CANCELLED:
382               retval = SHARING_SEND_CANCELLED;
383               break;
384             case SHARING_HTTP_RUNRES_CONNECTION_PROBLEM:
385               retval = SHARING_SEND_ERROR_CONNECTION;
386               break;
387             default:
388               retval = SHARING_SEND_ERROR_UNKNOWN;
389             }
390
391           sharing_http_unref (http);
392           g_free (title);
393         }
394
395       sharing_account_free (account);
396       g_free (mime);
397     }
398
399   return retval;
400 }
401
402 SharingPluginInterfaceEditAccountResult
403 twitmulti_account_edit                  (GtkWindow       *parent,
404                                          SharingAccount  *account,
405                                          ConIcConnection *con,
406                                          gboolean        *dead_mans_switch)
407 {
408   gint response;
409   GtkWidget *d, *label;
410   enum { RESPONSE_EDIT, RESPONSE_REMOVE };
411
412   g_return_val_if_fail (account && dead_mans_switch, SHARING_EDIT_ACCOUNT_ERROR_UNKNOWN);
413
414   d = gtk_dialog_new ();
415   gtk_window_set_title (GTK_WINDOW (d), "Edit account - Twitter");
416   gtk_window_set_transient_for (GTK_WINDOW (d), parent);
417   gtk_dialog_add_button (GTK_DIALOG (d), GTK_STOCK_REMOVE, RESPONSE_REMOVE);
418   gtk_dialog_add_button (GTK_DIALOG (d), GTK_STOCK_EDIT, RESPONSE_EDIT);
419
420   label = gtk_label_new ("Press 'Edit' to open the Twitter web page.\n"
421                          "After that, enter the PIN number here\n"
422                          "to confirm your changes.");
423
424   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (d)->vbox), label);
425
426   gtk_widget_show_all (d);
427   response = gtk_dialog_run (GTK_DIALOG (d));
428   gtk_widget_destroy (d);
429
430   switch (response)
431     {
432     case RESPONSE_EDIT:
433       if (open_auth_url (account, parent, con) &&
434           twitmulti_account_enter_pin (account, parent))
435         {
436           return SHARING_EDIT_ACCOUNT_SUCCESS;
437         }
438       else
439         {
440           return SHARING_EDIT_ACCOUNT_CANCELLED;
441         }
442     case RESPONSE_REMOVE:
443       return SHARING_EDIT_ACCOUNT_DELETE;
444     case GTK_RESPONSE_DELETE_EVENT:
445       return SHARING_EDIT_ACCOUNT_NOT_STARTED;
446     }
447
448   return SHARING_EDIT_ACCOUNT_ERROR_UNKNOWN;
449 }