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;
84 FileSource::~FileSource()
89 FileSource::prevent_deletion ()
91 /* if this file already exists, it cannot be removed, ever
94 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
95 cerr << " ... " << _path << " already exists, marking immutable\n";
97 if (!(_flags & Destructive)) {
100 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
106 FileSource::removable () const
108 bool r = ((_flags & Removable)
109 && ((_flags & RemoveAtDestroy) ||
110 ((_flags & RemovableIfEmpty) && empty())));
116 FileSource::init (const string& pathstr, bool must_exist)
118 _timeline_position = 0;
120 if (Stateful::loading_state_version < 3000) {
121 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
122 throw MissingSource (pathstr, _type);
125 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
126 throw MissingSource (pathstr, _type);
130 set_within_session_from_path (_path);
132 _name = Glib::path_get_basename (_path);
135 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
136 throw MissingSource (pathstr, _type);
144 FileSource::set_state (const XMLNode& node, int /*version*/)
146 const XMLProperty* prop;
148 if ((prop = node.property (X_("channel"))) != 0) {
149 _channel = atoi (prop->value());
154 if ((prop = node.property (X_("origin"))) != 0) {
155 _origin = prop->value();
162 FileSource::mark_take (const string& id)
170 FileSource::move_to_trash (const string& trash_dir_name)
172 if (!within_session() || !writable()) {
176 /* don't move the file across filesystems, just stick it in the
177 trash_dir_name directory on whichever filesystem it was already on
181 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
182 v.push_back (trash_dir_name);
183 v.push_back (Glib::path_get_basename (_path));
185 string newpath = Glib::build_filename (v);
187 /* the new path already exists, try versioning */
189 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
190 char buf[PATH_MAX+1];
194 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
197 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
198 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
202 if (version == 999) {
203 PBD::error << string_compose (
204 _("there are already 1000 files with names like %1; versioning discontinued"),
211 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
212 PBD::error << string_compose (
213 _("cannot rename file source from %1 to %2 (%3)"),
214 _path, newpath, strerror (errno)) << endmsg;
218 if (move_dependents_to_trash() != 0) {
219 /* try to back out */
220 rename (newpath.c_str(), _path.c_str());
226 /* file can not be removed twice, since the operation is not idempotent */
227 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
232 /** Find the actual source file based on \a filename.
234 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
235 * If the source is external, \a filename should be a full path.
236 * In either case, found_path is set to the complete absolute path of the source file.
237 * \return true iff the file was found.
240 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
241 bool& isnew, uint16_t& /* chan */, string& found_path)
248 if (!Glib::path_is_absolute (path)) {
253 string search_path = s.source_search_path (type);
255 if (search_path.length() == 0) {
256 error << _("FileSource: search path not set") << endmsg;
260 split (search_path, dirs, ':');
264 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
266 fullpath = Glib::build_filename (*i, path);
268 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
270 hits.push_back (fullpath);
274 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
275 in the session path it is possible to arrive at the same file via more than one path.
277 I suppose this is not necessary on Windows.
280 vector<string> de_duped_hits;
282 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
284 vector<string>::iterator j = i;
287 while (j != hits.end()) {
288 if (PBD::equivalent_paths (*i, *j)) {
289 /* *i and *j are the same file; break out of the loop early */
296 if (j == hits.end ()) {
297 de_duped_hits.push_back (*i);
301 if (de_duped_hits.size() > 1) {
303 /* more than one match: ask the user */
305 int which = FileSource::AmbiguousFileName (path, search_path, de_duped_hits).get_value_or (-1);
310 keeppath = de_duped_hits[which];
313 } else if (de_duped_hits.size() == 0) {
315 /* no match: error */
318 error << string_compose(
319 _("Filesource: cannot find required file (%1): while searching %2"),
320 path, search_path) << endmsg;
327 /* only one match: happy days */
329 keeppath = de_duped_hits[0];
336 /* Current find() is unable to parse relative path names to yet non-existant
337 sources. QuickFix(tm)
339 if (keeppath == "") {
341 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
347 found_path = keeppath;
355 /** Find the actual source file based on \a filename.
357 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
358 * If the source is external, \a filename should be a full path.
359 * In either case, found_path is set to the complete absolute path of the source file.
360 * \return true iff the file was found.
363 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
364 bool& isnew, uint16_t& chan, string& found_path)
366 string search_path = s.source_search_path (type);
368 string pathstr = path;
369 string::size_type pos;
374 if (!Glib::path_is_absolute (pathstr)) {
376 /* non-absolute pathname: find pathstr in search path */
383 if (search_path.length() == 0) {
384 error << _("FileSource: search path not set") << endmsg;
388 split (search_path, dirs, ':');
392 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
394 fullpath = Glib::build_filename (*i, pathstr);
396 /* i (paul) made a nasty design error by using ':' as a special character in
397 Ardour 0.99 .. this hack tries to make things sort of work.
400 if ((pos = pathstr.find_last_of (':')) != string::npos) {
402 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
404 /* its a real file, no problem */
413 /* might be an older session using file:channel syntax. see if the version
414 without the :suffix exists
417 string shorter = pathstr.substr (0, pos);
418 fullpath = Glib::build_filename (*i, shorter);
420 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
421 chan = atoi (pathstr.substr (pos+1));
429 /* new derived file (e.g. for timefx) being created in a newer session */
436 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
445 error << string_compose (
446 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
447 pathstr, search_path) << endmsg;
450 } else if (cnt == 0) {
453 error << string_compose(
454 _("Filesource: cannot find required file (%1): while searching %2"),
455 pathstr, search_path) << endmsg;
462 /* Current find() is unable to parse relative path names to yet non-existant
463 sources. QuickFix(tm) */
464 if (keeppath == "") {
466 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
472 found_path = keeppath;
478 /* external files and/or very very old style sessions include full paths */
480 /* ugh, handle ':' situation */
482 if ((pos = pathstr.find_last_of (':')) != string::npos) {
484 string shorter = pathstr.substr (0, pos);
486 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
487 chan = atoi (pathstr.substr (pos+1));
492 found_path = pathstr;
494 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
496 /* file does not exist or we cannot read it */
499 error << string_compose(
500 _("Filesource: cannot find required file (%1): %2"),
501 path, strerror (errno)) << endmsg;
505 if (errno != ENOENT) {
506 error << string_compose(
507 _("Filesource: cannot check for existing file (%1): %2"),
508 path, strerror (errno)) << endmsg;
528 FileSource::mark_immutable ()
530 /* destructive sources stay writable, and their other flags don't change. */
531 if (!(_flags & Destructive)) {
532 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
537 FileSource::mark_immutable_except_write ()
539 /* destructive sources stay writable, and their other flags don't change. */
540 if (!(_flags & Destructive)) {
541 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
546 FileSource::mark_nonremovable ()
548 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
552 FileSource::set_within_session_from_path (const std::string& path)
554 _within_session = _session.path_is_within_session (path);
558 FileSource::set_path (const std::string& newpath)
564 FileSource::inc_use_count ()
566 Source::inc_use_count ();
570 FileSource::is_stub () const
580 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {