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