Use config variable for Freesound download folder location.
[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 \r
58 using namespace PBD;\r
59 \r
60 static const std::string base_url = "http://www.freesound.org/api";\r
61 static const std::string api_key = "9d77cb8d841b4bcfa960e1aae62224eb"; // ardour3\r
62 \r
63 //------------------------------------------------------------------------\r
64 Mootcher::Mootcher()\r
65         : curl(curl_easy_init())\r
66 {\r
67         cancel_download_btn.set_label (_("Cancel"));\r
68         progress_hbox.pack_start (progress_bar, true, true);\r
69         progress_hbox.pack_end (cancel_download_btn, false, false);\r
70         progress_bar.show();\r
71         cancel_download_btn.show();\r
72         cancel_download_btn.signal_clicked().connect(sigc::mem_fun (*this, &Mootcher::cancelDownload));\r
73 };\r
74 //------------------------------------------------------------------------\r
75 Mootcher:: ~Mootcher()\r
76 {\r
77         curl_easy_cleanup(curl);\r
78 }\r
79 \r
80 //------------------------------------------------------------------------\r
81 \r
82 void Mootcher::ensureWorkingDir ()\r
83 {\r
84         std::string p = ARDOUR::Config->get_freesound_download_dir();\r
85 \r
86         if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) {\r
87                 if (g_mkdir_with_parents (p.c_str(), 0775) != 0) {\r
88                         PBD::error << "Unable to create Mootcher working dir" << endmsg;\r
89                 }\r
90         }\r
91         basePath = p;\r
92 #ifdef __WIN32__\r
93         std::string replace = "/";\r
94         size_t pos = basePath.find("\\");\r
95         while( pos != std::string::npos ){\r
96                 basePath.replace(pos, 1, replace);\r
97                 pos = basePath.find("\\");\r
98         }\r
99 #endif\r
100 }\r
101         \r
102 \r
103 //------------------------------------------------------------------------\r
104 size_t Mootcher::WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)\r
105 {\r
106         register int realsize = (int)(size * nmemb);\r
107         struct MemoryStruct *mem = (struct MemoryStruct *)data;\r
108 \r
109         mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);\r
110 \r
111         if (mem->memory) {\r
112                 memcpy(&(mem->memory[mem->size]), ptr, realsize);\r
113                 mem->size += realsize;\r
114                 mem->memory[mem->size] = 0;\r
115         }\r
116         return realsize;\r
117 }\r
118 \r
119 \r
120 //------------------------------------------------------------------------\r
121 \r
122 std::string Mootcher::sortMethodString(enum sortMethod sort) {\r
123 // given a sort type, returns the string value to be passed to the API to\r
124 // sort the results in the requested way.\r
125 \r
126         switch (sort) {\r
127                 case sort_duration_desc:        return "duration_desc"; \r
128                 case sort_duration_asc:         return "duration_asc";\r
129                 case sort_created_desc:         return "created_desc";\r
130                 case sort_created_asc:          return "created_asc";\r
131                 case sort_downloads_desc:       return "downloads_desc";\r
132                 case sort_downloads_asc:        return "downloads_asc";\r
133                 case sort_rating_desc:          return "rating_desc";\r
134                 case sort_rating_asc:           return "rating_asc";\r
135                 default:                        return "";      \r
136         }\r
137 }\r
138 \r
139 //------------------------------------------------------------------------\r
140 void Mootcher::setcUrlOptions()\r
141 {\r
142         // basic init for curl\r
143         curl_global_init(CURL_GLOBAL_ALL);\r
144         // some servers don't like requests that are made without a user-agent field, so we provide one\r
145         curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");\r
146         // setup curl error buffer\r
147         curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);\r
148         // Allow redirection\r
149         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);\r
150         \r
151         // Allow connections to time out (without using signals)\r
152         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);\r
153         curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);\r
154 \r
155 \r
156 }\r
157 \r
158 std::string Mootcher::doRequest(std::string uri, std::string params)\r
159 {\r
160         std::string result;\r
161         struct MemoryStruct xml_page;\r
162         xml_page.memory = NULL;\r
163         xml_page.size = 0;\r
164 \r
165         setcUrlOptions();\r
166         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);\r
167         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &xml_page);\r
168 \r
169         // curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);\r
170         // curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postMessage.c_str());\r
171         // curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1);\r
172 \r
173         // the url to get\r
174         std::string url = base_url + uri + "?";\r
175         if (params != "") {\r
176                 url += params + "&api_key=" + api_key + "&format=xml";\r
177         } else {\r
178                 url += "api_key=" + api_key + "&format=xml";\r
179         }\r
180                 \r
181         curl_easy_setopt(curl, CURLOPT_URL, url.c_str() );\r
182         \r
183         // perform online request\r
184         CURLcode res = curl_easy_perform(curl);\r
185         if( res != 0 ) {\r
186                 error << string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res)) << endmsg;\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         char *eq = curl_easy_escape(curl, query.c_str(), query.length());\r
214         params += "q=\"" + std::string(eq) + "\"";\r
215         free(eq);\r
216 \r
217         if (filter != "") {\r
218                 char *ef = curl_easy_escape(curl, filter.c_str(), filter.length());\r
219                 params += "&f=" + std::string(ef);\r
220                 free(ef);\r
221         }\r
222         \r
223         if (sort)\r
224                 params += "&s=" + sortMethodString(sort);\r
225 \r
226         params += "&fields=id,original_filename,duration,filesize,samplerate,license,serve";\r
227         params += "&sounds_per_page=100";\r
228 \r
229         return doRequest("/sounds/search", params);\r
230 }\r
231 \r
232 //------------------------------------------------------------------------\r
233 \r
234 std::string Mootcher::getSoundResourceFile(std::string ID)\r
235 {\r
236 \r
237         std::string originalSoundURI;\r
238         std::string audioFileName;\r
239         std::string xml;\r
240 \r
241 \r
242         // download the xmlfile into xml_page\r
243         xml = doRequest("/sounds/" + ID, "");\r
244 \r
245         XMLTree doc;\r
246         doc.read_buffer( xml.c_str() );\r
247         XMLNode *freesound = doc.root();\r
248 \r
249         // if the page is not a valid xml document with a 'freesound' root\r
250         if (freesound == NULL) {\r
251                 error << _("getSoundResourceFile: There is no valid root in the xml file") << endmsg;\r
252                 return "";\r
253         }\r
254 \r
255         if (strcmp(doc.root()->name().c_str(), "response") != 0) {\r
256                 error << string_compose (_("getSoundResourceFile: root = %1, != response"), doc.root()->name()) << endmsg;\r
257                 return "";\r
258         }\r
259 \r
260         XMLNode *name = freesound->child("original_filename");\r
261 \r
262         // get the file name and size from xml file\r
263         if (name) {\r
264 \r
265                 audioFileName = Glib::build_filename (basePath, ID + "-" + name->child("text")->content());\r
266 \r
267                 //store all the tags in the database\r
268                 XMLNode *tags = freesound->child("tags");\r
269                 if (tags) {\r
270                         XMLNodeList children = tags->children();\r
271                         XMLNodeConstIterator niter;\r
272                         std::vector<std::string> strings;\r
273                         for (niter = children.begin(); niter != children.end(); ++niter) {\r
274                                 XMLNode *node = *niter;\r
275                                 if( strcmp( node->name().c_str(), "resource") == 0 ) {\r
276                                         XMLNode *text = node->child("text");\r
277                                         if (text) {\r
278                                                 // std::cerr << "tag: " << text->content() << std::endl;\r
279                                                 strings.push_back(text->content());\r
280                                         }\r
281                                 }\r
282                         }\r
283                         ARDOUR::Library->set_tags (std::string("//")+audioFileName, strings);\r
284                         ARDOUR::Library->save_changes ();\r
285                 }\r
286         }\r
287 \r
288         return audioFileName;\r
289 }\r
290 \r
291 int audioFileWrite(void *buffer, size_t size, size_t nmemb, void *file)\r
292 {\r
293         return (int)fwrite(buffer, size, nmemb, (FILE*) file);\r
294 };\r
295 \r
296 //------------------------------------------------------------------------\r
297 std::string Mootcher::getAudioFile(std::string originalFileName, std::string ID, std::string audioURL, SoundFileBrowser *caller)\r
298 {\r
299         ensureWorkingDir();\r
300         audioFileName = Glib::build_filename (basePath, ID + "-" + originalFileName);\r
301 \r
302         // check to see if audio file already exists\r
303         FILE *testFile = g_fopen(audioFileName.c_str(), "r");\r
304         if (testFile) {  \r
305                 fseek (testFile , 0 , SEEK_END);\r
306                 if (ftell (testFile) > 256) {\r
307                         fclose (testFile);\r
308                         return audioFileName;\r
309                 }\r
310                 \r
311                 // else file was small, probably an error, delete it and try again\r
312                 fclose(testFile);\r
313                 remove( audioFileName.c_str() );  \r
314         }\r
315 \r
316         if (!curl) {\r
317                 return "";\r
318         }\r
319 \r
320         // if already cancelling a previous download, bail out here  ( this can happen b/c getAudioFile gets called by various UI update funcs )\r
321         if ( caller->freesound_download_cancel ) {\r
322                 return "";\r
323         }\r
324         \r
325         // now download the actual file\r
326         FILE* theFile;\r
327         theFile = g_fopen( audioFileName.c_str(), "wb" );\r
328 \r
329         if (!theFile) {\r
330                 return "";\r
331         }\r
332         \r
333         // create the download url\r
334         audioURL += "?api_key=" + api_key;\r
335 \r
336         setcUrlOptions();\r
337         curl_easy_setopt(curl, CURLOPT_URL, audioURL.c_str() );\r
338         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, audioFileWrite);\r
339         curl_easy_setopt(curl, CURLOPT_WRITEDATA, theFile);\r
340 \r
341         /* hack to get rid of the barber-pole stripes */\r
342         caller->freesound_progress_bar.hide();\r
343         caller->freesound_progress_bar.show();\r
344 \r
345         std::string prog;\r
346         prog = string_compose (_("%1"), originalFileName);\r
347         caller->freesound_progress_bar.set_text(prog);\r
348 \r
349         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); // turn on the progress bar\r
350         curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, progress_callback);\r
351         curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, caller);\r
352 \r
353         CURLcode res = curl_easy_perform(curl);\r
354         fclose(theFile);\r
355 \r
356         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); // turn off the progress bar\r
357         caller->freesound_progress_bar.set_fraction(0.0);\r
358         caller->freesound_progress_bar.set_text("");\r
359         \r
360         if( res != 0 ) {\r
361                 /* it's not an error if the user pressed the stop button */\r
362                 if (res != CURLE_ABORTED_BY_CALLBACK) {\r
363                         error <<  string_compose (_("curl error %1 (%2)"), res, curl_easy_strerror(res)) << endmsg;\r
364                 }\r
365                 remove( audioFileName.c_str() );  \r
366                 return "";\r
367         } else {\r
368                 // now download the tags &c.\r
369                 getSoundResourceFile(ID);\r
370         }\r
371 \r
372         return audioFileName;\r
373 }\r
374 \r
375 //---------\r
376 int Mootcher::progress_callback(void *caller, double dltotal, double dlnow, double /*ultotal*/, double /*ulnow*/)\r
377 {\r
378 \r
379 SoundFileBrowser *sfb = (SoundFileBrowser *) caller;\r
380         //XXX I hope it's OK to do GTK things in this callback. Otherwise\r
381         // I'll have to do stuff like in interthread_progress_window.\r
382         if (sfb->freesound_download_cancel) {\r
383                 return -1;\r
384         }\r
385         \r
386         \r
387         sfb->freesound_progress_bar.set_fraction(dlnow/dltotal);\r
388         /* Make sure the progress widget gets updated */\r
389         while (Glib::MainContext::get_default()->iteration (false)) {\r
390                 /* do nothing */\r
391         }\r
392         return 0;\r
393 }\r
394 \r