/* Copyright (C) 2006-2009 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include "pbd/convert.h" #include "pbd/basename.h" #include "pbd/stl_delete.h" #include "pbd/strsplit.h" #include "pbd/shortpath.h" #include "pbd/enumwriter.h" #include "pbd/file_utils.h" #include #include #include #include "ardour/data_type.h" #include "ardour/file_source.h" #include "ardour/session.h" #include "ardour/source.h" #include "ardour/utils.h" #include "pbd/i18n.h" using namespace std; using namespace ARDOUR; using namespace PBD; using namespace Glib; PBD::Signal2 > FileSource::AmbiguousFileName; FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag) : Source(session, type, path, flag) , _path (path) , _file_is_new (!origin.empty()) // if origin is left unspecified (empty string) then file must exist , _channel (0) , _origin (origin) { set_within_session_from_path (path); } FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/) : Source (session, node) , _file_is_new (false) , _channel (0) { /* this setting of _path is temporary - we expect derived classes to call ::init() which will actually locate the file and reset _path and _within_session correctly. */ _path = _name; _within_session = true; } FileSource::~FileSource() { } void FileSource::existence_check () { if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) { prevent_deletion (); } } void FileSource::prevent_deletion () { if (!(_flags & Destructive)) { mark_immutable (); } else { _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy)); } } bool FileSource::removable () const { bool r = ((_flags & Removable) && ((_flags & RemoveAtDestroy) || ((_flags & RemovableIfEmpty) && empty()))); return r; } int FileSource::init (const string& pathstr, bool must_exist) { _timeline_position = 0; if (Stateful::loading_state_version < 3000) { if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) { throw MissingSource (pathstr, _type); } } else { if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) { throw MissingSource (pathstr, _type); } } set_within_session_from_path (_path); _name = Glib::path_get_basename (_path); if (must_exist) { if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) { throw MissingSource (pathstr, _type); } } return 0; } int FileSource::set_state (const XMLNode& node, int /*version*/) { XMLProperty const * prop; if ((prop = node.property (X_("channel"))) != 0) { _channel = atoi (prop->value()); } else { _channel = 0; } if ((prop = node.property (X_("origin"))) != 0) { _origin = prop->value(); } return 0; } void FileSource::mark_take (const string& id) { if (writable ()) { _take_id = id; } } int FileSource::move_to_trash (const string& trash_dir_name) { if (!within_session() || !writable()) { return -1; } /* don't move the file across filesystems, just stick it in the trash_dir_name directory on whichever filesystem it was already on */ vector v; v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path))); v.push_back (trash_dir_name); v.push_back (Glib::path_get_basename (_path)); string newpath = Glib::build_filename (v); /* the new path already exists, try versioning */ if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) { char buf[PATH_MAX+1]; int version = 1; string newpath_v; snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version); newpath_v = buf; while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) { snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version); newpath_v = buf; } if (version == 999) { PBD::error << string_compose ( _("there are already 1000 files with names like %1; versioning discontinued"), newpath) << endmsg; } else { newpath = newpath_v; } } if (::g_rename (_path.c_str(), newpath.c_str()) != 0) { PBD::error << string_compose ( _("cannot rename file source from %1 to %2 (%3)"), _path, newpath, g_strerror (errno)) << endmsg; return -1; } if (move_dependents_to_trash() != 0) { /* try to back out */ ::g_rename (newpath.c_str(), _path.c_str()); return -1; } _path = newpath; /* file can not be removed twice, since the operation is not idempotent */ _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty)); return 0; } /** Find the actual source file based on \a filename. * * If the source is within the session tree, \a path should be a simple filename (no slashes). * If the source is external, \a path should be a full path. * In either case, found_path is set to the complete absolute path of the source file. * \return true if the file was found. */ bool FileSource::find (Session& s, DataType type, const string& path, bool must_exist, bool& isnew, uint16_t& /* chan */, string& found_path) { bool ret = false; string keeppath; isnew = false; if (!Glib::path_is_absolute (path)) { vector hits; string fullpath; std::vector dirs = s.source_search_path (type); if (dirs.size() == 0) { error << _("FileSource: search path not set") << endmsg; goto out; } for (vector::iterator i = dirs.begin(); i != dirs.end(); ++i) { fullpath = Glib::build_filename (*i, path); if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) { keeppath = fullpath; hits.push_back (fullpath); } } /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks in the session path it is possible to arrive at the same file via more than one path. I suppose this is not necessary on Windows. */ vector de_duped_hits; for (vector::iterator i = hits.begin(); i != hits.end(); ++i) { vector::iterator j = i; ++j; while (j != hits.end()) { if (PBD::equivalent_paths (*i, *j)) { /* *i and *j are the same file; break out of the loop early */ break; } ++j; } if (j == hits.end ()) { de_duped_hits.push_back (*i); } } if (de_duped_hits.size() > 1) { /* more than one match: ask the user */ int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1); if (which < 0) { goto out; } else { keeppath = de_duped_hits[which]; } } else if (de_duped_hits.size() == 0) { /* no match: error */ if (must_exist) { /* do not generate an error here, leave that to whoever deals with the false return value. */ goto out; } else { isnew = true; } } else { /* only one match: happy days */ keeppath = de_duped_hits[0]; } } else { keeppath = path; } /* Current find() is unable to parse relative path names to yet non-existant sources. QuickFix(tm) */ if (keeppath.empty()) { if (must_exist) { error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl; } else { keeppath = path; } } found_path = keeppath; ret = true; out: return ret; } /** Find the actual source file based on \a filename. * * If the source is within the session tree, \a filename should be a simple filename (no slashes). * If the source is external, \a filename should be a full path. * In either case, found_path is set to the complete absolute path of the source file. * \return true iff the file was found. */ bool FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist, bool& isnew, uint16_t& chan, string& found_path) { string pathstr = path; string::size_type pos; bool ret = false; isnew = false; if (!Glib::path_is_absolute (pathstr)) { /* non-absolute pathname: find pathstr in search path */ vector dirs = s.source_search_path (type); int cnt; string fullpath; string keeppath; if (dirs.size() == 0) { error << _("FileSource: search path not set") << endmsg; goto out; } cnt = 0; for (vector::iterator i = dirs.begin(); i != dirs.end(); ++i) { fullpath = Glib::build_filename (*i, pathstr); /* i (paul) made a nasty design error by using ':' as a special character in Ardour 0.99 .. this hack tries to make things sort of work. */ if ((pos = pathstr.find_last_of (':')) != string::npos) { if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) { /* its a real file, no problem */ keeppath = fullpath; ++cnt; } else { if (must_exist) { /* might be an older session using file:channel syntax. see if the version without the :suffix exists */ string shorter = pathstr.substr (0, pos); fullpath = Glib::build_filename (*i, shorter); if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) { chan = atoi (pathstr.substr (pos+1)); pathstr = shorter; keeppath = fullpath; ++cnt; } } else { /* new derived file (e.g. for timefx) being created in a newer session */ } } } else { if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) { keeppath = fullpath; ++cnt; } } } if (cnt > 1) { error << string_compose ( _("FileSource: \"%1\" is ambiguous when searching\n\t"), pathstr) << endmsg; goto out; } else if (cnt == 0) { if (must_exist) { error << string_compose( _("Filesource: cannot find required file (%1)"), pathstr) << endmsg; goto out; } else { isnew = true; } } /* Current find() is unable to parse relative path names to yet non-existant sources. QuickFix(tm) */ if (keeppath == "") { if (must_exist) { error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl; } else { keeppath = pathstr; } } found_path = keeppath; ret = true; } else { /* external files and/or very very old style sessions include full paths */ /* ugh, handle ':' situation */ if ((pos = pathstr.find_last_of (':')) != string::npos) { string shorter = pathstr.substr (0, pos); if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) { chan = atoi (pathstr.substr (pos+1)); pathstr = shorter; } } found_path = pathstr; if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) { /* file does not exist or we cannot read it */ if (must_exist) { error << string_compose( _("Filesource: cannot find required file (%1): %2"), path, g_strerror (errno)) << endmsg; goto out; } #ifndef PLATFORM_WINDOWS if (errno != ENOENT) { error << string_compose( _("Filesource: cannot check for existing file (%1): %2"), path, g_strerror (errno)) << endmsg; goto out; } #endif /* a new file */ isnew = true; ret = true; } else { /* already exists */ ret = true; } } out: return ret; } void FileSource::mark_immutable () { /* destructive sources stay writable, and their other flags don't change. */ if (!(_flags & Destructive)) { _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename)); } } void FileSource::mark_immutable_except_write () { /* destructive sources stay writable, and their other flags don't change. */ if (!(_flags & Destructive)) { _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename)); } } void FileSource::mark_nonremovable () { _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy)); } void FileSource::set_within_session_from_path (const std::string& path) { _within_session = _session.path_is_within_session (path); } void FileSource::set_path (const std::string& newpath) { close (); _path = newpath; set_within_session_from_path (newpath); if (_within_session) { _origin = Glib::path_get_basename (newpath); } else { _origin = newpath; } } void FileSource::replace_file (const std::string& newpath) { close (); _path = newpath; _name = Glib::path_get_basename (newpath); } void FileSource::inc_use_count () { Source::inc_use_count (); } bool FileSource::is_stub () const { if (!empty()) { return false; } if (!removable()) { return false; } if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) { return false; } return true; } int FileSource::rename (const string& newpath) { Glib::Threads::Mutex::Lock lm (_lock); string oldpath = _path; // Test whether newpath exists, if yes notify the user but continue. if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) { 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; return -1; } if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) { /* rename only needed if file exists on disk */ if (::g_rename (oldpath.c_str(), newpath.c_str()) != 0) { error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, g_strerror(errno)) << endmsg; return -1; } } _name = Glib::path_get_basename (newpath); _path = newpath; return 0; }