Merge branch 'master' into cairocanvas
[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 #include "ardour/rc_configuration.h"\r
57 #include "pbd/pthread_utils.h"\r
58 #include "gui_thread.h"\r
59 \r
60 using namespace PBD;\r
61 \r
62 static const std::string base_url = "http://www.freesound.org/api";\r
63 static const std::string api_key = "9d77cb8d841b4bcfa960e1aae62224eb"; // ardour3\r
64 \r
65 //------------------------------------------------------------------------\r
66 Mootcher::Mootcher()\r
67         : curl(curl_easy_init())\r
68 {\r
69         cancel_download_btn.set_label (_("Cancel"));\r
70         progress_hbox.pack_start (progress_bar, true, true);\r
71         progress_hbox.pack_end (cancel_download_btn, false, false);\r
72         progress_bar.show();\r
73         cancel_download_btn.show();\r
74         cancel_download_btn.signal_clicked().connect(sigc::mem_fun (*this, &Mootcher::cancelDownload));\r
75 };\r
76 //------------------------------------------------------------------------\r
77 Mootcher:: ~Mootcher()\r
78 {\r
79         curl_easy_cleanup(curl);\r
80 }\r
81 \r
82 //------------------------------------------------------------------------\r
83 \r
84 void Mootcher::ensureWorkingDir ()\r
85 {\r
86         std::string p = ARDOUR::Config->get_freesound_download_dir();\r
87 \r
88         if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) {\r
89                 if (g_mkdir_with_parents (p.c_str(), 0775) != 0) {\r
90                         PBD::error << "Unable to create Mootcher working dir" << endmsg;\r
91                 }\r
92         }\r
93         basePath = p;\r
94 #ifdef PLATFORM_WINDOWS\r
95         std::string replace = "/";\r
96         size_t pos = basePath.find("\\");\r
97         while( pos != std::string::npos ){\r
98                 basePath.replace(pos, 1, replace);\r
99                 pos = basePath.find("\\");\r
100         }\r
101 #endif\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 {\r
126 // given a sort type, returns the string value to be passed to the API to\r
127 // sort the results in the requested way.\r
128 \r
129         switch (sort) {\r
130                 case sort_duration_desc:        return "duration_desc"; \r
131                 case sort_duration_asc:         return "duration_asc";\r
132                 case sort_created_desc:         return "created_desc";\r
133                 case sort_created_asc:          return "created_asc";\r
134                 case sort_downloads_desc:       return "downloads_desc";\r
135                 case sort_downloads_asc:        return "downloads_asc";\r
136                 case sort_rating_desc:          return "rating_desc";\r
137                 case sort_rating_asc:           return "rating_asc";\r
138                 default:                        return "";      \r
139         }\r
140 }\r
141 \r
142 //------------------------------------------------------------------------\r
143 void Mootcher::setcUrlOptions()\r
144 {\r
145         // basic init for curl\r
146         curl_global_init(CURL_GLOBAL_ALL);\r
147         // some servers don't like requests that are made without a user-agent field, so we provide one\r
148         curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");\r
149         // setup curl error buffer\r
150         curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);\r
151         // Allow redirection\r
152         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);\r
153         \r
154         // Allow connections to time out (without using signals)\r
155         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);\r
156         curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);\r
157 \r
158 \r
159 }\r
160 \r
161 std::string Mootcher::doRequest(std::string uri, std::string params)\r
162 {\r
163         std::string result;\r
164         struct MemoryStruct xml_page;\r
165         xml_page.memory = NULL;\r
166         xml_page.size = 0;\r
167 \r
168         setcUrlOptions();\r
169         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);\r
170         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &xml_page);\r
171 \r
172         // curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);\r
173         // curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postMessage.c_str());\r
174         // curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1);\r
175 \r
176         // the url to get\r
177         std::string url = base_url + uri + "?";\r
178         if (params != "") {\r
179                 url += params + "&api_key=" + api_key + "&format=xml";\r
180         } else {\r
181                 url += "api_key=" + api_key + "&format=xml";\r
182         }\r
183                 \r
184         curl_easy_setopt(curl, CURLOPT_URL, url.c_str() );\r
185         \r
186         // perform online request\r
187         CURLcode res = curl_easy_perform(curl);\r
188         if( res != 0 ) {\r
189                 error << string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res)) << endmsg;\r
190                 return "";\r
191         }\r
192 \r
193         // free the memory\r
194         if (xml_page.memory) {\r
195                 result = xml_page.memory;\r
196         }\r
197         \r
198         free (xml_page.memory);\r
199         xml_page.memory = NULL;\r
200         xml_page.size = 0;\r
201 \r
202         return result;\r
203 }\r
204 \r
205 \r
206 std::string Mootcher::searchSimilar(std::string id)\r
207 {\r
208         std::string params = "";\r
209 \r
210         params += "&fields=id,original_filename,duration,filesize,samplerate,license,serve";\r
211         params += "&num_results=100";\r
212 \r
213         return doRequest("/sounds/" + id + "/similar", params);\r
214 }\r
215 \r
216 //------------------------------------------------------------------------\r
217 \r
218 std::string Mootcher::searchText(std::string query, int page, std::string filter, enum sortMethod sort)\r
219 {\r
220         std::string params = "";\r
221         char buf[24];\r
222 \r
223         if (page > 1) {\r
224                 snprintf(buf, 23, "p=%d&", page);\r
225                 params += buf;\r
226         }\r
227 \r
228         char *eq = curl_easy_escape(curl, query.c_str(), query.length());\r
229         params += "q=\"" + std::string(eq) + "\"";\r
230         free(eq);\r
231 \r
232         if (filter != "") {\r
233                 char *ef = curl_easy_escape(curl, filter.c_str(), filter.length());\r
234                 params += "&f=" + std::string(ef);\r
235                 free(ef);\r
236         }\r
237         \r
238         if (sort)\r
239                 params += "&s=" + sortMethodString(sort);\r
240 \r
241         params += "&fields=id,original_filename,duration,filesize,samplerate,license,serve";\r
242         params += "&sounds_per_page=100";\r
243 \r
244         return doRequest("/sounds/search", params);\r
245 }\r
246 \r
247 //------------------------------------------------------------------------\r
248 \r
249 std::string Mootcher::getSoundResourceFile(std::string ID)\r
250 {\r
251 \r
252         std::string originalSoundURI;\r
253         std::string audioFileName;\r
254         std::string xml;\r
255 \r
256 \r
257         // download the xmlfile into xml_page\r
258         xml = doRequest("/sounds/" + ID, "");\r
259 \r
260         XMLTree doc;\r
261         doc.read_buffer( xml.c_str() );\r
262         XMLNode *freesound = doc.root();\r
263 \r
264         // if the page is not a valid xml document with a 'freesound' root\r
265         if (freesound == NULL) {\r
266                 error << _("getSoundResourceFile: There is no valid root in the xml file") << endmsg;\r
267                 return "";\r
268         }\r
269 \r
270         if (strcmp(doc.root()->name().c_str(), "response") != 0) {\r
271                 error << string_compose (_("getSoundResourceFile: root = %1, != response"), doc.root()->name()) << endmsg;\r
272                 return "";\r
273         }\r
274 \r
275         XMLNode *name = freesound->child("original_filename");\r
276 \r
277         // get the file name and size from xml file\r
278         if (name) {\r
279 \r
280                 audioFileName = Glib::build_filename (basePath, ID + "-" + name->child("text")->content());\r
281 \r
282                 //store all the tags in the database\r
283                 XMLNode *tags = freesound->child("tags");\r
284                 if (tags) {\r
285                         XMLNodeList children = tags->children();\r
286                         XMLNodeConstIterator niter;\r
287                         std::vector<std::string> strings;\r
288                         for (niter = children.begin(); niter != children.end(); ++niter) {\r
289                                 XMLNode *node = *niter;\r
290                                 if( strcmp( node->name().c_str(), "resource") == 0 ) {\r
291                                         XMLNode *text = node->child("text");\r
292                                         if (text) {\r
293                                                 // std::cerr << "tag: " << text->content() << std::endl;\r
294                                                 strings.push_back(text->content());\r
295                                         }\r
296                                 }\r
297                         }\r
298                         ARDOUR::Library->set_tags (std::string("//")+audioFileName, strings);\r
299                         ARDOUR::Library->save_changes ();\r
300                 }\r
301         }\r
302 \r
303         return audioFileName;\r
304 }\r
305 \r
306 int audioFileWrite(void *buffer, size_t size, size_t nmemb, void *file)\r
307 {\r
308         return (int)fwrite(buffer, size, nmemb, (FILE*) file);\r
309 };\r
310 \r
311 //------------------------------------------------------------------------\r
312 \r
313 void *\r
314 Mootcher::threadFunc() {\r
315 CURLcode res;\r
316 \r
317         res = curl_easy_perform (curl);\r
318         fclose (theFile);\r
319         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); // turn off the progress bar\r
320         \r
321         if (res != CURLE_OK) {\r
322                 /* it's not an error if the user pressed the stop button */\r
323                 if (res != CURLE_ABORTED_BY_CALLBACK) {\r
324                         error <<  string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res)) << endmsg;\r
325                 }\r
326                 remove ( (audioFileName+".part").c_str() );  \r
327         } else {\r
328                 rename ( (audioFileName+".part").c_str(), audioFileName.c_str() );\r
329                 // now download the tags &c.\r
330                 getSoundResourceFile(ID);\r
331         }\r
332 \r
333         return (void *) res;\r
334 }\r
335  \r
336 void\r
337 Mootcher::doneWithMootcher()\r
338 {\r
339 \r
340         // update the sound info pane if the selection in the list box is still us \r
341         sfb->refresh_display(ID, audioFileName);\r
342 \r
343         delete this; // this should be OK to do as long as Progress and Finished signals are always received in the order in which they are emitted\r
344 }\r
345 \r
346 static void *\r
347 freesound_download_thread_func(void *arg) \r
348\r
349         Mootcher *thisMootcher = (Mootcher *) arg;\r
350         void *res;\r
351 \r
352         // std::cerr << "freesound_download_thread_func(" << arg << ")" << std::endl;\r
353         res = thisMootcher->threadFunc();\r
354 \r
355         thisMootcher->Finished(); /* EMIT SIGNAL */\r
356         return res;\r
357 }\r
358 \r
359 \r
360 //------------------------------------------------------------------------\r
361 \r
362 bool Mootcher::checkAudioFile(std::string originalFileName, std::string theID)\r
363 {\r
364         ensureWorkingDir();\r
365         ID = theID;\r
366         audioFileName = Glib::build_filename (basePath, ID + "-" + originalFileName);\r
367 \r
368         // check to see if audio file already exists\r
369         FILE *testFile = g_fopen(audioFileName.c_str(), "r");\r
370         if (testFile) {  \r
371                 fseek (testFile , 0 , SEEK_END);\r
372                 if (ftell (testFile) > 256) {\r
373                         fclose (testFile);\r
374                         return true;\r
375                 }\r
376                 \r
377                 // else file was small, probably an error, delete it \r
378                 fclose(testFile);\r
379                 remove( audioFileName.c_str() );  \r
380         }\r
381         return false;\r
382 }\r
383 \r
384 \r
385 bool Mootcher::fetchAudioFile(std::string originalFileName, std::string theID, std::string audioURL, SoundFileBrowser *caller)\r
386 {\r
387         ensureWorkingDir();\r
388         ID = theID;\r
389         audioFileName = Glib::build_filename (basePath, ID + "-" + originalFileName);\r
390 \r
391         if (!curl) {\r
392                 return false;\r
393         }\r
394         // now download the actual file\r
395         theFile = g_fopen( (audioFileName + ".part").c_str(), "wb" );\r
396 \r
397         if (!theFile) {\r
398                 return false;\r
399         }\r
400         \r
401         // create the download url\r
402         audioURL += "?api_key=" + api_key;\r
403 \r
404         setcUrlOptions();\r
405         curl_easy_setopt(curl, CURLOPT_URL, audioURL.c_str() );\r
406         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, audioFileWrite);\r
407         curl_easy_setopt(curl, CURLOPT_WRITEDATA, theFile);\r
408 \r
409         std::string prog;\r
410         prog = string_compose (_("%1"), originalFileName);\r
411         progress_bar.set_text(prog);\r
412 \r
413         Gtk::VBox *freesound_vbox = dynamic_cast<Gtk::VBox *> (caller->notebook.get_nth_page(2));\r
414         freesound_vbox->pack_start(progress_hbox, Gtk::PACK_SHRINK);\r
415         progress_hbox.show();\r
416         cancel_download = false;\r
417         sfb = caller;\r
418 \r
419         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); // turn on the progress bar\r
420         curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, progress_callback);\r
421         curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, this);\r
422 \r
423         Progress.connect(*this, invalidator (*this), boost::bind(&Mootcher::updateProgress, this, _1, _2), gui_context());\r
424         Finished.connect(*this, invalidator (*this), boost::bind(&Mootcher::doneWithMootcher, this), gui_context());\r
425         pthread_t freesound_download_thread;\r
426         pthread_create_and_store("freesound_import", &freesound_download_thread, freesound_download_thread_func, this);\r
427 \r
428         return true;\r
429 }\r
430 \r
431 //---------\r
432 \r
433 void \r
434 Mootcher::updateProgress(double dlnow, double dltotal) \r
435 {\r
436         if (dltotal > 0) {\r
437                 double fraction = dlnow / dltotal;\r
438                 // std::cerr << "progress idle: " << progress->bar->get_text() << ". " << progress->dlnow << " / " << progress->dltotal << " = " << fraction << std::endl;\r
439                 if (fraction > 1.0) {\r
440                         fraction = 1.0;\r
441                 } else if (fraction < 0.0) {\r
442                         fraction = 0.0;\r
443                 }\r
444                 progress_bar.set_fraction(fraction);\r
445         }\r
446 }\r
447 \r
448 int \r
449 Mootcher::progress_callback(void *caller, double dltotal, double dlnow, double /*ultotal*/, double /*ulnow*/)\r
450 {\r
451         // It may seem curious to pass a pointer to an instance of an object to a static\r
452         // member function, but we can't use a normal member function as a curl progress callback,\r
453         // and we want access to some private members of Mootcher.\r
454 \r
455         Mootcher *thisMootcher = (Mootcher *) caller;\r
456         \r
457         if (thisMootcher->cancel_download) {\r
458                 return -1;\r
459         }\r
460 \r
461         thisMootcher->Progress(dlnow, dltotal); /* EMIT SIGNAL */\r
462         return 0;\r
463 }\r
464 \r