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