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*/)
148 XMLProperty const * prop;
150 if ((prop = node.property (X_("channel"))) != 0) {
151 _channel = atoi (prop->value());
156 if ((prop = node.property (X_("origin"))) != 0) {
157 _origin = prop->value();
160 if ((prop = node.property (X_("gain"))) != 0) {
161 _gain = atof (prop->value());
170 FileSource::mark_take (const string& id)
178 FileSource::move_to_trash (const string& trash_dir_name)
180 if (!within_session() || !writable()) {
184 /* don't move the file across filesystems, just stick it in the
185 trash_dir_name directory on whichever filesystem it was already on
189 v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
190 v.push_back (trash_dir_name);
191 v.push_back (Glib::path_get_basename (_path));
193 string newpath = Glib::build_filename (v);
195 /* the new path already exists, try versioning */
197 if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
198 char buf[PATH_MAX+1];
202 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
205 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
206 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
210 if (version == 999) {
211 PBD::error << string_compose (
212 _("there are already 1000 files with names like %1; versioning discontinued"),
219 if (::g_rename (_path.c_str(), newpath.c_str()) != 0) {
220 PBD::error << string_compose (
221 _("cannot rename file source from %1 to %2 (%3)"),
222 _path, newpath, g_strerror (errno)) << endmsg;
226 if (move_dependents_to_trash() != 0) {
227 /* try to back out */
228 ::g_rename (newpath.c_str(), _path.c_str());
234 /* file can not be removed twice, since the operation is not idempotent */
235 _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
240 /** Find the actual source file based on \a filename.
242 * If the source is within the session tree, \a path should be a simple filename (no slashes).
243 * If the source is external, \a path should be a full path.
244 * In either case, found_path is set to the complete absolute path of the source file.
245 * \return true if the file was found.
248 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
249 bool& isnew, uint16_t& /* chan */, string& found_path)
256 if (!Glib::path_is_absolute (path)) {
259 std::vector<std::string> dirs = s.source_search_path (type);
261 if (dirs.size() == 0) {
262 error << _("FileSource: search path not set") << endmsg;
266 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
268 fullpath = Glib::build_filename (*i, path);
270 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
272 hits.push_back (fullpath);
276 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
277 in the session path it is possible to arrive at the same file via more than one path.
279 I suppose this is not necessary on Windows.
282 vector<string> de_duped_hits;
284 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
286 vector<string>::iterator j = i;
289 while (j != hits.end()) {
290 if (PBD::equivalent_paths (*i, *j)) {
291 /* *i and *j are the same file; break out of the loop early */
298 if (j == hits.end ()) {
299 de_duped_hits.push_back (*i);
303 if (de_duped_hits.size() > 1) {
305 /* more than one match: ask the user */
307 int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1);
312 keeppath = de_duped_hits[which];
315 } else if (de_duped_hits.size() == 0) {
317 /* no match: error */
320 /* do not generate an error here, leave that to
321 whoever deals with the false return value.
329 /* only one match: happy days */
331 keeppath = de_duped_hits[0];
338 /* Current find() is unable to parse relative path names to yet non-existant
339 sources. QuickFix(tm)
342 if (keeppath.empty()) {
344 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
350 found_path = keeppath;
357 /** Find the actual source file based on \a filename.
359 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
360 * If the source is external, \a filename should be a full path.
361 * In either case, found_path is set to the complete absolute path of the source file.
362 * \return true iff the file was found.
365 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
366 bool& isnew, uint16_t& chan, string& found_path)
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 */
378 vector<string> dirs = s.source_search_path (type);
384 if (dirs.size() == 0) {
385 error << _("FileSource: search path not set") << endmsg;
391 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
393 fullpath = Glib::build_filename (*i, pathstr);
395 /* i (paul) made a nasty design error by using ':' as a special character in
396 Ardour 0.99 .. this hack tries to make things sort of work.
399 if ((pos = pathstr.find_last_of (':')) != string::npos) {
401 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
403 /* its a real file, no problem */
412 /* might be an older session using file:channel syntax. see if the version
413 without the :suffix exists
416 string shorter = pathstr.substr (0, pos);
417 fullpath = Glib::build_filename (*i, shorter);
419 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
420 chan = atoi (pathstr.substr (pos+1));
428 /* new derived file (e.g. for timefx) being created in a newer session */
435 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
444 error << string_compose (
445 _("FileSource: \"%1\" is ambiguous when searching\n\t"), pathstr) << endmsg;
448 } else if (cnt == 0) {
451 error << string_compose(
452 _("Filesource: cannot find required file (%1)"), pathstr) << endmsg;
459 /* Current find() is unable to parse relative path names to yet non-existant
460 sources. QuickFix(tm) */
461 if (keeppath == "") {
463 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
469 found_path = keeppath;
475 /* external files and/or very very old style sessions include full paths */
477 /* ugh, handle ':' situation */
479 if ((pos = pathstr.find_last_of (':')) != string::npos) {
481 string shorter = pathstr.substr (0, pos);
483 if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
484 chan = atoi (pathstr.substr (pos+1));
489 found_path = pathstr;
491 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
493 /* file does not exist or we cannot read it */
496 error << string_compose(
497 _("Filesource: cannot find required file (%1): %2"),
498 path, g_strerror (errno)) << endmsg;
502 #ifndef PLATFORM_WINDOWS
503 if (errno != ENOENT) {
504 error << string_compose(
505 _("Filesource: cannot check for existing file (%1): %2"),
506 path, g_strerror (errno)) << endmsg;
526 FileSource::mark_immutable ()
528 /* destructive sources stay writable, and their other flags don't change. */
529 if (!(_flags & Destructive)) {
530 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
535 FileSource::mark_immutable_except_write ()
537 /* destructive sources stay writable, and their other flags don't change. */
538 if (!(_flags & Destructive)) {
539 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
544 FileSource::mark_nonremovable ()
546 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
550 FileSource::set_within_session_from_path (const std::string& path)
552 _within_session = _session.path_is_within_session (path);
556 FileSource::set_path (const std::string& newpath)
560 set_within_session_from_path (newpath);
561 if (_within_session) {
562 _origin = Glib::path_get_basename (newpath);
570 FileSource::replace_file (const std::string& newpath)
574 _name = Glib::path_get_basename (newpath);
578 FileSource::inc_use_count ()
580 Source::inc_use_count ();
584 FileSource::is_stub () const
594 if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
602 FileSource::rename (const string& newpath)
604 Glib::Threads::Mutex::Lock lm (_lock);
605 string oldpath = _path;
607 // Test whether newpath exists, if yes notify the user but continue.
608 if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
609 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;
613 if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) {
614 /* rename only needed if file exists on disk */
615 if (::g_rename (oldpath.c_str(), newpath.c_str()) != 0) {
616 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, g_strerror(errno)) << endmsg;
621 _name = Glib::path_get_basename (newpath);