6833bae7684c44320878837fae3958b9bd0b770d
[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 \r
45 #include <sys/stat.h>\r
46 #include <sys/types.h>\r
47 #include <iostream>\r
48 \r
49 #include "ardour/audio_library.h"\r
50 \r
51 static const std::string base_url = "http://www.freesound.org/api";\r
52 static const std::string api_key = "9d77cb8d841b4bcfa960e1aae62224eb"; // ardour3\r
53 \r
54 \r
55 //------------------------------------------------------------------------\r
56 Mootcher::Mootcher(const char *saveLocation)\r
57         : curl(curl_easy_init())\r
58 {\r
59         changeWorkingDir(saveLocation);\r
60 };\r
61 //------------------------------------------------------------------------\r
62 Mootcher:: ~Mootcher()\r
63 {\r
64 }\r
65 \r
66 //------------------------------------------------------------------------\r
67 const char* Mootcher::changeWorkingDir(const char *saveLocation)\r
68 {\r
69         basePath = saveLocation;\r
70 #ifdef __WIN32__\r
71         std::string replace = "/";\r
72         size_t pos = basePath.find("\\");\r
73         while( pos != std::string::npos ){\r
74                 basePath.replace(pos, 1, replace);\r
75                 pos = basePath.find("\\");\r
76         }\r
77 #endif\r
78         //\r
79         size_t pos2 = basePath.find_last_of("/");\r
80         if(basePath.length() != (pos2+1)) basePath += "/";\r
81 \r
82         // create Freesound directory and sound dir\r
83         std::string sndLocation = basePath;\r
84         mkdir(sndLocation.c_str(), 0777);\r
85         sndLocation += "snd";\r
86         mkdir(sndLocation.c_str(), 0777);\r
87 \r
88         return basePath.c_str();\r
89 }\r
90 \r
91 //------------------------------------------------------------------------\r
92 size_t Mootcher::WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)\r
93 {\r
94         register int realsize = (int)(size * nmemb);\r
95         struct MemoryStruct *mem = (struct MemoryStruct *)data;\r
96 \r
97         mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);\r
98 \r
99         if (mem->memory) {\r
100                 memcpy(&(mem->memory[mem->size]), ptr, realsize);\r
101                 mem->size += realsize;\r
102                 mem->memory[mem->size] = 0;\r
103         }\r
104         return realsize;\r
105 }\r
106 \r
107 \r
108 //------------------------------------------------------------------------\r
109 \r
110 std::string Mootcher::sortMethodString(enum sortMethod sort) {\r
111 \r
112         switch (sort) {\r
113                 case sort_duration_desc:        return "duration_desc"; \r
114                 case sort_duration_asc:         return "duration_asc";\r
115                 case sort_created_desc:         return "created_desc";\r
116                 case sort_created_asc:          return "created_asc";\r
117                 case sort_downloads_desc:       return "downloads_desc";\r
118                 case sort_downloads_asc:        return "downloads_asc";\r
119                 case sort_rating_desc:          return "rating_desc";\r
120                 case sort_rating_asc:           return "rating_asc";\r
121                 default:                        return "";      \r
122         }\r
123 }\r
124 \r
125 //------------------------------------------------------------------------\r
126 void Mootcher::setcUrlOptions()\r
127 {\r
128         // basic init for curl\r
129         curl_global_init(CURL_GLOBAL_ALL);\r
130         // some servers don't like requests that are made without a user-agent field, so we provide one\r
131         curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");\r
132         // setup curl error buffer\r
133         curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);\r
134         // Allow redirection\r
135         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);\r
136         \r
137         // Allow connections to time out (without using signals)\r
138         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);\r
139         curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);\r
140 \r
141 \r
142 }\r
143 \r
144 std::string Mootcher::doRequest(std::string uri, std::string params)\r
145 {\r
146         std::string result;\r
147         struct MemoryStruct xml_page;\r
148         xml_page.memory = NULL;\r
149         xml_page.size = 0;\r
150 \r
151         setcUrlOptions();\r
152         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);\r
153         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &xml_page);\r
154 \r
155         // curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);\r
156         // curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postMessage.c_str());\r
157         // curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1);\r
158 \r
159         // the url to get\r
160         std::string url = base_url + uri + "?";\r
161         if (params != "") {\r
162                 url += params + "&api_key=" + api_key + "&format=xml";\r
163         } else {\r
164                 url += "api_key=" + api_key + "&format=xml";\r
165         }\r
166                 \r
167         curl_easy_setopt(curl, CURLOPT_URL, url.c_str() );\r
168         std::cerr << "doRequest: " << url << std::endl;\r
169         \r
170         // perform online request\r
171         CURLcode res = curl_easy_perform(curl);\r
172         if( res != 0 ) {\r
173                 std::cerr << "curl error " << res << " (" << curl_easy_strerror(res) << ")" << std::endl;\r
174                 return "";\r
175         }\r
176 \r
177         result = xml_page.memory;\r
178         // free the memory\r
179         if(xml_page.memory){\r
180                 free( xml_page.memory );\r
181                 xml_page.memory = NULL;\r
182                 xml_page.size = 0;\r
183         }\r
184 \r
185         return result;\r
186 \r
187 }\r
188 \r
189 \r
190 std::string Mootcher::searchText(std::string query, int page, std::string filter, enum sortMethod sort)\r
191 {\r
192         std::string params = "";\r
193         char buf[24];\r
194 \r
195         if (page > 1) {\r
196                 snprintf(buf, 23, "p=%d&", page);\r
197                 params += buf;\r
198         }\r
199         \r
200         params += "q=" + query; \r
201 \r
202         if (filter != "")\r
203                 params += "&f=" + filter;\r
204         \r
205         if (sort)\r
206                 params += "&s=" + sortMethodString(sort);\r
207 \r
208         return doRequest("/sounds/search", params);\r
209 }\r
210 \r
211 //------------------------------------------------------------------------\r
212 \r
213 std::string Mootcher::getSoundResourceFile(std::string ID)\r
214 {\r
215 \r
216         std::string originalSoundURI;\r
217         std::string audioFileName;\r
218         std::string xmlFileName;\r
219         std::string xml;\r
220 \r
221 \r
222         std::cerr << "getSoundResourceFile(" << ID << ")" << std::endl;\r
223 \r
224         // download the xmlfile into xml_page\r
225         xml = doRequest("/sounds/" + ID, "");\r
226 \r
227         XMLTree doc;\r
228         doc.read_buffer( xml.c_str() );\r
229         XMLNode *freesound = doc.root();\r
230 \r
231         // if the page is not a valid xml document with a 'freesound' root\r
232         if (freesound == NULL) {\r
233                 std::cerr << "getSoundResourceFile: There is no valid root in the xml file" << std::endl;\r
234                 return "";\r
235         }\r
236 \r
237         if (strcmp(doc.root()->name().c_str(), "response") != 0) {\r
238                 std::cerr << "getSoundResourceFile: root =" << doc.root()->name() << ", != response" << std::endl;\r
239                 return "";\r
240         }\r
241 \r
242         XMLNode *name = freesound->child("original_filename");\r
243         XMLNode *filesize = freesound->child("filesize");\r
244 \r
245 \r
246         // get the file name and size from xml file\r
247         if (name && filesize) {\r
248 \r
249                 audioFileName = basePath + "snd/" + ID + "-" + name->child("text")->content();\r
250 \r
251                 // create new filename with the ID number\r
252                 xmlFileName = basePath;\r
253                 xmlFileName += "snd/";\r
254                 xmlFileName += freesound->child("id")->child("text")->content();\r
255                 xmlFileName += "-";\r
256                 xmlFileName += name->child("text")->content();\r
257                 xmlFileName += ".xml";\r
258 \r
259                 // std::cerr << "getSoundResourceFile: saving XML: " << xmlFileName << std::endl;\r
260 \r
261                 // save the xml file to disk\r
262                 doc.write(xmlFileName.c_str());\r
263 \r
264                 //store all the tags in the database\r
265                 XMLNode *tags = freesound->child("tags");\r
266                 if (tags) {\r
267                         XMLNodeList children = tags->children();\r
268                         XMLNodeConstIterator niter;\r
269                         std::vector<std::string> strings;\r
270                         for (niter = children.begin(); niter != children.end(); ++niter) {\r
271                                 XMLNode *node = *niter;\r
272                                 if( strcmp( node->name().c_str(), "resource") == 0 ) {\r
273                                         XMLNode *text = node->child("text");\r
274                                         if (text) {\r
275                                                 // std::cerr << "tag: " << text->content() << std::endl;\r
276                                                 strings.push_back(text->content());\r
277                                         }\r
278                                 }\r
279                         }\r
280                         ARDOUR::Library->set_tags (std::string("//")+audioFileName, strings);\r
281                         ARDOUR::Library->save_changes ();\r
282                 }\r
283         }\r
284 \r
285         return audioFileName;\r
286 }\r
287 \r
288 int audioFileWrite(void *buffer, size_t size, size_t nmemb, void *file)\r
289 {\r
290         return (int)fwrite(buffer, size, nmemb, (FILE*) file);\r
291 };\r
292 \r
293 //------------------------------------------------------------------------\r
294 std::string Mootcher::getAudioFile(std::string originalFileName, std::string ID, std::string audioURL, Gtk::ProgressBar *progress_bar)\r
295 {\r
296 \r
297         std::string audioFileName = basePath + "snd/" + ID + "-" + originalFileName;\r
298 \r
299         //check to see if audio file already exists\r
300         FILE *testFile = fopen(audioFileName.c_str(), "r");\r
301         if (testFile) {  \r
302                 fseek (testFile , 0 , SEEK_END);\r
303                 if (ftell (testFile) > 256) {\r
304                         std::cerr << "audio file " << audioFileName << " already exists" << std::endl;\r
305                         fclose (testFile);\r
306                         return audioFileName;\r
307                 }\r
308                 \r
309                 // else file was small, probably an error, delete it and try again\r
310                 fclose(testFile);\r
311                 remove( audioFileName.c_str() );  \r
312         }\r
313 \r
314         //now download the actual file\r
315         if (curl) {\r
316 \r
317                 FILE* theFile;\r
318                 theFile = fopen( audioFileName.c_str(), "wb" );\r
319 \r
320                 if (theFile) {\r
321                 \r
322                         // create the download url\r
323                         audioURL += "?api_key=" + api_key;\r
324                 \r
325                         setcUrlOptions();\r
326                         curl_easy_setopt(curl, CURLOPT_URL, audioURL.c_str() );\r
327                         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, audioFileWrite);\r
328                         curl_easy_setopt(curl, CURLOPT_WRITEDATA, theFile);\r
329 \r
330                         std::cerr << "downloading " << audioFileName << " from " << audioURL << "..." << std::endl;\r
331 \r
332                         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); // turn on the progress bar\r
333                         curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, progress_callback);\r
334                         curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, progress_bar);\r
335 \r
336                         CURLcode res = curl_easy_perform(curl);\r
337                         fclose(theFile);\r
338 \r
339                         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); // turn off the progress bar\r
340                         progress_bar->set_fraction(0.0);\r
341 \r
342                         if( res != 0 ) {\r
343                                 std::cerr <<  "curl error " << res << " (" << curl_easy_strerror(res) << ")" << std::endl;\r
344                                 remove( audioFileName.c_str() );  \r
345                                 return "";\r
346                         } else {\r
347                                 std::cerr << "done!" << std::endl;\r
348                                 // now download the tags &c.\r
349                                 getSoundResourceFile(ID);\r
350                         }\r
351                 }\r
352         }\r
353 \r
354         return audioFileName;\r
355 }\r
356 \r
357 //---------\r
358 int Mootcher::progress_callback(void *bar, double dltotal, double dlnow, double ultotal, double ulnow)\r
359 {\r
360 \r
361         //XXX I hope it's OK to do GTK things in this callback. Otherwise\r
362         // I'll have to do stuff like in interthread_progress_window.\r
363         \r
364         Gtk::ProgressBar *progress_bar = (Gtk::ProgressBar *) bar;\r
365         progress_bar->set_fraction(dlnow/dltotal);\r
366         /* Make sure the progress widget gets updated */\r
367         while (Glib::MainContext::get_default()->iteration (false)) {\r
368                 /* do nothing */\r
369         }\r
370         std::cerr << "progress: " << dlnow << " of " << dltotal << " \r";\r
371         return 0;\r
372 }\r
373 \r