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::Signal3<int,std::string,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()) // origin empty => new file VS. origin !empty => new file
64 set_within_session_from_path (path);
69 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
70 : Source (session, node)
71 , _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;
85 FileSource::prevent_deletion ()
87 /* if this file already exists, it cannot be removed, ever
90 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
91 if (!(_flags & Destructive)) {
94 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
100 FileSource::removable () const
102 bool r = ((_flags & Removable)
103 && ((_flags & RemoveAtDestroy) ||
104 ((_flags & RemovableIfEmpty) && empty())));
110 FileSource::init (const string& pathstr, bool must_exist)
112 _timeline_position = 0;
114 if (Stateful::loading_state_version < 3000) {
115 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
116 throw MissingSource (pathstr, _type);
119 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
120 throw MissingSource (pathstr, _type);
124 set_within_session_from_path (_path);
126 _name = Glib::path_get_basename (_path);
129 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
130 throw MissingSource (pathstr, _type);
138 FileSource::set_state (const XMLNode& node, int /*version*/)
140 const XMLProperty* prop;
142 if ((prop = node.property (X_("channel"))) != 0) {
143 _channel = atoi (prop->value());
148 if ((prop = node.property (X_("origin"))) != 0) {
149 _origin = prop->value();
156 FileSource::mark_take (const string& id)
164 FileSource::move_to_trash (const string& trash_dir_name)
166 if (!within_session() || !writable()) {
170 /* don't move the file across filesystems, just stick it in the
171 trash_dir_name directory on whichever filesystem it was already on
175 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
176 v.push_back (trash_dir_name);
177 v.push_back (Glib::path_get_basename (_path));
179 string newpath = Glib::build_filename (v);
181 /* the new path already exists, try versioning */
183 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
184 char buf[PATH_MAX+1];
188 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
191 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
192 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
196 if (version == 999) {
197 PBD::error << string_compose (
198 _("there are already 1000 files with names like %1; versioning discontinued"),
205 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
206 PBD::error << string_compose (
207 _("cannot rename file source from %1 to %2 (%3)"),
208 _path, newpath, strerror (errno)) << endmsg;
212 if (move_dependents_to_trash() != 0) {
213 /* try to back out */
214 rename (newpath.c_str(), _path.c_str());
220 /* file can not be removed twice, since the operation is not idempotent */
221 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
226 /** Find the actual source file based on \a filename.
228 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
229 * If the source is external, \a filename should be a full path.
230 * In either case, found_path is set to the complete absolute path of the source file.
231 * \return true iff the file was found.
234 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
235 bool& isnew, uint16_t& /* chan */, string& found_path)
242 if (!Glib::path_is_absolute (path)) {
247 string search_path = s.source_search_path (type);
249 if (search_path.length() == 0) {
250 error << _("FileSource: search path not set") << endmsg;
254 split (search_path, dirs, ':');
258 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
260 fullpath = Glib::build_filename (*i, path);
262 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
264 hits.push_back (fullpath);
268 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
269 in the session path it is possible to arrive at the same file via more than one path.
271 I suppose this is not necessary on Windows.
274 vector<string> de_duped_hits;
276 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
278 vector<string>::iterator j = i;
281 while (j != hits.end()) {
282 if (PBD::equivalent_paths (*i, *j)) {
283 /* *i and *j are the same file; break out of the loop early */
290 if (j == hits.end ()) {
291 de_duped_hits.push_back (*i);
295 if (de_duped_hits.size() > 1) {
297 /* more than one match: ask the user */
299 int which = FileSource::AmbiguousFileName (path, search_path, de_duped_hits).get_value_or (-1);
304 keeppath = de_duped_hits[which];
307 } else if (de_duped_hits.size() == 0) {
309 /* no match: error */
312 error << string_compose(
313 _("Filesource: cannot find required file (%1): while searching %2"),
314 path, search_path) << endmsg;
321 /* only one match: happy days */
323 keeppath = de_duped_hits[0];
330 /* Current find() is unable to parse relative path names to yet non-existant
331 sources. QuickFix(tm)
333 if (keeppath == "") {
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 search_path = s.source_search_path (type);
362 string pathstr = path;
363 string::size_type pos;
368 if (!Glib::path_is_absolute (pathstr)) {
370 /* non-absolute pathname: find pathstr in search path */
377 if (search_path.length() == 0) {
378 error << _("FileSource: search path not set") << endmsg;
382 split (search_path, dirs, ':');
386 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
388 fullpath = Glib::build_filename (*i, pathstr);
390 /* i (paul) made a nasty design error by using ':' as a special character in
391 Ardour 0.99 .. this hack tries to make things sort of work.
394 if ((pos = pathstr.find_last_of (':')) != string::npos) {
396 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
398 /* its a real file, no problem */
407 /* might be an older session using file:channel syntax. see if the version
408 without the :suffix exists
411 string shorter = pathstr.substr (0, pos);
412 fullpath = Glib::build_filename (*i, shorter);
414 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
415 chan = atoi (pathstr.substr (pos+1));
423 /* new derived file (e.g. for timefx) being created in a newer session */
430 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
439 error << string_compose (
440 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
441 pathstr, search_path) << endmsg;
444 } else if (cnt == 0) {
447 error << string_compose(
448 _("Filesource: cannot find required file (%1): while searching %2"),
449 pathstr, search_path) << endmsg;
456 /* Current find() is unable to parse relative path names to yet non-existant
457 sources. QuickFix(tm) */
458 if (keeppath == "") {
460 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
466 found_path = keeppath;
472 /* external files and/or very very old style sessions include full paths */
474 /* ugh, handle ':' situation */
476 if ((pos = pathstr.find_last_of (':')) != string::npos) {
478 string shorter = pathstr.substr (0, pos);
480 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
481 chan = atoi (pathstr.substr (pos+1));
486 found_path = pathstr;
488 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
490 /* file does not exist or we cannot read it */
493 error << string_compose(
494 _("Filesource: cannot find required file (%1): %2"),
495 path, strerror (errno)) << endmsg;
499 if (errno != ENOENT) {
500 error << string_compose(
501 _("Filesource: cannot check for existing file (%1): %2"),
502 path, strerror (errno)) << endmsg;
522 FileSource::set_source_name (const string& newname, bool destructive)
524 Glib::Threads::Mutex::Lock lm (_lock);
525 string oldpath = _path;
526 string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
528 if (newpath.empty()) {
529 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
533 // Test whether newpath exists, if yes notify the user but continue.
534 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
535 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;
539 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
540 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
544 _name = Glib::path_get_basename (newpath);
551 FileSource::mark_immutable ()
553 /* destructive sources stay writable, and their other flags don't change. */
554 if (!(_flags & Destructive)) {
555 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
560 FileSource::mark_immutable_except_write ()
562 /* destructive sources stay writable, and their other flags don't change. */
563 if (!(_flags & Destructive)) {
564 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
569 FileSource::mark_nonremovable ()
571 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
575 FileSource::set_within_session_from_path (const std::string& path)
577 _within_session = _session.path_is_within_session (path);
581 FileSource::set_path (const std::string& newpath)
587 FileSource::inc_use_count ()
589 Source::inc_use_count ();
593 FileSource::is_stub () const
603 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {