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)
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;
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 error << string_compose(
312 _("Filesource: cannot find required file (%1)"), path) << endmsg;
319 /* only one match: happy days */
321 keeppath = de_duped_hits[0];
328 /* Current find() is unable to parse relative path names to yet non-existant
329 sources. QuickFix(tm)
331 if (keeppath == "") {
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)
552 FileSource::inc_use_count ()
554 Source::inc_use_count ();
558 FileSource::is_stub () const
568 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
576 FileSource::rename (const string& newpath)
578 Glib::Threads::Mutex::Lock lm (_lock);
579 string oldpath = _path;
581 // Test whether newpath exists, if yes notify the user but continue.
582 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
583 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;
587 if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
588 /* rename only needed if file exists on disk */
589 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
590 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
595 _name = Glib::path_get_basename (newpath);