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/mountpoint.h"
32 #include "pbd/stl_delete.h"
33 #include "pbd/strsplit.h"
34 #include "pbd/shortpath.h"
35 #include "pbd/enumwriter.h"
37 #include <glibmm/miscutils.h>
38 #include <glibmm/fileutils.h>
39 #include <glibmm/thread.h>
41 #include "ardour/file_source.h"
42 #include "ardour/directory_names.h"
43 #include "ardour/session.h"
44 #include "ardour/session_directory.h"
45 #include "ardour/source_factory.h"
46 #include "ardour/filename_extensions.h"
51 using namespace ARDOUR
;
55 map
<DataType
, ustring
> FileSource::search_paths
;
57 FileSource::FileSource (Session
& session
, DataType type
, const ustring
& path
, Source::Flag flag
)
58 : Source(session
, type
, path
, flag
)
63 set_within_session_from_path (path
);
66 FileSource::FileSource (Session
& session
, const XMLNode
& node
, bool /*must_exist*/)
67 : Source(session
, node
)
68 , _file_is_new (false)
70 /* this setting of _path is temporary - we expect derived classes
71 to call ::init() which will actually locate the file
72 and reset _path and _within_session correctly.
76 _within_session
= true;
80 FileSource::removable () const
82 bool r
= (_path
.find (stub_dir_name
) != string::npos
) ||
84 && ((_flags
& RemoveAtDestroy
) ||
85 ((_flags
& RemovableIfEmpty
) && empty() == 0)));
87 cerr
<< "is " << _path
<< " removable ? " << r
<< endl
;
93 FileSource::init (const ustring
& pathstr
, bool must_exist
)
95 _timeline_position
= 0;
97 if (!find (_type
, pathstr
, must_exist
, _file_is_new
, _channel
, _path
)) {
98 throw MissingSource ();
101 set_within_session_from_path (pathstr
);
103 if (_within_session
) {
104 _name
= Glib::path_get_basename (_name
);
107 if (_file_is_new
&& must_exist
) {
115 FileSource::set_state (const XMLNode
& node
, int /*version*/)
117 const XMLProperty
* prop
;
119 if ((prop
= node
.property (X_("channel"))) != 0) {
120 _channel
= atoi (prop
->value());
129 FileSource::mark_take (const ustring
& id
)
137 FileSource::move_to_trash (const ustring
& trash_dir_name
)
139 if (!within_session() || !writable()) {
143 /* don't move the file across filesystems, just stick it in the
144 trash_dir_name directory on whichever filesystem it was already on
148 v
.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path
)));
149 v
.push_back (trash_dir_name
);
150 v
.push_back (Glib::path_get_basename (_path
));
152 string newpath
= Glib::build_filename (v
);
154 /* the new path already exists, try versioning */
156 if (Glib::file_test (newpath
.c_str(), Glib::FILE_TEST_EXISTS
)) {
157 char buf
[PATH_MAX
+1];
161 snprintf (buf
, sizeof (buf
), "%s.%d", newpath
.c_str(), version
);
164 while (access (newpath_v
.c_str(), F_OK
) == 0 && version
< 999) {
165 snprintf (buf
, sizeof (buf
), "%s.%d", newpath
.c_str(), ++version
);
169 if (version
== 999) {
170 PBD::error
<< string_compose (
171 _("there are already 1000 files with names like %1; versioning discontinued"),
178 if (::rename (_path
.c_str(), newpath
.c_str()) != 0) {
179 PBD::error
<< string_compose (
180 _("cannot rename file source from %1 to %2 (%3)"),
181 _path
, newpath
, strerror (errno
)) << endmsg
;
185 if (move_dependents_to_trash() != 0) {
186 /* try to back out */
187 rename (newpath
.c_str(), _path
.c_str());
193 /* file can not be removed twice, since the operation is not idempotent */
194 _flags
= Flag (_flags
& ~(RemoveAtDestroy
|Removable
|RemovableIfEmpty
));
199 /** Find the actual source file based on \a filename.
201 * If the source is within the session tree, \a filename should be a simple filename (no slashes).
202 * If the source is external, \a filename should be a full path.
203 * In either case, found_path is set to the complete absolute path of the source file.
204 * \return true iff the file was found.
207 FileSource::find (DataType type
, const ustring
& path
, bool must_exist
,
208 bool& isnew
, uint16_t& chan
, ustring
& found_path
)
210 Glib::ustring search_path
= search_paths
[type
];
212 ustring pathstr
= path
;
213 ustring::size_type pos
;
218 if (pathstr
[0] != '/') {
220 /* non-absolute pathname: find pathstr in search path */
222 vector
<ustring
> dirs
;
227 if (search_path
.length() == 0) {
228 error
<< _("FileSource: search path not set") << endmsg
;
232 split (search_path
, dirs
, ':');
236 for (vector
<ustring
>::iterator i
= dirs
.begin(); i
!= dirs
.end(); ++i
) {
238 if (fullpath
[fullpath
.length()-1] != '/') {
244 /* i (paul) made a nasty design error by using ':' as a special character in
245 Ardour 0.99 .. this hack tries to make things sort of work.
248 if ((pos
= pathstr
.find_last_of (':')) != ustring::npos
) {
250 if (Glib::file_test (fullpath
, Glib::FILE_TEST_EXISTS
|Glib::FILE_TEST_IS_REGULAR
)) {
252 /* its a real file, no problem */
261 /* might be an older session using file:channel syntax. see if the version
262 without the :suffix exists
265 ustring shorter
= pathstr
.substr (0, pos
);
268 if (fullpath
[fullpath
.length()-1] != '/') {
274 if (Glib::file_test (pathstr
, Glib::FILE_TEST_EXISTS
|Glib::FILE_TEST_IS_REGULAR
)) {
275 chan
= atoi (pathstr
.substr (pos
+1));
283 /* new derived file (e.g. for timefx) being created in a newer session */
290 if (Glib::file_test (fullpath
, Glib::FILE_TEST_EXISTS
|Glib::FILE_TEST_IS_REGULAR
)) {
299 error
<< string_compose (
300 _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
301 pathstr
, search_path
) << endmsg
;
304 } else if (cnt
== 0) {
307 error
<< string_compose(
308 _("Filesource: cannot find required file (%1): while searching %2"),
309 pathstr
, search_path
) << endmsg
;
316 /* Current find() is unable to parse relative path names to yet non-existant
317 sources. QuickFix(tm) */
318 if (keeppath
== "") {
320 error
<< "FileSource::find(), keeppath = \"\", but the file must exist" << endl
;
326 found_path
= keeppath
;
332 /* external files and/or very very old style sessions include full paths */
334 /* ugh, handle ':' situation */
336 if ((pos
= pathstr
.find_last_of (':')) != ustring::npos
) {
338 ustring shorter
= pathstr
.substr (0, pos
);
340 if (Glib::file_test (shorter
, Glib::FILE_TEST_EXISTS
|Glib::FILE_TEST_IS_REGULAR
)) {
341 chan
= atoi (pathstr
.substr (pos
+1));
346 found_path
= pathstr
;
348 if (!Glib::file_test (pathstr
, Glib::FILE_TEST_EXISTS
|Glib::FILE_TEST_IS_REGULAR
)) {
350 /* file does not exist or we cannot read it */
353 error
<< string_compose(
354 _("Filesource: cannot find required file (%1): %2"),
355 path
, strerror (errno
)) << endmsg
;
359 if (errno
!= ENOENT
) {
360 error
<< string_compose(
361 _("Filesource: cannot check for existing file (%1): %2"),
362 path
, strerror (errno
)) << endmsg
;
382 FileSource::set_source_name (const ustring
& newname
, bool destructive
)
384 Glib::Mutex::Lock
lm (_lock
);
385 ustring oldpath
= _path
;
386 ustring newpath
= _session
.change_source_path_by_name (oldpath
, _name
, newname
, destructive
);
388 if (newpath
.empty()) {
389 error
<< string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg
;
393 // Test whether newpath exists, if yes notify the user but continue.
394 if (Glib::file_test (newpath
, Glib::FILE_TEST_EXISTS
)) {
395 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
;
399 if (::rename (oldpath
.c_str(), newpath
.c_str()) != 0) {
400 error
<< string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath
, newpath
, strerror(errno
)) << endmsg
;
404 _name
= Glib::path_get_basename (newpath
);
411 FileSource::set_search_path (DataType type
, const ustring
& p
)
413 search_paths
[type
] = p
;
417 FileSource::mark_immutable ()
419 /* destructive sources stay writable, and their other flags don't change. */
420 if (!(_flags
& Destructive
)) {
421 _flags
= Flag (_flags
& ~(Writable
|Removable
|RemovableIfEmpty
|RemoveAtDestroy
|CanRename
));
426 FileSource::mark_nonremovable ()
428 _flags
= Flag (_flags
& ~(Removable
|RemovableIfEmpty
|RemoveAtDestroy
));
432 FileSource::set_within_session_from_path (const std::string
& path
)
434 _within_session
= _session
.path_is_within_session (path
);
438 FileSource::unstubify ()
440 string::size_type pos
= _path
.find (stub_dir_name
);
442 if (pos
== string::npos
|| (_flags
& Destructive
)) {
448 v
.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path
)));
449 v
.push_back (Glib::path_get_basename(_path
));
451 string newpath
= Glib::build_filename (v
);
453 if (::rename (_path
.c_str(), newpath
.c_str()) != 0) {
454 error
<< string_compose (_("rename from %1 to %2 failed: %3)"), _path
, newpath
, strerror (errno
)) << endmsg
;
464 FileSource::set_path (const std::string
& newpath
)
470 FileSource::inc_use_count ()
472 Source::inc_use_count ();