1 /*
2 * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
3 * Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
4 * Copyright (C) 2009-2016 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2012-2017 Tim Mayberry <mojofunk@gmail.com>
6 * Copyright (C) 2015-2018 Robin Gareus <robin@gareus.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #include <vector>
24
25 #include <sys/time.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <errno.h>
30
31 #include <glib.h>
32 #include <glib/gstdio.h>
33
34 #include "pbd/convert.h"
35 #include "pbd/basename.h"
36 #include "pbd/stl_delete.h"
37 #include "pbd/strsplit.h"
38 #include "pbd/shortpath.h"
39 #include "pbd/enumwriter.h"
40 #include "pbd/file_utils.h"
41
42 #include <glibmm/miscutils.h>
43 #include <glibmm/fileutils.h>
44 #include <glibmm/threads.h>
45
46 #include "ardour/data_type.h"
47 #include "ardour/file_source.h"
48 #include "ardour/session.h"
49 #include "ardour/source.h"
50 #include "ardour/utils.h"
51
52 #include "pbd/i18n.h"
53
54 using namespace std;
55 using namespace ARDOUR;
56 using namespace PBD;
57 using namespace Glib;
58
59 PBD::Signal2<int,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
60
FileSource(Session & session,DataType type,const string & path,const string & origin,Source::Flag flag)61 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
62 : Source(session, type, path, flag)
63 , _path (path)
64 , _file_is_new (!origin.empty()) // if origin is left unspecified (empty string) then file must exist
65 , _channel (0)
66 , _origin (origin)
67 , _gain (1.f)
68 {
69 set_within_session_from_path (path);
70 }
71
FileSource(Session & session,const XMLNode & node,bool)72 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
73 : Source (session, node)
74 , _file_is_new (false)
75 , _channel (0)
76 , _gain (1.f)
77 {
78 /* this setting of _path is temporary - we expect derived classes
79 to call ::init() which will actually locate the file
80 and reset _path and _within_session correctly.
81 */
82
83 _path = _name;
84 _within_session = true;
85 }
86
~FileSource()87 FileSource::~FileSource()
88 {
89 }
90
91 void
existence_check()92 FileSource::existence_check ()
93 {
94 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
95 prevent_deletion ();
96 }
97 }
98
99 void
prevent_deletion()100 FileSource::prevent_deletion ()
101 {
102 mark_immutable ();
103 }
104
105 bool
removable() const106 FileSource::removable () const
107 {
108 bool r = ((_flags & Removable)
109 && ((_flags & RemoveAtDestroy) ||
110 ((_flags & RemovableIfEmpty) && empty())));
111
112 return r;
113 }
114
115 int
init(const string & pathstr,bool must_exist)116 FileSource::init (const string& pathstr, bool must_exist)
117 {
118 if (Stateful::loading_state_version < 3000) {
119 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
120 throw MissingSource (pathstr, _type);
121 }
122 } else {
123 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
124 throw MissingSource (pathstr, _type);
125 }
126 }
127
128 set_within_session_from_path (_path);
129
130 _name = Glib::path_get_basename (_path);
131
132 if (must_exist) {
133 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
134 throw MissingSource (pathstr, _type);
135 }
136 }
137
138 return 0;
139 }
140
141 int
set_state(const XMLNode & node,int)142 FileSource::set_state (const XMLNode& node, int /*version*/)
143 {
144 if (!node.get_property (X_("channel"), _channel)) {
145 _channel = 0;
146 }
147
148 node.get_property (X_("origin"), _origin);
149
150 if (!node.get_property (X_("gain"), _gain)) {
151 _gain = 1.f;
152 }
153
154 return 0;
155 }
156
157 void
mark_take(const string & id)158 FileSource::mark_take (const string& id)
159 {
160 if (writable ()) {
161 _take_id = id;
162 }
163 }
164
165 int
move_to_trash(const string & trash_dir_name)166 FileSource::move_to_trash (const string& trash_dir_name)
167 {
168 if (!within_session() || !writable()) {
169 return -1;
170 }
171
172 /* don't move the file across filesystems, just stick it in the
173 trash_dir_name directory on whichever filesystem it was already on
174 */
175
176 vector<string> v;
177 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
178 v.push_back (trash_dir_name);
179 v.push_back (Glib::path_get_basename (_path));
180
181 string newpath = Glib::build_filename (v);
182
183 /* the new path already exists, try versioning */
184
185 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
186 char buf[PATH_MAX+1];
187 int version = 1;
188 string newpath_v;
189
190 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
191 newpath_v = buf;
192
193 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
194 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
195 newpath_v = buf;
196 }
197
198 if (version == 999) {
199 PBD::error << string_compose (
200 _("there are already 1000 files with names like %1; versioning discontinued"),
201 newpath) << endmsg;
202 } else {
203 newpath = newpath_v;
204 }
205 }
206
207 if (::g_rename (_path.c_str(), newpath.c_str()) != 0) {
208 PBD::error << string_compose (
209 _("cannot rename file source from %1 to %2 (%3)"),
210 _path, newpath, g_strerror (errno)) << endmsg;
211 return -1;
212 }
213
214 if (move_dependents_to_trash() != 0) {
215 /* try to back out */
216 ::g_rename (newpath.c_str(), _path.c_str());
217 return -1;
218 }
219
220 _path = newpath;
221
222 /* file can not be removed twice, since the operation is not idempotent */
223 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
224
225 return 0;
226 }
227
228 /** Find the actual source file based on \a filename.
229 *
230 * If the source is within the session tree, \a path should be a simple filename (no slashes).
231 * If the source is external, \a path should be a full path.
232 * In either case, found_path is set to the complete absolute path of the source file.
233 * \return true if the file was found.
234 */
235 bool
find(Session & s,DataType type,const string & path,bool must_exist,bool & isnew,uint16_t &,string & found_path)236 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
237 bool& isnew, uint16_t& /* chan */, string& found_path)
238 {
239 bool ret = false;
240 string keeppath;
241
242 isnew = false;
243
244 if (!Glib::path_is_absolute (path)) {
245 vector<string> hits;
246 string fullpath;
247 std::vector<std::string> dirs = s.source_search_path (type);
248
249 if (dirs.size() == 0) {
250 error << _("FileSource: search path not set") << endmsg;
251 goto out;
252 }
253
254 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
255
256 fullpath = Glib::build_filename (*i, path);
257
258 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
259 keeppath = fullpath;
260 hits.push_back (fullpath);
261 }
262 }
263
264 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
265 in the session path it is possible to arrive at the same file via more than one path.
266
267 I suppose this is not necessary on Windows.
268 */
269
270 vector<string> de_duped_hits;
271
272 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
273
274 vector<string>::iterator j = i;
275 ++j;
276
277 while (j != hits.end()) {
278 if (PBD::equivalent_paths (*i, *j)) {
279 /* *i and *j are the same file; break out of the loop early */
280 break;
281 }
282
283 ++j;
284 }
285
286 if (j == hits.end ()) {
287 de_duped_hits.push_back (*i);
288 }
289 }
290
291 if (de_duped_hits.size() > 1) {
292
293 /* more than one match: ask the user */
294
295 int which = FileSource::AmbiguousFileName (path, de_duped_hits).value_or (-1);
296
297 if (which < 0) {
298 goto out;
299 } else {
300 keeppath = de_duped_hits[which];
301 }
302
303 } else if (de_duped_hits.size() == 0) {
304
305 /* no match: error */
306
307 if (must_exist) {
308 /* do not generate an error here, leave that to
309 whoever deals with the false return value.
310 */
311 goto out;
312 } else {
313 isnew = true;
314 }
315 } else {
316
317 /* only one match: happy days */
318
319 keeppath = de_duped_hits[0];
320 }
321
322 } else {
323 keeppath = path;
324 }
325
326 /* Current find() is unable to parse relative path names to yet non-existant
327 sources. QuickFix(tm)
328 */
329
330 if (keeppath.empty()) {
331 if (must_exist) {
332 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
333 } else {
334 keeppath = path;
335 }
336 }
337
338 found_path = keeppath;
339 ret = true;
340
341 out:
342 return ret;
343 }
344
345 /** Find the actual source file based on \a filename.
346 *
347 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
348 * If the source is external, \a filename should be a full path.
349 * In either case, found_path is set to the complete absolute path of the source file.
350 * \return true iff the file was found.
351 */
352 bool
find_2X(Session & s,DataType type,const string & path,bool must_exist,bool & isnew,uint16_t & chan,string & found_path)353 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
354 bool& isnew, uint16_t& chan, string& found_path)
355 {
356 string pathstr = path;
357 string::size_type pos;
358 bool ret = false;
359
360 isnew = false;
361
362 if (!Glib::path_is_absolute (pathstr)) {
363
364 /* non-absolute pathname: find pathstr in search path */
365
366 vector<string> dirs = s.source_search_path (type);
367
368 int cnt;
369 string fullpath;
370 string keeppath;
371
372 if (dirs.size() == 0) {
373 error << _("FileSource: search path not set") << endmsg;
374 goto out;
375 }
376
377 cnt = 0;
378
379 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
380
381 fullpath = Glib::build_filename (*i, pathstr);
382
383 /* i (paul) made a nasty design error by using ':' as a special character in
384 Ardour 0.99 .. this hack tries to make things sort of work.
385 */
386
387 if ((pos = pathstr.find_last_of (':')) != string::npos) {
388
389 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
390
391 /* its a real file, no problem */
392
393 keeppath = fullpath;
394 ++cnt;
395
396 } else {
397
398 if (must_exist) {
399
400 /* might be an older session using file:channel syntax. see if the version
401 without the :suffix exists
402 */
403
404 string shorter = pathstr.substr (0, pos);
405 fullpath = Glib::build_filename (*i, shorter);
406
407 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
408 chan = atoi (pathstr.substr (pos+1));
409 pathstr = shorter;
410 keeppath = fullpath;
411 ++cnt;
412 }
413
414 } else {
415
416 /* new derived file (e.g. for timefx) being created in a newer session */
417
418 }
419 }
420
421 } else {
422
423 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
424 keeppath = fullpath;
425 ++cnt;
426 }
427 }
428 }
429
430 if (cnt > 1) {
431
432 error << string_compose (
433 _("FileSource: \"%1\" is ambiguous when searching\n\t"), pathstr) << endmsg;
434 goto out;
435
436 } else if (cnt == 0) {
437
438 if (must_exist) {
439 error << string_compose(
440 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
441 goto out;
442 } else {
443 isnew = true;
444 }
445 }
446
447 /* Current find() is unable to parse relative path names to yet non-existant
448 sources. QuickFix(tm) */
449 if (keeppath == "") {
450 if (must_exist) {
451 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
452 } else {
453 keeppath = pathstr;
454 }
455 }
456
457 found_path = keeppath;
458
459 ret = true;
460
461 } else {
462
463 /* external files and/or very very old style sessions include full paths */
464
465 /* ugh, handle ':' situation */
466
467 if ((pos = pathstr.find_last_of (':')) != string::npos) {
468
469 string shorter = pathstr.substr (0, pos);
470
471 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
472 chan = atoi (pathstr.substr (pos+1));
473 pathstr = shorter;
474 }
475 }
476
477 found_path = pathstr;
478
479 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
480
481 /* file does not exist or we cannot read it */
482
483 if (must_exist) {
484 error << string_compose(
485 _("Filesource: cannot find required file (%1): %2"),
486 path, g_strerror (errno)) << endmsg;
487 goto out;
488 }
489
490 #ifndef PLATFORM_WINDOWS
491 if (errno != ENOENT) {
492 error << string_compose(
493 _("Filesource: cannot check for existing file (%1): %2"),
494 path, g_strerror (errno)) << endmsg;
495 goto out;
496 }
497 #endif
498 /* a new file */
499 isnew = true;
500 ret = true;
501
502 } else {
503
504 /* already exists */
505 ret = true;
506 }
507 }
508
509 out:
510 return ret;
511 }
512
513 void
mark_immutable()514 FileSource::mark_immutable ()
515 {
516 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
517 close();
518 }
519
520 void
mark_immutable_except_write()521 FileSource::mark_immutable_except_write ()
522 {
523 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
524 }
525
526 void
mark_nonremovable()527 FileSource::mark_nonremovable ()
528 {
529 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
530 }
531
532 void
set_within_session_from_path(const std::string & path)533 FileSource::set_within_session_from_path (const std::string& path)
534 {
535 _within_session = _session.path_is_within_session (path);
536 }
537
538 void
set_path(const std::string & newpath)539 FileSource::set_path (const std::string& newpath)
540 {
541 close ();
542 _path = newpath;
543 set_within_session_from_path (newpath);
544 if (_within_session) {
545 _origin = Glib::path_get_basename (newpath);
546 } else {
547 _origin = newpath;
548 }
549 }
550
551
552 void
replace_file(const std::string & newpath)553 FileSource::replace_file (const std::string& newpath)
554 {
555 close ();
556 _path = newpath;
557 _name = Glib::path_get_basename (newpath);
558 }
559
560 void
inc_use_count()561 FileSource::inc_use_count ()
562 {
563 Source::inc_use_count ();
564 }
565
566 bool
is_stub() const567 FileSource::is_stub () const
568 {
569 if (!empty()) {
570 return false;
571 }
572
573 if (!removable()) {
574 return false;
575 }
576
577 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
578 return false;
579 }
580
581 return true;
582 }
583
584 int
rename(const string & newpath)585 FileSource::rename (const string& newpath)
586 {
587 Glib::Threads::Mutex::Lock lm (_lock);
588 string oldpath = _path;
589
590 // Test whether newpath exists, if yes notify the user but continue.
591 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
592 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;
593 return -1;
594 }
595
596 if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
597 /* rename only needed if file exists on disk */
598 if (::g_rename (oldpath.c_str(), newpath.c_str()) != 0) {
599 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, g_strerror(errno)) << endmsg;
600 return -1;
601 }
602 }
603
604 _name = Glib::path_get_basename (newpath);
605 _path = newpath;
606
607 return 0;
608 }
609
610
611