summaryrefslogtreecommitdiff
path: root/libs/ardour/file_source.cc
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2009-02-17 02:11:49 +0000
committerDavid Robillard <d@drobilla.net>2009-02-17 02:11:49 +0000
commit022818b4a796f52c0a91eea42e65aec0bc7bed43 (patch)
treef82bde33d44c7b3e16af7e837536665e17fe0189 /libs/ardour/file_source.cc
parent4565b73a3993b0cb5ccb9170e276180f2b5c1372 (diff)
Fix the horrible mess that was anything related to sources and paths.
Most significant changes: - Factor out FileSource from AudioFileSource, use for SMFSource too - Explicitly pass embedded rather than mysterious name mangling or whatever - Destroy a ton of duplicated or very-nearly-duplicated code - Clean up and document all that weird source stuff in session.cc git-svn-id: svn://localhost/ardour2/branches/3.0@4609 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/ardour/file_source.cc')
-rw-r--r--libs/ardour/file_source.cc419
1 files changed, 419 insertions, 0 deletions
diff --git a/libs/ardour/file_source.cc b/libs/ardour/file_source.cc
new file mode 100644
index 0000000000..ed9c80338a
--- /dev/null
+++ b/libs/ardour/file_source.cc
@@ -0,0 +1,419 @@
+/*
+ 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 <vector>
+
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <stdio.h> // for rename(), sigh
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <pbd/convert.h>
+#include <pbd/basename.h>
+#include <pbd/mountpoint.h>
+#include <pbd/stl_delete.h>
+#include <pbd/strsplit.h>
+#include <pbd/shortpath.h>
+#include <pbd/enumwriter.h>
+
+#include <glibmm/miscutils.h>
+#include <glibmm/fileutils.h>
+#include <glibmm/thread.h>
+
+#include <ardour/file_source.h>
+#include <ardour/session.h>
+#include <ardour/session_directory.h>
+#include <ardour/source_factory.h>
+#include <ardour/filename_extensions.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace Glib;
+
+static const std::string PATH_SEP = "/"; // I don't do windows
+
+map<DataType, ustring> FileSource::search_paths;
+
+FileSource::FileSource (Session& session, DataType type,
+ const ustring& path, bool embedded, Source::Flag flag)
+ : Source(session, type, path, flag)
+ , _path(path)
+ , _file_is_new(true)
+ , _channel (0)
+ , _is_embedded(embedded)
+{
+}
+
+FileSource::FileSource (Session& session, const XMLNode& node, bool must_exist)
+ : Source(session, node)
+ , _file_is_new(false)
+{
+ _path = _name;
+ _is_embedded = (_path.find(PATH_SEP) != string::npos);
+}
+
+bool
+FileSource::removable () const
+{
+ return (_flags & Removable)
+ && ( (_flags & RemoveAtDestroy)
+ || ((_flags & RemovableIfEmpty) && length() == 0));
+}
+
+int
+FileSource::init (const ustring& pathstr, bool must_exist)
+{
+ _length = 0;
+ _timeline_position = 0;
+
+ if (!find (_type, pathstr, must_exist, _file_is_new, _channel)) {
+ throw MissingSource ();
+ }
+
+ if (_file_is_new && must_exist) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+FileSource::set_state (const XMLNode& node)
+{
+ const XMLProperty* prop;
+
+ if ((prop = node.property (X_("channel"))) != 0) {
+ _channel = atoi (prop->value());
+ } else {
+ _channel = 0;
+ }
+
+ _is_embedded = (_name.find(PATH_SEP) == string::npos);
+
+ return 0;
+}
+
+void
+FileSource::mark_take (const ustring& id)
+{
+ if (writable ()) {
+ _take_id = id;
+ }
+}
+
+int
+FileSource::move_to_trash (const ustring& trash_dir_name)
+{
+ if (is_embedded()) {
+ cerr << "tried to move an embedded region to trash" << endl;
+ return -1;
+ }
+
+ if (!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
+ */
+
+ ustring newpath;
+ newpath = Glib::path_get_dirname (_path);
+ newpath = Glib::path_get_dirname (newpath);
+
+ newpath += string(PATH_SEP) + trash_dir_name + PATH_SEP;
+ newpath += Glib::path_get_basename (_path);
+
+ /* the new path already exists, try versioning */
+ if (access (newpath.c_str(), F_OK) == 0) {
+ char buf[PATH_MAX+1];
+ int version = 1;
+ ustring newpath_v;
+
+ snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
+ newpath_v = buf;
+
+ while (access (newpath_v.c_str(), F_OK) == 0 && 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 (::rename (_path.c_str(), newpath.c_str()) != 0) {
+ PBD::error << string_compose (
+ _("cannot rename file source from %1 to %2 (%3)"),
+ _path, newpath, strerror (errno)) << endmsg;
+ return -1;
+ }
+
+ if (move_dependents_to_trash() != 0) {
+ /* try to back out */
+ 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 path.
+ *
+ * If the source is embedded, \a path should be a filename (no slashes).
+ * If the source is external, \a path should be a full path.
+ * In either case, _path is set to the complete absolute path of the source file.
+ * \return true iff the file was found.
+ */
+bool
+FileSource::find (DataType type, const ustring& path, bool must_exist, bool& isnew, uint16_t& chan)
+{
+ Glib::ustring search_path = search_paths[type];
+
+ ustring pathstr = path;
+ ustring::size_type pos;
+ bool ret = false;
+
+ isnew = false;
+
+ if (pathstr[0] != '/') {
+
+ /* non-absolute pathname: find pathstr in search path */
+
+ vector<ustring> dirs;
+ int cnt;
+ ustring fullpath;
+ ustring keeppath;
+
+ if (search_path.length() == 0) {
+ error << _("FileSource: search path not set") << endmsg;
+ goto out;
+ }
+
+ split (search_path, dirs, ':');
+
+ cnt = 0;
+
+ for (vector<ustring>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
+ fullpath = *i;
+ if (fullpath[fullpath.length()-1] != '/') {
+ fullpath += '/';
+ }
+
+ fullpath += 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 (':')) != ustring::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
+ */
+
+ ustring shorter = pathstr.substr (0, pos);
+ fullpath = *i;
+
+ if (fullpath[fullpath.length()-1] != '/') {
+ fullpath += '/';
+ }
+
+ fullpath += 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 ambigous when searching %2\n\t"),
+ pathstr, search_path) << endmsg;
+ goto out;
+
+ } else if (cnt == 0) {
+
+ if (must_exist) {
+ error << string_compose(
+ _("Filesource: cannot find required file (%1): while searching %2"),
+ pathstr, search_path) << 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;
+ }
+ }
+
+ _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 (':')) != ustring::npos) {
+
+ ustring 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;
+ }
+ }
+
+ _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, strerror (errno)) << endmsg;
+ goto out;
+ }
+
+ if (errno != ENOENT) {
+ error << string_compose(
+ _("Filesource: cannot check for existing file (%1): %2"),
+ _path, strerror (errno)) << endmsg;
+ goto out;
+ }
+
+ /* a new file */
+ isnew = true;
+ ret = true;
+
+ } else {
+
+ /* already exists */
+ ret = true;
+ }
+ }
+
+ if (_is_embedded) {
+ _name = Glib::path_get_basename (_name);
+ }
+
+out:
+ return ret;
+}
+
+int
+FileSource::set_source_name (const ustring& newname, bool destructive)
+{
+ Glib::Mutex::Lock lm (_lock);
+ ustring oldpath = _path;
+ ustring newpath = Session::change_source_path_by_name (oldpath, _name, newname, destructive);
+
+ if (newpath.empty()) {
+ error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
+ return -1;
+ }
+
+ // Test whether newpath exists, if yes notify the user but continue.
+ if (access(newpath.c_str(),F_OK) == 0) {
+ error << _("Programming error! Ardour tried to rename a file over another file! It's safe to continue working, but please report this to the developers.") << endmsg;
+ return -1;
+ }
+
+ if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
+ error << string_compose (_("cannot rename audio file %1 to %2"), _name, newpath) << endmsg;
+ return -1;
+ }
+
+ _name = Glib::path_get_basename (newpath);
+ _path = newpath;
+
+ return 0;
+}
+
+void
+FileSource::set_search_path (DataType type, const ustring& p)
+{
+ search_paths[type] = p;
+}
+
+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));
+ }
+}
+