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