Merge branch 'ripple-mode-cc' into cairocanvas
[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 {
71         /* this setting of _path is temporary - we expect derived classes
72            to call ::init() which will actually locate the file
73            and reset _path and _within_session correctly.
74         */
75
76         _path = _name;
77         _within_session = true;
78 }
79
80 FileSource::~FileSource()
81 {
82 }
83
84 void
85 FileSource::existence_check ()
86 {
87         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
88                 prevent_deletion ();
89         }
90 }
91
92 void
93 FileSource::prevent_deletion ()
94 {
95         if (!(_flags & Destructive)) {
96                 mark_immutable ();
97         } else {
98                 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
99         }
100 }
101
102 bool
103 FileSource::removable () const
104 {
105         bool r = ((_flags & Removable)
106                   && ((_flags & RemoveAtDestroy) ||
107                       ((_flags & RemovableIfEmpty) && empty())));
108
109         return r;
110 }
111
112 int
113 FileSource::init (const string& pathstr, bool must_exist)
114 {
115         _timeline_position = 0;
116
117         if (Stateful::loading_state_version < 3000) {
118                 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
119                         throw MissingSource (pathstr, _type);
120                 }
121         } else {
122                 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
123                         throw MissingSource (pathstr, _type);
124                 }
125         }
126
127         set_within_session_from_path (_path);
128
129         _name = Glib::path_get_basename (_path);
130
131         if (must_exist) {
132                 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
133                         throw MissingSource (pathstr, _type);
134                 }
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 (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && 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> hits;
247                 string fullpath;
248                 std::vector<std::string> dirs = s.source_search_path (type);
249
250                 if (dirs.size() == 0) {
251                         error << _("FileSource: search path not set") << endmsg;
252                         goto out;
253                 }
254
255                 hits.clear ();
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                                 error << string_compose(
312                                         _("Filesource: cannot find required file (%1)"), 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 pathstr = path;
359         string::size_type pos;
360         bool ret = false;
361
362         isnew = false;
363
364         if (!Glib::path_is_absolute (pathstr)) {
365
366                 /* non-absolute pathname: find pathstr in search path */
367
368                 vector<string> dirs = s.source_search_path (type);
369
370                 int cnt;
371                 string fullpath;
372                 string keeppath;
373
374                 if (dirs.size() == 0) {
375                         error << _("FileSource: search path not set") << endmsg;
376                         goto out;
377                 }
378
379                 cnt = 0;
380
381                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
382
383                         fullpath = Glib::build_filename (*i, pathstr);
384
385                         /* i (paul) made a nasty design error by using ':' as a special character in
386                            Ardour 0.99 .. this hack tries to make things sort of work.
387                         */
388
389                         if ((pos = pathstr.find_last_of (':')) != string::npos) {
390
391                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
392
393                                         /* its a real file, no problem */
394
395                                         keeppath = fullpath;
396                                         ++cnt;
397
398                                 } else {
399
400                                         if (must_exist) {
401
402                                                 /* might be an older session using file:channel syntax. see if the version
403                                                    without the :suffix exists
404                                                  */
405
406                                                 string shorter = pathstr.substr (0, pos);
407                                                 fullpath = Glib::build_filename (*i, shorter);
408
409                                                 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
410                                                         chan = atoi (pathstr.substr (pos+1));
411                                                         pathstr = shorter;
412                                                         keeppath = fullpath;
413                                                         ++cnt;
414                                                 }
415
416                                         } else {
417
418                                                 /* new derived file (e.g. for timefx) being created in a newer session */
419
420                                         }
421                                 }
422
423                         } else {
424
425                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
426                                         keeppath = fullpath;
427                                         ++cnt;
428                                 }
429                         }
430                 }
431
432                 if (cnt > 1) {
433
434                         error << string_compose (
435                                         _("FileSource: \"%1\" is ambigous when searching\n\t"), pathstr) << endmsg;
436                         goto out;
437
438                 } else if (cnt == 0) {
439
440                         if (must_exist) {
441                                 error << string_compose(
442                                                 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
443                                 goto out;
444                         } else {
445                                 isnew = true;
446                         }
447                 }
448
449                 /* Current find() is unable to parse relative path names to yet non-existant
450                    sources. QuickFix(tm) */
451                 if (keeppath == "") {
452                         if (must_exist) {
453                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
454                         } else {
455                                 keeppath = pathstr;
456                         }
457                 }
458
459                 found_path = keeppath;
460
461                 ret = true;
462
463         } else {
464
465                 /* external files and/or very very old style sessions include full paths */
466
467                 /* ugh, handle ':' situation */
468
469                 if ((pos = pathstr.find_last_of (':')) != string::npos) {
470
471                         string shorter = pathstr.substr (0, pos);
472
473                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
474                                 chan = atoi (pathstr.substr (pos+1));
475                                 pathstr = shorter;
476                         }
477                 }
478
479                 found_path = pathstr;
480
481                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
482
483                         /* file does not exist or we cannot read it */
484
485                         if (must_exist) {
486                                 error << string_compose(
487                                                 _("Filesource: cannot find required file (%1): %2"),
488                                                 path, strerror (errno)) << endmsg;
489                                 goto out;
490                         }
491
492 #ifndef PLATFORM_WINDOWS
493                         if (errno != ENOENT) {
494                                 error << string_compose(
495                                                 _("Filesource: cannot check for existing file (%1): %2"),
496                                                 path, strerror (errno)) << endmsg;
497                                 goto out;
498                         }
499 #endif
500                         /* a new file */
501                         isnew = true;
502                         ret = true;
503
504                 } else {
505
506                         /* already exists */
507                         ret = true;
508                 }
509         }
510
511 out:
512         return ret;
513 }
514
515 void
516 FileSource::mark_immutable ()
517 {
518         /* destructive sources stay writable, and their other flags don't change.  */
519         if (!(_flags & Destructive)) {
520                 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
521         }
522 }
523
524 void
525 FileSource::mark_immutable_except_write ()
526 {
527         /* destructive sources stay writable, and their other flags don't change.  */
528         if (!(_flags & Destructive)) {
529                 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
530         }
531 }
532
533 void
534 FileSource::mark_nonremovable ()
535 {
536         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
537 }
538
539 void
540 FileSource::set_within_session_from_path (const std::string& path)
541 {
542         _within_session = _session.path_is_within_session (path);
543 }
544
545 void
546 FileSource::set_path (const std::string& newpath)
547 {
548         _path = newpath;
549 }
550
551 void
552 FileSource::inc_use_count ()
553 {
554         Source::inc_use_count ();
555 }
556
557 bool
558 FileSource::is_stub () const
559 {
560         if (!empty()) {
561                 return false;
562         }
563         
564         if (!removable()) {
565                 return false;
566         }
567
568         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
569                 return false;
570         }
571
572         return true;
573 }
574                 
575 int
576 FileSource::rename (const string& newpath)
577 {
578         Glib::Threads::Mutex::Lock lm (_lock);
579         string oldpath = _path;
580
581         // Test whether newpath exists, if yes notify the user but continue.
582         if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
583                 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;
584                 return -1;
585         }
586
587         if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) { 
588                 /* rename only needed if file exists on disk */
589                 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
590                         error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
591                         return -1;
592                 }
593         }
594
595         _name = Glib::path_get_basename (newpath);
596         _path = newpath;
597
598         return 0;
599 }