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