changes from 2.X starting in march 2009 through oct 20 2009 (5826 inclusive)
[ardour.git] / libs / ardour / file_source.cc
1 /*
2     Copyright (C) 2006-2009 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <vector>
21
22 #include <sys/time.h>
23 #include <sys/stat.h>
24 #include <stdio.h> // for rename(), sigh
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <errno.h>
28
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"
36
37 #include <glibmm/miscutils.h>
38 #include <glibmm/fileutils.h>
39 #include <glibmm/thread.h>
40
41 #include "ardour/file_source.h"
42 #include "ardour/session.h"
43 #include "ardour/session_directory.h"
44 #include "ardour/source_factory.h"
45 #include "ardour/filename_extensions.h"
46
47 #include "i18n.h"
48
49 using namespace std;
50 using namespace ARDOUR;
51 using namespace PBD;
52 using namespace Glib;
53
54 static const std::string PATH_SEP = "/"; // I don't do windows
55
56 map<DataType, ustring> FileSource::search_paths;
57
58 FileSource::FileSource (Session& session, DataType type, const ustring& path, Source::Flag flag)
59         : Source(session, type, path, flag)
60         , _path(path)
61         , _file_is_new(true)
62         , _channel (0)
63 {
64         set_within_session_from_path (path);
65 }
66
67 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
68         : Source(session, node)
69         , _file_is_new (false)
70 {
71         /* this setting of _path is temporary - we expect derived classes
72            to call ::init() which will actually locate the file
73            and reset _path and _within_session correctly.
74         */
75
76         _path = _name;
77         _within_session = true;
78 }
79
80 bool
81 FileSource::removable () const
82 {
83         return (_flags & Removable)
84                 && ((_flags & RemoveAtDestroy) || 
85                     ((_flags & RemovableIfEmpty) && length(timeline_position()) == 0));
86 }
87
88 int
89 FileSource::init (const ustring& pathstr, bool must_exist)
90 {
91         _timeline_position = 0;
92
93         if (!find (_type, pathstr, must_exist, _file_is_new, _channel, _path)) {
94                 throw MissingSource ();
95         }
96
97         set_within_session_from_path (pathstr);
98
99         if (_within_session) {
100                 _name = Glib::path_get_basename (_name);
101         }
102
103         if (_file_is_new && must_exist) {
104                 return -1;
105         }
106
107         return 0;
108 }
109
110 int
111 FileSource::set_state (const XMLNode& node, int /*version*/)
112 {
113         const XMLProperty* prop;
114
115         if ((prop = node.property (X_("channel"))) != 0) {
116                 _channel = atoi (prop->value());
117         } else {
118                 _channel = 0;
119         }
120
121         return 0;
122 }
123
124 void
125 FileSource::mark_take (const ustring& id)
126 {
127         if (writable ()) {
128                 _take_id = id;
129         }
130 }
131
132 int
133 FileSource::move_to_trash (const ustring& trash_dir_name)
134 {
135         if (!within_session() || !writable()) {
136                 return -1;
137         }
138
139         /* don't move the file across filesystems, just stick it in the
140            trash_dir_name directory on whichever filesystem it was already on
141         */
142
143         ustring newpath;
144         newpath = Glib::path_get_dirname (_path);
145         newpath = Glib::path_get_dirname (newpath);
146
147         newpath += string(PATH_SEP) + trash_dir_name + PATH_SEP;
148         newpath += Glib::path_get_basename (_path);
149
150         /* the new path already exists, try versioning */
151         if (access (newpath.c_str(), F_OK) == 0) {
152                 char buf[PATH_MAX+1];
153                 int version = 1;
154                 ustring newpath_v;
155
156                 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
157                 newpath_v = buf;
158
159                 while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
160                         snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
161                         newpath_v = buf;
162                 }
163
164                 if (version == 999) {
165                         PBD::error << string_compose (
166                                         _("there are already 1000 files with names like %1; versioning discontinued"),
167                                         newpath) << endmsg;
168                 } else {
169                         newpath = newpath_v;
170                 }
171         }
172
173         if (::rename (_path.c_str(), newpath.c_str()) != 0) {
174                 PBD::error << string_compose (
175                                 _("cannot rename file source from %1 to %2 (%3)"),
176                                 _path, newpath, strerror (errno)) << endmsg;
177                 return -1;
178         }
179
180         if (move_dependents_to_trash() != 0) {
181                 /* try to back out */
182                 rename (newpath.c_str(), _path.c_str());
183                 return -1;
184         }
185
186         _path = newpath;
187
188         /* file can not be removed twice, since the operation is not idempotent */
189         _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
190
191         return 0;
192 }
193
194 /** Find the actual source file based on \a filename.
195  *
196  * If the source is within the session tree, \a filename should be a simple filename (no slashes).
197  * If the source is external, \a filename should be a full path.
198  * In either case, found_path is set to the complete absolute path of the source file.
199  * \return true iff the file was found.
200  */
201 bool
202 FileSource::find (DataType type, const ustring& path, bool must_exist,
203                   bool& isnew, uint16_t& chan, ustring& found_path)
204 {
205         Glib::ustring search_path = search_paths[type];
206
207         ustring pathstr = path;
208         ustring::size_type pos;
209         bool ret = false;
210
211         isnew = false;
212
213         if (pathstr[0] != '/') {
214
215                 /* non-absolute pathname: find pathstr in search path */
216
217                 vector<ustring> dirs;
218                 int cnt;
219                 ustring fullpath;
220                 ustring keeppath;
221
222                 if (search_path.length() == 0) {
223                         error << _("FileSource: search path not set") << endmsg;
224                         goto out;
225                 }
226
227                 split (search_path, dirs, ':');
228
229                 cnt = 0;
230
231                 for (vector<ustring>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
232                         fullpath = *i;
233                         if (fullpath[fullpath.length()-1] != '/') {
234                                 fullpath += '/';
235                         }
236
237                         fullpath += pathstr;
238
239                         /* i (paul) made a nasty design error by using ':' as a special character in
240                            Ardour 0.99 .. this hack tries to make things sort of work.
241                         */
242
243                         if ((pos = pathstr.find_last_of (':')) != ustring::npos) {
244
245                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
246
247                                         /* its a real file, no problem */
248
249                                         keeppath = fullpath;
250                                         ++cnt;
251
252                                 } else {
253
254                                         if (must_exist) {
255
256                                                 /* might be an older session using file:channel syntax. see if the version
257                                                    without the :suffix exists
258                                                  */
259
260                                                 ustring shorter = pathstr.substr (0, pos);
261                                                 fullpath = *i;
262
263                                                 if (fullpath[fullpath.length()-1] != '/') {
264                                                         fullpath += '/';
265                                                 }
266
267                                                 fullpath += shorter;
268
269                                                 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
270                                                         chan = atoi (pathstr.substr (pos+1));
271                                                         pathstr = shorter;
272                                                         keeppath = fullpath;
273                                                         ++cnt;
274                                                 }
275
276                                         } else {
277
278                                                 /* new derived file (e.g. for timefx) being created in a newer session */
279
280                                         }
281                                 }
282
283                         } else {
284
285                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
286                                         keeppath = fullpath;
287                                         ++cnt;
288                                 }
289                         }
290                 }
291
292                 if (cnt > 1) {
293
294                         error << string_compose (
295                                         _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
296                                         pathstr, search_path) << endmsg;
297                         goto out;
298
299                 } else if (cnt == 0) {
300
301                         if (must_exist) {
302                                 error << string_compose(
303                                                 _("Filesource: cannot find required file (%1): while searching %2"),
304                                                 pathstr, search_path) << endmsg;
305                                 goto out;
306                         } else {
307                                 isnew = true;
308                         }
309                 }
310
311                 /* Current find() is unable to parse relative path names to yet non-existant
312                    sources. QuickFix(tm) */
313                 if (keeppath == "") {
314                         if (must_exist) {
315                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
316                         } else {
317                                 keeppath = pathstr;
318                         }
319                 }
320
321                 found_path = keeppath;
322
323                 ret = true;
324
325         } else {
326
327                 /* external files and/or very very old style sessions include full paths */
328
329                 /* ugh, handle ':' situation */
330
331                 if ((pos = pathstr.find_last_of (':')) != ustring::npos) {
332
333                         ustring shorter = pathstr.substr (0, pos);
334
335                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
336                                 chan = atoi (pathstr.substr (pos+1));
337                                 pathstr = shorter;
338                         }
339                 }
340
341                 found_path = pathstr;
342
343                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
344
345                         /* file does not exist or we cannot read it */
346
347                         if (must_exist) {
348                                 error << string_compose(
349                                                 _("Filesource: cannot find required file (%1): %2"),
350                                                 path, strerror (errno)) << endmsg;
351                                 goto out;
352                         }
353
354                         if (errno != ENOENT) {
355                                 error << string_compose(
356                                                 _("Filesource: cannot check for existing file (%1): %2"),
357                                                 path, strerror (errno)) << endmsg;
358                                 goto out;
359                         }
360
361                         /* a new file */
362                         isnew = true;
363                         ret = true;
364
365                 } else {
366
367                         /* already exists */
368                         ret = true;
369                 }
370         }
371
372 out:
373         return ret;
374 }
375
376 int
377 FileSource::set_source_name (const ustring& newname, bool destructive)
378 {
379         Glib::Mutex::Lock lm (_lock);
380         ustring oldpath = _path;
381         ustring newpath = Session::change_source_path_by_name (oldpath, _name, newname, destructive);
382
383         if (newpath.empty()) {
384                 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
385                 return -1;
386         }
387
388         // Test whether newpath exists, if yes notify the user but continue.
389         if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
390                 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;
391                 return -1;
392         }
393
394         if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
395                 error << string_compose (_("cannot rename audio file %1 to %2"), _name, newpath) << endmsg;
396                 return -1;
397         }
398
399         _name = Glib::path_get_basename (newpath);
400         _path = newpath;
401
402         return 0;
403 }
404
405 void
406 FileSource::set_search_path (DataType type, const ustring& p)
407 {
408         search_paths[type] = p;
409 }
410
411 void
412 FileSource::mark_immutable ()
413 {
414         /* destructive sources stay writable, and their other flags don't change.  */
415         if (!(_flags & Destructive)) {
416                 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
417         }
418 }
419
420 void
421 FileSource::set_within_session_from_path (const std::string& path)
422 {
423         _within_session = _session.path_is_within_session (path);
424 }