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/mountpoint.h"
32 #include "pbd/stl_delete.h"
33 #include "pbd/strsplit.h"
34 #include "pbd/shortpath.h"
35 #include "pbd/enumwriter.h"
37 #include <glibmm/miscutils.h>
38 #include <glibmm/fileutils.h>
39 #include <glibmm/thread.h>
41 #include "ardour/file_source.h"
42 #include "ardour/directory_names.h"
43 #include "ardour/session.h"
44 #include "ardour/session_directory.h"
45 #include "ardour/source_factory.h"
46 #include "ardour/filename_extensions.h"
51 using namespace ARDOUR;
55 PBD::Signal3<int,std::string,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
57 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
58 : Source(session, type, path, flag)
65 set_within_session_from_path (path);
70 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
71 : Source (session, node)
72 , _file_is_new (false)
74 /* this setting of _path is temporary - we expect derived classes
75 to call ::init() which will actually locate the file
76 and reset _path and _within_session correctly.
80 _within_session = true;
86 FileSource::prevent_deletion ()
88 /* if this file already exists, it cannot be removed, ever
91 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
92 if (!(_flags & Destructive)) {
95 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
101 FileSource::removable () const
103 bool r = ((_flags & Removable)
104 && ((_flags & RemoveAtDestroy) ||
105 ((_flags & RemovableIfEmpty) && empty() == 0)));
111 FileSource::init (const string& pathstr, bool must_exist)
113 _timeline_position = 0;
115 if (Stateful::loading_state_version < 3000) {
116 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
117 throw MissingSource (pathstr, _type);
120 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
121 throw MissingSource (pathstr, _type);
125 set_within_session_from_path (_path);
127 if (!within_session()) {
128 _session.ensure_search_path_includes (Glib::path_get_dirname (_path), _type);
131 _name = Glib::path_get_basename (_path);
133 if (_file_is_new && must_exist) {
141 FileSource::set_state (const XMLNode& node, int /*version*/)
143 const XMLProperty* prop;
145 if ((prop = node.property (X_("channel"))) != 0) {
146 _channel = atoi (prop->value());
151 if ((prop = node.property (X_("origin"))) != 0) {
152 _origin = prop->value();
159 FileSource::mark_take (const string& id)
167 FileSource::move_to_trash (const string& trash_dir_name)
169 if (!within_session() || !writable()) {
173 /* don't move the file across filesystems, just stick it in the
174 trash_dir_name directory on whichever filesystem it was already on
178 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
179 v.push_back (trash_dir_name);
180 v.push_back (Glib::path_get_basename (_path));
182 string newpath = Glib::build_filename (v);
184 /* the new path already exists, try versioning */
186 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
187 char buf[PATH_MAX+1];
191 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
194 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
195 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
199 if (version == 999) {
200 PBD::error << string_compose (
201 _("there are already 1000 files with names like %1; versioning discontinued"),
208 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
209 PBD::error << string_compose (
210 _("cannot rename file source from %1 to %2 (%3)"),
211 _path, newpath, strerror (errno)) << endmsg;
215 if (move_dependents_to_trash() != 0) {
216 /* try to back out */
217 rename (newpath.c_str(), _path.c_str());
223 /* file can not be removed twice, since the operation is not idempotent */
224 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
229 /** Find the actual source file based on \a filename.
231 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
232 * If the source is external, \a filename should be a full path.
233 * In either case, found_path is set to the complete absolute path of the source file.
234 * \return true iff the file was found.
237 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
238 bool& isnew, uint16_t& chan, string& found_path)
245 if (!Glib::path_is_absolute (path)) {
250 string search_path = s.source_search_path (type);
252 if (search_path.length() == 0) {
253 error << _("FileSource: search path not set") << endmsg;
257 split (search_path, dirs, ':');
261 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
263 fullpath = Glib::build_filename (*i, path);
265 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
267 hits.push_back (fullpath);
271 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
272 in the session path it is possible to arrive at the same file via more than one path.
275 vector<string> de_duped_hits;
277 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
279 vector<string>::iterator j = i;
282 while (j != hits.end()) {
285 int const rA = stat (i->c_str(), &bufA);
287 int const rB = stat (j->c_str(), &bufB);
289 if (rA == 0 && rB == 0 && bufA.st_ino == bufB.st_ino) {
290 /* *i and *j are the same file; break out of the loop early */
297 if (j == hits.end ()) {
298 de_duped_hits.push_back (*i);
302 if (de_duped_hits.size() > 1) {
304 /* more than one match: ask the user */
306 int which = FileSource::AmbiguousFileName (path, search_path, de_duped_hits).get_value_or (-1);
311 keeppath = de_duped_hits[which];
314 } else if (de_duped_hits.size() == 0) {
316 /* no match: error */
319 error << string_compose(
320 _("Filesource: cannot find required file (%1): while searching %2"),
321 path, search_path) << endmsg;
328 /* only one match: happy days */
330 keeppath = de_duped_hits[0];
337 /* Current find() is unable to parse relative path names to yet non-existant
338 sources. QuickFix(tm)
340 if (keeppath == "") {
342 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
348 found_path = keeppath;
356 /** Find the actual source file based on \a filename.
358 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
359 * If the source is external, \a filename should be a full path.
360 * In either case, found_path is set to the complete absolute path of the source file.
361 * \return true iff the file was found.
364 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
365 bool& isnew, uint16_t& chan, string& found_path)
367 string search_path = s.source_search_path (type);
369 string pathstr = path;
370 string::size_type pos;
375 if (!Glib::path_is_absolute (pathstr)) {
377 /* non-absolute pathname: find pathstr in search path */
384 if (search_path.length() == 0) {
385 error << _("FileSource: search path not set") << endmsg;
389 split (search_path, dirs, ':');
393 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
395 fullpath = Glib::build_filename (*i, pathstr);
397 /* i (paul) made a nasty design error by using ':' as a special character in
398 Ardour 0.99 .. this hack tries to make things sort of work.
401 if ((pos = pathstr.find_last_of (':')) != string::npos) {
403 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
405 /* its a real file, no problem */
414 /* might be an older session using file:channel syntax. see if the version
415 without the :suffix exists
418 string shorter = pathstr.substr (0, pos);
419 fullpath = Glib::build_filename (*i, shorter);
421 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
422 chan = atoi (pathstr.substr (pos+1));
430 /* new derived file (e.g. for timefx) being created in a newer session */
437 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
446 error << string_compose (
447 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
448 pathstr, search_path) << endmsg;
451 } else if (cnt == 0) {
454 error << string_compose(
455 _("Filesource: cannot find required file (%1): while searching %2"),
456 pathstr, search_path) << endmsg;
463 /* Current find() is unable to parse relative path names to yet non-existant
464 sources. QuickFix(tm) */
465 if (keeppath == "") {
467 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
473 found_path = keeppath;
479 /* external files and/or very very old style sessions include full paths */
481 /* ugh, handle ':' situation */
483 if ((pos = pathstr.find_last_of (':')) != string::npos) {
485 string shorter = pathstr.substr (0, pos);
487 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
488 chan = atoi (pathstr.substr (pos+1));
493 found_path = pathstr;
495 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
497 /* file does not exist or we cannot read it */
500 error << string_compose(
501 _("Filesource: cannot find required file (%1): %2"),
502 path, strerror (errno)) << endmsg;
506 if (errno != ENOENT) {
507 error << string_compose(
508 _("Filesource: cannot check for existing file (%1): %2"),
509 path, strerror (errno)) << endmsg;
529 FileSource::set_source_name (const string& newname, bool destructive)
531 Glib::Mutex::Lock lm (_lock);
532 string oldpath = _path;
533 string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
535 if (newpath.empty()) {
536 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
540 // Test whether newpath exists, if yes notify the user but continue.
541 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
542 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;
546 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
547 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
551 _name = Glib::path_get_basename (newpath);
558 FileSource::mark_immutable ()
560 /* destructive sources stay writable, and their other flags don't change. */
561 if (!(_flags & Destructive)) {
562 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
567 FileSource::mark_nonremovable ()
569 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
573 FileSource::set_within_session_from_path (const std::string& path)
575 _within_session = _session.path_is_within_session (path);
579 FileSource::set_path (const std::string& newpath)
585 FileSource::inc_use_count ()
587 Source::inc_use_count ();