a1cb4a969e222bc67f079892264227c3cb41b19e
[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         params += "q=" + query; \r
216 \r
217         if (filter != "")\r
218                 params += "&f=" + filter;\r
219         \r
220         if (sort)\r
221                 params += "&s=" + sortMethodString(sort);\r
222 \r
223         params += "&fields=id,original_filename,duration,serve";        \r
224 \r
225         return doRequest("/sounds/search", params);\r
226 }\r
227 \r
228 //------------------------------------------------------------------------\r
229 \r
230 std::string Mootcher::getSoundResourceFile(std::string ID)\r
231 {\r
232 \r
233         std::string originalSoundURI;\r
234         std::string audioFileName;\r
235         std::string xml;\r
236 \r
237 \r
238         // download the xmlfile into xml_page\r
239         xml = doRequest("/sounds/" + ID, "");\r
240 \r
241         XMLTree doc;\r
242         doc.read_buffer( xml.c_str() );\r
243         XMLNode *freesound = doc.root();\r
244 \r
245         // if the page is not a valid xml document with a 'freesound' root\r
246         if (freesound == NULL) {\r
247                 error << _("getSoundResourceFile: There is no valid root in the xml file") << endmsg;\r
248                 return "";\r
249         }\r
250 \r
251         if (strcmp(doc.root()->name().c_str(), "response") != 0) {\r
252                 error << string_compose (_("getSoundResourceFile: root = %1, != response"), doc.root()->name()) << endmsg;\r
253                 return "";\r
254         }\r
255 \r
256         XMLNode *name = freesound->child("original_filename");\r
257 \r
258         // get the file name and size from xml file\r
259         if (name) {\r
260 \r
261                 audioFileName = basePath + "snd/" + ID + "-" + name->child("text")->content();\r
262 \r
263                 //store all the tags in the database\r
264                 XMLNode *tags = freesound->child("tags");\r
265                 if (tags) {\r
266                         XMLNodeList children = tags->children();\r
267                         XMLNodeConstIterator niter;\r
268                         std::vector<std::string> strings;\r
269                         for (niter = children.begin(); niter != children.end(); ++niter) {\r
270                                 XMLNode *node = *niter;\r
271                                 if( strcmp( node->name().c_str(), "resource") == 0 ) {\r
272                                         XMLNode *text = node->child("text");\r
273                                         if (text) {\r
274                                                 // std::cerr << "tag: " << text->content() << std::endl;\r
275                                                 strings.push_back(text->content());\r
276                                         }\r
277                                 }\r
278                         }\r
279                         ARDOUR::Library->set_tags (std::string("//")+audioFileName, strings);\r
280                         ARDOUR::Library->save_changes ();\r
281                 }\r
282         }\r
283 \r
284         return audioFileName;\r
285 }\r
286 \r
287 int audioFileWrite(void *buffer, size_t size, size_t nmemb, void *file)\r
288 {\r
289         return (int)fwrite(buffer, size, nmemb, (FILE*) file);\r
290 };\r
291 \r
292 //------------------------------------------------------------------------\r
293 std::string Mootcher::getAudioFile(std::string originalFileName, std::string ID, std::string audioURL, SoundFileBrowser *caller)\r
294 {\r
295         ensureWorkingDir();\r
296         std::string audioFileName = basePath + "snd/" + ID + "-" + originalFileName;\r
297 \r
298         // check to see if audio file already exists\r
299         FILE *testFile = g_fopen(audioFileName.c_str(), "r");\r
300         if (testFile) {  \r
301                 fseek (testFile , 0 , SEEK_END);\r
302                 if (ftell (testFile) > 256) {\r
303                         fclose (testFile);\r
304                         return audioFileName;\r
305                 }\r
306                 \r
307                 // else file was small, probably an error, delete it and try again\r
308                 fclose(testFile);\r
309                 remove( audioFileName.c_str() );  \r
310         }\r
311 \r
312         if (!curl) {\r
313                 return "";\r
314         }\r
315 \r
316         //if already canceling a previous download, bail out here  ( this can happen b/c getAudioFile gets called by various UI update funcs )\r
317         if ( caller->freesound_download_cancel ) {\r
318                 return "";\r
319         }\r
320         \r
321         //now download the actual file\r
322         FILE* theFile;\r
323         theFile = g_fopen( audioFileName.c_str(), "wb" );\r
324 \r
325         if (!theFile) {\r
326                 return "";\r
327         }\r
328         \r
329         // create the download url\r
330         audioURL += "?api_key=" + api_key;\r
331 \r
332         setcUrlOptions();\r
333         curl_easy_setopt(curl, CURLOPT_URL, audioURL.c_str() );\r
334         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, audioFileWrite);\r
335         curl_easy_setopt(curl, CURLOPT_WRITEDATA, theFile);\r
336 \r
337         /* hack to get rid of the barber-pole stripes */\r
338         caller->freesound_progress_bar.hide();\r
339         caller->freesound_progress_bar.show();\r
340 \r
341         std::string prog;\r
342         prog = string_compose (_("%1: [Stop]->"), originalFileName);\r
343         caller->freesound_progress_bar.set_text(prog);\r
344 \r
345         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); // turn on the progress bar\r
346         curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, progress_callback);\r
347         curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, caller);\r
348 \r
349         CURLcode res = curl_easy_perform(curl);\r
350         fclose(theFile);\r
351 \r
352         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); // turn off the progress bar\r
353         caller->freesound_progress_bar.set_fraction(0.0);\r
354         caller->freesound_progress_bar.set_text("");\r
355         \r
356         if( res != 0 ) {\r
357                 error <<  string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res)) << endmsg;\r
358                 remove( audioFileName.c_str() );  \r
359                 return "";\r
360         } else {\r
361                 // now download the tags &c.\r
362                 getSoundResourceFile(ID);\r
363         }\r
364 \r
365         return audioFileName;\r
366 }\r
367 \r
368 //---------\r
369 int Mootcher::progress_callback(void *caller, double dltotal, double dlnow, double /*ultotal*/, double /*ulnow*/)\r
370 {\r
371 \r
372 SoundFileBrowser *sfb = (SoundFileBrowser *) caller;\r
373         //XXX I hope it's OK to do GTK things in this callback. Otherwise\r
374         // I'll have to do stuff like in interthread_progress_window.\r
375         if (sfb->freesound_download_cancel) {\r
376                 return -1;\r
377         }\r
378         \r
379         \r
380         sfb->freesound_progress_bar.set_fraction(dlnow/dltotal);\r
381         /* Make sure the progress widget gets updated */\r
382         while (Glib::MainContext::get_default()->iteration (false)) {\r
383                 /* do nothing */\r
384         }\r
385         return 0;\r
386 }\r
387 \r