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