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>
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.
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.
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.
32 #include <glib/gstdio.h>
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"
42 #include <glibmm/miscutils.h>
43 #include <glibmm/fileutils.h>
44 #include <glibmm/threads.h>
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"
55 using namespace ARDOUR;
59 PBD::Signal2<int,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
61 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
62 : Source(session, type, path, flag)
64 , _file_is_new (!origin.empty()) // if origin is left unspecified (empty string) then file must exist
69 set_within_session_from_path (path);
72 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
73 : Source (session, node)
74 , _file_is_new (false)
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.
84 _within_session = true;
87 FileSource::~FileSource()
92 FileSource::existence_check ()
94 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
100 FileSource::prevent_deletion ()
102 if (!(_flags & Destructive)) {
105 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
110 FileSource::removable () const
112 bool r = ((_flags & Removable)
113 && ((_flags & RemoveAtDestroy) ||
114 ((_flags & RemovableIfEmpty) && empty())));
120 FileSource::init (const string& pathstr, bool must_exist)
122 if (Stateful::loading_state_version < 3000) {
123 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
124 throw MissingSource (pathstr, _type);
127 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
128 throw MissingSource (pathstr, _type);
132 set_within_session_from_path (_path);
134 _name = Glib::path_get_basename (_path);
137 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
138 throw MissingSource (pathstr, _type);
146 FileSource::set_state (const XMLNode& node, int /*version*/)
148 if (!node.get_property (X_("channel"), _channel)) {
152 node.get_property (X_("origin"), _origin);
154 if (!node.get_property (X_("gain"), _gain)) {
162 FileSource::mark_take (const string& id)
170 FileSource::move_to_trash (const string& trash_dir_name)
172 if (!within_session() || !writable()) {
176 /* don't move the file across filesystems, just stick it in the
177 trash_dir_name directory on whichever filesystem it was already on
181 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
182 v.push_back (trash_dir_name);
183 v.push_back (Glib::path_get_basename (_path));
185 string newpath = Glib::build_filename (v);
187 /* the new path already exists, try versioning */
189 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
190 char buf[PATH_MAX+1];
194 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
197 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
198 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
202 if (version == 999) {
203 PBD::error << string_compose (
204 _("there are already 1000 files with names like %1; versioning discontinued"),
211 if (::g_rename (_path.c_str(), newpath.c_str()) != 0) {
212 PBD::error << string_compose (
213 _("cannot rename file source from %1 to %2 (%3)"),
214 _path, newpath, g_strerror (errno)) << endmsg;
218 if (move_dependents_to_trash() != 0) {
219 /* try to back out */
220 ::g_rename (newpath.c_str(), _path.c_str());
226 /* file can not be removed twice, since the operation is not idempotent */
227 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
232 /** Find the actual source file based on \a filename.
234 * If the source is within the session tree, \a path should be a simple filename (no slashes).
235 * If the source is external, \a path should be a full path.
236 * In either case, found_path is set to the complete absolute path of the source file.
237 * \return true if the file was found.
240 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
241 bool& isnew, uint16_t& /* chan */, string& found_path)
248 if (!Glib::path_is_absolute (path)) {
251 std::vector<std::string> dirs = s.source_search_path (type);
253 if (dirs.size() == 0) {
254 error << _("FileSource: search path not set") << endmsg;
258 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
260 fullpath = Glib::build_filename (*i, path);
262 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
264 hits.push_back (fullpath);
268 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
269 in the session path it is possible to arrive at the same file via more than one path.
271 I suppose this is not necessary on Windows.
274 vector<string> de_duped_hits;
276 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
278 vector<string>::iterator j = i;
281 while (j != hits.end()) {
282 if (PBD::equivalent_paths (*i, *j)) {
283 /* *i and *j are the same file; break out of the loop early */
290 if (j == hits.end ()) {
291 de_duped_hits.push_back (*i);
295 if (de_duped_hits.size() > 1) {
297 /* more than one match: ask the user */
299 int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1);
304 keeppath = de_duped_hits[which];
307 } else if (de_duped_hits.size() == 0) {
309 /* no match: error */
312 /* do not generate an error here, leave that to
313 whoever deals with the false return value.
321 /* only one match: happy days */
323 keeppath = de_duped_hits[0];
330 /* Current find() is unable to parse relative path names to yet non-existant
331 sources. QuickFix(tm)
334 if (keeppath.empty()) {
336 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
342 found_path = keeppath;
349 /** Find the actual source file based on \a filename.
351 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
352 * If the source is external, \a filename should be a full path.
353 * In either case, found_path is set to the complete absolute path of the source file.
354 * \return true iff the file was found.
357 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
358 bool& isnew, uint16_t& chan, string& found_path)
360 string pathstr = path;
361 string::size_type pos;
366 if (!Glib::path_is_absolute (pathstr)) {
368 /* non-absolute pathname: find pathstr in search path */
370 vector<string> dirs = s.source_search_path (type);
376 if (dirs.size() == 0) {
377 error << _("FileSource: search path not set") << endmsg;
383 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
385 fullpath = Glib::build_filename (*i, pathstr);
387 /* i (paul) made a nasty design error by using ':' as a special character in
388 Ardour 0.99 .. this hack tries to make things sort of work.
391 if ((pos = pathstr.find_last_of (':')) != string::npos) {
393 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
395 /* its a real file, no problem */
404 /* might be an older session using file:channel syntax. see if the version
405 without the :suffix exists
408 string shorter = pathstr.substr (0, pos);
409 fullpath = Glib::build_filename (*i, shorter);
411 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
412 chan = atoi (pathstr.substr (pos+1));
420 /* new derived file (e.g. for timefx) being created in a newer session */
427 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
436 error << string_compose (
437 _("FileSource: \"%1\" is ambiguous when searching\n\t"), pathstr) << endmsg;
440 } else if (cnt == 0) {
443 error << string_compose(
444 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
451 /* Current find() is unable to parse relative path names to yet non-existant
452 sources. QuickFix(tm) */
453 if (keeppath == "") {
455 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
461 found_path = keeppath;
467 /* external files and/or very very old style sessions include full paths */
469 /* ugh, handle ':' situation */
471 if ((pos = pathstr.find_last_of (':')) != string::npos) {
473 string shorter = pathstr.substr (0, pos);
475 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
476 chan = atoi (pathstr.substr (pos+1));
481 found_path = pathstr;
483 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
485 /* file does not exist or we cannot read it */
488 error << string_compose(
489 _("Filesource: cannot find required file (%1): %2"),
490 path, g_strerror (errno)) << endmsg;
494 #ifndef PLATFORM_WINDOWS
495 if (errno != ENOENT) {
496 error << string_compose(
497 _("Filesource: cannot check for existing file (%1): %2"),
498 path, g_strerror (errno)) << endmsg;
518 FileSource::mark_immutable ()
520 /* destructive sources stay writable, and their other flags don't change. */
521 if (!(_flags & Destructive)) {
522 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
528 FileSource::mark_immutable_except_write ()
530 /* destructive sources stay writable, and their other flags don't change. */
531 if (!(_flags & Destructive)) {
532 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
537 FileSource::mark_nonremovable ()
539 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
543 FileSource::set_within_session_from_path (const std::string& path)
545 _within_session = _session.path_is_within_session (path);
549 FileSource::set_path (const std::string& newpath)
553 set_within_session_from_path (newpath);
554 if (_within_session) {
555 _origin = Glib::path_get_basename (newpath);
563 FileSource::replace_file (const std::string& newpath)
567 _name = Glib::path_get_basename (newpath);
571 FileSource::inc_use_count ()
573 Source::inc_use_count ();
577 FileSource::is_stub () const
587 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
595 FileSource::rename (const string& newpath)
597 Glib::Threads::Mutex::Lock lm (_lock);
598 string oldpath = _path;
600 // Test whether newpath exists, if yes notify the user but continue.
601 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
602 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;
606 if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
607 /* rename only needed if file exists on disk */
608 if (::g_rename (oldpath.c_str(), newpath.c_str()) != 0) {
609 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, g_strerror(errno)) << endmsg;
614 _name = Glib::path_get_basename (newpath);