fix crash when copy'ing latent plugins
[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 "pbd/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 {
63         set_within_session_from_path (path);
64 }
65
66 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
67         : Source (session, node)
68         , _file_is_new (false)
69         , _channel (0)
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         XMLProperty const * 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                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
256
257                         fullpath = Glib::build_filename (*i, path);
258
259                         if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
260                                 keeppath = fullpath;
261                                 hits.push_back (fullpath);
262                         }
263                 }
264
265                 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
266                    in the session path it is possible to arrive at the same file via more than one path.
267
268                    I suppose this is not necessary on Windows.
269                 */
270
271                 vector<string> de_duped_hits;
272
273                 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
274
275                         vector<string>::iterator j = i;
276                         ++j;
277
278                         while (j != hits.end()) {
279                                 if (PBD::equivalent_paths (*i, *j)) {
280                                         /* *i and *j are the same file; break out of the loop early */
281                                         break;
282                                 }
283
284                                 ++j;
285                         }
286
287                         if (j == hits.end ()) {
288                                 de_duped_hits.push_back (*i);
289                         }
290                 }
291
292                 if (de_duped_hits.size() > 1) {
293
294                         /* more than one match: ask the user */
295
296                         int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1);
297
298                         if (which < 0) {
299                                 goto out;
300                         } else {
301                                 keeppath = de_duped_hits[which];
302                         }
303
304                 } else if (de_duped_hits.size() == 0) {
305
306                         /* no match: error */
307
308                         if (must_exist) {
309                                 /* do not generate an error here, leave that to
310                                    whoever deals with the false return value.
311                                 */
312                                 goto out;
313                         } else {
314                                 isnew = true;
315                         }
316                 } else {
317
318                         /* only one match: happy days */
319
320                         keeppath = de_duped_hits[0];
321                 }
322
323        } else {
324                 keeppath = path;
325         }
326
327         /* Current find() is unable to parse relative path names to yet non-existant
328            sources. QuickFix(tm)
329         */
330
331         if (keeppath.empty()) {
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         ret = true;
341
342   out:
343         return ret;
344 }
345
346 /** Find the actual source file based on \a filename.
347  *
348  * If the source is within the session tree, \a filename should be a simple filename (no slashes).
349  * If the source is external, \a filename should be a full path.
350  * In either case, found_path is set to the complete absolute path of the source file.
351  * \return true iff the file was found.
352  */
353 bool
354 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
355                      bool& isnew, uint16_t& chan, string& found_path)
356 {
357         string pathstr = path;
358         string::size_type pos;
359         bool ret = false;
360
361         isnew = false;
362
363         if (!Glib::path_is_absolute (pathstr)) {
364
365                 /* non-absolute pathname: find pathstr in search path */
366
367                 vector<string> dirs = s.source_search_path (type);
368
369                 int cnt;
370                 string fullpath;
371                 string keeppath;
372
373                 if (dirs.size() == 0) {
374                         error << _("FileSource: search path not set") << endmsg;
375                         goto out;
376                 }
377
378                 cnt = 0;
379
380                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
381
382                         fullpath = Glib::build_filename (*i, pathstr);
383
384                         /* i (paul) made a nasty design error by using ':' as a special character in
385                            Ardour 0.99 .. this hack tries to make things sort of work.
386                         */
387
388                         if ((pos = pathstr.find_last_of (':')) != string::npos) {
389
390                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
391
392                                         /* its a real file, no problem */
393
394                                         keeppath = fullpath;
395                                         ++cnt;
396
397                                 } else {
398
399                                         if (must_exist) {
400
401                                                 /* might be an older session using file:channel syntax. see if the version
402                                                    without the :suffix exists
403                                                  */
404
405                                                 string shorter = pathstr.substr (0, pos);
406                                                 fullpath = Glib::build_filename (*i, shorter);
407
408                                                 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
409                                                         chan = atoi (pathstr.substr (pos+1));
410                                                         pathstr = shorter;
411                                                         keeppath = fullpath;
412                                                         ++cnt;
413                                                 }
414
415                                         } else {
416
417                                                 /* new derived file (e.g. for timefx) being created in a newer session */
418
419                                         }
420                                 }
421
422                         } else {
423
424                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
425                                         keeppath = fullpath;
426                                         ++cnt;
427                                 }
428                         }
429                 }
430
431                 if (cnt > 1) {
432
433                         error << string_compose (
434                                         _("FileSource: \"%1\" is ambigous when searching\n\t"), pathstr) << endmsg;
435                         goto out;
436
437                 } else if (cnt == 0) {
438
439                         if (must_exist) {
440                                 error << string_compose(
441                                                 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
442                                 goto out;
443                         } else {
444                                 isnew = true;
445                         }
446                 }
447
448                 /* Current find() is unable to parse relative path names to yet non-existant
449                    sources. QuickFix(tm) */
450                 if (keeppath == "") {
451                         if (must_exist) {
452                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
453                         } else {
454                                 keeppath = pathstr;
455                         }
456                 }
457
458                 found_path = keeppath;
459
460                 ret = true;
461
462         } else {
463
464                 /* external files and/or very very old style sessions include full paths */
465
466                 /* ugh, handle ':' situation */
467
468                 if ((pos = pathstr.find_last_of (':')) != string::npos) {
469
470                         string shorter = pathstr.substr (0, pos);
471
472                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
473                                 chan = atoi (pathstr.substr (pos+1));
474                                 pathstr = shorter;
475                         }
476                 }
477
478                 found_path = pathstr;
479
480                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
481
482                         /* file does not exist or we cannot read it */
483
484                         if (must_exist) {
485                                 error << string_compose(
486                                                 _("Filesource: cannot find required file (%1): %2"),
487                                                 path, strerror (errno)) << endmsg;
488                                 goto out;
489                         }
490
491 #ifndef PLATFORM_WINDOWS
492                         if (errno != ENOENT) {
493                                 error << string_compose(
494                                                 _("Filesource: cannot check for existing file (%1): %2"),
495                                                 path, strerror (errno)) << endmsg;
496                                 goto out;
497                         }
498 #endif
499                         /* a new file */
500                         isnew = true;
501                         ret = true;
502
503                 } else {
504
505                         /* already exists */
506                         ret = true;
507                 }
508         }
509
510 out:
511         return ret;
512 }
513
514 void
515 FileSource::mark_immutable ()
516 {
517         /* destructive sources stay writable, and their other flags don't change.  */
518         if (!(_flags & Destructive)) {
519                 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
520         }
521 }
522
523 void
524 FileSource::mark_immutable_except_write ()
525 {
526         /* destructive sources stay writable, and their other flags don't change.  */
527         if (!(_flags & Destructive)) {
528                 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
529         }
530 }
531
532 void
533 FileSource::mark_nonremovable ()
534 {
535         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
536 }
537
538 void
539 FileSource::set_within_session_from_path (const std::string& path)
540 {
541         _within_session = _session.path_is_within_session (path);
542 }
543
544 void
545 FileSource::set_path (const std::string& newpath)
546 {
547         close ();
548         _path = newpath;
549         set_within_session_from_path (newpath);
550         if (_within_session) {
551                 _origin = Glib::path_get_basename (newpath);
552         } else {
553                 _origin = newpath;
554         }
555 }
556
557 void
558 FileSource::inc_use_count ()
559 {
560         Source::inc_use_count ();
561 }
562
563 bool
564 FileSource::is_stub () const
565 {
566         if (!empty()) {
567                 return false;
568         }
569
570         if (!removable()) {
571                 return false;
572         }
573
574         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
575                 return false;
576         }
577
578         return true;
579 }
580
581 int
582 FileSource::rename (const string& newpath)
583 {
584         Glib::Threads::Mutex::Lock lm (_lock);
585         string oldpath = _path;
586
587         // Test whether newpath exists, if yes notify the user but continue.
588         if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
589                 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;
590                 return -1;
591         }
592
593         if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
594                 /* rename only needed if file exists on disk */
595                 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
596                         error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
597                         return -1;
598                 }
599         }
600
601         _name = Glib::path_get_basename (newpath);
602         _path = newpath;
603
604         return 0;
605 }
606
607