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