fix freesound URL parameter escape
[ardour.git] / gtk2_ardour / sfdb_freesound_mootcher.cc
1 /* sfdb_freesound_mootcher.cpp **********************************************************************\r
2 \r
3         Adapted for Ardour by Ben Loftis, March 2008\r
4         Updated to new Freesound API by Colin Fletcher, November 2011\r
5 \r
6         Mootcher 23-8-2005\r
7 \r
8         Mootcher Online Access to thefreesoundproject website\r
9         http://freesound.iua.upf.edu/\r
10 \r
11         GPL 2005 Jorn Lemon\r
12         mail for questions/remarks: mootcher@twistedlemon.nl\r
13         or go to the freesound website forum\r
14 \r
15         -----------------------------------------------------------------\r
16 \r
17         Includes:\r
18                 curl.h    (version 7.14.0)\r
19         Librarys:\r
20                 libcurl.lib\r
21 \r
22         -----------------------------------------------------------------\r
23         Licence GPL:\r
24 \r
25         This program is free software; you can redistribute it and/or\r
26         modify it under the terms of the GNU General Public License\r
27         as published by the Free Software Foundation; either version 2\r
28         of the License, or (at your option) any later version.\r
29 \r
30         This program is distributed in the hope that it will be useful,\r
31         but WITHOUT ANY WARRANTY; without even the implied warranty of\r
32         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
33         GNU General Public License for more details.\r
34 \r
35         You should have received a copy of the GNU General Public License\r
36         along with this program; if not, write to the Free Software\r
37         Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\r
38 \r
39 \r
40 *************************************************************************************/\r
41 #include "sfdb_freesound_mootcher.h"\r
42 \r
43 #include "pbd/xml++.h"\r
44 #include "pbd/error.h"\r
45 \r
46 #include <sys/stat.h>\r
47 #include <sys/types.h>\r
48 #include <iostream>\r
49 \r
50 #include <glib.h>\r
51 #include <glib/gstdio.h>\r
52 \r
53 #include "i18n.h"\r
54 \r
55 #include "ardour/audio_library.h"\r
56 \r
57 using namespace PBD;\r
58 \r
59 static const std::string base_url = "http://www.freesound.org/api";\r
60 static const std::string api_key = "9d77cb8d841b4bcfa960e1aae62224eb"; // ardour3\r
61 \r
62 //------------------------------------------------------------------------\r
63 Mootcher::Mootcher()\r
64         : curl(curl_easy_init())\r
65 {\r
66         std::string path;\r
67         path = Glib::get_home_dir() + "/Freesound/";\r
68         changeWorkingDir ( path.c_str() );\r
69 };\r
70 //------------------------------------------------------------------------\r
71 Mootcher:: ~Mootcher()\r
72 {\r
73         curl_easy_cleanup(curl);\r
74 }\r
75 \r
76 //------------------------------------------------------------------------\r
77 void Mootcher::changeWorkingDir(const char *saveLocation)\r
78 {\r
79         basePath = saveLocation;\r
80 #ifdef __WIN32__\r
81         std::string replace = "/";\r
82         size_t pos = basePath.find("\\");\r
83         while( pos != std::string::npos ){\r
84                 basePath.replace(pos, 1, replace);\r
85                 pos = basePath.find("\\");\r
86         }\r
87 #endif\r
88         //\r
89         size_t pos2 = basePath.find_last_of("/");\r
90         if(basePath.length() != (pos2+1)) basePath += "/";\r
91 }\r
92 \r
93 void Mootcher::ensureWorkingDir ()\r
94 {\r
95         std::string p = Glib::build_filename (basePath, "snd");\r
96 \r
97         if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) {\r
98                 if (g_mkdir_with_parents (p.c_str(), 0775) != 0) {\r
99                         PBD::error << "Unable to create Mootcher working dir" << endmsg;\r
100                 }\r
101         }\r
102 }\r
103         \r
104 \r
105 //------------------------------------------------------------------------\r
106 size_t Mootcher::WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)\r
107 {\r
108         register int realsize = (int)(size * nmemb);\r
109         struct MemoryStruct *mem = (struct MemoryStruct *)data;\r
110 \r
111         mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);\r
112 \r
113         if (mem->memory) {\r
114                 memcpy(&(mem->memory[mem->size]), ptr, realsize);\r
115                 mem->size += realsize;\r
116                 mem->memory[mem->size] = 0;\r
117         }\r
118         return realsize;\r
119 }\r
120 \r
121 \r
122 //------------------------------------------------------------------------\r
123 \r
124 std::string Mootcher::sortMethodString(enum sortMethod sort) {\r
125 // given a sort type, returns the string value to be passed to the API to\r
126 // sort the results in the requested way.\r
127 \r
128         switch (sort) {\r
129                 case sort_duration_desc:        return "duration_desc"; \r
130                 case sort_duration_asc:         return "duration_asc";\r
131                 case sort_created_desc:         return "created_desc";\r
132                 case sort_created_asc:          return "created_asc";\r
133                 case sort_downloads_desc:       return "downloads_desc";\r
134                 case sort_downloads_asc:        return "downloads_asc";\r
135                 case sort_rating_desc:          return "rating_desc";\r
136                 case sort_rating_asc:           return "rating_asc";\r
137                 default:                        return "";      \r
138         }\r
139 }\r
140 \r
141 //------------------------------------------------------------------------\r
142 void Mootcher::setcUrlOptions()\r
143 {\r
144         // basic init for curl\r
145         curl_global_init(CURL_GLOBAL_ALL);\r
146         // some servers don't like requests that are made without a user-agent field, so we provide one\r
147         curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");\r
148         // setup curl error buffer\r
149         curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);\r
150         // Allow redirection\r
151         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);\r
152         \r
153         // Allow connections to time out (without using signals)\r
154         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);\r
155         curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);\r
156 \r
157 \r
158 }\r
159 \r
160 std::string Mootcher::doRequest(std::string uri, std::string params)\r
161 {\r
162         std::string result;\r
163         struct MemoryStruct xml_page;\r
164         xml_page.memory = NULL;\r
165         xml_page.size = 0;\r
166 \r
167         setcUrlOptions();\r
168         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);\r
169         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &xml_page);\r
170 \r
171         // curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);\r
172         // curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postMessage.c_str());\r
173         // curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1);\r
174 \r
175         // the url to get\r
176         std::string url = base_url + uri + "?";\r
177         if (params != "") {\r
178                 url += params + "&api_key=" + api_key + "&format=xml";\r
179         } else {\r
180                 url += "api_key=" + api_key + "&format=xml";\r
181         }\r
182                 \r
183         curl_easy_setopt(curl, CURLOPT_URL, url.c_str() );\r
184         \r
185         // perform online request\r
186         CURLcode res = curl_easy_perform(curl);\r
187         if( res != 0 ) {\r
188                 error << string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res)) << endmsg;\r
189                 return "";\r
190         }\r
191 \r
192         // free the memory\r
193         if (xml_page.memory) {\r
194                 result = xml_page.memory;\r
195         }\r
196         \r
197         free (xml_page.memory);\r
198         xml_page.memory = NULL;\r
199         xml_page.size = 0;\r
200 \r
201         return result;\r
202 }\r
203 \r
204 \r
205 std::string Mootcher::searchText(std::string query, int page, std::string filter, enum sortMethod sort)\r
206 {\r
207         std::string params = "";\r
208         char buf[24];\r
209 \r
210         if (page > 1) {\r
211                 snprintf(buf, 23, "p=%d&", page);\r
212                 params += buf;\r
213         }\r
214 \r
215         char *eq = curl_easy_escape(curl, query.c_str(), query.length());\r
216         params += "q=" + std::string(eq);\r
217         free(eq);\r
218 \r
219         if (filter != "") {\r
220                 char *ef = curl_easy_escape(curl, filter.c_str(), filter.length());\r
221                 params += "&f=" + std::string(ef);\r
222                 free(ef);\r
223         }\r
224         \r
225         if (sort)\r
226                 params += "&s=" + sortMethodString(sort);\r
227 \r
228         params += "&fields=id,original_filename,duration,serve";        \r
229 \r
230         return doRequest("/sounds/search", params);\r
231 }\r
232 \r
233 //------------------------------------------------------------------------\r
234 \r
235 std::string Mootcher::getSoundResourceFile(std::string ID)\r
236 {\r
237 \r
238         std::string originalSoundURI;\r
239         std::string audioFileName;\r
240         std::string xml;\r
241 \r
242 \r
243         // download the xmlfile into xml_page\r
244         xml = doRequest("/sounds/" + ID, "");\r
245 \r
246         XMLTree doc;\r
247         doc.read_buffer( xml.c_str() );\r
248         XMLNode *freesound = doc.root();\r
249 \r
250         // if the page is not a valid xml document with a 'freesound' root\r
251         if (freesound == NULL) {\r
252                 error << _("getSoundResourceFile: There is no valid root in the xml file") << endmsg;\r
253                 return "";\r
254         }\r
255 \r
256         if (strcmp(doc.root()->name().c_str(), "response") != 0) {\r
257                 error << string_compose (_("getSoundResourceFile: root = %1, != response"), doc.root()->name()) << endmsg;\r
258                 return "";\r
259         }\r
260 \r
261         XMLNode *name = freesound->child("original_filename");\r
262 \r
263         // get the file name and size from xml file\r
264         if (name) {\r
265 \r
266                 audioFileName = basePath + "snd/" + ID + "-" + name->child("text")->content();\r
267 \r
268                 //store all the tags in the database\r
269                 XMLNode *tags = freesound->child("tags");\r
270                 if (tags) {\r
271                         XMLNodeList children = tags->children();\r
272                         XMLNodeConstIterator niter;\r
273                         std::vector<std::string> strings;\r
274                         for (niter = children.begin(); niter != children.end(); ++niter) {\r
275                                 XMLNode *node = *niter;\r
276                                 if( strcmp( node->name().c_str(), "resource") == 0 ) {\r
277                                         XMLNode *text = node->child("text");\r
278                                         if (text) {\r
279                                                 // std::cerr << "tag: " << text->content() << std::endl;\r
280                                                 strings.push_back(text->content());\r
281                                         }\r
282                                 }\r
283                         }\r
284                         ARDOUR::Library->set_tags (std::string("//")+audioFileName, strings);\r
285                         ARDOUR::Library->save_changes ();\r
286                 }\r
287         }\r
288 \r
289         return audioFileName;\r
290 }\r
291 \r
292 int audioFileWrite(void *buffer, size_t size, size_t nmemb, void *file)\r
293 {\r
294         return (int)fwrite(buffer, size, nmemb, (FILE*) file);\r
295 };\r
296 \r
297 //------------------------------------------------------------------------\r
298 std::string Mootcher::getAudioFile(std::string originalFileName, std::string ID, std::string audioURL, SoundFileBrowser *caller)\r
299 {\r
300         ensureWorkingDir();\r
301         std::string audioFileName = basePath + "snd/" + ID + "-" + originalFileName;\r
302 \r
303         // check to see if audio file already exists\r
304         FILE *testFile = g_fopen(audioFileName.c_str(), "r");\r
305         if (testFile) {  \r
306                 fseek (testFile , 0 , SEEK_END);\r
307                 if (ftell (testFile) > 256) {\r
308                         fclose (testFile);\r
309                         return audioFileName;\r
310                 }\r
311                 \r
312                 // else file was small, probably an error, delete it and try again\r
313                 fclose(testFile);\r
314                 remove( audioFileName.c_str() );  \r
315         }\r
316 \r
317         if (!curl) {\r
318                 return "";\r
319         }\r
320 \r
321         //if already canceling a previous download, bail out here  ( this can happen b/c getAudioFile gets called by various UI update funcs )\r
322         if ( caller->freesound_download_cancel ) {\r
323                 return "";\r
324         }\r
325         \r
326         //now download the actual file\r
327         FILE* theFile;\r
328         theFile = g_fopen( audioFileName.c_str(), "wb" );\r
329 \r
330         if (!theFile) {\r
331                 return "";\r
332         }\r
333         \r
334         // create the download url\r
335         audioURL += "?api_key=" + api_key;\r
336 \r
337         setcUrlOptions();\r
338         curl_easy_setopt(curl, CURLOPT_URL, audioURL.c_str() );\r
339         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, audioFileWrite);\r
340         curl_easy_setopt(curl, CURLOPT_WRITEDATA, theFile);\r
341 \r
342         /* hack to get rid of the barber-pole stripes */\r
343         caller->freesound_progress_bar.hide();\r
344         caller->freesound_progress_bar.show();\r
345 \r
346         std::string prog;\r
347         prog = string_compose (_("%1: [Stop]->"), originalFileName);\r
348         caller->freesound_progress_bar.set_text(prog);\r
349 \r
350         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); // turn on the progress bar\r
351         curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, progress_callback);\r
352         curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, caller);\r
353 \r
354         CURLcode res = curl_easy_perform(curl);\r
355         fclose(theFile);\r
356 \r
357         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); // turn off the progress bar\r
358         caller->freesound_progress_bar.set_fraction(0.0);\r
359         caller->freesound_progress_bar.set_text("");\r
360         \r
361         if( res != 0 ) {\r
362                 error <<  string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res)) << endmsg;\r
363                 remove( audioFileName.c_str() );  \r
364                 return "";\r
365         } else {\r
366                 // now download the tags &c.\r
367                 getSoundResourceFile(ID);\r
368         }\r
369 \r
370         return audioFileName;\r
371 }\r
372 \r
373 //---------\r
374 int Mootcher::progress_callback(void *caller, double dltotal, double dlnow, double /*ultotal*/, double /*ulnow*/)\r
375 {\r
376 \r
377 SoundFileBrowser *sfb = (SoundFileBrowser *) caller;\r
378         //XXX I hope it's OK to do GTK things in this callback. Otherwise\r
379         // I'll have to do stuff like in interthread_progress_window.\r
380         if (sfb->freesound_download_cancel) {\r
381                 return -1;\r
382         }\r
383         \r
384         \r
385         sfb->freesound_progress_bar.set_fraction(dlnow/dltotal);\r
386         /* Make sure the progress widget gets updated */\r
387         while (Glib::MainContext::get_default()->iteration (false)) {\r
388                 /* do nothing */\r
389         }\r
390         return 0;\r
391 }\r
392 \r