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.
274 I suppose this is not necessary on Windows.
277 vector<string> de_duped_hits;
279 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
281 vector<string>::iterator j = i;
284 while (j != hits.end()) {
285 if (inodes_same (*i, *j)) {
286 /* *i and *j are the same file; break out of the loop early */
293 if (j == hits.end ()) {
294 de_duped_hits.push_back (*i);
298 if (de_duped_hits.size() > 1) {
300 /* more than one match: ask the user */
302 int which = FileSource::AmbiguousFileName (path, search_path, de_duped_hits).get_value_or (-1);
307 keeppath = de_duped_hits[which];
310 } else if (de_duped_hits.size() == 0) {
312 /* no match: error */
315 error << string_compose(
316 _("Filesource: cannot find required file (%1): while searching %2"),
317 path, search_path) << endmsg;
324 /* only one match: happy days */
326 keeppath = de_duped_hits[0];
333 /* Current find() is unable to parse relative path names to yet non-existant
334 sources. QuickFix(tm)
336 if (keeppath == "") {
338 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
344 found_path = keeppath;
352 /** Find the actual source file based on \a filename.
354 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
355 * If the source is external, \a filename should be a full path.
356 * In either case, found_path is set to the complete absolute path of the source file.
357 * \return true iff the file was found.
360 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
361 bool& isnew, uint16_t& chan, string& found_path)
363 string search_path = s.source_search_path (type);
365 string pathstr = path;
366 string::size_type pos;
371 if (!Glib::path_is_absolute (pathstr)) {
373 /* non-absolute pathname: find pathstr in search path */
380 if (search_path.length() == 0) {
381 error << _("FileSource: search path not set") << endmsg;
385 split (search_path, dirs, ':');
389 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
391 fullpath = Glib::build_filename (*i, pathstr);
393 /* i (paul) made a nasty design error by using ':' as a special character in
394 Ardour 0.99 .. this hack tries to make things sort of work.
397 if ((pos = pathstr.find_last_of (':')) != string::npos) {
399 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
401 /* its a real file, no problem */
410 /* might be an older session using file:channel syntax. see if the version
411 without the :suffix exists
414 string shorter = pathstr.substr (0, pos);
415 fullpath = Glib::build_filename (*i, shorter);
417 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
418 chan = atoi (pathstr.substr (pos+1));
426 /* new derived file (e.g. for timefx) being created in a newer session */
433 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
442 error << string_compose (
443 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
444 pathstr, search_path) << endmsg;
447 } else if (cnt == 0) {
450 error << string_compose(
451 _("Filesource: cannot find required file (%1): while searching %2"),
452 pathstr, search_path) << endmsg;
459 /* Current find() is unable to parse relative path names to yet non-existant
460 sources. QuickFix(tm) */
461 if (keeppath == "") {
463 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
469 found_path = keeppath;
475 /* external files and/or very very old style sessions include full paths */
477 /* ugh, handle ':' situation */
479 if ((pos = pathstr.find_last_of (':')) != string::npos) {
481 string shorter = pathstr.substr (0, pos);
483 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
484 chan = atoi (pathstr.substr (pos+1));
489 found_path = pathstr;
491 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
493 /* file does not exist or we cannot read it */
496 error << string_compose(
497 _("Filesource: cannot find required file (%1): %2"),
498 path, strerror (errno)) << endmsg;
502 if (errno != ENOENT) {
503 error << string_compose(
504 _("Filesource: cannot check for existing file (%1): %2"),
505 path, strerror (errno)) << endmsg;
525 FileSource::set_source_name (const string& newname, bool destructive)
527 Glib::Mutex::Lock lm (_lock);
528 string oldpath = _path;
529 string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
531 if (newpath.empty()) {
532 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
536 // Test whether newpath exists, if yes notify the user but continue.
537 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
538 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;
542 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
543 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
547 _name = Glib::path_get_basename (newpath);
554 FileSource::mark_immutable ()
556 /* destructive sources stay writable, and their other flags don't change. */
557 if (!(_flags & Destructive)) {
558 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
563 FileSource::mark_nonremovable ()
565 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
569 FileSource::set_within_session_from_path (const std::string& path)
571 _within_session = _session.path_is_within_session (path);
575 FileSource::set_path (const std::string& newpath)
581 FileSource::inc_use_count ()
583 Source::inc_use_count ();