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 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)) {
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;
347 /** Find the actual source file based on \a filename.
349 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
350 * If the source is external, \a filename should be a full path.
351 * In either case, found_path is set to the complete absolute path of the source file.
352 * \return true iff the file was found.
355 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
356 bool& isnew, uint16_t& chan, string& found_path)
358 string pathstr = path;
359 string::size_type pos;
364 if (!Glib::path_is_absolute (pathstr)) {
366 /* non-absolute pathname: find pathstr in search path */
368 vector<string> dirs = s.source_search_path (type);
374 if (dirs.size() == 0) {
375 error << _("FileSource: search path not set") << endmsg;
381 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
383 fullpath = Glib::build_filename (*i, pathstr);
385 /* i (paul) made a nasty design error by using ':' as a special character in
386 Ardour 0.99 .. this hack tries to make things sort of work.
389 if ((pos = pathstr.find_last_of (':')) != string::npos) {
391 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
393 /* its a real file, no problem */
402 /* might be an older session using file:channel syntax. see if the version
403 without the :suffix exists
406 string shorter = pathstr.substr (0, pos);
407 fullpath = Glib::build_filename (*i, shorter);
409 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
410 chan = atoi (pathstr.substr (pos+1));
418 /* new derived file (e.g. for timefx) being created in a newer session */
425 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
434 error << string_compose (
435 _("FileSource: \"%1\" is ambigous when searching\n\t"), pathstr) << endmsg;
438 } else if (cnt == 0) {
441 error << string_compose(
442 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
449 /* Current find() is unable to parse relative path names to yet non-existant
450 sources. QuickFix(tm) */
451 if (keeppath == "") {
453 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
459 found_path = keeppath;
465 /* external files and/or very very old style sessions include full paths */
467 /* ugh, handle ':' situation */
469 if ((pos = pathstr.find_last_of (':')) != string::npos) {
471 string shorter = pathstr.substr (0, pos);
473 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
474 chan = atoi (pathstr.substr (pos+1));
479 found_path = pathstr;
481 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
483 /* file does not exist or we cannot read it */
486 error << string_compose(
487 _("Filesource: cannot find required file (%1): %2"),
488 path, strerror (errno)) << endmsg;
492 #ifndef PLATFORM_WINDOWS
493 if (errno != ENOENT) {
494 error << string_compose(
495 _("Filesource: cannot check for existing file (%1): %2"),
496 path, strerror (errno)) << endmsg;
516 FileSource::mark_immutable ()
518 /* destructive sources stay writable, and their other flags don't change. */
519 if (!(_flags & Destructive)) {
520 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
525 FileSource::mark_immutable_except_write ()
527 /* destructive sources stay writable, and their other flags don't change. */
528 if (!(_flags & Destructive)) {
529 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
534 FileSource::mark_nonremovable ()
536 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
540 FileSource::set_within_session_from_path (const std::string& path)
542 _within_session = _session.path_is_within_session (path);
546 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);