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
64 set_within_session_from_path (path);
67 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
68 : Source (session, node)
69 , _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;
82 FileSource::~FileSource()
87 FileSource::existence_check ()
89 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
95 FileSource::prevent_deletion ()
97 if (!(_flags & Destructive)) {
100 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
105 FileSource::removable () const
107 bool r = ((_flags & Removable)
108 && ((_flags & RemoveAtDestroy) ||
109 ((_flags & RemovableIfEmpty) && empty())));
115 FileSource::init (const string& pathstr, bool must_exist)
117 _timeline_position = 0;
119 if (Stateful::loading_state_version < 3000) {
120 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
121 throw MissingSource (pathstr, _type);
124 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
125 throw MissingSource (pathstr, _type);
129 set_within_session_from_path (_path);
131 _name = Glib::path_get_basename (_path);
134 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
135 throw MissingSource (pathstr, _type);
143 FileSource::set_state (const XMLNode& node, int /*version*/)
145 const XMLProperty* prop;
147 if ((prop = node.property (X_("channel"))) != 0) {
148 _channel = atoi (prop->value());
153 if ((prop = node.property (X_("origin"))) != 0) {
154 _origin = prop->value();
161 FileSource::mark_take (const string& id)
169 FileSource::move_to_trash (const string& trash_dir_name)
171 if (!within_session() || !writable()) {
175 /* don't move the file across filesystems, just stick it in the
176 trash_dir_name directory on whichever filesystem it was already on
180 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
181 v.push_back (trash_dir_name);
182 v.push_back (Glib::path_get_basename (_path));
184 string newpath = Glib::build_filename (v);
186 /* the new path already exists, try versioning */
188 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
189 char buf[PATH_MAX+1];
193 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
196 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
197 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
201 if (version == 999) {
202 PBD::error << string_compose (
203 _("there are already 1000 files with names like %1; versioning discontinued"),
210 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
211 PBD::error << string_compose (
212 _("cannot rename file source from %1 to %2 (%3)"),
213 _path, newpath, strerror (errno)) << endmsg;
217 if (move_dependents_to_trash() != 0) {
218 /* try to back out */
219 ::rename (newpath.c_str(), _path.c_str());
225 /* file can not be removed twice, since the operation is not idempotent */
226 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
231 /** Find the actual source file based on \a filename.
233 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
234 * If the source is external, \a filename should be a full path.
235 * In either case, found_path is set to the complete absolute path of the source file.
236 * \return true iff the file was found.
239 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
240 bool& isnew, uint16_t& /* chan */, string& found_path)
247 if (!Glib::path_is_absolute (path)) {
250 std::vector<std::string> dirs = s.source_search_path (type);
252 if (dirs.size() == 0) {
253 error << _("FileSource: search path not set") << endmsg;
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::equivalent_paths (*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, 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 /* do not generate an error here, leave that to
312 whoever deals with the false return value.
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)
333 if (keeppath.empty()) {
335 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
341 found_path = keeppath;
349 /** Find the actual source file based on \a filename.
351 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
352 * If the source is external, \a filename should be a full path.
353 * In either case, found_path is set to the complete absolute path of the source file.
354 * \return true iff the file was found.
357 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
358 bool& isnew, uint16_t& chan, string& found_path)
360 string pathstr = path;
361 string::size_type pos;
366 if (!Glib::path_is_absolute (pathstr)) {
368 /* non-absolute pathname: find pathstr in search path */
370 vector<string> dirs = s.source_search_path (type);
376 if (dirs.size() == 0) {
377 error << _("FileSource: search path not set") << endmsg;
383 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
385 fullpath = Glib::build_filename (*i, pathstr);
387 /* i (paul) made a nasty design error by using ':' as a special character in
388 Ardour 0.99 .. this hack tries to make things sort of work.
391 if ((pos = pathstr.find_last_of (':')) != string::npos) {
393 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
395 /* its a real file, no problem */
404 /* might be an older session using file:channel syntax. see if the version
405 without the :suffix exists
408 string shorter = pathstr.substr (0, pos);
409 fullpath = Glib::build_filename (*i, shorter);
411 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
412 chan = atoi (pathstr.substr (pos+1));
420 /* new derived file (e.g. for timefx) being created in a newer session */
427 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
436 error << string_compose (
437 _("FileSource: \"%1\" is ambigous when searching\n\t"), pathstr) << endmsg;
440 } else if (cnt == 0) {
443 error << string_compose(
444 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
451 /* Current find() is unable to parse relative path names to yet non-existant
452 sources. QuickFix(tm) */
453 if (keeppath == "") {
455 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
461 found_path = keeppath;
467 /* external files and/or very very old style sessions include full paths */
469 /* ugh, handle ':' situation */
471 if ((pos = pathstr.find_last_of (':')) != string::npos) {
473 string shorter = pathstr.substr (0, pos);
475 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
476 chan = atoi (pathstr.substr (pos+1));
481 found_path = pathstr;
483 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
485 /* file does not exist or we cannot read it */
488 error << string_compose(
489 _("Filesource: cannot find required file (%1): %2"),
490 path, strerror (errno)) << endmsg;
494 #ifndef PLATFORM_WINDOWS
495 if (errno != ENOENT) {
496 error << string_compose(
497 _("Filesource: cannot check for existing file (%1): %2"),
498 path, strerror (errno)) << endmsg;
518 FileSource::mark_immutable ()
520 /* destructive sources stay writable, and their other flags don't change. */
521 if (!(_flags & Destructive)) {
522 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
527 FileSource::mark_immutable_except_write ()
529 /* destructive sources stay writable, and their other flags don't change. */
530 if (!(_flags & Destructive)) {
531 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
536 FileSource::mark_nonremovable ()
538 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
542 FileSource::set_within_session_from_path (const std::string& path)
544 _within_session = _session.path_is_within_session (path);
548 FileSource::set_path (const std::string& newpath)
551 set_within_session_from_path (newpath);
552 if (_within_session) {
553 _origin = Glib::path_get_basename (newpath);
560 FileSource::inc_use_count ()
562 Source::inc_use_count ();
566 FileSource::is_stub () const
576 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
584 FileSource::rename (const string& newpath)
586 Glib::Threads::Mutex::Lock lm (_lock);
587 string oldpath = _path;
589 // Test whether newpath exists, if yes notify the user but continue.
590 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
591 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;
595 if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
596 /* rename only needed if file exists on disk */
597 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
598 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
603 _name = Glib::path_get_basename (newpath);