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