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/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;
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 if (!within_session()) {
127 _session.ensure_search_path_includes (Glib::path_get_dirname (_path), _type);
130 _name = Glib::path_get_basename (_path);
132 if (_file_is_new && must_exist) {
140 FileSource::set_state (const XMLNode& node, int /*version*/)
142 const XMLProperty* prop;
144 if ((prop = node.property (X_("channel"))) != 0) {
145 _channel = atoi (prop->value());
150 if ((prop = node.property (X_("origin"))) != 0) {
151 _origin = prop->value();
158 FileSource::mark_take (const string& id)
166 FileSource::move_to_trash (const string& trash_dir_name)
168 if (!within_session() || !writable()) {
172 /* don't move the file across filesystems, just stick it in the
173 trash_dir_name directory on whichever filesystem it was already on
177 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
178 v.push_back (trash_dir_name);
179 v.push_back (Glib::path_get_basename (_path));
181 string newpath = Glib::build_filename (v);
183 /* the new path already exists, try versioning */
185 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
186 char buf[PATH_MAX+1];
190 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
193 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
194 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
198 if (version == 999) {
199 PBD::error << string_compose (
200 _("there are already 1000 files with names like %1; versioning discontinued"),
207 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
208 PBD::error << string_compose (
209 _("cannot rename file source from %1 to %2 (%3)"),
210 _path, newpath, strerror (errno)) << endmsg;
214 if (move_dependents_to_trash() != 0) {
215 /* try to back out */
216 rename (newpath.c_str(), _path.c_str());
222 /* file can not be removed twice, since the operation is not idempotent */
223 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
228 /** Find the actual source file based on \a filename.
230 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
231 * If the source is external, \a filename should be a full path.
232 * In either case, found_path is set to the complete absolute path of the source file.
233 * \return true iff the file was found.
236 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
237 bool& isnew, uint16_t& /* chan */, string& found_path)
244 if (!Glib::path_is_absolute (path)) {
249 string search_path = s.source_search_path (type);
251 if (search_path.length() == 0) {
252 error << _("FileSource: search path not set") << endmsg;
256 split (search_path, dirs, ':');
260 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
262 fullpath = Glib::build_filename (*i, path);
264 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
266 hits.push_back (fullpath);
270 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
271 in the session path it is possible to arrive at the same file via more than one path.
273 I suppose this is not necessary on Windows.
276 vector<string> de_duped_hits;
278 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
280 vector<string>::iterator j = i;
283 while (j != hits.end()) {
284 if (inodes_same (*i, *j)) {
285 /* *i and *j are the same file; break out of the loop early */
292 if (j == hits.end ()) {
293 de_duped_hits.push_back (*i);
297 if (de_duped_hits.size() > 1) {
299 /* more than one match: ask the user */
301 int which = FileSource::AmbiguousFileName (path, search_path, de_duped_hits).get_value_or (-1);
306 keeppath = de_duped_hits[which];
309 } else if (de_duped_hits.size() == 0) {
311 /* no match: error */
314 error << string_compose(
315 _("Filesource: cannot find required file (%1): while searching %2"),
316 path, search_path) << endmsg;
323 /* only one match: happy days */
325 keeppath = de_duped_hits[0];
332 /* Current find() is unable to parse relative path names to yet non-existant
333 sources. QuickFix(tm)
335 if (keeppath == "") {
337 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
343 found_path = keeppath;
351 /** Find the actual source file based on \a filename.
353 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
354 * If the source is external, \a filename should be a full path.
355 * In either case, found_path is set to the complete absolute path of the source file.
356 * \return true iff the file was found.
359 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
360 bool& isnew, uint16_t& chan, string& found_path)
362 string search_path = s.source_search_path (type);
364 string pathstr = path;
365 string::size_type pos;
370 if (!Glib::path_is_absolute (pathstr)) {
372 /* non-absolute pathname: find pathstr in search path */
379 if (search_path.length() == 0) {
380 error << _("FileSource: search path not set") << endmsg;
384 split (search_path, dirs, ':');
388 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
390 fullpath = Glib::build_filename (*i, pathstr);
392 /* i (paul) made a nasty design error by using ':' as a special character in
393 Ardour 0.99 .. this hack tries to make things sort of work.
396 if ((pos = pathstr.find_last_of (':')) != string::npos) {
398 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
400 /* its a real file, no problem */
409 /* might be an older session using file:channel syntax. see if the version
410 without the :suffix exists
413 string shorter = pathstr.substr (0, pos);
414 fullpath = Glib::build_filename (*i, shorter);
416 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
417 chan = atoi (pathstr.substr (pos+1));
425 /* new derived file (e.g. for timefx) being created in a newer session */
432 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
441 error << string_compose (
442 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
443 pathstr, search_path) << endmsg;
446 } else if (cnt == 0) {
449 error << string_compose(
450 _("Filesource: cannot find required file (%1): while searching %2"),
451 pathstr, search_path) << endmsg;
458 /* Current find() is unable to parse relative path names to yet non-existant
459 sources. QuickFix(tm) */
460 if (keeppath == "") {
462 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
468 found_path = keeppath;
474 /* external files and/or very very old style sessions include full paths */
476 /* ugh, handle ':' situation */
478 if ((pos = pathstr.find_last_of (':')) != string::npos) {
480 string shorter = pathstr.substr (0, pos);
482 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
483 chan = atoi (pathstr.substr (pos+1));
488 found_path = pathstr;
490 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
492 /* file does not exist or we cannot read it */
495 error << string_compose(
496 _("Filesource: cannot find required file (%1): %2"),
497 path, strerror (errno)) << endmsg;
501 if (errno != ENOENT) {
502 error << string_compose(
503 _("Filesource: cannot check for existing file (%1): %2"),
504 path, strerror (errno)) << endmsg;
524 FileSource::set_source_name (const string& newname, bool destructive)
526 Glib::Mutex::Lock lm (_lock);
527 string oldpath = _path;
528 string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
530 if (newpath.empty()) {
531 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
535 // Test whether newpath exists, if yes notify the user but continue.
536 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
537 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;
541 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
542 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
546 _name = Glib::path_get_basename (newpath);
553 FileSource::mark_immutable ()
555 /* destructive sources stay writable, and their other flags don't change. */
556 if (!(_flags & Destructive)) {
557 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
562 FileSource::mark_nonremovable ()
564 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
568 FileSource::set_within_session_from_path (const std::string& path)
570 _within_session = _session.path_is_within_session (path);
574 FileSource::set_path (const std::string& newpath)
580 FileSource::inc_use_count ()
582 Source::inc_use_count ();