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