ed9c80338abeb30724e6cf0d90972a4dbe7b5cca
[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 ARDOUR;
50 using namespace PBD;
51 using namespace Glib;
52
53 static const std::string PATH_SEP = "/"; // I don't do windows
54
55 map<DataType, ustring> FileSource::search_paths;
56
57 FileSource::FileSource (Session& session, DataType type,
58                 const ustring& path, bool embedded, Source::Flag flag)
59         : Source(session, type, path, flag)
60         , _path(path)
61         , _file_is_new(true)
62         , _channel (0)
63         , _is_embedded(embedded)
64 {
65 }
66
67 FileSource::FileSource (Session& session, const XMLNode& node, bool must_exist)
68         : Source(session, node)
69         , _file_is_new(false)
70 {
71         _path = _name;
72         _is_embedded = (_path.find(PATH_SEP) != string::npos);
73 }
74
75 bool
76 FileSource::removable () const
77 {
78         return (_flags & Removable)
79                 && (   (_flags & RemoveAtDestroy)
80                         || ((_flags & RemovableIfEmpty) && length() == 0));
81 }
82
83 int
84 FileSource::init (const ustring& pathstr, bool must_exist)
85 {
86         _length = 0;
87         _timeline_position = 0;
88
89         if (!find (_type, pathstr, must_exist, _file_is_new, _channel)) {
90                 throw MissingSource ();
91         }
92
93         if (_file_is_new && must_exist) {
94                 return -1;
95         }
96         
97         return 0;
98 }
99
100 int
101 FileSource::set_state (const XMLNode& node)
102 {
103         const XMLProperty* prop;
104
105         if ((prop = node.property (X_("channel"))) != 0) {
106                 _channel = atoi (prop->value());
107         } else {
108                 _channel = 0;
109         }
110
111         _is_embedded = (_name.find(PATH_SEP) == string::npos);
112
113         return 0;
114 }
115
116 void
117 FileSource::mark_take (const ustring& id)
118 {
119         if (writable ()) {
120                 _take_id = id;
121         }
122 }
123
124 int
125 FileSource::move_to_trash (const ustring& trash_dir_name)
126 {
127         if (is_embedded()) {
128                 cerr << "tried to move an embedded region to trash" << endl;
129                 return -1;
130         }
131
132         if (!writable()) {
133                 return -1;
134         }
135
136         /* don't move the file across filesystems, just stick it in the
137            trash_dir_name directory on whichever filesystem it was already on
138         */
139         
140         ustring newpath;
141         newpath = Glib::path_get_dirname (_path);
142         newpath = Glib::path_get_dirname (newpath); 
143
144         newpath += string(PATH_SEP) + trash_dir_name + PATH_SEP;
145         newpath += Glib::path_get_basename (_path);
146
147         /* the new path already exists, try versioning */
148         if (access (newpath.c_str(), F_OK) == 0) {
149                 char buf[PATH_MAX+1];
150                 int version = 1;
151                 ustring newpath_v;
152
153                 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
154                 newpath_v = buf;
155
156                 while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
157                         snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
158                         newpath_v = buf;
159                 }
160                 
161                 if (version == 999) {
162                         PBD::error << string_compose (
163                                         _("there are already 1000 files with names like %1; versioning discontinued"),
164                                         newpath) << endmsg;
165                 } else {
166                         newpath = newpath_v;
167                 }
168         }
169
170         if (::rename (_path.c_str(), newpath.c_str()) != 0) {
171                 PBD::error << string_compose (
172                                 _("cannot rename file source from %1 to %2 (%3)"),
173                                 _path, newpath, strerror (errno)) << endmsg;
174                 return -1;
175         }
176
177         if (move_dependents_to_trash() != 0) {
178                 /* try to back out */
179                 rename (newpath.c_str(), _path.c_str());
180                 return -1;
181         }
182             
183         _path = newpath;
184         
185         /* file can not be removed twice, since the operation is not idempotent */
186         _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
187
188         return 0;
189 }
190
191 /** Find the actual source file based on \a path.
192  * 
193  * If the source is embedded, \a path should be a filename (no slashes).
194  * If the source is external, \a path should be a full path.
195  * In either case, _path is set to the complete absolute path of the source file.
196  * \return true iff the file was found.
197  */
198 bool
199 FileSource::find (DataType type, const ustring& path, bool must_exist, bool& isnew, uint16_t& chan)
200 {
201         Glib::ustring search_path = search_paths[type];
202
203         ustring pathstr = path;
204         ustring::size_type pos;
205         bool ret = false;
206
207         isnew = false;
208
209         if (pathstr[0] != '/') {
210
211                 /* non-absolute pathname: find pathstr in search path */
212
213                 vector<ustring> dirs;
214                 int cnt;
215                 ustring fullpath;
216                 ustring keeppath;
217
218                 if (search_path.length() == 0) {
219                         error << _("FileSource: search path not set") << endmsg;
220                         goto out;
221                 }
222
223                 split (search_path, dirs, ':');
224
225                 cnt = 0;
226                 
227                 for (vector<ustring>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
228                         fullpath = *i;
229                         if (fullpath[fullpath.length()-1] != '/') {
230                                 fullpath += '/';
231                         }
232
233                         fullpath += pathstr;
234
235                         /* i (paul) made a nasty design error by using ':' as a special character in
236                            Ardour 0.99 .. this hack tries to make things sort of work.
237                         */
238                         
239                         if ((pos = pathstr.find_last_of (':')) != ustring::npos) {
240                                 
241                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
242
243                                         /* its a real file, no problem */
244                                         
245                                         keeppath = fullpath;
246                                         ++cnt;
247
248                                 } else {
249                                         
250                                         if (must_exist) {
251                                                 
252                                                 /* might be an older session using file:channel syntax. see if the version
253                                                    without the :suffix exists
254                                                  */
255                                                 
256                                                 ustring shorter = pathstr.substr (0, pos);
257                                                 fullpath = *i;
258
259                                                 if (fullpath[fullpath.length()-1] != '/') {
260                                                         fullpath += '/';
261                                                 }
262
263                                                 fullpath += shorter;
264
265                                                 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
266                                                         chan = atoi (pathstr.substr (pos+1));
267                                                         pathstr = shorter;
268                                                         keeppath = fullpath;
269                                                         ++cnt;
270                                                 } 
271                                                 
272                                         } else {
273                                                 
274                                                 /* new derived file (e.g. for timefx) being created in a newer session */
275                                                 
276                                         }
277                                 }
278
279                         } else {
280
281                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
282                                         keeppath = fullpath;
283                                         ++cnt;
284                                 } 
285                         }
286                 }
287
288                 if (cnt > 1) {
289
290                         error << string_compose (
291                                         _("FileSource: \"%1\" is ambigous when searching %2\n\t"),
292                                         pathstr, search_path) << endmsg;
293                         goto out;
294
295                 } else if (cnt == 0) {
296
297                         if (must_exist) {
298                                 error << string_compose(
299                                                 _("Filesource: cannot find required file (%1): while searching %2"),
300                                                 pathstr, search_path) << endmsg;
301                                 goto out;
302                         } else {
303                                 isnew = true;
304                         }
305                 }
306
307                 /* Current find() is unable to parse relative path names to yet non-existant
308                    sources. QuickFix(tm) */
309                 if (keeppath == "") {
310                         if (must_exist) {
311                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
312                         } else {
313                                 keeppath = pathstr;
314                         }
315                 }
316
317                 _path = keeppath;
318                 
319                 ret = true;
320
321         } else {
322                 
323                 /* external files and/or very very old style sessions include full paths */
324
325                 /* ugh, handle ':' situation */
326
327                 if ((pos = pathstr.find_last_of (':')) != ustring::npos) {
328                         
329                         ustring shorter = pathstr.substr (0, pos);
330
331                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
332                                 chan = atoi (pathstr.substr (pos+1));
333                                 pathstr = shorter;
334                         }
335                 }
336                 
337                 _path = pathstr;
338
339                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
340
341                         /* file does not exist or we cannot read it */
342                         
343                         if (must_exist) {
344                                 error << string_compose(
345                                                 _("Filesource: cannot find required file (%1): %2"),
346                                                 _path, strerror (errno)) << endmsg;
347                                 goto out;
348                         }
349                         
350                         if (errno != ENOENT) {
351                                 error << string_compose(
352                                                 _("Filesource: cannot check for existing file (%1): %2"),
353                                                 _path, strerror (errno)) << endmsg;
354                                 goto out;
355                         }
356                         
357                         /* a new file */
358                         isnew = true;
359                         ret = true;
360
361                 } else {
362                         
363                         /* already exists */
364                         ret = true;
365                 }
366         }
367         
368         if (_is_embedded) {
369                 _name = Glib::path_get_basename (_name);
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 (access(newpath.c_str(),F_OK) == 0) {
390                 error << _("Programming error! Ardour tried to rename a file over another file! It's safe to continue working, but please report this to the developers.") << 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