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::Signal2<int,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)
59 , _file_is_new (!origin.empty()) // origin empty => new file VS. origin !empty => new file
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;
114 if (Stateful::loading_state_version < 3000) {
115 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
116 throw MissingSource (pathstr, _type);
119 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
120 throw MissingSource (pathstr, _type);
124 set_within_session_from_path (_path);
126 _name = Glib::path_get_basename (_path);
129 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
130 throw MissingSource (pathstr, _type);
138 FileSource::set_state (const XMLNode& node, int /*version*/)
140 const XMLProperty* prop;
142 if ((prop = node.property (X_("channel"))) != 0) {
143 _channel = atoi (prop->value());
148 if ((prop = node.property (X_("origin"))) != 0) {
149 _origin = prop->value();
156 FileSource::mark_take (const string& id)
164 FileSource::move_to_trash (const string& trash_dir_name)
166 if (!within_session() || !writable()) {
170 /* don't move the file across filesystems, just stick it in the
171 trash_dir_name directory on whichever filesystem it was already on
175 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
176 v.push_back (trash_dir_name);
177 v.push_back (Glib::path_get_basename (_path));
179 string newpath = Glib::build_filename (v);
181 /* the new path already exists, try versioning */
183 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
184 char buf[PATH_MAX+1];
188 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
191 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
192 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
196 if (version == 999) {
197 PBD::error << string_compose (
198 _("there are already 1000 files with names like %1; versioning discontinued"),
205 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
206 PBD::error << string_compose (
207 _("cannot rename file source from %1 to %2 (%3)"),
208 _path, newpath, strerror (errno)) << endmsg;
212 if (move_dependents_to_trash() != 0) {
213 /* try to back out */
214 rename (newpath.c_str(), _path.c_str());
220 /* file can not be removed twice, since the operation is not idempotent */
221 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
226 /** Find the actual source file based on \a filename.
228 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
229 * If the source is external, \a filename should be a full path.
230 * In either case, found_path is set to the complete absolute path of the source file.
231 * \return true iff the file was found.
234 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
235 bool& isnew, uint16_t& /* chan */, string& found_path)
242 if (!Glib::path_is_absolute (path)) {
245 std::vector<std::string> dirs = s.source_search_path (type);
247 if (dirs.size() == 0) {
248 error << _("FileSource: search path not set") << endmsg;
254 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
256 fullpath = Glib::build_filename (*i, path);
258 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
260 hits.push_back (fullpath);
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.
267 I suppose this is not necessary on Windows.
270 vector<string> de_duped_hits;
272 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
274 vector<string>::iterator j = i;
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 */
286 if (j == hits.end ()) {
287 de_duped_hits.push_back (*i);
291 if (de_duped_hits.size() > 1) {
293 /* more than one match: ask the user */
295 int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1);
300 keeppath = de_duped_hits[which];
303 } else if (de_duped_hits.size() == 0) {
305 /* no match: error */
308 error << string_compose(
309 _("Filesource: cannot find required file (%1)"), path) << endmsg;
316 /* only one match: happy days */
318 keeppath = de_duped_hits[0];
325 /* Current find() is unable to parse relative path names to yet non-existant
326 sources. QuickFix(tm)
328 if (keeppath == "") {
330 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
336 found_path = keeppath;
344 /** Find the actual source file based on \a filename.
346 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
347 * If the source is external, \a filename should be a full path.
348 * In either case, found_path is set to the complete absolute path of the source file.
349 * \return true iff the file was found.
352 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
353 bool& isnew, uint16_t& chan, string& found_path)
355 string pathstr = path;
356 string::size_type pos;
361 if (!Glib::path_is_absolute (pathstr)) {
363 /* non-absolute pathname: find pathstr in search path */
365 vector<string> dirs = s.source_search_path (type);
371 if (dirs.size() == 0) {
372 error << _("FileSource: search path not set") << endmsg;
378 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
380 fullpath = Glib::build_filename (*i, pathstr);
382 /* i (paul) made a nasty design error by using ':' as a special character in
383 Ardour 0.99 .. this hack tries to make things sort of work.
386 if ((pos = pathstr.find_last_of (':')) != string::npos) {
388 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
390 /* its a real file, no problem */
399 /* might be an older session using file:channel syntax. see if the version
400 without the :suffix exists
403 string shorter = pathstr.substr (0, pos);
404 fullpath = Glib::build_filename (*i, shorter);
406 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
407 chan = atoi (pathstr.substr (pos+1));
415 /* new derived file (e.g. for timefx) being created in a newer session */
422 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
431 error << string_compose (
432 _("FileSource: \"%1\" is ambigous when searching\n\t"), pathstr) << endmsg;
435 } else if (cnt == 0) {
438 error << string_compose(
439 _("Filesource: cannot find required file (%1): while searching")
440 , pathstr) << endmsg;
447 /* Current find() is unable to parse relative path names to yet non-existant
448 sources. QuickFix(tm) */
449 if (keeppath == "") {
451 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
457 found_path = keeppath;
463 /* external files and/or very very old style sessions include full paths */
465 /* ugh, handle ':' situation */
467 if ((pos = pathstr.find_last_of (':')) != string::npos) {
469 string shorter = pathstr.substr (0, pos);
471 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
472 chan = atoi (pathstr.substr (pos+1));
477 found_path = pathstr;
479 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
481 /* file does not exist or we cannot read it */
484 error << string_compose(
485 _("Filesource: cannot find required file (%1): %2"),
486 path, strerror (errno)) << endmsg;
490 #ifndef PLATFORM_WINDOWS
491 if (errno != ENOENT) {
492 error << string_compose(
493 _("Filesource: cannot check for existing file (%1): %2"),
494 path, strerror (errno)) << endmsg;
514 FileSource::set_source_name (const string& newname, bool destructive)
516 Glib::Threads::Mutex::Lock lm (_lock);
517 string oldpath = _path;
518 string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
520 if (newpath.empty()) {
521 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
525 // Test whether newpath exists, if yes notify the user but continue.
526 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
527 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;
531 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
532 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
536 _name = Glib::path_get_basename (newpath);
543 FileSource::mark_immutable ()
545 /* destructive sources stay writable, and their other flags don't change. */
546 if (!(_flags & Destructive)) {
547 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
552 FileSource::mark_immutable_except_write ()
554 /* destructive sources stay writable, and their other flags don't change. */
555 if (!(_flags & Destructive)) {
556 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
561 FileSource::mark_nonremovable ()
563 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
567 FileSource::set_within_session_from_path (const std::string& path)
569 _within_session = _session.path_is_within_session (path);
573 FileSource::set_path (const std::string& newpath)
579 FileSource::inc_use_count ()
581 Source::inc_use_count ();