Add and use a DEBUG flag for Soundcloud uploads.
[ardour.git] / libs / ardour / soundcloud_upload.cc
1 /* soundcloud_export.cpp **********************************************************************
2
3         Adapted for Ardour by Ben Loftis, March 2012
4
5         Licence GPL:
6
7         This program is free software; you can redistribute it and/or
8         modify it under the terms of the GNU General Public License
9         as published by the Free Software Foundation; either version 2
10         of the License, or (at your option) any later version.
11
12         This program is distributed in the hope that it will be useful,
13         but WITHOUT ANY WARRANTY; without even the implied warranty of
14         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15         GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program; if not, write to the Free Software
19         Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21
22 *************************************************************************************/
23 #include "ardour/debug.h"
24 #include "ardour/soundcloud_upload.h"
25
26 #include "pbd/xml++.h"
27 #include <pbd/error.h>
28 //#include "pbd/filesystem.h"
29
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <iostream>
33 #include <glib/gstdio.h>
34
35 #include "i18n.h"
36
37 using namespace PBD;
38
39 // static const std::string base_url = "http://api.soundcloud.com/tracks/13158665?client_id=";
40
41 size_t
42 WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
43 {
44         register int realsize = (int)(size * nmemb);
45         struct MemoryStruct *mem = (struct MemoryStruct *)data;
46
47         mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);
48
49         if (mem->memory) {
50                 memcpy(&(mem->memory[mem->size]), ptr, realsize);
51                 mem->size += realsize;
52                 mem->memory[mem->size] = 0;
53         }
54         return realsize;
55 }
56
57 SoundcloudUploader::SoundcloudUploader()
58 {
59         curl_handle = curl_easy_init();
60         multi_handle = curl_multi_init();
61 }
62
63 std::string
64 SoundcloudUploader::Get_Auth_Token( std::string username, std::string password )
65 {
66         struct MemoryStruct xml_page;
67         xml_page.memory = NULL;
68         xml_page.size = 0;
69
70         setcUrlOptions();
71
72         curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
73         curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &xml_page);
74
75         struct curl_httppost *formpost=NULL;
76         struct curl_httppost *lastptr=NULL;
77
78         /* Fill in the filename field */ 
79         curl_formadd(&formpost,
80                         &lastptr,
81                         CURLFORM_COPYNAME, "client_id",
82                         CURLFORM_COPYCONTENTS, "e7ac891eef866f139773cf8102b7a719",
83                         CURLFORM_END);
84
85         curl_formadd(&formpost,
86                         &lastptr,
87                         CURLFORM_COPYNAME, "client_secret",
88                         CURLFORM_COPYCONTENTS, "d78f34d19f09d26731801a0cb0f382c4",
89                         CURLFORM_END);
90
91         curl_formadd(&formpost,
92                         &lastptr,
93                         CURLFORM_COPYNAME, "grant_type",
94                         CURLFORM_COPYCONTENTS, "password",
95                         CURLFORM_END);
96
97         curl_formadd(&formpost,
98                         &lastptr,
99                         CURLFORM_COPYNAME, "username",
100                         CURLFORM_COPYCONTENTS, username.c_str(),
101                         CURLFORM_END);
102
103         curl_formadd(&formpost,
104                         &lastptr,
105                         CURLFORM_COPYNAME, "password",
106                         CURLFORM_COPYCONTENTS, password.c_str(),
107                         CURLFORM_END);
108
109         struct curl_slist *headerlist=NULL;
110         headerlist = curl_slist_append(headerlist, "Expect:");
111         headerlist = curl_slist_append(headerlist, "Accept: application/xml");
112         curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerlist);
113
114         /* what URL that receives this POST */ 
115         std::string url = "https://api.soundcloud.com/oauth2/token";
116         curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
117         curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, formpost);
118
119         // curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
120
121         // perform online request
122         CURLcode res = curl_easy_perform(curl_handle);
123         if( res != 0 ) {
124                 DEBUG_TRACE (DEBUG::Soundcloud, string_compose ("curl error %1 (%2)", res, curl_easy_strerror(res) ) );
125                 return "";
126         }
127
128         if(xml_page.memory){
129                 //cheesy way to parse the json return value.  find access_token, then advance 3 quotes
130
131                 if ( strstr ( xml_page.memory , "access_token" ) == NULL) {
132                         error << _("Upload to Soundcloud failed.  Perhaps your email or password are incorrect?\n") << endmsg;
133                         return "";
134                 }
135
136                 std::string token = strtok( xml_page.memory, "access_token" );
137                 token = strtok( NULL, "\"" );
138                 token = strtok( NULL, "\"" );
139                 token = strtok( NULL, "\"" );
140
141                 free( xml_page.memory );
142                 return token;
143         }
144
145         return "";
146 }
147
148 int
149 SoundcloudUploader::progress_callback(void *caller, double dltotal, double dlnow, double ultotal, double ulnow)
150 {
151         SoundcloudUploader *scu = (SoundcloudUploader *) caller;
152         DEBUG_TRACE (DEBUG::Soundcloud, string_compose ("%1: uploaded %2 of %3", scu->title, ulnow, ultotal) );
153         scu->caller->SoundcloudProgress(ultotal, ulnow, scu->title); /* EMIT SIGNAL */
154         return 0;
155 }
156
157
158 std::string
159 SoundcloudUploader::Upload(std::string file_path, std::string title, std::string token, bool ispublic, bool downloadable, ARDOUR::ExportHandler *caller)
160 {
161         int still_running;
162
163         struct MemoryStruct xml_page;
164         xml_page.memory = NULL;
165         xml_page.size = 0;
166
167         setcUrlOptions();
168
169         curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
170         curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &xml_page);
171
172         struct curl_httppost *formpost=NULL;
173         struct curl_httppost *lastptr=NULL;
174
175         /* Fill in the file upload field. This makes libcurl load data from
176            the given file name when curl_easy_perform() is called. */ 
177         curl_formadd(&formpost,
178                         &lastptr,
179                         CURLFORM_COPYNAME, "track[asset_data]",
180                         CURLFORM_FILE, file_path.c_str(),
181                         CURLFORM_END);
182
183         /* Fill in the filename field */ 
184         curl_formadd(&formpost,
185                         &lastptr,
186                         CURLFORM_COPYNAME, "oauth_token",
187                         CURLFORM_COPYCONTENTS, token.c_str(),
188                         CURLFORM_END);
189
190         curl_formadd(&formpost,
191                         &lastptr,
192                         CURLFORM_COPYNAME, "track[title]",
193                         CURLFORM_COPYCONTENTS, title.c_str(),
194                         CURLFORM_END);
195
196         curl_formadd(&formpost,
197                         &lastptr,
198                         CURLFORM_COPYNAME, "track[sharing]",
199                         CURLFORM_COPYCONTENTS, ispublic ? "public" : "private",
200                         CURLFORM_END);
201
202         curl_formadd(&formpost,
203                         &lastptr,
204                         CURLFORM_COPYNAME, "track[downloadable]",
205                         CURLFORM_COPYCONTENTS, downloadable ? "true" : "false",
206                         CURLFORM_END);
207
208
209
210         /* initalize custom header list (stating that Expect: 100-continue is not
211            wanted */ 
212         struct curl_slist *headerlist=NULL;
213         static const char buf[] = "Expect:";
214         headerlist = curl_slist_append(headerlist, buf);
215
216
217         if (curl_handle && multi_handle) {
218
219                 /* what URL that receives this POST */ 
220                 std::string url = "https://api.soundcloud.com/tracks";
221                 curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
222                 // curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
223
224                 curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerlist);
225                 curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, formpost);
226
227                 this->title = title; // save title to show in progress bar
228                 this->caller = caller;
229
230                 curl_easy_setopt (curl_handle, CURLOPT_NOPROGRESS, 0); // turn on the progress bar
231                 curl_easy_setopt (curl_handle, CURLOPT_PROGRESSFUNCTION, &SoundcloudUploader::progress_callback);
232                 curl_easy_setopt (curl_handle, CURLOPT_PROGRESSDATA, this);
233
234                 curl_multi_add_handle(multi_handle, curl_handle);
235
236                 curl_multi_perform(multi_handle, &still_running);
237
238
239                 while(still_running) {
240                         struct timeval timeout;
241                         int rc; /* select() return code */ 
242
243                         fd_set fdread;
244                         fd_set fdwrite;
245                         fd_set fdexcep;
246                         int maxfd = -1;
247
248                         long curl_timeo = -1;
249
250                         FD_ZERO(&fdread);
251                         FD_ZERO(&fdwrite);
252                         FD_ZERO(&fdexcep);
253
254                         /* set a suitable timeout to play around with */ 
255                         timeout.tv_sec = 1;
256                         timeout.tv_usec = 0;
257
258                         curl_multi_timeout(multi_handle, &curl_timeo);
259                         if(curl_timeo >= 0) {
260                                 timeout.tv_sec = curl_timeo / 1000;
261                                 if(timeout.tv_sec > 1)
262                                         timeout.tv_sec = 1;
263                                 else
264                                         timeout.tv_usec = (curl_timeo % 1000) * 1000;
265                         }
266
267                         /* get file descriptors from the transfers */ 
268                         curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
269
270                         /* In a real-world program you OF COURSE check the return code of the
271                            function calls.  On success, the value of maxfd is guaranteed to be
272                            greater or equal than -1.  We call select(maxfd + 1, ...), specially in
273                            case of (maxfd == -1), we call select(0, ...), which is basically equal
274                            to sleep. */ 
275
276                         rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
277
278                         switch(rc) {
279                                 case -1:
280                                         /* select error */ 
281                                         break;
282                                 case 0:
283                                 default:
284                                         /* timeout or readable/writable sockets */ 
285                                         curl_multi_perform(multi_handle, &still_running);
286                                         break;
287                         }
288                 } 
289
290                 /* then cleanup the formpost chain */ 
291                 curl_formfree(formpost);
292
293                 /* free slist */ 
294                 curl_slist_free_all (headerlist);
295         }
296
297         curl_easy_setopt (curl_handle, CURLOPT_NOPROGRESS, 1); // turn off the progress bar
298
299         if(xml_page.memory){
300
301                 DEBUG_TRACE (DEBUG::Soundcloud, xml_page.memory);
302
303                 XMLTree doc;
304                 doc.read_buffer( xml_page.memory );
305                 XMLNode *root = doc.root();
306
307                 if (!root) {
308                         DEBUG_TRACE (DEBUG::Soundcloud, "no root XML node!");
309                         return "";
310                 }
311
312                 XMLNode *url_node = root->child("permalink-url");
313                 if (!url_node) {
314                         DEBUG_TRACE (DEBUG::Soundcloud, "no child node \"permalink-url\" found!");
315                         return "";
316                 }
317
318                 XMLNode *text_node = url_node->child("text");
319                 if (!text_node) {
320                         DEBUG_TRACE (DEBUG::Soundcloud, "no text node found!");
321                         return "";
322                 }
323
324                 free( xml_page.memory );
325                 return text_node->content();
326         }
327
328         return "";
329 };
330
331
332 SoundcloudUploader:: ~SoundcloudUploader()
333 {
334         curl_easy_cleanup(curl_handle);
335         curl_multi_cleanup(multi_handle);
336 }
337
338
339 void
340 SoundcloudUploader::setcUrlOptions()
341 {
342         // basic init for curl
343         curl_global_init(CURL_GLOBAL_ALL);
344         // some servers don't like requests that are made without a user-agent field, so we provide one
345         curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
346         // setup curl error buffer
347         curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, errorBuffer);
348         // Allow redirection
349         curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
350         
351         // Allow connections to time out (without using signals)
352         curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
353         curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 30);
354
355         curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
356         curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
357 }
358