Merge big changes (mostly Controllable) from trunk
[ardour.git] / libs / ardour / audio_library.cc
1 /*
2     Copyright (C) 2003 Paul Davis 
3     Author: Taybin Rutkin
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19     $Id$
20 */
21
22 #include <cstdio> // Needed so that libraptor (included in lrdf) won't complain
23 #include <cerrno>
24 #include <iostream>
25 #include <sstream>
26 #include <cctype>
27
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fts.h>
31
32 #include <lrdf.h>
33
34 #include <pbd/compose.h>
35
36 #include <ardour/ardour.h>
37 #include <ardour/configuration.h>
38 #include <ardour/audio_library.h>
39 #include <ardour/utils.h>
40
41 #include "i18n.h"
42
43 using namespace std;
44 using namespace ARDOUR;
45 using namespace PBD;
46
47 static char* SOUNDFILE = "http://ardour.org/ontology/Soundfile";
48
49 AudioLibrary::AudioLibrary ()
50 {
51 //      sfdb_paths.push_back("/Users/taybin/sounds");
52
53         src = "file:" + get_user_ardour_path() + "sfdb";
54
55         // workaround for possible bug in raptor that crashes when saving to a
56         // non-existant file.
57         touch_file(get_user_ardour_path() + "sfdb");
58
59         lrdf_read_file(src.c_str());
60
61         lrdf_statement pattern;
62
63         pattern.subject = SOUNDFILE;
64         pattern.predicate = RDF_TYPE;
65         pattern.object = RDFS_CLASS;
66         pattern.object_type = lrdf_uri;
67
68         lrdf_statement* matches = lrdf_matches(&pattern);
69
70         // if empty DB, create basic schema
71         if (matches == 0) {
72                 initialize_db ();
73                 save_changes();
74         } 
75
76         lrdf_free_statements(matches);
77
78         XMLNode* state = instant_xml(X_("AudioLibrary"), get_user_ardour_path());
79         if (state) {
80                 set_state(*state);
81         }
82         scan_paths();
83 }
84
85 AudioLibrary::~AudioLibrary ()
86 {
87         add_instant_xml(get_state(), get_user_ardour_path());
88 }
89
90 void
91 AudioLibrary::initialize_db ()
92 {
93         // define ardour:Soundfile
94         lrdf_add_triple(src.c_str(), SOUNDFILE, RDF_TYPE, RDFS_CLASS, lrdf_uri);
95
96         // add intergral fields
97         add_field(_("channels"));
98         add_field(_("samplerate"));
99         add_field(_("resolution"));
100         add_field(_("format"));
101 }
102
103 void
104 AudioLibrary::save_changes ()
105 {
106         if (lrdf_export_by_source(src.c_str(), src.substr(5).c_str())) {
107                 warning << string_compose(_("Could not open %1.  Audio Library not saved"), src) << endmsg;
108         }
109 }
110
111 void
112 AudioLibrary::add_member (string member)
113 {
114         string file_uri(string_compose("file:%1", member));
115
116         lrdf_add_triple(src.c_str(), file_uri.c_str(), RDF_TYPE, 
117                         SOUNDFILE, lrdf_uri);
118 }
119
120 void
121 AudioLibrary::remove_member (string uri)
122 {
123         lrdf_remove_uri_matches (uri.c_str());
124 }
125
126 void
127 AudioLibrary::search_members_and (vector<string>& members, 
128                                                                   const map<string,string>& fields)
129 {
130         lrdf_statement **head;
131         lrdf_statement* pattern = 0;
132         lrdf_statement* old = 0;
133         head = &pattern;
134
135         map<string,string>::const_iterator i;
136         for (i = fields.begin(); i != fields.end(); ++i){
137                 pattern = new lrdf_statement;
138                 pattern->subject = "?";
139                 pattern->predicate = strdup(field_uri(i->first).c_str());
140                 pattern->object = strdup((i->second).c_str());
141                 pattern->next = old;
142
143                 old = pattern;
144         }
145
146         if (*head != 0) {
147                 lrdf_uris* ulist = lrdf_match_multi(*head);
148                 for (uint32_t j = 0; ulist && j < ulist->count; ++j) {
149 //                      printf("AND: %s\n", ulist->items[j]);
150                         members.push_back(ulist->items[j]);
151                 }
152                 lrdf_free_uris(ulist);
153
154                 compact_vector(members);
155         }
156
157         // memory clean up
158         pattern = *head;
159         while(pattern){
160                 free(pattern->predicate);
161                 free(pattern->object);
162                 old = pattern;
163                 pattern = pattern->next;
164                 delete old;
165         }
166 }
167
168 void
169 AudioLibrary::search_members_or (vector<string>& members, 
170                                                                  const map<string,string>& fields)
171 {
172         map<string,string>::const_iterator i;
173
174         lrdf_statement pattern;
175         for (i = fields.begin(); i != fields.end(); ++i) {
176                 pattern.subject = 0;
177                 pattern.predicate = strdup(field_uri(i->first).c_str());
178                 pattern.object = strdup((i->second).c_str());
179                 pattern.object_type = lrdf_literal;
180
181                 lrdf_statement* matched = lrdf_matches(&pattern);
182
183                 lrdf_statement* old = matched;
184                 while(matched) {
185 //                      printf ("OR: %s\n", matched->subject);
186                         members.push_back(matched->subject);
187                         matched = matched->next;
188                 }
189
190                 free(pattern.predicate);
191                 free(pattern.object);
192                 lrdf_free_statements (old);
193         }
194
195         compact_vector(members);
196 }
197
198 void
199 AudioLibrary::add_field (string name)
200 {
201         string local_field = field_uri(name);
202         lrdf_statement pattern;
203         pattern.subject = strdup(local_field.c_str());
204         pattern.predicate = RDF_TYPE;
205         pattern.object = RDF_BASE "Property";
206         pattern.object_type = lrdf_uri;
207
208         if(lrdf_exists_match(&pattern)) {
209                 return;
210         }
211
212         // of type rdf:Property
213         lrdf_add_triple(src.c_str(), local_field.c_str(), RDF_TYPE, 
214                         RDF_BASE "Property", lrdf_uri);
215         // of range ardour:Soundfile
216         lrdf_add_triple(src.c_str(), local_field.c_str(), RDFS_BASE "range",
217                         SOUNDFILE, lrdf_uri);
218         // of domain rdf:Literal
219         lrdf_add_triple(src.c_str(), local_field.c_str(), RDFS_BASE "domain", 
220                                         RDF_BASE "Literal", lrdf_uri);
221
222         set_label (local_field, name);
223         
224         fields_changed(); /* EMIT SIGNAL */
225 }
226
227 void
228 AudioLibrary::get_fields (vector<string>& fields)
229 {
230         lrdf_statement pattern;
231
232         pattern.subject = 0;
233         pattern.predicate = RDFS_BASE "range";
234         pattern.object = SOUNDFILE;
235         pattern.object_type = lrdf_uri;
236
237         lrdf_statement* matches = lrdf_matches(&pattern);
238
239         lrdf_statement* current = matches;
240         while (current != 0) {
241                 fields.push_back(get_label(current->subject));
242
243                 current = current->next;
244         }
245
246         lrdf_free_statements(matches);
247
248         compact_vector(fields);
249 }
250
251 void
252 AudioLibrary::remove_field (string name)
253 {
254         lrdf_remove_uri_matches(field_uri(name).c_str());
255         fields_changed (); /* EMIT SIGNAL */
256 }
257
258 string 
259 AudioLibrary::get_field (string uri, string field)
260 {
261         lrdf_statement pattern;
262
263         pattern.subject = strdup(uri.c_str());
264
265         pattern.predicate = strdup(field_uri(field).c_str());
266
267         pattern.object = 0;
268         pattern.object_type = lrdf_literal;
269
270         lrdf_statement* matches = lrdf_matches(&pattern);
271         free(pattern.subject);
272         free(pattern.predicate);
273
274         stringstream object;
275         if (matches != 0){
276                 object << matches->object;
277         }
278
279         lrdf_free_statements(matches);
280         return object.str();
281 }
282
283 void 
284 AudioLibrary::set_field (string uri, string field, string literal)
285 {
286         lrdf_statement pattern;
287
288         pattern.subject = strdup(uri.c_str());
289
290         string local_field = field_uri(field);
291         pattern.predicate = strdup(local_field.c_str());
292
293         pattern.object = 0;
294         pattern.object_type = lrdf_literal;
295
296         lrdf_remove_matches(&pattern);
297         free(pattern.subject);
298         free(pattern.predicate);
299
300         lrdf_add_triple(src.c_str(), uri.c_str(), local_field.c_str(), 
301                         literal.c_str(), lrdf_literal);
302
303          fields_changed(); /* EMIT SIGNAL */
304 }
305
306 string
307 AudioLibrary::field_uri (string name)
308 {
309         stringstream local_field;
310         local_field << "file:sfdb/fields/" << name;
311
312         return local_field.str();
313 }
314
315 string
316 AudioLibrary::get_label (string uri)
317 {
318         lrdf_statement pattern;
319         pattern.subject = strdup(uri.c_str());
320         pattern.predicate = RDFS_BASE "label";
321         pattern.object = 0;
322         pattern.object_type = lrdf_literal;
323
324         lrdf_statement* matches = lrdf_matches (&pattern);
325         free(pattern.subject);
326
327         stringstream label;
328         if (matches != 0){
329                 label << matches->object;
330         }
331
332         lrdf_free_statements(matches);
333
334         return label.str();
335 }
336
337 void
338 AudioLibrary::set_label (string uri, string label)
339 {
340         lrdf_statement pattern;
341         pattern.subject = strdup(uri.c_str());
342         pattern.predicate = RDFS_BASE "label";
343         pattern.object = 0;
344         pattern.object_type = lrdf_literal;
345
346         lrdf_remove_matches(&pattern);
347         free(pattern.subject);
348
349         lrdf_add_triple(src.c_str(), uri.c_str(), RDFS_BASE "label", 
350                         label.c_str(), lrdf_literal);
351 }
352
353 void
354 AudioLibrary::compact_vector(vector<string>& vec)
355 {
356     sort(vec.begin(), vec.end());
357     unique(vec.begin(), vec.end());
358 }
359
360 void 
361 AudioLibrary::set_paths (vector<string> paths)
362 {
363         sfdb_paths = paths;
364 }
365
366 vector<string> 
367 AudioLibrary::get_paths ()
368 {
369         return sfdb_paths;
370 }
371
372 void
373 AudioLibrary::scan_paths ()
374 {
375         if (sfdb_paths.size() < 1) {
376                 return;
377         }
378
379         vector<char *> pathv(sfdb_paths.size());
380         unsigned int i;
381         for (i = 0; i < sfdb_paths.size(); ++i) {
382                 pathv[i] = new char[sfdb_paths[i].length() +1];
383                 sfdb_paths[i].copy(pathv[i], string::npos);
384                 pathv[i][sfdb_paths[i].length()] = 0;
385         }
386         pathv[i] = 0;
387
388         FTS* ft = fts_open(&pathv[0], FTS_LOGICAL|FTS_NOSTAT|FTS_PHYSICAL|FTS_XDEV, 0);
389         if (errno) {
390                 error << strerror(errno) << endmsg;
391                 return;
392         }
393
394         lrdf_statement s;
395         s.predicate = RDF_TYPE;
396         s.object = SOUNDFILE;
397         s.object_type = lrdf_uri;
398         string filename;
399         while (FTSENT* file = fts_read(ft)) {
400                 if ((file->fts_info & FTS_F) && (safe_file_extension(file->fts_name))) {
401                         filename = "file:";
402                         filename.append(file->fts_accpath);
403                         s.subject = strdup(filename.c_str());
404                         if (lrdf_exists_match(&s)) {
405                                 continue;
406                         } else {
407                                 add_member(file->fts_accpath);
408                                 cout << file->fts_accpath << endl;
409                         }
410                         free(s.subject);
411                 }
412         }
413         fts_close(ft);
414
415         for (i = 0; i < pathv.size(); ++i) {
416                 delete[] pathv[i];
417         }
418
419         save_changes();
420 }
421
422 bool
423 AudioLibrary::safe_file_extension(string file)
424 {
425         return !(file.rfind(".wav") == string::npos &&
426         file.rfind(".aiff")== string::npos &&
427         file.rfind(".aif") == string::npos &&
428         file.rfind(".snd") == string::npos &&
429         file.rfind(".au")  == string::npos &&
430         file.rfind(".raw") == string::npos &&
431         file.rfind(".sf")  == string::npos &&
432         file.rfind(".cdr") == string::npos &&
433         file.rfind(".smp") == string::npos &&
434         file.rfind(".maud")== string::npos &&
435         file.rfind(".vwe") == string::npos &&
436         file.rfind(".paf") == string::npos &&
437 #ifdef HAVE_COREAUDIO
438                 file.rfind(".mp3") == string::npos &&
439                 file.rfind(".aac") == string::npos &&
440                 file.rfind(".mp4") == string::npos &&
441 #endif // HAVE_COREAUDIO
442         file.rfind(".voc") == string::npos);
443 }
444
445 XMLNode&
446 AudioLibrary::get_state ()
447 {
448         XMLNode* root = new XMLNode(X_("AudioLibrary"));
449         
450         for (vector<string>::iterator i = sfdb_paths.begin(); i != sfdb_paths.end(); ++i) {
451                 XMLNode* node = new XMLNode(X_("Path"));
452                 node->add_property("value", *i);
453                 root->add_child_nocopy(*node);
454         }
455         
456         return *root;
457 }
458
459 int
460 AudioLibrary::set_state (const XMLNode& node)
461 {
462         if (node.name() != X_("AudioLibrary")) {
463                 fatal << "programming error: AudioLibrary: incorrect XML node sent to set_state()" << endmsg;
464                 return -1;
465         }
466         
467         XMLNodeList nodes = node.children(X_("Path"));
468         
469         vector<string> paths;
470         XMLProperty* prop;
471         XMLNode* child;
472         for (XMLNodeConstIterator iter = nodes.begin(); iter != nodes.end(); ++iter) {
473                 child = *iter;
474                 
475                 if ((prop = child->property(X_("value"))) != 0) {
476                         paths.push_back(prop->value());
477                 }
478         }
479         
480         sfdb_paths = paths;
481         
482         return 0;
483 }