remove entire "stub" file concept; open new audio and MIDI files on demand (at first...
[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/mountpoint.h"
32 #include "pbd/stl_delete.h"
33 #include "pbd/strsplit.h"
34 #include "pbd/shortpath.h"
35 #include "pbd/enumwriter.h"
36
37 #include <glibmm/miscutils.h>
38 #include <glibmm/fileutils.h>
39 #include <glibmm/thread.h>
40
41 #include "ardour/file_source.h"
42 #include "ardour/directory_names.h"
43 #include "ardour/session.h"
44 #include "ardour/session_directory.h"
45 #include "ardour/source_factory.h"
46 #include "ardour/filename_extensions.h"
47
48 #include "i18n.h"
49
50 using namespace std;
51 using namespace ARDOUR;
52 using namespace PBD;
53 using namespace Glib;
54
55 PBD::Signal3<int,std::string,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
56
57 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
58         : Source(session, type, path, flag)
59         , _path(path)
60         , _file_is_new(true)
61         , _channel (0)
62         , _origin (origin)
63         , _open (false)
64 {
65         set_within_session_from_path (path);
66
67         prevent_deletion ();
68 }
69
70 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
71         : Source(session, node)
72         , _file_is_new (false)
73 {
74         /* this setting of _path is temporary - we expect derived classes
75            to call ::init() which will actually locate the file
76            and reset _path and _within_session correctly.
77         */
78
79         _path = _name;
80         _within_session = true;
81
82         prevent_deletion ();
83 }
84
85 void
86 FileSource::prevent_deletion ()
87 {
88         /* if this file already exists, it cannot be removed, ever
89          */
90         
91         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
92                 if (!(_flags & Destructive)) {
93                         mark_immutable ();
94                 } else {
95                         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
96                 }
97         }
98 }
99
100 bool
101 FileSource::removable () const
102 {
103         bool r = ((_flags & Removable)
104                   && ((_flags & RemoveAtDestroy) || 
105                       ((_flags & RemovableIfEmpty) && empty() == 0)));
106         
107         return r;
108 }
109
110 int
111 FileSource::init (const string& pathstr, bool must_exist)
112 {
113         _timeline_position = 0;
114
115         if (Stateful::loading_state_version < 3000) {
116                 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
117                         throw MissingSource (pathstr, _type);
118                 }
119         } else {
120                 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
121                         throw MissingSource (pathstr, _type);
122                 }
123         }
124
125         set_within_session_from_path (_path);
126
127         if (!within_session()) {
128                 _session.ensure_search_path_includes (Glib::path_get_dirname (_path), _type);
129         }
130
131         _name = Glib::path_get_basename (_path);
132
133         if (_file_is_new && must_exist) {
134                 return -1;
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 (access (newpath_v.c_str(), F_OK) == 0 && 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> dirs;
247                 vector<string> hits;
248                 int cnt;
249                 string fullpath;
250
251                 string search_path = s.source_search_path (type);
252
253                 if (search_path.length() == 0) {
254                         error << _("FileSource: search path not set") << endmsg;
255                         goto out;
256                 }
257
258                 split (search_path, dirs, ':');
259                 
260                 cnt = 0;
261                 hits.clear ();
262                 
263                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
264                         
265                         fullpath = Glib::build_filename (*i, path);
266                         
267                         if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
268                                 keeppath = fullpath;
269                                 hits.push_back (fullpath);
270                                 ++cnt;
271                         }
272                 }
273                 
274                 if (cnt > 1) {
275                         
276                         int which = FileSource::AmbiguousFileName (path, search_path, hits).get_value_or (-1);
277                         
278                         if (which < 0) {
279                                 goto out;
280                         } else {
281                                 keeppath = hits[which];
282                         }
283                         
284                 } else if (cnt == 0) {
285                         
286                         if (must_exist) {
287                                 error << string_compose(
288                                         _("Filesource: cannot find required file (%1): while searching %2"),
289                                         path, search_path) << endmsg;
290                                 goto out;
291                         } else {
292                                 isnew = true;
293                         }
294                 }
295         } else {
296                 keeppath = path;
297         }
298         
299         /* Current find() is unable to parse relative path names to yet non-existant
300            sources. QuickFix(tm) 
301         */
302         if (keeppath == "") {
303                 if (must_exist) {
304                         error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
305                 } else {
306                         keeppath = path;
307                 }
308         }
309         
310         found_path = keeppath;
311         
312         ret = true;
313         
314   out:
315         return ret;
316 }
317
318 /** Find the actual source file based on \a filename.
319  *
320  * If the source is within the session tree, \a filename should be a simple filename (no slashes).
321  * If the source is external, \a filename should be a full path.
322  * In either case, found_path is set to the complete absolute path of the source file.
323  * \return true iff the file was found.
324  */
325 bool
326 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
327                      bool& isnew, uint16_t& chan, string& found_path)
328 {
329         string search_path = s.source_search_path (type);
330
331         string pathstr = path;
332         string::size_type pos;
333         bool ret = false;
334
335         isnew = false;
336
337         if (!Glib::path_is_absolute (pathstr)) {
338
339                 /* non-absolute pathname: find pathstr in search path */
340
341                 vector<string> dirs;
342                 int cnt;
343                 string fullpath;
344                 string keeppath;
345
346                 if (search_path.length() == 0) {
347                         error << _("FileSource: search path not set") << endmsg;
348                         goto out;
349                 }
350
351                 split (search_path, dirs, ':');
352
353                 cnt = 0;
354
355                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
356                         
357                         fullpath = Glib::build_filename (*i, pathstr);
358
359                         /* i (paul) made a nasty design error by using ':' as a special character in
360                            Ardour 0.99 .. this hack tries to make things sort of work.
361                         */
362
363                         if ((pos = pathstr.find_last_of (':')) != string::npos) {
364
365                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
366
367                                         /* its a real file, no problem */
368
369                                         keeppath = fullpath;
370                                         ++cnt;
371
372                                 } else {
373
374                                         if (must_exist) {
375
376                                                 /* might be an older session using file:channel syntax. see if the version
377                                                    without the :suffix exists
378                                                  */
379
380                                                 string shorter = pathstr.substr (0, pos);
381                                                 fullpath = Glib::build_filename (*i, shorter);
382
383                                                 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
384                                                         chan = atoi (pathstr.substr (pos+1));
385                                                         pathstr = shorter;
386                                                         keeppath = fullpath;
387                                                         ++cnt;
388                                                 }
389
390                                         } else {
391
392                                                 /* new derived file (e.g. for timefx) being created in a newer session */
393
394                                         }
395                                 }
396
397                         } else {
398
399                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
400                                         keeppath = fullpath;
401                                         ++cnt;
402                                 }
403                         }
404                 }
405
406                 if (cnt > 1) {
407
408                         error << string_compose (
409                                         _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
410                                         pathstr, search_path) << endmsg;
411                         goto out;
412
413                 } else if (cnt == 0) {
414
415                         if (must_exist) {
416                                 error << string_compose(
417                                                 _("Filesource: cannot find required file (%1): while searching %2"),
418                                                 pathstr, search_path) << endmsg;
419                                 goto out;
420                         } else {
421                                 isnew = true;
422                         }
423                 }
424
425                 /* Current find() is unable to parse relative path names to yet non-existant
426                    sources. QuickFix(tm) */
427                 if (keeppath == "") {
428                         if (must_exist) {
429                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
430                         } else {
431                                 keeppath = pathstr;
432                         }
433                 }
434
435                 found_path = keeppath;
436
437                 ret = true;
438
439         } else {
440
441                 /* external files and/or very very old style sessions include full paths */
442
443                 /* ugh, handle ':' situation */
444
445                 if ((pos = pathstr.find_last_of (':')) != string::npos) {
446
447                         string shorter = pathstr.substr (0, pos);
448
449                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
450                                 chan = atoi (pathstr.substr (pos+1));
451                                 pathstr = shorter;
452                         }
453                 }
454
455                 found_path = pathstr;
456
457                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
458
459                         /* file does not exist or we cannot read it */
460
461                         if (must_exist) {
462                                 error << string_compose(
463                                                 _("Filesource: cannot find required file (%1): %2"),
464                                                 path, strerror (errno)) << endmsg;
465                                 goto out;
466                         }
467
468                         if (errno != ENOENT) {
469                                 error << string_compose(
470                                                 _("Filesource: cannot check for existing file (%1): %2"),
471                                                 path, strerror (errno)) << endmsg;
472                                 goto out;
473                         }
474
475                         /* a new file */
476                         isnew = true;
477                         ret = true;
478
479                 } else {
480
481                         /* already exists */
482                         ret = true;
483                 }
484         }
485
486 out:
487         return ret;
488 }
489
490 int
491 FileSource::set_source_name (const string& newname, bool destructive)
492 {
493         Glib::Mutex::Lock lm (_lock);
494         string oldpath = _path;
495         string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
496
497         if (newpath.empty()) {
498                 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
499                 return -1;
500         }
501
502         // Test whether newpath exists, if yes notify the user but continue.
503         if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
504                 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;
505                 return -1;
506         }
507         
508         if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
509                 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
510                 return -1;
511         }
512
513         _name = Glib::path_get_basename (newpath);
514         _path = newpath;
515
516         return 0;
517 }
518
519 void
520 FileSource::mark_immutable ()
521 {
522         /* destructive sources stay writable, and their other flags don't change.  */
523         if (!(_flags & Destructive)) {
524                 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
525         }
526 }
527
528 void
529 FileSource::mark_nonremovable ()
530 {
531         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
532 }
533
534 void
535 FileSource::set_within_session_from_path (const std::string& path)
536 {
537         _within_session = _session.path_is_within_session (path);
538 }
539
540 void
541 FileSource::set_path (const std::string& newpath)
542 {
543         _path = newpath;
544 }
545
546 void 
547 FileSource::inc_use_count ()
548 {
549         Source::inc_use_count ();
550 }
551