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()) // 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)) {
251 std::vector<std::string> dirs = s.source_search_path (type);
253 if (dirs.size() == 0) {
254 error << _("FileSource: search path not set") << endmsg;
260 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
262 fullpath = Glib::build_filename (*i, path);
264 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
266 hits.push_back (fullpath);
270 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
271 in the session path it is possible to arrive at the same file via more than one path.
273 I suppose this is not necessary on Windows.
276 vector<string> de_duped_hits;
278 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
280 vector<string>::iterator j = i;
283 while (j != hits.end()) {
284 if (PBD::equivalent_paths (*i, *j)) {
285 /* *i and *j are the same file; break out of the loop early */
292 if (j == hits.end ()) {
293 de_duped_hits.push_back (*i);
297 if (de_duped_hits.size() > 1) {
299 /* more than one match: ask the user */
301 int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1);
306 keeppath = de_duped_hits[which];
309 } else if (de_duped_hits.size() == 0) {
311 /* no match: error */
314 error << string_compose(
315 _("Filesource: cannot find required file (%1)"), path) << endmsg;
322 /* only one match: happy days */
324 keeppath = de_duped_hits[0];
331 /* Current find() is unable to parse relative path names to yet non-existant
332 sources. QuickFix(tm)
334 if (keeppath == "") {
336 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
342 found_path = keeppath;
350 /** Find the actual source file based on \a filename.
352 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
353 * If the source is external, \a filename should be a full path.
354 * In either case, found_path is set to the complete absolute path of the source file.
355 * \return true iff the file was found.
358 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
359 bool& isnew, uint16_t& chan, string& found_path)
361 string pathstr = path;
362 string::size_type pos;
367 if (!Glib::path_is_absolute (pathstr)) {
369 /* non-absolute pathname: find pathstr in search path */
371 vector<string> dirs = s.source_search_path (type);
377 if (dirs.size() == 0) {
378 error << _("FileSource: search path not set") << endmsg;
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\n\t"), pathstr) << endmsg;
441 } else if (cnt == 0) {
444 error << string_compose(
445 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
452 /* Current find() is unable to parse relative path names to yet non-existant
453 sources. QuickFix(tm) */
454 if (keeppath == "") {
456 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
462 found_path = keeppath;
468 /* external files and/or very very old style sessions include full paths */
470 /* ugh, handle ':' situation */
472 if ((pos = pathstr.find_last_of (':')) != string::npos) {
474 string shorter = pathstr.substr (0, pos);
476 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
477 chan = atoi (pathstr.substr (pos+1));
482 found_path = pathstr;
484 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
486 /* file does not exist or we cannot read it */
489 error << string_compose(
490 _("Filesource: cannot find required file (%1): %2"),
491 path, strerror (errno)) << endmsg;
495 #ifndef PLATFORM_WINDOWS
496 if (errno != ENOENT) {
497 error << string_compose(
498 _("Filesource: cannot check for existing file (%1): %2"),
499 path, strerror (errno)) << endmsg;
519 FileSource::set_source_name (const string& newname, bool destructive)
521 Glib::Threads::Mutex::Lock lm (_lock);
522 string oldpath = _path;
523 string newpath = _session.generate_new_source_path_from_name (oldpath, _name, newname, destructive);
525 if (newpath.empty()) {
526 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
530 // Test whether newpath exists, if yes notify the user but continue.
531 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
532 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;
536 if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
537 /* rename only needed if file exists on disk */
538 if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
539 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)) {