2 Copyright (C) 2006-2009 Paul Davis
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.
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.
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.
24 #include <stdio.h> // for rename(), sigh
29 #include "pbd/convert.h"
30 #include "pbd/basename.h"
31 #include "pbd/stl_delete.h"
32 #include "pbd/strsplit.h"
33 #include "pbd/shortpath.h"
34 #include "pbd/enumwriter.h"
35 #include "pbd/file_utils.h"
37 #include <glibmm/miscutils.h>
38 #include <glibmm/fileutils.h>
39 #include <glibmm/threads.h>
41 #include "ardour/data_type.h"
42 #include "ardour/file_source.h"
43 #include "ardour/session.h"
44 #include "ardour/source.h"
45 #include "ardour/utils.h"
50 using namespace ARDOUR;
54 PBD::Signal3<int,std::string,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
56 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
57 : Source(session, type, path, flag)
64 set_within_session_from_path (path);
69 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
70 : Source (session, node)
71 , _file_is_new (false)
73 /* this setting of _path is temporary - we expect derived classes
74 to call ::init() which will actually locate the file
75 and reset _path and _within_session correctly.
79 _within_session = true;
85 FileSource::prevent_deletion ()
87 /* if this file already exists, it cannot be removed, ever
90 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
91 if (!(_flags & Destructive)) {
94 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
100 FileSource::removable () const
102 bool r = ((_flags & Removable)
103 && ((_flags & RemoveAtDestroy) ||
104 ((_flags & RemovableIfEmpty) && empty() == 0)));
110 FileSource::init (const string& pathstr, bool must_exist)
112 _timeline_position = 0;
116 if (Stateful::loading_state_version < 3000) {
117 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
118 throw MissingSource (pathstr, _type);
123 if (!_origin.empty()) {
129 if (!find (_session, _type, look_for, must_exist, _file_is_new, _channel, _path)) {
130 throw MissingSource (pathstr, _type);
135 set_within_session_from_path (_path);
137 _name = Glib::path_get_basename (_path);
139 if (_file_is_new && must_exist) {
147 FileSource::set_state (const XMLNode& node, int /*version*/)
149 const XMLProperty* prop;
151 if ((prop = node.property (X_("channel"))) != 0) {
152 _channel = atoi (prop->value());
157 if ((prop = node.property (X_("origin"))) != 0) {
158 _origin = prop->value();
165 FileSource::mark_take (const string& id)
173 FileSource::move_to_trash (const string& trash_dir_name)
175 if (!within_session() || !writable()) {
179 /* don't move the file across filesystems, just stick it in the
180 trash_dir_name directory on whichever filesystem it was already on
184 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
185 v.push_back (trash_dir_name);
186 v.push_back (Glib::path_get_basename (_path));
188 string newpath = Glib::build_filename (v);
190 /* the new path already exists, try versioning */
192 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
193 char buf[PATH_MAX+1];
197 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
200 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
201 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
205 if (version == 999) {
206 PBD::error << string_compose (
207 _("there are already 1000 files with names like %1; versioning discontinued"),
214 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
215 PBD::error << string_compose (
216 _("cannot rename file source from %1 to %2 (%3)"),
217 _path, newpath, strerror (errno)) << endmsg;
221 if (move_dependents_to_trash() != 0) {
222 /* try to back out */
223 rename (newpath.c_str(), _path.c_str());
229 /* file can not be removed twice, since the operation is not idempotent */
230 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
235 /** Find the actual source file based on \a filename.
237 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
238 * If the source is external, \a filename should be a full path.
239 * In either case, found_path is set to the complete absolute path of the source file.
240 * \return true iff the file was found.
243 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
244 bool& isnew, uint16_t& /* chan */, string& found_path)
251 if (!Glib::path_is_absolute (path)) {
256 string search_path = s.source_search_path (type);
258 if (search_path.length() == 0) {
259 error << _("FileSource: search path not set") << endmsg;
263 split (search_path, dirs, ':');
267 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
269 fullpath = Glib::build_filename (*i, path);
271 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
273 hits.push_back (fullpath);
277 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
278 in the session path it is possible to arrive at the same file via more than one path.
280 I suppose this is not necessary on Windows.
283 vector<string> de_duped_hits;
285 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
287 vector<string>::iterator j = i;
290 while (j != hits.end()) {
291 if (PBD::equivalent_paths (*i, *j)) {
292 /* *i and *j are the same file; break out of the loop early */
299 if (j == hits.end ()) {
300 de_duped_hits.push_back (*i);
304 if (de_duped_hits.size() > 1) {
306 /* more than one match: ask the user */
308 int which = FileSource::AmbiguousFileName (path, search_path, de_duped_hits).get_value_or (-1);
313 keeppath = de_duped_hits[which];
316 } else if (de_duped_hits.size() == 0) {
318 /* no match: error */
321 error << string_compose(
322 _("Filesource: cannot find required file (%1): while searching %2"),
323 path, search_path) << endmsg;
330 /* only one match: happy days */
332 keeppath = de_duped_hits[0];
339 /* Current find() is unable to parse relative path names to yet non-existant
340 sources. QuickFix(tm)
342 if (keeppath == "") {
344 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
350 found_path = keeppath;
358 /** Find the actual source file based on \a filename.
360 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
361 * If the source is external, \a filename should be a full path.
362 * In either case, found_path is set to the complete absolute path of the source file.
363 * \return true iff the file was found.
366 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
367 bool& isnew, uint16_t& chan, string& found_path)
369 string search_path = s.source_search_path (type);
371 string pathstr = path;
372 string::size_type pos;
377 if (!Glib::path_is_absolute (pathstr)) {
379 /* non-absolute pathname: find pathstr in search path */
386 if (search_path.length() == 0) {
387 error << _("FileSource: search path not set") << endmsg;
391 split (search_path, dirs, ':');
395 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
397 fullpath = Glib::build_filename (*i, pathstr);
399 /* i (paul) made a nasty design error by using ':' as a special character in
400 Ardour 0.99 .. this hack tries to make things sort of work.
403 if ((pos = pathstr.find_last_of (':')) != string::npos) {
405 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
407 /* its a real file, no problem */
416 /* might be an older session using file:channel syntax. see if the version
417 without the :suffix exists
420 string shorter = pathstr.substr (0, pos);
421 fullpath = Glib::build_filename (*i, shorter);
423 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
424 chan = atoi (pathstr.substr (pos+1));
432 /* new derived file (e.g. for timefx) being created in a newer session */
439 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
448 error << string_compose (
449 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
450 pathstr, search_path) << endmsg;
453 } else if (cnt == 0) {
456 error << string_compose(
457 _("Filesource: cannot find required file (%1): while searching %2"),
458 pathstr, search_path) << endmsg;
465 /* Current find() is unable to parse relative path names to yet non-existant
466 sources. QuickFix(tm) */
467 if (keeppath == "") {
469 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
475 found_path = keeppath;
481 /* external files and/or very very old style sessions include full paths */
483 /* ugh, handle ':' situation */
485 if ((pos = pathstr.find_last_of (':')) != string::npos) {
487 string shorter = pathstr.substr (0, pos);
489 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
490 chan = atoi (pathstr.substr (pos+1));
495 found_path = pathstr;
497 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
499 /* file does not exist or we cannot read it */
502 error << string_compose(
503 _("Filesource: cannot find required file (%1): %2"),
504 path, strerror (errno)) << endmsg;
508 if (errno != ENOENT) {
509 error << string_compose(
510 _("Filesource: cannot check for existing file (%1): %2"),
511 path, strerror (errno)) << endmsg;
531 FileSource::set_source_name (const string& newname, bool destructive)
533 Glib::Threads::Mutex::Lock lm (_lock);
534 string oldpath = _path;
535 string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
537 if (newpath.empty()) {
538 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
542 // Test whether newpath exists, if yes notify the user but continue.
543 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
544 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;
548 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
549 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
553 _name = Glib::path_get_basename (newpath);
560 FileSource::mark_immutable ()
562 /* destructive sources stay writable, and their other flags don't change. */
563 if (!(_flags & Destructive)) {
564 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
569 FileSource::mark_immutable_except_write ()
571 /* destructive sources stay writable, and their other flags don't change. */
572 if (!(_flags & Destructive)) {
573 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
578 FileSource::mark_nonremovable ()
580 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
584 FileSource::set_within_session_from_path (const std::string& path)
586 _within_session = _session.path_is_within_session (path);
590 FileSource::set_path (const std::string& newpath)
596 FileSource::inc_use_count ()
598 Source::inc_use_count ();