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"
36 #include "pbd/filesystem.h"
38 #include <glibmm/miscutils.h>
39 #include <glibmm/fileutils.h>
40 #include <glibmm/thread.h>
42 #include "ardour/data_type.h"
43 #include "ardour/file_source.h"
44 #include "ardour/session.h"
45 #include "ardour/source.h"
46 #include "ardour/utils.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 _name = Glib::path_get_basename (_path);
129 if (_file_is_new && must_exist) {
137 FileSource::set_state (const XMLNode& node, int /*version*/)
139 const XMLProperty* prop;
141 if ((prop = node.property (X_("channel"))) != 0) {
142 _channel = atoi (prop->value());
147 if ((prop = node.property (X_("origin"))) != 0) {
148 _origin = prop->value();
155 FileSource::mark_take (const string& id)
163 FileSource::move_to_trash (const string& trash_dir_name)
165 if (!within_session() || !writable()) {
169 /* don't move the file across filesystems, just stick it in the
170 trash_dir_name directory on whichever filesystem it was already on
174 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
175 v.push_back (trash_dir_name);
176 v.push_back (Glib::path_get_basename (_path));
178 string newpath = Glib::build_filename (v);
180 /* the new path already exists, try versioning */
182 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
183 char buf[PATH_MAX+1];
187 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
190 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
191 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
195 if (version == 999) {
196 PBD::error << string_compose (
197 _("there are already 1000 files with names like %1; versioning discontinued"),
204 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
205 PBD::error << string_compose (
206 _("cannot rename file source from %1 to %2 (%3)"),
207 _path, newpath, strerror (errno)) << endmsg;
211 if (move_dependents_to_trash() != 0) {
212 /* try to back out */
213 rename (newpath.c_str(), _path.c_str());
219 /* file can not be removed twice, since the operation is not idempotent */
220 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
225 /** Find the actual source file based on \a filename.
227 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
228 * If the source is external, \a filename should be a full path.
229 * In either case, found_path is set to the complete absolute path of the source file.
230 * \return true iff the file was found.
233 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
234 bool& isnew, uint16_t& /* chan */, string& found_path)
241 if (!Glib::path_is_absolute (path)) {
246 string search_path = s.source_search_path (type);
248 if (search_path.length() == 0) {
249 error << _("FileSource: search path not set") << endmsg;
253 split (search_path, dirs, ':');
257 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
259 fullpath = Glib::build_filename (*i, path);
261 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
263 hits.push_back (fullpath);
267 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
268 in the session path it is possible to arrive at the same file via more than one path.
270 I suppose this is not necessary on Windows.
273 vector<string> de_duped_hits;
275 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
277 vector<string>::iterator j = i;
280 while (j != hits.end()) {
281 if (PBD::sys::inodes_same (*i, *j)) {
282 /* *i and *j are the same file; break out of the loop early */
289 if (j == hits.end ()) {
290 de_duped_hits.push_back (*i);
294 if (de_duped_hits.size() > 1) {
296 /* more than one match: ask the user */
298 int which = FileSource::AmbiguousFileName (path, search_path, de_duped_hits).get_value_or (-1);
303 keeppath = de_duped_hits[which];
306 } else if (de_duped_hits.size() == 0) {
308 /* no match: error */
311 error << string_compose(
312 _("Filesource: cannot find required file (%1): while searching %2"),
313 path, search_path) << endmsg;
320 /* only one match: happy days */
322 keeppath = de_duped_hits[0];
329 /* Current find() is unable to parse relative path names to yet non-existant
330 sources. QuickFix(tm)
332 if (keeppath == "") {
334 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
340 found_path = keeppath;
348 /** Find the actual source file based on \a filename.
350 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
351 * If the source is external, \a filename should be a full path.
352 * In either case, found_path is set to the complete absolute path of the source file.
353 * \return true iff the file was found.
356 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
357 bool& isnew, uint16_t& chan, string& found_path)
359 string search_path = s.source_search_path (type);
361 string pathstr = path;
362 string::size_type pos;
367 if (!Glib::path_is_absolute (pathstr)) {
369 /* non-absolute pathname: find pathstr in search path */
376 if (search_path.length() == 0) {
377 error << _("FileSource: search path not set") << endmsg;
381 split (search_path, dirs, ':');
385 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
387 fullpath = Glib::build_filename (*i, pathstr);
389 /* i (paul) made a nasty design error by using ':' as a special character in
390 Ardour 0.99 .. this hack tries to make things sort of work.
393 if ((pos = pathstr.find_last_of (':')) != string::npos) {
395 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
397 /* its a real file, no problem */
406 /* might be an older session using file:channel syntax. see if the version
407 without the :suffix exists
410 string shorter = pathstr.substr (0, pos);
411 fullpath = Glib::build_filename (*i, shorter);
413 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
414 chan = atoi (pathstr.substr (pos+1));
422 /* new derived file (e.g. for timefx) being created in a newer session */
429 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
438 error << string_compose (
439 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
440 pathstr, search_path) << endmsg;
443 } else if (cnt == 0) {
446 error << string_compose(
447 _("Filesource: cannot find required file (%1): while searching %2"),
448 pathstr, search_path) << endmsg;
455 /* Current find() is unable to parse relative path names to yet non-existant
456 sources. QuickFix(tm) */
457 if (keeppath == "") {
459 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
465 found_path = keeppath;
471 /* external files and/or very very old style sessions include full paths */
473 /* ugh, handle ':' situation */
475 if ((pos = pathstr.find_last_of (':')) != string::npos) {
477 string shorter = pathstr.substr (0, pos);
479 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
480 chan = atoi (pathstr.substr (pos+1));
485 found_path = pathstr;
487 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
489 /* file does not exist or we cannot read it */
492 error << string_compose(
493 _("Filesource: cannot find required file (%1): %2"),
494 path, strerror (errno)) << endmsg;
498 if (errno != ENOENT) {
499 error << string_compose(
500 _("Filesource: cannot check for existing file (%1): %2"),
501 path, strerror (errno)) << endmsg;
521 FileSource::set_source_name (const string& newname, bool destructive)
523 Glib::Mutex::Lock lm (_lock);
524 string oldpath = _path;
525 string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
527 if (newpath.empty()) {
528 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
532 // Test whether newpath exists, if yes notify the user but continue.
533 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
534 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;
538 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
539 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
543 _name = Glib::path_get_basename (newpath);
550 FileSource::mark_immutable ()
552 /* destructive sources stay writable, and their other flags don't change. */
553 if (!(_flags & Destructive)) {
554 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
559 FileSource::mark_nonremovable ()
561 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
565 FileSource::set_within_session_from_path (const std::string& path)
567 _within_session = _session.path_is_within_session (path);
571 FileSource::set_path (const std::string& newpath)
577 FileSource::inc_use_count ()
579 Source::inc_use_count ();