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/thread.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)
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() == 0)));
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);
128 if (_file_is_new && must_exist) {
136 FileSource::set_state (const XMLNode& node, int /*version*/)
138 const XMLProperty* prop;
140 if ((prop = node.property (X_("channel"))) != 0) {
141 _channel = atoi (prop->value());
146 if ((prop = node.property (X_("origin"))) != 0) {
147 _origin = prop->value();
154 FileSource::mark_take (const string& id)
162 FileSource::move_to_trash (const string& trash_dir_name)
164 if (!within_session() || !writable()) {
168 /* don't move the file across filesystems, just stick it in the
169 trash_dir_name directory on whichever filesystem it was already on
173 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
174 v.push_back (trash_dir_name);
175 v.push_back (Glib::path_get_basename (_path));
177 string newpath = Glib::build_filename (v);
179 /* the new path already exists, try versioning */
181 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
182 char buf[PATH_MAX+1];
186 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
189 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
190 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
194 if (version == 999) {
195 PBD::error << string_compose (
196 _("there are already 1000 files with names like %1; versioning discontinued"),
203 if (::rename (_path.c_str(), newpath.c_str()) != 0) {
204 PBD::error << string_compose (
205 _("cannot rename file source from %1 to %2 (%3)"),
206 _path, newpath, strerror (errno)) << endmsg;
210 if (move_dependents_to_trash() != 0) {
211 /* try to back out */
212 rename (newpath.c_str(), _path.c_str());
218 /* file can not be removed twice, since the operation is not idempotent */
219 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
224 /** Find the actual source file based on \a filename.
226 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
227 * If the source is external, \a filename should be a full path.
228 * In either case, found_path is set to the complete absolute path of the source file.
229 * \return true iff the file was found.
232 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
233 bool& isnew, uint16_t& /* chan */, string& found_path)
240 if (!Glib::path_is_absolute (path)) {
245 string search_path = s.source_search_path (type);
247 if (search_path.length() == 0) {
248 error << _("FileSource: search path not set") << endmsg;
252 split (search_path, dirs, ':');
256 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
258 fullpath = Glib::build_filename (*i, path);
260 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
262 hits.push_back (fullpath);
266 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
267 in the session path it is possible to arrive at the same file via more than one path.
269 I suppose this is not necessary on Windows.
272 vector<string> de_duped_hits;
274 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
276 vector<string>::iterator j = i;
279 while (j != hits.end()) {
280 if (PBD::equivalent_paths (*i, *j)) {
281 /* *i and *j are the same file; break out of the loop early */
288 if (j == hits.end ()) {
289 de_duped_hits.push_back (*i);
293 if (de_duped_hits.size() > 1) {
295 /* more than one match: ask the user */
297 int which = FileSource::AmbiguousFileName (path, search_path, de_duped_hits).get_value_or (-1);
302 keeppath = de_duped_hits[which];
305 } else if (de_duped_hits.size() == 0) {
307 /* no match: error */
310 error << string_compose(
311 _("Filesource: cannot find required file (%1): while searching %2"),
312 path, search_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 search_path = s.source_search_path (type);
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 */
375 if (search_path.length() == 0) {
376 error << _("FileSource: search path not set") << endmsg;
380 split (search_path, dirs, ':');
384 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
386 fullpath = Glib::build_filename (*i, pathstr);
388 /* i (paul) made a nasty design error by using ':' as a special character in
389 Ardour 0.99 .. this hack tries to make things sort of work.
392 if ((pos = pathstr.find_last_of (':')) != string::npos) {
394 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
396 /* its a real file, no problem */
405 /* might be an older session using file:channel syntax. see if the version
406 without the :suffix exists
409 string shorter = pathstr.substr (0, pos);
410 fullpath = Glib::build_filename (*i, shorter);
412 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
413 chan = atoi (pathstr.substr (pos+1));
421 /* new derived file (e.g. for timefx) being created in a newer session */
428 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
437 error << string_compose (
438 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
439 pathstr, search_path) << endmsg;
442 } else if (cnt == 0) {
445 error << string_compose(
446 _("Filesource: cannot find required file (%1): while searching %2"),
447 pathstr, search_path) << endmsg;
454 /* Current find() is unable to parse relative path names to yet non-existant
455 sources. QuickFix(tm) */
456 if (keeppath == "") {
458 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
464 found_path = keeppath;
470 /* external files and/or very very old style sessions include full paths */
472 /* ugh, handle ':' situation */
474 if ((pos = pathstr.find_last_of (':')) != string::npos) {
476 string shorter = pathstr.substr (0, pos);
478 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
479 chan = atoi (pathstr.substr (pos+1));
484 found_path = pathstr;
486 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
488 /* file does not exist or we cannot read it */
491 error << string_compose(
492 _("Filesource: cannot find required file (%1): %2"),
493 path, strerror (errno)) << endmsg;
497 if (errno != ENOENT) {
498 error << string_compose(
499 _("Filesource: cannot check for existing file (%1): %2"),
500 path, strerror (errno)) << endmsg;
520 FileSource::set_source_name (const string& newname, bool destructive)
522 Glib::Mutex::Lock lm (_lock);
523 string oldpath = _path;
524 string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
526 if (newpath.empty()) {
527 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
531 // Test whether newpath exists, if yes notify the user but continue.
532 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
533 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;
537 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
538 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
542 _name = Glib::path_get_basename (newpath);
549 FileSource::mark_immutable ()
551 /* destructive sources stay writable, and their other flags don't change. */
552 if (!(_flags & Destructive)) {
553 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
558 FileSource::mark_nonremovable ()
560 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
564 FileSource::set_within_session_from_path (const std::string& path)
566 _within_session = _session.path_is_within_session (path);
570 FileSource::set_path (const std::string& newpath)
576 FileSource::inc_use_count ()
578 Source::inc_use_count ();