Merged with trunk R708
[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 string AudioLibrary::state_node_name = "AudioLibrary";
50
51 AudioLibrary::AudioLibrary ()
52 {
53 //      sfdb_paths.push_back("/Users/taybin/sounds");
54
55         src = "file:" + get_user_ardour_path() + "sfdb";
56
57         // workaround for possible bug in raptor that crashes when saving to a
58         // non-existant file.
59         touch_file(get_user_ardour_path() + "sfdb");
60
61         lrdf_read_file(src.c_str());
62
63         lrdf_statement pattern;
64
65         pattern.subject = SOUNDFILE;
66         pattern.predicate = RDF_TYPE;
67         pattern.object = RDFS_CLASS;
68         pattern.object_type = lrdf_uri;
69
70         lrdf_statement* matches = lrdf_matches(&pattern);
71
72         // if empty DB, create basic schema
73         if (matches == 0) {
74                 initialize_db ();
75                 save_changes();
76         } 
77
78         lrdf_free_statements(matches);
79 }
80
81 AudioLibrary::~AudioLibrary ()
82 {
83 }
84
85 void
86 AudioLibrary::initialize_db ()
87 {
88         // define ardour:Soundfile
89         lrdf_add_triple(src.c_str(), SOUNDFILE, RDF_TYPE, RDFS_CLASS, lrdf_uri);
90
91         // add intergral fields
92         add_field(_("channels"));
93         add_field(_("samplerate"));
94         add_field(_("resolution"));
95         add_field(_("format"));
96 }
97
98 void
99 AudioLibrary::save_changes ()
100 {
101         if (lrdf_export_by_source(src.c_str(), src.substr(5).c_str())) {
102                 warning << string_compose(_("Could not open %1.  Audio Library not saved"), src) << endmsg;
103         }
104 }
105
106 void
107 AudioLibrary::add_member (string member)
108 {
109         string file_uri(string_compose("file:%1", member));
110
111         lrdf_add_triple(src.c_str(), file_uri.c_str(), RDF_TYPE, 
112                         SOUNDFILE, lrdf_uri);
113 }
114
115 void
116 AudioLibrary::remove_member (string uri)
117 {
118         lrdf_remove_uri_matches (uri.c_str());
119 }
120
121 void
122 AudioLibrary::search_members_and (vector<string>& members, 
123                                                                   const map<string,string>& fields)
124 {
125         lrdf_statement **head;
126         lrdf_statement* pattern = 0;
127         lrdf_statement* old = 0;
128         head = &pattern;
129
130         map<string,string>::const_iterator i;
131         for (i = fields.begin(); i != fields.end(); ++i){
132                 pattern = new lrdf_statement;
133                 pattern->subject = "?";
134                 pattern->predicate = strdup(field_uri(i->first).c_str());
135                 pattern->object = strdup((i->second).c_str());
136                 pattern->next = old;
137
138                 old = pattern;
139         }
140
141         if (*head != 0) {
142                 lrdf_uris* ulist = lrdf_match_multi(*head);
143                 for (uint32_t j = 0; ulist && j < ulist->count; ++j) {
144 //                      printf("AND: %s\n", ulist->items[j]);
145                         members.push_back(ulist->items[j]);
146                 }
147                 lrdf_free_uris(ulist);
148
149                 compact_vector(members);
150         }
151
152         // memory clean up
153         pattern = *head;
154         while(pattern){
155                 free(pattern->predicate);
156                 free(pattern->object);
157                 old = pattern;
158                 pattern = pattern->next;
159                 delete old;
160         }
161 }
162
163 void
164 AudioLibrary::search_members_or (vector<string>& members, 
165                                                                  const map<string,string>& fields)
166 {
167         map<string,string>::const_iterator i;
168
169         lrdf_statement pattern;
170         for (i = fields.begin(); i != fields.end(); ++i) {
171                 pattern.subject = 0;
172                 pattern.predicate = strdup(field_uri(i->first).c_str());
173                 pattern.object = strdup((i->second).c_str());
174                 pattern.object_type = lrdf_literal;
175
176                 lrdf_statement* matched = lrdf_matches(&pattern);
177
178                 lrdf_statement* old = matched;
179                 while(matched) {
180 //                      printf ("OR: %s\n", matched->subject);
181                         members.push_back(matched->subject);
182                         matched = matched->next;
183                 }
184
185                 free(pattern.predicate);
186                 free(pattern.object);
187                 lrdf_free_statements (old);
188         }
189
190         compact_vector(members);
191 }
192
193 void
194 AudioLibrary::add_field (string name)
195 {
196         string local_field = field_uri(name);
197         lrdf_statement pattern;
198         pattern.subject = strdup(local_field.c_str());
199         pattern.predicate = RDF_TYPE;
200         pattern.object = RDF_BASE "Property";
201         pattern.object_type = lrdf_uri;
202
203         if(lrdf_exists_match(&pattern)) {
204                 return;
205         }
206
207         // of type rdf:Property
208         lrdf_add_triple(src.c_str(), local_field.c_str(), RDF_TYPE, 
209                         RDF_BASE "Property", lrdf_uri);
210         // of range ardour:Soundfile
211         lrdf_add_triple(src.c_str(), local_field.c_str(), RDFS_BASE "range",
212                         SOUNDFILE, lrdf_uri);
213         // of domain rdf:Literal
214         lrdf_add_triple(src.c_str(), local_field.c_str(), RDFS_BASE "domain", 
215                                         RDF_BASE "Literal", lrdf_uri);
216
217         set_label (local_field, name);
218         
219         fields_changed(); /* EMIT SIGNAL */
220 }
221
222 void
223 AudioLibrary::get_fields (vector<string>& fields)
224 {
225         lrdf_statement pattern;
226
227         pattern.subject = 0;
228         pattern.predicate = RDFS_BASE "range";
229         pattern.object = SOUNDFILE;
230         pattern.object_type = lrdf_uri;
231
232         lrdf_statement* matches = lrdf_matches(&pattern);
233
234         lrdf_statement* current = matches;
235         while (current != 0) {
236                 fields.push_back(get_label(current->subject));
237
238                 current = current->next;
239         }
240
241         lrdf_free_statements(matches);
242
243         compact_vector(fields);
244 }
245
246 void
247 AudioLibrary::remove_field (string name)
248 {
249         lrdf_remove_uri_matches(field_uri(name).c_str());
250         fields_changed (); /* EMIT SIGNAL */
251 }
252
253 string 
254 AudioLibrary::get_field (string uri, string field)
255 {
256         lrdf_statement pattern;
257
258         pattern.subject = strdup(uri.c_str());
259
260         pattern.predicate = strdup(field_uri(field).c_str());
261
262         pattern.object = 0;
263         pattern.object_type = lrdf_literal;
264
265         lrdf_statement* matches = lrdf_matches(&pattern);
266         free(pattern.subject);
267         free(pattern.predicate);
268
269         stringstream object;
270         if (matches != 0){
271                 object << matches->object;
272         }
273
274         lrdf_free_statements(matches);
275         return object.str();
276 }
277
278 void 
279 AudioLibrary::set_field (string uri, string field, string literal)
280 {
281         lrdf_statement pattern;
282
283         pattern.subject = strdup(uri.c_str());
284
285         string local_field = field_uri(field);
286         pattern.predicate = strdup(local_field.c_str());
287
288         pattern.object = 0;
289         pattern.object_type = lrdf_literal;
290
291         lrdf_remove_matches(&pattern);
292         free(pattern.subject);
293         free(pattern.predicate);
294
295         lrdf_add_triple(src.c_str(), uri.c_str(), local_field.c_str(), 
296                         literal.c_str(), lrdf_literal);
297
298          fields_changed(); /* EMIT SIGNAL */
299 }
300
301 string
302 AudioLibrary::field_uri (string name)
303 {
304         stringstream local_field;
305         local_field << "file:sfdb/fields/" << name;
306
307         return local_field.str();
308 }
309
310 string
311 AudioLibrary::get_label (string uri)
312 {
313         lrdf_statement pattern;
314         pattern.subject = strdup(uri.c_str());
315         pattern.predicate = RDFS_BASE "label";
316         pattern.object = 0;
317         pattern.object_type = lrdf_literal;
318
319         lrdf_statement* matches = lrdf_matches (&pattern);
320         free(pattern.subject);
321
322         stringstream label;
323         if (matches != 0){
324                 label << matches->object;
325         }
326
327         lrdf_free_statements(matches);
328
329         return label.str();
330 }
331
332 void
333 AudioLibrary::set_label (string uri, string label)
334 {
335         lrdf_statement pattern;
336         pattern.subject = strdup(uri.c_str());
337         pattern.predicate = RDFS_BASE "label";
338         pattern.object = 0;
339         pattern.object_type = lrdf_literal;
340
341         lrdf_remove_matches(&pattern);
342         free(pattern.subject);
343
344         lrdf_add_triple(src.c_str(), uri.c_str(), RDFS_BASE "label", 
345                         label.c_str(), lrdf_literal);
346 }
347
348 void
349 AudioLibrary::compact_vector(vector<string>& vec)
350 {
351     sort(vec.begin(), vec.end());
352     unique(vec.begin(), vec.end());
353 }
354
355 void 
356 AudioLibrary::set_paths (vector<string> paths)
357 {
358         sfdb_paths = paths;
359         
360         scan_paths ();
361 }
362
363 vector<string> 
364 AudioLibrary::get_paths ()
365 {
366         return sfdb_paths;
367 }
368
369 void
370 AudioLibrary::scan_paths ()
371 {
372         if (sfdb_paths.size() < 1) {
373                 return;
374         }
375
376         vector<char *> pathv(sfdb_paths.size());
377         unsigned int i;
378         for (i = 0; i < sfdb_paths.size(); ++i) {
379                 pathv[i] = new char[sfdb_paths[i].length() +1];
380                 sfdb_paths[i].copy(pathv[i], string::npos);
381                 pathv[i][sfdb_paths[i].length()] = 0;
382         }
383         pathv[i] = 0;
384
385         FTS* ft = fts_open(&pathv[0], FTS_LOGICAL|FTS_NOSTAT|FTS_PHYSICAL|FTS_XDEV, 0);
386         if (errno) {
387                 error << strerror(errno) << endmsg;
388                 return;
389         }
390
391         lrdf_statement s;
392         s.predicate = RDF_TYPE;
393         s.object = SOUNDFILE;
394         s.object_type = lrdf_uri;
395         string filename;
396         while (FTSENT* file = fts_read(ft)) {
397                 if ((file->fts_info & FTS_F) && (safe_file_extension(file->fts_name))) {
398                         filename = "file:";
399                         filename.append(file->fts_accpath);
400                         s.subject = strdup(filename.c_str());
401                         if (lrdf_exists_match(&s)) {
402                                 continue;
403                         } else {
404                                 add_member(file->fts_accpath);
405                                 cout << file->fts_accpath << endl;
406                         }
407                         free(s.subject);
408                 }
409         }
410         fts_close(ft);
411
412         for (i = 0; i < pathv.size(); ++i) {
413                 delete[] pathv[i];
414         }
415
416         save_changes();
417 }
418
419 bool
420 AudioLibrary::safe_file_extension(string file)
421 {
422         return !(file.rfind(".wav") == string::npos &&
423         file.rfind(".aiff")== string::npos &&
424         file.rfind(".aif") == string::npos &&
425         file.rfind(".snd") == string::npos &&
426         file.rfind(".au")  == string::npos &&
427         file.rfind(".raw") == string::npos &&
428         file.rfind(".sf")  == string::npos &&
429         file.rfind(".cdr") == string::npos &&
430         file.rfind(".smp") == string::npos &&
431         file.rfind(".maud")== string::npos &&
432         file.rfind(".vwe") == string::npos &&
433         file.rfind(".paf") == string::npos &&
434 #ifdef HAVE_COREAUDIO
435                 file.rfind(".mp3") == string::npos &&
436                 file.rfind(".aac") == string::npos &&
437                 file.rfind(".mp4") == string::npos &&
438 #endif // HAVE_COREAUDIO
439         file.rfind(".voc") == string::npos);
440 }
441
442 XMLNode&
443 AudioLibrary::get_state ()
444 {
445         XMLNode* root = new XMLNode(X_("AudioLibrary"));
446         
447         for (vector<string>::iterator i = sfdb_paths.begin(); i != sfdb_paths.end(); ++i) {
448                 XMLNode* node = new XMLNode(X_("Path"));
449                 node->add_property("value", *i);
450                 root->add_child_nocopy(*node);
451         }
452         
453         return *root;
454 }
455
456 int
457 AudioLibrary::set_state (const XMLNode& node)
458 {
459         if (node.name() != X_("AudioLibrary")) {
460                 fatal << "programming error: AudioLibrary: incorrect XML node sent to set_state()" << endmsg;
461                 return -1;
462         }
463         
464         XMLNodeList nodes = node.children(X_("Path"));
465         
466         vector<string> paths;
467         XMLProperty* prop;
468         XMLNode* child;
469         for (XMLNodeConstIterator iter = nodes.begin(); iter != nodes.end(); ++iter) {
470                 child = *iter;
471                 
472                 if ((prop = child->property(X_("value"))) != 0) {
473                         paths.push_back(prop->value());
474                 }
475         }
476         
477         set_paths (paths);
478         
479         return 0;
480 }