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()) // if origin is left unspecified (empty string) then file must exist
63 set_within_session_from_path (path);
66 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
67 : Source (session, node)
68 , _file_is_new (false)
71 /* this setting of _path is temporary - we expect derived classes
72 to call ::init() which will actually locate the file
73 and reset _path and _within_session correctly.
77 _within_session = true;
80 FileSource::~FileSource()
85 FileSource::existence_check ()
87 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
93 FileSource::prevent_deletion ()
95 if (!(_flags & Destructive)) {
98 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
103 FileSource::removable () const
105 bool r = ((_flags & Removable)
106 && ((_flags & RemoveAtDestroy) ||
107 ((_flags & RemovableIfEmpty) && empty())));
113 FileSource::init (const string& pathstr, bool must_exist)
115 _timeline_position = 0;
117 if (Stateful::loading_state_version < 3000) {
118 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
119 throw MissingSource (pathstr, _type);
122 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
123 throw MissingSource (pathstr, _type);
127 set_within_session_from_path (_path);
129 _name = Glib::path_get_basename (_path);
132 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
133 throw MissingSource (pathstr, _type);
141 FileSource::set_state (const XMLNode& node, int /*version*/)
143 XMLProperty const * 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)) {
248 std::vector<std::string> dirs = s.source_search_path (type);
250 if (dirs.size() == 0) {
251 error << _("FileSource: search path not set") << endmsg;
255 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
257 fullpath = Glib::build_filename (*i, path);
259 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
261 hits.push_back (fullpath);
265 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
266 in the session path it is possible to arrive at the same file via more than one path.
268 I suppose this is not necessary on Windows.
271 vector<string> de_duped_hits;
273 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
275 vector<string>::iterator j = i;
278 while (j != hits.end()) {
279 if (PBD::equivalent_paths (*i, *j)) {
280 /* *i and *j are the same file; break out of the loop early */
287 if (j == hits.end ()) {
288 de_duped_hits.push_back (*i);
292 if (de_duped_hits.size() > 1) {
294 /* more than one match: ask the user */
296 int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1);
301 keeppath = de_duped_hits[which];
304 } else if (de_duped_hits.size() == 0) {
306 /* no match: error */
309 /* do not generate an error here, leave that to
310 whoever deals with the false return value.
318 /* only one match: happy days */
320 keeppath = de_duped_hits[0];
327 /* Current find() is unable to parse relative path names to yet non-existant
328 sources. QuickFix(tm)
331 if (keeppath.empty()) {
333 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
339 found_path = keeppath;
346 /** Find the actual source file based on \a filename.
348 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
349 * If the source is external, \a filename should be a full path.
350 * In either case, found_path is set to the complete absolute path of the source file.
351 * \return true iff the file was found.
354 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
355 bool& isnew, uint16_t& chan, string& found_path)
357 string pathstr = path;
358 string::size_type pos;
363 if (!Glib::path_is_absolute (pathstr)) {
365 /* non-absolute pathname: find pathstr in search path */
367 vector<string> dirs = s.source_search_path (type);
373 if (dirs.size() == 0) {
374 error << _("FileSource: search path not set") << endmsg;
380 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
382 fullpath = Glib::build_filename (*i, pathstr);
384 /* i (paul) made a nasty design error by using ':' as a special character in
385 Ardour 0.99 .. this hack tries to make things sort of work.
388 if ((pos = pathstr.find_last_of (':')) != string::npos) {
390 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
392 /* its a real file, no problem */
401 /* might be an older session using file:channel syntax. see if the version
402 without the :suffix exists
405 string shorter = pathstr.substr (0, pos);
406 fullpath = Glib::build_filename (*i, shorter);
408 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
409 chan = atoi (pathstr.substr (pos+1));
417 /* new derived file (e.g. for timefx) being created in a newer session */
424 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
433 error << string_compose (
434 _("FileSource: \"%1\" is ambigous when searching\n\t"), pathstr) << endmsg;
437 } else if (cnt == 0) {
440 error << string_compose(
441 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
448 /* Current find() is unable to parse relative path names to yet non-existant
449 sources. QuickFix(tm) */
450 if (keeppath == "") {
452 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
458 found_path = keeppath;
464 /* external files and/or very very old style sessions include full paths */
466 /* ugh, handle ':' situation */
468 if ((pos = pathstr.find_last_of (':')) != string::npos) {
470 string shorter = pathstr.substr (0, pos);
472 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
473 chan = atoi (pathstr.substr (pos+1));
478 found_path = pathstr;
480 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
482 /* file does not exist or we cannot read it */
485 error << string_compose(
486 _("Filesource: cannot find required file (%1): %2"),
487 path, strerror (errno)) << endmsg;
491 #ifndef PLATFORM_WINDOWS
492 if (errno != ENOENT) {
493 error << string_compose(
494 _("Filesource: cannot check for existing file (%1): %2"),
495 path, strerror (errno)) << endmsg;
515 FileSource::mark_immutable ()
517 /* destructive sources stay writable, and their other flags don't change. */
518 if (!(_flags & Destructive)) {
519 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
524 FileSource::mark_immutable_except_write ()
526 /* destructive sources stay writable, and their other flags don't change. */
527 if (!(_flags & Destructive)) {
528 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
533 FileSource::mark_nonremovable ()
535 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
539 FileSource::set_within_session_from_path (const std::string& path)
541 _within_session = _session.path_is_within_session (path);
545 FileSource::set_path (const std::string& newpath)
549 set_within_session_from_path (newpath);
550 if (_within_session) {
551 _origin = Glib::path_get_basename (newpath);
558 FileSource::inc_use_count ()
560 Source::inc_use_count ();
564 FileSource::is_stub () const
574 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
582 FileSource::rename (const string& newpath)
584 Glib::Threads::Mutex::Lock lm (_lock);
585 string oldpath = _path;
587 // Test whether newpath exists, if yes notify the user but continue.
588 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
589 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;
593 if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
594 /* rename only needed if file exists on disk */
595 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
596 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
601 _name = Glib::path_get_basename (newpath);