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.
29 #include <glib/gstdio.h>
31 #include "pbd/convert.h"
32 #include "pbd/basename.h"
33 #include "pbd/stl_delete.h"
34 #include "pbd/strsplit.h"
35 #include "pbd/shortpath.h"
36 #include "pbd/enumwriter.h"
37 #include "pbd/file_utils.h"
39 #include <glibmm/miscutils.h>
40 #include <glibmm/fileutils.h>
41 #include <glibmm/threads.h>
43 #include "ardour/data_type.h"
44 #include "ardour/file_source.h"
45 #include "ardour/session.h"
46 #include "ardour/source.h"
47 #include "ardour/utils.h"
52 using namespace ARDOUR;
56 PBD::Signal2<int,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
58 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
59 : Source(session, type, path, flag)
61 , _file_is_new (!origin.empty()) // if origin is left unspecified (empty string) then file must exist
66 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)
75 /* this setting of _path is temporary - we expect derived classes
76 to call ::init() which will actually locate the file
77 and reset _path and _within_session correctly.
81 _within_session = true;
84 FileSource::~FileSource()
89 FileSource::existence_check ()
91 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
97 FileSource::prevent_deletion ()
99 if (!(_flags & Destructive)) {
102 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
107 FileSource::removable () const
109 bool r = ((_flags & Removable)
110 && ((_flags & RemoveAtDestroy) ||
111 ((_flags & RemovableIfEmpty) && empty())));
117 FileSource::init (const string& pathstr, bool must_exist)
119 _timeline_position = 0;
121 if (Stateful::loading_state_version < 3000) {
122 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
123 throw MissingSource (pathstr, _type);
126 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
127 throw MissingSource (pathstr, _type);
131 set_within_session_from_path (_path);
133 _name = Glib::path_get_basename (_path);
136 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
137 throw MissingSource (pathstr, _type);
145 FileSource::set_state (const XMLNode& node, int /*version*/)
147 if (!node.get_property (X_("channel"), _channel)) {
151 node.get_property (X_("origin"), _origin);
153 if (!node.get_property (X_("gain"), _gain)) {
161 FileSource::mark_take (const string& id)
169 FileSource::move_to_trash (const string& trash_dir_name)
171 if (!within_session() || !writable()) {
175 /* don't move the file across filesystems, just stick it in the
176 trash_dir_name directory on whichever filesystem it was already on
180 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
181 v.push_back (trash_dir_name);
182 v.push_back (Glib::path_get_basename (_path));
184 string newpath = Glib::build_filename (v);
186 /* the new path already exists, try versioning */
188 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
189 char buf[PATH_MAX+1];
193 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
196 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
197 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
201 if (version == 999) {
202 PBD::error << string_compose (
203 _("there are already 1000 files with names like %1; versioning discontinued"),
210 if (::g_rename (_path.c_str(), newpath.c_str()) != 0) {
211 PBD::error << string_compose (
212 _("cannot rename file source from %1 to %2 (%3)"),
213 _path, newpath, g_strerror (errno)) << endmsg;
217 if (move_dependents_to_trash() != 0) {
218 /* try to back out */
219 ::g_rename (newpath.c_str(), _path.c_str());
225 /* file can not be removed twice, since the operation is not idempotent */
226 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
231 /** Find the actual source file based on \a filename.
233 * If the source is within the session tree, \a path should be a simple filename (no slashes).
234 * If the source is external, \a path should be a full path.
235 * In either case, found_path is set to the complete absolute path of the source file.
236 * \return true if the file was found.
239 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
240 bool& isnew, uint16_t& /* chan */, string& found_path)
247 if (!Glib::path_is_absolute (path)) {
250 std::vector<std::string> dirs = s.source_search_path (type);
252 if (dirs.size() == 0) {
253 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 /* do not generate an error here, leave that to
312 whoever deals with the false return value.
320 /* only one match: happy days */
322 keeppath = de_duped_hits[0];
329 /* Current find() is unable to parse relative path names to yet non-existant
330 sources. QuickFix(tm)
333 if (keeppath.empty()) {
335 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
341 found_path = keeppath;
348 /** Find the actual source file based on \a filename.
350 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
351 * If the source is external, \a filename should be a full path.
352 * In either case, found_path is set to the complete absolute path of the source file.
353 * \return true iff the file was found.
356 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
357 bool& isnew, uint16_t& chan, string& found_path)
359 string pathstr = path;
360 string::size_type pos;
365 if (!Glib::path_is_absolute (pathstr)) {
367 /* non-absolute pathname: find pathstr in search path */
369 vector<string> dirs = s.source_search_path (type);
375 if (dirs.size() == 0) {
376 error << _("FileSource: search path not set") << endmsg;
382 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
384 fullpath = Glib::build_filename (*i, pathstr);
386 /* i (paul) made a nasty design error by using ':' as a special character in
387 Ardour 0.99 .. this hack tries to make things sort of work.
390 if ((pos = pathstr.find_last_of (':')) != string::npos) {
392 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
394 /* its a real file, no problem */
403 /* might be an older session using file:channel syntax. see if the version
404 without the :suffix exists
407 string shorter = pathstr.substr (0, pos);
408 fullpath = Glib::build_filename (*i, shorter);
410 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
411 chan = atoi (pathstr.substr (pos+1));
419 /* new derived file (e.g. for timefx) being created in a newer session */
426 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
435 error << string_compose (
436 _("FileSource: \"%1\" is ambiguous when searching\n\t"), pathstr) << endmsg;
439 } else if (cnt == 0) {
442 error << string_compose(
443 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
450 /* Current find() is unable to parse relative path names to yet non-existant
451 sources. QuickFix(tm) */
452 if (keeppath == "") {
454 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
460 found_path = keeppath;
466 /* external files and/or very very old style sessions include full paths */
468 /* ugh, handle ':' situation */
470 if ((pos = pathstr.find_last_of (':')) != string::npos) {
472 string shorter = pathstr.substr (0, pos);
474 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
475 chan = atoi (pathstr.substr (pos+1));
480 found_path = pathstr;
482 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
484 /* file does not exist or we cannot read it */
487 error << string_compose(
488 _("Filesource: cannot find required file (%1): %2"),
489 path, g_strerror (errno)) << endmsg;
493 #ifndef PLATFORM_WINDOWS
494 if (errno != ENOENT) {
495 error << string_compose(
496 _("Filesource: cannot check for existing file (%1): %2"),
497 path, g_strerror (errno)) << endmsg;
517 FileSource::mark_immutable ()
519 /* destructive sources stay writable, and their other flags don't change. */
520 if (!(_flags & Destructive)) {
521 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
526 FileSource::mark_immutable_except_write ()
528 /* destructive sources stay writable, and their other flags don't change. */
529 if (!(_flags & Destructive)) {
530 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
535 FileSource::mark_nonremovable ()
537 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
541 FileSource::set_within_session_from_path (const std::string& path)
543 _within_session = _session.path_is_within_session (path);
547 FileSource::set_path (const std::string& newpath)
551 set_within_session_from_path (newpath);
552 if (_within_session) {
553 _origin = Glib::path_get_basename (newpath);
561 FileSource::replace_file (const std::string& newpath)
565 _name = Glib::path_get_basename (newpath);
569 FileSource::inc_use_count ()
571 Source::inc_use_count ();
575 FileSource::is_stub () const
585 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
593 FileSource::rename (const string& newpath)
595 Glib::Threads::Mutex::Lock lm (_lock);
596 string oldpath = _path;
598 // Test whether newpath exists, if yes notify the user but continue.
599 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
600 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;
604 if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
605 /* rename only needed if file exists on disk */
606 if (::g_rename (oldpath.c_str(), newpath.c_str()) != 0) {
607 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, g_strerror(errno)) << endmsg;
612 _name = Glib::path_get_basename (newpath);