Fix compilation with --no-lv2 (#0006169).
[ardour.git] / libs / ardour / file_source.cc
1 /*
2     Copyright (C) 2006-2009 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <vector>
21
22 #include <sys/time.h>
23 #include <sys/stat.h>
24 #include <stdio.h> // for rename(), sigh
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <errno.h>
28
29 #include "pbd/convert.h"
30 #include "pbd/basename.h"
31 #include "pbd/stl_delete.h"
32 #include "pbd/strsplit.h"
33 #include "pbd/shortpath.h"
34 #include "pbd/enumwriter.h"
35 #include "pbd/file_utils.h"
36
37 #include <glibmm/miscutils.h>
38 #include <glibmm/fileutils.h>
39 #include <glibmm/threads.h>
40
41 #include "ardour/data_type.h"
42 #include "ardour/file_source.h"
43 #include "ardour/session.h"
44 #include "ardour/source.h"
45 #include "ardour/utils.h"
46
47 #include "i18n.h"
48
49 using namespace std;
50 using namespace ARDOUR;
51 using namespace PBD;
52 using namespace Glib;
53
54 PBD::Signal2<int,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
55
56 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
57         : Source(session, type, path, flag)
58         , _path (path)
59         , _file_is_new (!origin.empty()) // if origin is left unspecified (empty string) then file must exist
60         , _channel (0)
61         , _origin (origin)
62 {
63         set_within_session_from_path (path);
64 }
65
66 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
67         : Source (session, node)
68         , _file_is_new (false)
69         , _channel (0)
70 {
71         /* this setting of _path is temporary - we expect derived classes
72            to call ::init() which will actually locate the file
73            and reset _path and _within_session correctly.
74         */
75
76         _path = _name;
77         _within_session = true;
78 }
79
80 FileSource::~FileSource()
81 {
82 }
83
84 void
85 FileSource::existence_check ()
86 {
87         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
88                 prevent_deletion ();
89         }
90 }
91
92 void
93 FileSource::prevent_deletion ()
94 {
95         if (!(_flags & Destructive)) {
96                 mark_immutable ();
97         } else {
98                 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
99         }
100 }
101
102 bool
103 FileSource::removable () const
104 {
105         bool r = ((_flags & Removable)
106                   && ((_flags & RemoveAtDestroy) ||
107                       ((_flags & RemovableIfEmpty) && empty())));
108
109         return r;
110 }
111
112 int
113 FileSource::init (const string& pathstr, bool must_exist)
114 {
115         _timeline_position = 0;
116
117         if (Stateful::loading_state_version < 3000) {
118                 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
119                         throw MissingSource (pathstr, _type);
120                 }
121         } else {
122                 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
123                         throw MissingSource (pathstr, _type);
124                 }
125         }
126
127         set_within_session_from_path (_path);
128
129         _name = Glib::path_get_basename (_path);
130
131         if (must_exist) {
132                 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
133                         throw MissingSource (pathstr, _type);
134                 }
135         }
136
137         return 0;
138 }
139
140 int
141 FileSource::set_state (const XMLNode& node, int /*version*/)
142 {
143         const XMLProperty* prop;
144
145         if ((prop = node.property (X_("channel"))) != 0) {
146                 _channel = atoi (prop->value());
147         } else {
148                 _channel = 0;
149         }
150
151         if ((prop = node.property (X_("origin"))) != 0) {
152                 _origin = prop->value();
153         }
154
155         return 0;
156 }
157
158 void
159 FileSource::mark_take (const string& id)
160 {
161         if (writable ()) {
162                 _take_id = id;
163         }
164 }
165
166 int
167 FileSource::move_to_trash (const string& trash_dir_name)
168 {
169         if (!within_session() || !writable()) {
170                 return -1;
171         }
172
173         /* don't move the file across filesystems, just stick it in the
174            trash_dir_name directory on whichever filesystem it was already on
175         */
176
177         vector<string> v;
178         v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
179         v.push_back (trash_dir_name);
180         v.push_back (Glib::path_get_basename (_path));
181
182         string newpath = Glib::build_filename (v);
183
184         /* the new path already exists, try versioning */
185
186         if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
187                 char buf[PATH_MAX+1];
188                 int version = 1;
189                 string newpath_v;
190
191                 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
192                 newpath_v = buf;
193
194                 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
195                         snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
196                         newpath_v = buf;
197                 }
198
199                 if (version == 999) {
200                         PBD::error << string_compose (
201                                         _("there are already 1000 files with names like %1; versioning discontinued"),
202                                         newpath) << endmsg;
203                 } else {
204                         newpath = newpath_v;
205                 }
206         }
207
208         if (::rename (_path.c_str(), newpath.c_str()) != 0) {
209                 PBD::error << string_compose (
210                                 _("cannot rename file source from %1 to %2 (%3)"),
211                                 _path, newpath, strerror (errno)) << endmsg;
212                 return -1;
213         }
214
215         if (move_dependents_to_trash() != 0) {
216                 /* try to back out */
217                 ::rename (newpath.c_str(), _path.c_str());
218                 return -1;
219         }
220
221         _path = newpath;
222
223         /* file can not be removed twice, since the operation is not idempotent */
224         _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
225
226         return 0;
227 }
228
229 /** Find the actual source file based on \a filename.
230  *
231  * If the source is within the session tree, \a filename should be a simple filename (no slashes).
232  * If the source is external, \a filename should be a full path.
233  * In either case, found_path is set to the complete absolute path of the source file.
234  * \return true iff the file was found.
235  */
236 bool
237 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
238                   bool& isnew, uint16_t& /* chan */, string& found_path)
239 {
240         bool ret = false;
241         string keeppath;
242
243         isnew = false;
244
245         if (!Glib::path_is_absolute (path)) {
246                 vector<string> hits;
247                 string fullpath;
248                 std::vector<std::string> dirs = s.source_search_path (type);
249
250                 if (dirs.size() == 0) {
251                         error << _("FileSource: search path not set") << endmsg;
252                         goto out;
253                 }
254
255                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
256
257                         fullpath = Glib::build_filename (*i, path);
258
259                         if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
260                                 keeppath = fullpath;
261                                 hits.push_back (fullpath);
262                         }
263                 }
264
265                 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
266                    in the session path it is possible to arrive at the same file via more than one path.
267
268                    I suppose this is not necessary on Windows.
269                 */
270
271                 vector<string> de_duped_hits;
272
273                 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
274
275                         vector<string>::iterator j = i;
276                         ++j;
277                         
278                         while (j != hits.end()) {
279                                 if (PBD::equivalent_paths (*i, *j)) {
280                                         /* *i and *j are the same file; break out of the loop early */
281                                         break;
282                                 }
283
284                                 ++j;
285                         }
286
287                         if (j == hits.end ()) {
288                                 de_duped_hits.push_back (*i);
289                         }
290                 }
291
292                 if (de_duped_hits.size() > 1) {
293
294                         /* more than one match: ask the user */
295
296                         int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1);
297
298                         if (which < 0) {
299                                 goto out;
300                         } else {
301                                 keeppath = de_duped_hits[which];
302                         }
303
304                 } else if (de_duped_hits.size() == 0) {
305
306                         /* no match: error */
307
308                         if (must_exist) {
309                                 /* do not generate an error here, leave that to
310                                    whoever deals with the false return value.
311                                 */
312                                 goto out;
313                         } else {
314                                 isnew = true;
315                         }
316                 } else {
317
318                         /* only one match: happy days */
319                         
320                         keeppath = de_duped_hits[0];
321                 }
322                                                    
323        } else {
324                 keeppath = path;
325         }
326
327         /* Current find() is unable to parse relative path names to yet non-existant
328            sources. QuickFix(tm)
329         */
330
331         if (keeppath.empty()) {
332                 if (must_exist) {
333                         error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
334                 } else {
335                         keeppath = path;
336                 }
337         }
338
339         found_path = keeppath;
340
341         ret = true;
342
343   out:
344         return ret;
345 }
346
347 /** Find the actual source file based on \a filename.
348  *
349  * If the source is within the session tree, \a filename should be a simple filename (no slashes).
350  * If the source is external, \a filename should be a full path.
351  * In either case, found_path is set to the complete absolute path of the source file.
352  * \return true iff the file was found.
353  */
354 bool
355 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
356                      bool& isnew, uint16_t& chan, string& found_path)
357 {
358         string pathstr = path;
359         string::size_type pos;
360         bool ret = false;
361
362         isnew = false;
363
364         if (!Glib::path_is_absolute (pathstr)) {
365
366                 /* non-absolute pathname: find pathstr in search path */
367
368                 vector<string> dirs = s.source_search_path (type);
369
370                 int cnt;
371                 string fullpath;
372                 string keeppath;
373
374                 if (dirs.size() == 0) {
375                         error << _("FileSource: search path not set") << endmsg;
376                         goto out;
377                 }
378
379                 cnt = 0;
380
381                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
382
383                         fullpath = Glib::build_filename (*i, pathstr);
384
385                         /* i (paul) made a nasty design error by using ':' as a special character in
386                            Ardour 0.99 .. this hack tries to make things sort of work.
387                         */
388
389                         if ((pos = pathstr.find_last_of (':')) != string::npos) {
390
391                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
392
393                                         /* its a real file, no problem */
394
395                                         keeppath = fullpath;
396                                         ++cnt;
397
398                                 } else {
399
400                                         if (must_exist) {
401
402                                                 /* might be an older session using file:channel syntax. see if the version
403                                                    without the :suffix exists
404                                                  */
405
406                                                 string shorter = pathstr.substr (0, pos);
407                                                 fullpath = Glib::build_filename (*i, shorter);
408
409                                                 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
410                                                         chan = atoi (pathstr.substr (pos+1));
411                                                         pathstr = shorter;
412                                                         keeppath = fullpath;
413                                                         ++cnt;
414                                                 }
415
416                                         } else {
417
418                                                 /* new derived file (e.g. for timefx) being created in a newer session */
419
420                                         }
421                                 }
422
423                         } else {
424
425                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
426                                         keeppath = fullpath;
427                                         ++cnt;
428                                 }
429                         }
430                 }
431
432                 if (cnt > 1) {
433
434                         error << string_compose (
435                                         _("FileSource: \"%1\" is ambigous when searching\n\t"), pathstr) << endmsg;
436                         goto out;
437
438                 } else if (cnt == 0) {
439
440                         if (must_exist) {
441                                 error << string_compose(
442                                                 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
443                                 goto out;
444                         } else {
445                                 isnew = true;
446                         }
447                 }
448
449                 /* Current find() is unable to parse relative path names to yet non-existant
450                    sources. QuickFix(tm) */
451                 if (keeppath == "") {
452                         if (must_exist) {
453                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
454                         } else {
455                                 keeppath = pathstr;
456                         }
457                 }
458
459                 found_path = keeppath;
460
461                 ret = true;
462
463         } else {
464
465                 /* external files and/or very very old style sessions include full paths */
466
467                 /* ugh, handle ':' situation */
468
469                 if ((pos = pathstr.find_last_of (':')) != string::npos) {
470
471                         string shorter = pathstr.substr (0, pos);
472
473                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
474                                 chan = atoi (pathstr.substr (pos+1));
475                                 pathstr = shorter;
476                         }
477                 }
478
479                 found_path = pathstr;
480
481                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
482
483                         /* file does not exist or we cannot read it */
484
485                         if (must_exist) {
486                                 error << string_compose(
487                                                 _("Filesource: cannot find required file (%1): %2"),
488                                                 path, strerror (errno)) << endmsg;
489                                 goto out;
490                         }
491
492 #ifndef PLATFORM_WINDOWS
493                         if (errno != ENOENT) {
494                                 error << string_compose(
495                                                 _("Filesource: cannot check for existing file (%1): %2"),
496                                                 path, strerror (errno)) << endmsg;
497                                 goto out;
498                         }
499 #endif
500                         /* a new file */
501                         isnew = true;
502                         ret = true;
503
504                 } else {
505
506                         /* already exists */
507                         ret = true;
508                 }
509         }
510
511 out:
512         return ret;
513 }
514
515 void
516 FileSource::mark_immutable ()
517 {
518         /* destructive sources stay writable, and their other flags don't change.  */
519         if (!(_flags & Destructive)) {
520                 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
521         }
522 }
523
524 void
525 FileSource::mark_immutable_except_write ()
526 {
527         /* destructive sources stay writable, and their other flags don't change.  */
528         if (!(_flags & Destructive)) {
529                 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
530         }
531 }
532
533 void
534 FileSource::mark_nonremovable ()
535 {
536         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
537 }
538
539 void
540 FileSource::set_within_session_from_path (const std::string& path)
541 {
542         _within_session = _session.path_is_within_session (path);
543 }
544
545 void
546 FileSource::set_path (const std::string& newpath)
547 {
548         _path = newpath;
549         set_within_session_from_path (newpath);
550         if (_within_session) {
551                 _origin = Glib::path_get_basename (newpath);
552         } else {
553                 _origin = newpath;
554         }
555 }
556
557 void
558 FileSource::inc_use_count ()
559 {
560         Source::inc_use_count ();
561 }
562
563 bool
564 FileSource::is_stub () const
565 {
566         if (!empty()) {
567                 return false;
568         }
569         
570         if (!removable()) {
571                 return false;
572         }
573
574         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
575                 return false;
576         }
577
578         return true;
579 }
580                 
581 int
582 FileSource::rename (const string& newpath)
583 {
584         Glib::Threads::Mutex::Lock lm (_lock);
585         string oldpath = _path;
586
587         // Test whether newpath exists, if yes notify the user but continue.
588         if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
589                 error << string_compose (_("Programming error! %1 tried to rename a file over another file! It's safe to continue working, but please report this to the developers."), PROGRAM_NAME) << endmsg;
590                 return -1;
591         }
592
593         if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) { 
594                 /* rename only needed if file exists on disk */
595                 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
596                         error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
597                         return -1;
598                 }
599         }
600
601         _name = Glib::path_get_basename (newpath);
602         _path = newpath;
603
604         return 0;
605 }
606
607