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