Remove ambiguous API implementation
[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 <unistd.h>
25 #include <fcntl.h>
26 #include <errno.h>
27
28 #include <glib.h>
29 #include <glib/gstdio.h>
30
31 #include "pbd/convert.h"
32 #include "pbd/basename.h"
33 #include "pbd/stl_delete.h"
34 #include "pbd/strsplit.h"
35 #include "pbd/shortpath.h"
36 #include "pbd/enumwriter.h"
37 #include "pbd/file_utils.h"
38
39 #include <glibmm/miscutils.h>
40 #include <glibmm/fileutils.h>
41 #include <glibmm/threads.h>
42
43 #include "ardour/data_type.h"
44 #include "ardour/file_source.h"
45 #include "ardour/session.h"
46 #include "ardour/source.h"
47 #include "ardour/utils.h"
48
49 #include "pbd/i18n.h"
50
51 using namespace std;
52 using namespace ARDOUR;
53 using namespace PBD;
54 using namespace Glib;
55
56 PBD::Signal2<int,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
57
58 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
59         : Source(session, type, path, flag)
60         , _path (path)
61         , _file_is_new (!origin.empty()) // if origin is left unspecified (empty string) then file must exist
62         , _channel (0)
63         , _origin (origin)
64         , _gain (1.f)
65 {
66         set_within_session_from_path (path);
67 }
68
69 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
70         : Source (session, node)
71         , _file_is_new (false)
72         , _channel (0)
73         , _gain (1.f)
74 {
75         /* this setting of _path is temporary - we expect derived classes
76            to call ::init() which will actually locate the file
77            and reset _path and _within_session correctly.
78         */
79
80         _path = _name;
81         _within_session = true;
82 }
83
84 FileSource::~FileSource()
85 {
86 }
87
88 void
89 FileSource::existence_check ()
90 {
91         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
92                 prevent_deletion ();
93         }
94 }
95
96 void
97 FileSource::prevent_deletion ()
98 {
99         if (!(_flags & Destructive)) {
100                 mark_immutable ();
101         } else {
102                 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
103         }
104 }
105
106 bool
107 FileSource::removable () const
108 {
109         bool r = ((_flags & Removable)
110                   && ((_flags & RemoveAtDestroy) ||
111                       ((_flags & RemovableIfEmpty) && empty())));
112
113         return r;
114 }
115
116 int
117 FileSource::init (const string& pathstr, bool must_exist)
118 {
119         _timeline_position = 0;
120
121         if (Stateful::loading_state_version < 3000) {
122                 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
123                         throw MissingSource (pathstr, _type);
124                 }
125         } else {
126                 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
127                         throw MissingSource (pathstr, _type);
128                 }
129         }
130
131         set_within_session_from_path (_path);
132
133         _name = Glib::path_get_basename (_path);
134
135         if (must_exist) {
136                 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
137                         throw MissingSource (pathstr, _type);
138                 }
139         }
140
141         return 0;
142 }
143
144 int
145 FileSource::set_state (const XMLNode& node, int /*version*/)
146 {
147         if (!node.get_property (X_("channel"), _channel)) {
148                 _channel = 0;
149         }
150
151         node.get_property (X_("origin"), _origin);
152
153         if (!node.get_property (X_("gain"), _gain)) {
154                 _gain = 1.f;
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 (::g_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, g_strerror (errno)) << endmsg;
214                 return -1;
215         }
216
217         if (move_dependents_to_trash() != 0) {
218                 /* try to back out */
219                 ::g_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 path should be a simple filename (no slashes).
234  * If the source is external, \a path 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 if 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         ret = true;
343
344   out:
345         return ret;
346 }
347
348 /** Find the actual source file based on \a filename.
349  *
350  * If the source is within the session tree, \a filename should be a simple filename (no slashes).
351  * If the source is external, \a filename should be a full path.
352  * In either case, found_path is set to the complete absolute path of the source file.
353  * \return true iff the file was found.
354  */
355 bool
356 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
357                      bool& isnew, uint16_t& chan, string& found_path)
358 {
359         string pathstr = path;
360         string::size_type pos;
361         bool ret = false;
362
363         isnew = false;
364
365         if (!Glib::path_is_absolute (pathstr)) {
366
367                 /* non-absolute pathname: find pathstr in search path */
368
369                 vector<string> dirs = s.source_search_path (type);
370
371                 int cnt;
372                 string fullpath;
373                 string keeppath;
374
375                 if (dirs.size() == 0) {
376                         error << _("FileSource: search path not set") << endmsg;
377                         goto out;
378                 }
379
380                 cnt = 0;
381
382                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
383
384                         fullpath = Glib::build_filename (*i, pathstr);
385
386                         /* i (paul) made a nasty design error by using ':' as a special character in
387                            Ardour 0.99 .. this hack tries to make things sort of work.
388                         */
389
390                         if ((pos = pathstr.find_last_of (':')) != string::npos) {
391
392                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
393
394                                         /* its a real file, no problem */
395
396                                         keeppath = fullpath;
397                                         ++cnt;
398
399                                 } else {
400
401                                         if (must_exist) {
402
403                                                 /* might be an older session using file:channel syntax. see if the version
404                                                    without the :suffix exists
405                                                  */
406
407                                                 string shorter = pathstr.substr (0, pos);
408                                                 fullpath = Glib::build_filename (*i, shorter);
409
410                                                 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
411                                                         chan = atoi (pathstr.substr (pos+1));
412                                                         pathstr = shorter;
413                                                         keeppath = fullpath;
414                                                         ++cnt;
415                                                 }
416
417                                         } else {
418
419                                                 /* new derived file (e.g. for timefx) being created in a newer session */
420
421                                         }
422                                 }
423
424                         } else {
425
426                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
427                                         keeppath = fullpath;
428                                         ++cnt;
429                                 }
430                         }
431                 }
432
433                 if (cnt > 1) {
434
435                         error << string_compose (
436                                         _("FileSource: \"%1\" is ambiguous when searching\n\t"), pathstr) << endmsg;
437                         goto out;
438
439                 } else if (cnt == 0) {
440
441                         if (must_exist) {
442                                 error << string_compose(
443                                                 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
444                                 goto out;
445                         } else {
446                                 isnew = true;
447                         }
448                 }
449
450                 /* Current find() is unable to parse relative path names to yet non-existant
451                    sources. QuickFix(tm) */
452                 if (keeppath == "") {
453                         if (must_exist) {
454                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
455                         } else {
456                                 keeppath = pathstr;
457                         }
458                 }
459
460                 found_path = keeppath;
461
462                 ret = true;
463
464         } else {
465
466                 /* external files and/or very very old style sessions include full paths */
467
468                 /* ugh, handle ':' situation */
469
470                 if ((pos = pathstr.find_last_of (':')) != string::npos) {
471
472                         string shorter = pathstr.substr (0, pos);
473
474                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
475                                 chan = atoi (pathstr.substr (pos+1));
476                                 pathstr = shorter;
477                         }
478                 }
479
480                 found_path = pathstr;
481
482                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
483
484                         /* file does not exist or we cannot read it */
485
486                         if (must_exist) {
487                                 error << string_compose(
488                                                 _("Filesource: cannot find required file (%1): %2"),
489                                                 path, g_strerror (errno)) << endmsg;
490                                 goto out;
491                         }
492
493 #ifndef PLATFORM_WINDOWS
494                         if (errno != ENOENT) {
495                                 error << string_compose(
496                                                 _("Filesource: cannot check for existing file (%1): %2"),
497                                                 path, g_strerror (errno)) << endmsg;
498                                 goto out;
499                         }
500 #endif
501                         /* a new file */
502                         isnew = true;
503                         ret = true;
504
505                 } else {
506
507                         /* already exists */
508                         ret = true;
509                 }
510         }
511
512 out:
513         return ret;
514 }
515
516 void
517 FileSource::mark_immutable ()
518 {
519         /* destructive sources stay writable, and their other flags don't change.  */
520         if (!(_flags & Destructive)) {
521                 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
522                 close();
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         close ();
551         _path = newpath;
552         set_within_session_from_path (newpath);
553         if (_within_session) {
554                 _origin = Glib::path_get_basename (newpath);
555         } else {
556                 _origin = newpath;
557         }
558 }
559
560
561 void
562 FileSource::replace_file (const std::string& newpath)
563 {
564         close ();
565         _path = newpath;
566         _name = Glib::path_get_basename (newpath);
567 }
568
569 void
570 FileSource::inc_use_count ()
571 {
572         Source::inc_use_count ();
573 }
574
575 bool
576 FileSource::is_stub () const
577 {
578         if (!empty()) {
579                 return false;
580         }
581
582         if (!removable()) {
583                 return false;
584         }
585
586         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
587                 return false;
588         }
589
590         return true;
591 }
592
593 int
594 FileSource::rename (const string& newpath)
595 {
596         Glib::Threads::Mutex::Lock lm (_lock);
597         string oldpath = _path;
598
599         // Test whether newpath exists, if yes notify the user but continue.
600         if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
601                 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;
602                 return -1;
603         }
604
605         if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
606                 /* rename only needed if file exists on disk */
607                 if (::g_rename (oldpath.c_str(), newpath.c_str()) != 0) {
608                         error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, g_strerror(errno)) << endmsg;
609                         return -1;
610                 }
611         }
612
613         _name = Glib::path_get_basename (newpath);
614         _path = newpath;
615
616         return 0;
617 }
618
619