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