Freesound tweaks from colinf (#4761).
[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/filesystem.h"\r
45 \r
46 #include <sys/stat.h>\r
47 #include <sys/types.h>\r
48 #include <iostream>\r
49 \r
50 #include "ardour/audio_library.h"\r
51 \r
52 static const std::string base_url = "http://www.freesound.org/api";\r
53 static const std::string api_key = "9d77cb8d841b4bcfa960e1aae62224eb"; // ardour3\r
54 \r
55 \r
56 //------------------------------------------------------------------------\r
57 Mootcher::Mootcher()\r
58         : curl(curl_easy_init())\r
59 {\r
60         std::string path;\r
61         path = Glib::get_home_dir() + "/Freesound/";\r
62         changeWorkingDir ( path.c_str() );\r
63 };\r
64 //------------------------------------------------------------------------\r
65 Mootcher:: ~Mootcher()\r
66 {\r
67 }\r
68 \r
69 //------------------------------------------------------------------------\r
70 void Mootcher::changeWorkingDir(const char *saveLocation)\r
71 {\r
72         basePath = saveLocation;\r
73 #ifdef __WIN32__\r
74         std::string replace = "/";\r
75         size_t pos = basePath.find("\\");\r
76         while( pos != std::string::npos ){\r
77                 basePath.replace(pos, 1, replace);\r
78                 pos = basePath.find("\\");\r
79         }\r
80 #endif\r
81         //\r
82         size_t pos2 = basePath.find_last_of("/");\r
83         if(basePath.length() != (pos2+1)) basePath += "/";\r
84 }\r
85 \r
86 void Mootcher::ensureWorkingDir ()\r
87 {\r
88         PBD::sys::path p = basePath;\r
89         p /= "snd";\r
90         if (!PBD::sys::is_directory (p)) {\r
91                 PBD::sys::create_directories (p);\r
92         }\r
93 }\r
94         \r
95 \r
96 //------------------------------------------------------------------------\r
97 size_t Mootcher::WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)\r
98 {\r
99         register int realsize = (int)(size * nmemb);\r
100         struct MemoryStruct *mem = (struct MemoryStruct *)data;\r
101 \r
102         mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);\r
103 \r
104         if (mem->memory) {\r
105                 memcpy(&(mem->memory[mem->size]), ptr, realsize);\r
106                 mem->size += realsize;\r
107                 mem->memory[mem->size] = 0;\r
108         }\r
109         return realsize;\r
110 }\r
111 \r
112 \r
113 //------------------------------------------------------------------------\r
114 \r
115 std::string Mootcher::sortMethodString(enum sortMethod sort) {\r
116 // given a sort type, returns the string value to be passed to the API to\r
117 // sort the results in the requested way.\r
118 \r
119         switch (sort) {\r
120                 case sort_duration_desc:        return "duration_desc"; \r
121                 case sort_duration_asc:         return "duration_asc";\r
122                 case sort_created_desc:         return "created_desc";\r
123                 case sort_created_asc:          return "created_asc";\r
124                 case sort_downloads_desc:       return "downloads_desc";\r
125                 case sort_downloads_asc:        return "downloads_asc";\r
126                 case sort_rating_desc:          return "rating_desc";\r
127                 case sort_rating_asc:           return "rating_asc";\r
128                 default:                        return "";      \r
129         }\r
130 }\r
131 \r
132 //------------------------------------------------------------------------\r
133 void Mootcher::setcUrlOptions()\r
134 {\r
135         // basic init for curl\r
136         curl_global_init(CURL_GLOBAL_ALL);\r
137         // some servers don't like requests that are made without a user-agent field, so we provide one\r
138         curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");\r
139         // setup curl error buffer\r
140         curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);\r
141         // Allow redirection\r
142         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);\r
143         \r
144         // Allow connections to time out (without using signals)\r
145         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);\r
146         curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);\r
147 \r
148 \r
149 }\r
150 \r
151 std::string Mootcher::doRequest(std::string uri, std::string params)\r
152 {\r
153         std::string result;\r
154         struct MemoryStruct xml_page;\r
155         xml_page.memory = NULL;\r
156         xml_page.size = 0;\r
157 \r
158         setcUrlOptions();\r
159         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);\r
160         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &xml_page);\r
161 \r
162         // curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);\r
163         // curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postMessage.c_str());\r
164         // curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1);\r
165 \r
166         // the url to get\r
167         std::string url = base_url + uri + "?";\r
168         if (params != "") {\r
169                 url += params + "&api_key=" + api_key + "&format=xml";\r
170         } else {\r
171                 url += "api_key=" + api_key + "&format=xml";\r
172         }\r
173                 \r
174         curl_easy_setopt(curl, CURLOPT_URL, url.c_str() );\r
175         std::cerr << "doRequest: " << url << std::endl;\r
176         \r
177         // perform online request\r
178         CURLcode res = curl_easy_perform(curl);\r
179         if( res != 0 ) {\r
180                 std::cerr << "curl error " << res << " (" << curl_easy_strerror(res) << ")" << std::endl;\r
181                 return "";\r
182         }\r
183 \r
184         result = xml_page.memory;\r
185         // free the memory\r
186         if(xml_page.memory){\r
187                 free( xml_page.memory );\r
188                 xml_page.memory = NULL;\r
189                 xml_page.size = 0;\r
190         }\r
191 \r
192         return result;\r
193 \r
194 }\r
195 \r
196 \r
197 std::string Mootcher::searchText(std::string query, int page, std::string filter, enum sortMethod sort)\r
198 {\r
199         std::string params = "";\r
200         char buf[24];\r
201 \r
202         if (page > 1) {\r
203                 snprintf(buf, 23, "p=%d&", page);\r
204                 params += buf;\r
205         }\r
206         \r
207         params += "q=" + query; \r
208 \r
209         if (filter != "")\r
210                 params += "&f=" + filter;\r
211         \r
212         if (sort)\r
213                 params += "&s=" + sortMethodString(sort);\r
214 \r
215         return doRequest("/sounds/search", params);\r
216 }\r
217 \r
218 //------------------------------------------------------------------------\r
219 \r
220 std::string Mootcher::getSoundResourceFile(std::string ID)\r
221 {\r
222 \r
223         std::string originalSoundURI;\r
224         std::string audioFileName;\r
225         std::string xmlFileName;\r
226         std::string xml;\r
227 \r
228 \r
229         std::cerr << "getSoundResourceFile(" << ID << ")" << std::endl;\r
230 \r
231         // download the xmlfile into xml_page\r
232         xml = doRequest("/sounds/" + ID, "");\r
233 \r
234         XMLTree doc;\r
235         doc.read_buffer( xml.c_str() );\r
236         XMLNode *freesound = doc.root();\r
237 \r
238         // if the page is not a valid xml document with a 'freesound' root\r
239         if (freesound == NULL) {\r
240                 std::cerr << "getSoundResourceFile: There is no valid root in the xml file" << std::endl;\r
241                 return "";\r
242         }\r
243 \r
244         if (strcmp(doc.root()->name().c_str(), "response") != 0) {\r
245                 std::cerr << "getSoundResourceFile: root =" << doc.root()->name() << ", != response" << std::endl;\r
246                 return "";\r
247         }\r
248 \r
249         XMLNode *name = freesound->child("original_filename");\r
250         XMLNode *filesize = freesound->child("filesize");\r
251 \r
252 \r
253         // get the file name and size from xml file\r
254         if (name && filesize) {\r
255 \r
256                 audioFileName = basePath + "snd/" + ID + "-" + name->child("text")->content();\r
257 \r
258                 // create new filename with the ID number\r
259                 xmlFileName = basePath;\r
260                 xmlFileName += "snd/";\r
261                 xmlFileName += freesound->child("id")->child("text")->content();\r
262                 xmlFileName += "-";\r
263                 xmlFileName += name->child("text")->content();\r
264                 xmlFileName += ".xml";\r
265 \r
266                 // std::cerr << "getSoundResourceFile: saving XML: " << xmlFileName << std::endl;\r
267 \r
268                 // save the xml file to disk\r
269                 ensureWorkingDir();\r
270                 doc.write(xmlFileName.c_str());\r
271 \r
272                 //store all the tags in the database\r
273                 XMLNode *tags = freesound->child("tags");\r
274                 if (tags) {\r
275                         XMLNodeList children = tags->children();\r
276                         XMLNodeConstIterator niter;\r
277                         std::vector<std::string> strings;\r
278                         for (niter = children.begin(); niter != children.end(); ++niter) {\r
279                                 XMLNode *node = *niter;\r
280                                 if( strcmp( node->name().c_str(), "resource") == 0 ) {\r
281                                         XMLNode *text = node->child("text");\r
282                                         if (text) {\r
283                                                 // std::cerr << "tag: " << text->content() << std::endl;\r
284                                                 strings.push_back(text->content());\r
285                                         }\r
286                                 }\r
287                         }\r
288                         ARDOUR::Library->set_tags (std::string("//")+audioFileName, strings);\r
289                         ARDOUR::Library->save_changes ();\r
290                 }\r
291         }\r
292 \r
293         return audioFileName;\r
294 }\r
295 \r
296 int audioFileWrite(void *buffer, size_t size, size_t nmemb, void *file)\r
297 {\r
298         return (int)fwrite(buffer, size, nmemb, (FILE*) file);\r
299 };\r
300 \r
301 //------------------------------------------------------------------------\r
302 std::string Mootcher::getAudioFile(std::string originalFileName, std::string ID, std::string audioURL, SoundFileBrowser *caller)\r
303 {\r
304         ensureWorkingDir();\r
305         std::string audioFileName = basePath + "snd/" + ID + "-" + originalFileName;\r
306 \r
307         // check to see if audio file already exists\r
308         FILE *testFile = fopen(audioFileName.c_str(), "r");\r
309         if (testFile) {  \r
310                 fseek (testFile , 0 , SEEK_END);\r
311                 if (ftell (testFile) > 256) {\r
312                         std::cerr << "audio file " << audioFileName << " already exists" << std::endl;\r
313                         fclose (testFile);\r
314                         return audioFileName;\r
315                 }\r
316                 \r
317                 // else file was small, probably an error, delete it and try again\r
318                 fclose(testFile);\r
319                 remove( audioFileName.c_str() );  \r
320         }\r
321 \r
322         if (!curl) {\r
323                 return "";\r
324         }\r
325 \r
326         //now download the actual file\r
327         FILE* theFile;\r
328         theFile = 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         std::cerr << "downloading " << audioFileName << " from " << audioURL << "..." << std::endl;\r
343         /* hack to get rid of the barber-pole stripes */\r
344         caller->progress_bar.hide();\r
345         caller->progress_bar.show();\r
346 \r
347         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); // turn on the progress bar\r
348         curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, progress_callback);\r
349         curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, caller);\r
350 \r
351         CURLcode res = curl_easy_perform(curl);\r
352         fclose(theFile);\r
353 \r
354         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); // turn off the progress bar\r
355         caller->progress_bar.set_fraction(0.0);\r
356         \r
357         if( res != 0 ) {\r
358                 std::cerr <<  "curl error " << res << " (" << curl_easy_strerror(res) << ")" << std::endl;\r
359                 remove( audioFileName.c_str() );  \r
360                 return "";\r
361         } else {\r
362                 std::cerr << "done!" << std::endl;\r
363                 // now download the tags &c.\r
364                 getSoundResourceFile(ID);\r
365         }\r
366 \r
367         return audioFileName;\r
368 }\r
369 \r
370 //---------\r
371 int Mootcher::progress_callback(void *caller, double dltotal, double dlnow, double ultotal, double ulnow)\r
372 {\r
373 \r
374 SoundFileBrowser *sfb = (SoundFileBrowser *) caller;\r
375         //XXX I hope it's OK to do GTK things in this callback. Otherwise\r
376         // I'll have to do stuff like in interthread_progress_window.\r
377         if (sfb->freesound_stop) {\r
378                 return -1;\r
379         }\r
380         \r
381         \r
382         sfb->progress_bar.set_fraction(dlnow/dltotal);\r
383         /* Make sure the progress widget gets updated */\r
384         while (Glib::MainContext::get_default()->iteration (false)) {\r
385                 /* do nothing */\r
386         }\r
387         std::cerr << "progress: " << dlnow << " of " << dltotal << " \r";\r
388         return 0;\r
389 }\r
390 \r