70d04abb6670e11a6f4eefe1bb995c93a556820b
[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 <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 static const std::string base_url = "http://www.freesound.org/api";\r
58 static const std::string api_key = "9d77cb8d841b4bcfa960e1aae62224eb"; // ardour3\r
59 \r
60 \r
61 //------------------------------------------------------------------------\r
62 Mootcher::Mootcher()\r
63         : curl(curl_easy_init())\r
64 {\r
65         std::string path;\r
66         path = Glib::get_home_dir() + "/Freesound/";\r
67         changeWorkingDir ( path.c_str() );\r
68 };\r
69 //------------------------------------------------------------------------\r
70 Mootcher:: ~Mootcher()\r
71 {\r
72         curl_easy_cleanup(curl);\r
73 }\r
74 \r
75 //------------------------------------------------------------------------\r
76 void Mootcher::changeWorkingDir(const char *saveLocation)\r
77 {\r
78         basePath = saveLocation;\r
79 #ifdef __WIN32__\r
80         std::string replace = "/";\r
81         size_t pos = basePath.find("\\");\r
82         while( pos != std::string::npos ){\r
83                 basePath.replace(pos, 1, replace);\r
84                 pos = basePath.find("\\");\r
85         }\r
86 #endif\r
87         //\r
88         size_t pos2 = basePath.find_last_of("/");\r
89         if(basePath.length() != (pos2+1)) basePath += "/";\r
90 }\r
91 \r
92 void Mootcher::ensureWorkingDir ()\r
93 {\r
94         PBD::sys::path p = basePath;\r
95         p /= "snd";\r
96         if (!PBD::sys::is_directory (p)) {\r
97                 PBD::sys::create_directories (p);\r
98         }\r
99 }\r
100         \r
101 \r
102 //------------------------------------------------------------------------\r
103 size_t Mootcher::WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)\r
104 {\r
105         register int realsize = (int)(size * nmemb);\r
106         struct MemoryStruct *mem = (struct MemoryStruct *)data;\r
107 \r
108         mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);\r
109 \r
110         if (mem->memory) {\r
111                 memcpy(&(mem->memory[mem->size]), ptr, realsize);\r
112                 mem->size += realsize;\r
113                 mem->memory[mem->size] = 0;\r
114         }\r
115         return realsize;\r
116 }\r
117 \r
118 \r
119 //------------------------------------------------------------------------\r
120 \r
121 std::string Mootcher::sortMethodString(enum sortMethod sort) {\r
122 // given a sort type, returns the string value to be passed to the API to\r
123 // sort the results in the requested way.\r
124 \r
125         switch (sort) {\r
126                 case sort_duration_desc:        return "duration_desc"; \r
127                 case sort_duration_asc:         return "duration_asc";\r
128                 case sort_created_desc:         return "created_desc";\r
129                 case sort_created_asc:          return "created_asc";\r
130                 case sort_downloads_desc:       return "downloads_desc";\r
131                 case sort_downloads_asc:        return "downloads_asc";\r
132                 case sort_rating_desc:          return "rating_desc";\r
133                 case sort_rating_asc:           return "rating_asc";\r
134                 default:                        return "";      \r
135         }\r
136 }\r
137 \r
138 //------------------------------------------------------------------------\r
139 void Mootcher::setcUrlOptions()\r
140 {\r
141         // basic init for curl\r
142         curl_global_init(CURL_GLOBAL_ALL);\r
143         // some servers don't like requests that are made without a user-agent field, so we provide one\r
144         curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");\r
145         // setup curl error buffer\r
146         curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);\r
147         // Allow redirection\r
148         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);\r
149         \r
150         // Allow connections to time out (without using signals)\r
151         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);\r
152         curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);\r
153 \r
154 \r
155 }\r
156 \r
157 std::string Mootcher::doRequest(std::string uri, std::string params)\r
158 {\r
159         std::string result;\r
160         struct MemoryStruct xml_page;\r
161         xml_page.memory = NULL;\r
162         xml_page.size = 0;\r
163 \r
164         setcUrlOptions();\r
165         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);\r
166         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &xml_page);\r
167 \r
168         // curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);\r
169         // curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postMessage.c_str());\r
170         // curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1);\r
171 \r
172         // the url to get\r
173         std::string url = base_url + uri + "?";\r
174         if (params != "") {\r
175                 url += params + "&api_key=" + api_key + "&format=xml";\r
176         } else {\r
177                 url += "api_key=" + api_key + "&format=xml";\r
178         }\r
179                 \r
180         curl_easy_setopt(curl, CURLOPT_URL, url.c_str() );\r
181         std::cerr << "doRequest: " << url << std::endl;\r
182         \r
183         // perform online request\r
184         CURLcode res = curl_easy_perform(curl);\r
185         if( res != 0 ) {\r
186                 std::cerr << "curl error " << res << " (" << curl_easy_strerror(res) << ")" << std::endl;\r
187                 return "";\r
188         }\r
189 \r
190         // free the memory\r
191         if (xml_page.memory) {\r
192                 result = xml_page.memory;\r
193         }\r
194         \r
195         free (xml_page.memory);\r
196         xml_page.memory = NULL;\r
197         xml_page.size = 0;\r
198 \r
199         return result;\r
200 }\r
201 \r
202 \r
203 std::string Mootcher::searchText(std::string query, int page, std::string filter, enum sortMethod sort)\r
204 {\r
205         std::string params = "";\r
206         char buf[24];\r
207 \r
208         if (page > 1) {\r
209                 snprintf(buf, 23, "p=%d&", page);\r
210                 params += buf;\r
211         }\r
212         \r
213         params += "q=" + query; \r
214 \r
215         if (filter != "")\r
216                 params += "&f=" + filter;\r
217         \r
218         if (sort)\r
219                 params += "&s=" + sortMethodString(sort);\r
220 \r
221         params += "&fields=id,original_filename,duration,serve";        \r
222 \r
223         return doRequest("/sounds/search", params);\r
224 }\r
225 \r
226 //------------------------------------------------------------------------\r
227 \r
228 std::string Mootcher::getSoundResourceFile(std::string ID)\r
229 {\r
230 \r
231         std::string originalSoundURI;\r
232         std::string audioFileName;\r
233         std::string xml;\r
234 \r
235 \r
236         std::cerr << "getSoundResourceFile(" << ID << ")" << std::endl;\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                 std::cerr << "getSoundResourceFile: There is no valid root in the xml file" << std::endl;\r
248                 return "";\r
249         }\r
250 \r
251         if (strcmp(doc.root()->name().c_str(), "response") != 0) {\r
252                 std::cerr << "getSoundResourceFile: root =" << doc.root()->name() << ", != response" << std::endl;\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                         std::cerr << "audio file " << audioFileName << " already exists" << std::endl;\r
304                         fclose (testFile);\r
305                         return audioFileName;\r
306                 }\r
307                 \r
308                 // else file was small, probably an error, delete it and try again\r
309                 fclose(testFile);\r
310                 remove( audioFileName.c_str() );  \r
311         }\r
312 \r
313         if (!curl) {\r
314                 return "";\r
315         }\r
316 \r
317         //if already canceling a previous download, bail out here  ( this can happen b/c getAudioFile gets called by various UI update funcs )\r
318         if ( caller->freesound_download_cancel ) {\r
319                 return "";\r
320         }\r
321         \r
322         //now download the actual file\r
323         FILE* theFile;\r
324         theFile = g_fopen( audioFileName.c_str(), "wb" );\r
325 \r
326         if (!theFile) {\r
327                 return "";\r
328         }\r
329         \r
330         // create the download url\r
331         audioURL += "?api_key=" + api_key;\r
332 \r
333         setcUrlOptions();\r
334         curl_easy_setopt(curl, CURLOPT_URL, audioURL.c_str() );\r
335         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, audioFileWrite);\r
336         curl_easy_setopt(curl, CURLOPT_WRITEDATA, theFile);\r
337 \r
338         std::cerr << "downloading " << audioFileName << " from " << audioURL << "..." << std::endl;\r
339         /* hack to get rid of the barber-pole stripes */\r
340         caller->freesound_progress_bar.hide();\r
341         caller->freesound_progress_bar.show();\r
342 \r
343         std::string prog;\r
344         prog = string_compose (_("%1: [Stop]->"), originalFileName);\r
345         caller->freesound_progress_bar.set_text(prog);\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->freesound_progress_bar.set_fraction(0.0);\r
356         caller->freesound_progress_bar.set_text("");\r
357         \r
358         if( res != 0 ) {\r
359                 std::cerr <<  "curl error " << res << " (" << curl_easy_strerror(res) << ")" << std::endl;\r
360                 remove( audioFileName.c_str() );  \r
361                 return "";\r
362         } else {\r
363                 std::cerr << "done!" << std::endl;\r
364                 // now download the tags &c.\r
365                 getSoundResourceFile(ID);\r
366         }\r
367 \r
368         return audioFileName;\r
369 }\r
370 \r
371 //---------\r
372 int Mootcher::progress_callback(void *caller, double dltotal, double dlnow, double /*ultotal*/, double /*ulnow*/)\r
373 {\r
374 \r
375 SoundFileBrowser *sfb = (SoundFileBrowser *) caller;\r
376         //XXX I hope it's OK to do GTK things in this callback. Otherwise\r
377         // I'll have to do stuff like in interthread_progress_window.\r
378         if (sfb->freesound_download_cancel) {\r
379                 return -1;\r
380         }\r
381         \r
382         \r
383         sfb->freesound_progress_bar.set_fraction(dlnow/dltotal);\r
384         /* Make sure the progress widget gets updated */\r
385         while (Glib::MainContext::get_default()->iteration (false)) {\r
386                 /* do nothing */\r
387         }\r
388         std::cerr << "progress: " << dlnow << " of " << dltotal << " \r";\r
389         return 0;\r
390 }\r
391 \r