merge (w/fix) with master
[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/stl_delete.h"
32 #include "pbd/strsplit.h"
33 #include "pbd/shortpath.h"
34 #include "pbd/enumwriter.h"
35 #include "pbd/file_utils.h"
36
37 #include <glibmm/miscutils.h>
38 #include <glibmm/fileutils.h>
39 #include <glibmm/threads.h>
40
41 #include "ardour/data_type.h"
42 #include "ardour/file_source.h"
43 #include "ardour/session.h"
44 #include "ardour/source.h"
45 #include "ardour/utils.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 PBD::Signal2<int,std::string,std::vector<std::string> > FileSource::AmbiguousFileName;
55
56 FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag)
57         : Source(session, type, path, flag)
58         , _path (path)
59         , _file_is_new (!origin.empty()) // origin empty => new file VS. origin !empty => new file
60         , _channel (0)
61         , _origin (origin)
62         , _open (false)
63 {
64         set_within_session_from_path (path);
65
66         prevent_deletion ();
67 }
68
69 FileSource::FileSource (Session& session, const XMLNode& node, bool /*must_exist*/)
70         : Source (session, node)
71         , _file_is_new (false)
72 {
73         /* this setting of _path is temporary - we expect derived classes
74            to call ::init() which will actually locate the file
75            and reset _path and _within_session correctly.
76         */
77
78         _path = _name;
79         _within_session = true;
80
81         prevent_deletion ();
82 }
83
84 void
85 FileSource::prevent_deletion ()
86 {
87         /* if this file already exists, it cannot be removed, ever
88          */
89
90         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
91                 if (!(_flags & Destructive)) {
92                         mark_immutable ();
93                 } else {
94                         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
95                 }
96         }
97 }
98
99 bool
100 FileSource::removable () const
101 {
102         bool r = ((_flags & Removable)
103                   && ((_flags & RemoveAtDestroy) ||
104                       ((_flags & RemovableIfEmpty) && empty() == 0)));
105
106         return r;
107 }
108
109 int
110 FileSource::init (const string& pathstr, bool must_exist)
111 {
112         _timeline_position = 0;
113
114         if (Stateful::loading_state_version < 3000) {
115                 if (!find_2X (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
116                         throw MissingSource (pathstr, _type);
117                 }
118         } else {
119                 if (!find (_session, _type, pathstr, must_exist, _file_is_new, _channel, _path)) {
120                         throw MissingSource (pathstr, _type);
121                 }
122         }
123
124         set_within_session_from_path (_path);
125
126         _name = Glib::path_get_basename (_path);
127
128         if (must_exist) {
129                 if (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
130                         throw MissingSource (pathstr, _type);
131                 }
132         }
133
134         return 0;
135 }
136
137 int
138 FileSource::set_state (const XMLNode& node, int /*version*/)
139 {
140         const XMLProperty* prop;
141
142         if ((prop = node.property (X_("channel"))) != 0) {
143                 _channel = atoi (prop->value());
144         } else {
145                 _channel = 0;
146         }
147
148         if ((prop = node.property (X_("origin"))) != 0) {
149                 _origin = prop->value();
150         }
151
152         return 0;
153 }
154
155 void
156 FileSource::mark_take (const string& id)
157 {
158         if (writable ()) {
159                 _take_id = id;
160         }
161 }
162
163 int
164 FileSource::move_to_trash (const string& trash_dir_name)
165 {
166         if (!within_session() || !writable()) {
167                 return -1;
168         }
169
170         /* don't move the file across filesystems, just stick it in the
171            trash_dir_name directory on whichever filesystem it was already on
172         */
173
174         vector<string> v;
175         v.push_back (Glib::path_get_dirname (Glib::path_get_dirname (_path)));
176         v.push_back (trash_dir_name);
177         v.push_back (Glib::path_get_basename (_path));
178
179         string newpath = Glib::build_filename (v);
180
181         /* the new path already exists, try versioning */
182
183         if (Glib::file_test (newpath.c_str(), Glib::FILE_TEST_EXISTS)) {
184                 char buf[PATH_MAX+1];
185                 int version = 1;
186                 string newpath_v;
187
188                 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
189                 newpath_v = buf;
190
191                 while (Glib::file_test (newpath_v, Glib::FILE_TEST_EXISTS) && version < 999) {
192                         snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
193                         newpath_v = buf;
194                 }
195
196                 if (version == 999) {
197                         PBD::error << string_compose (
198                                         _("there are already 1000 files with names like %1; versioning discontinued"),
199                                         newpath) << endmsg;
200                 } else {
201                         newpath = newpath_v;
202                 }
203         }
204
205         if (::rename (_path.c_str(), newpath.c_str()) != 0) {
206                 PBD::error << string_compose (
207                                 _("cannot rename file source from %1 to %2 (%3)"),
208                                 _path, newpath, strerror (errno)) << endmsg;
209                 return -1;
210         }
211
212         if (move_dependents_to_trash() != 0) {
213                 /* try to back out */
214                 rename (newpath.c_str(), _path.c_str());
215                 return -1;
216         }
217
218         _path = newpath;
219
220         /* file can not be removed twice, since the operation is not idempotent */
221         _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
222
223         return 0;
224 }
225
226 /** Find the actual source file based on \a filename.
227  *
228  * If the source is within the session tree, \a filename should be a simple filename (no slashes).
229  * If the source is external, \a filename should be a full path.
230  * In either case, found_path is set to the complete absolute path of the source file.
231  * \return true iff the file was found.
232  */
233 bool
234 FileSource::find (Session& s, DataType type, const string& path, bool must_exist,
235                   bool& isnew, uint16_t& /* chan */, string& found_path)
236 {
237         bool ret = false;
238         string keeppath;
239
240         isnew = false;
241
242         if (!Glib::path_is_absolute (path)) {
243                 vector<string> hits;
244                 string fullpath;
245                 std::vector<std::string> dirs = s.source_search_path (type);
246
247                 if (dirs.size() == 0) {
248                         error << _("FileSource: search path not set") << endmsg;
249                         goto out;
250                 }
251
252                 hits.clear ();
253
254                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
255
256                         fullpath = Glib::build_filename (*i, path);
257
258                         if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
259                                 keeppath = fullpath;
260                                 hits.push_back (fullpath);
261                         }
262                 }
263
264                 /* Remove duplicate inodes from the list of ambiguous files, since if there are symlinks
265                    in the session path it is possible to arrive at the same file via more than one path.
266
267                    I suppose this is not necessary on Windows.
268                 */
269
270                 vector<string> de_duped_hits;
271
272                 for (vector<string>::iterator i = hits.begin(); i != hits.end(); ++i) {
273
274                         vector<string>::iterator j = i;
275                         ++j;
276                         
277                         while (j != hits.end()) {
278                                 if (PBD::equivalent_paths (*i, *j)) {
279                                         /* *i and *j are the same file; break out of the loop early */
280                                         break;
281                                 }
282
283                                 ++j;
284                         }
285
286                         if (j == hits.end ()) {
287                                 de_duped_hits.push_back (*i);
288                         }
289                 }
290
291                 if (de_duped_hits.size() > 1) {
292
293                         /* more than one match: ask the user */
294
295                         int which = FileSource::AmbiguousFileName (path, de_duped_hits).get_value_or (-1);
296
297                         if (which < 0) {
298                                 goto out;
299                         } else {
300                                 keeppath = de_duped_hits[which];
301                         }
302
303                 } else if (de_duped_hits.size() == 0) {
304
305                         /* no match: error */
306
307                         if (must_exist) {
308                                 error << string_compose(
309                                         _("Filesource: cannot find required file (%1)"), path) << endmsg;
310                                 goto out;
311                         } else {
312                                 isnew = true;
313                         }
314                 } else {
315
316                         /* only one match: happy days */
317                         
318                         keeppath = de_duped_hits[0];
319                 }
320                                                   
321         } else {
322                 keeppath = path;
323         }
324
325         /* Current find() is unable to parse relative path names to yet non-existant
326            sources. QuickFix(tm)
327         */
328         if (keeppath == "") {
329                 if (must_exist) {
330                         error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
331                 } else {
332                         keeppath = path;
333                 }
334         }
335
336         found_path = keeppath;
337
338         ret = true;
339
340   out:
341         return ret;
342 }
343
344 /** Find the actual source file based on \a filename.
345  *
346  * If the source is within the session tree, \a filename should be a simple filename (no slashes).
347  * If the source is external, \a filename should be a full path.
348  * In either case, found_path is set to the complete absolute path of the source file.
349  * \return true iff the file was found.
350  */
351 bool
352 FileSource::find_2X (Session& s, DataType type, const string& path, bool must_exist,
353                      bool& isnew, uint16_t& chan, string& found_path)
354 {
355         string pathstr = path;
356         string::size_type pos;
357         bool ret = false;
358
359         isnew = false;
360
361         if (!Glib::path_is_absolute (pathstr)) {
362
363                 /* non-absolute pathname: find pathstr in search path */
364
365                 vector<string> dirs = s.source_search_path (type);
366
367                 int cnt;
368                 string fullpath;
369                 string keeppath;
370
371                 if (dirs.size() == 0) {
372                         error << _("FileSource: search path not set") << endmsg;
373                         goto out;
374                 }
375
376                 cnt = 0;
377
378                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
379
380                         fullpath = Glib::build_filename (*i, pathstr);
381
382                         /* i (paul) made a nasty design error by using ':' as a special character in
383                            Ardour 0.99 .. this hack tries to make things sort of work.
384                         */
385
386                         if ((pos = pathstr.find_last_of (':')) != string::npos) {
387
388                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
389
390                                         /* its a real file, no problem */
391
392                                         keeppath = fullpath;
393                                         ++cnt;
394
395                                 } else {
396
397                                         if (must_exist) {
398
399                                                 /* might be an older session using file:channel syntax. see if the version
400                                                    without the :suffix exists
401                                                  */
402
403                                                 string shorter = pathstr.substr (0, pos);
404                                                 fullpath = Glib::build_filename (*i, shorter);
405
406                                                 if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
407                                                         chan = atoi (pathstr.substr (pos+1));
408                                                         pathstr = shorter;
409                                                         keeppath = fullpath;
410                                                         ++cnt;
411                                                 }
412
413                                         } else {
414
415                                                 /* new derived file (e.g. for timefx) being created in a newer session */
416
417                                         }
418                                 }
419
420                         } else {
421
422                                 if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
423                                         keeppath = fullpath;
424                                         ++cnt;
425                                 }
426                         }
427                 }
428
429                 if (cnt > 1) {
430
431                         error << string_compose (
432                                         _("FileSource: \"%1\" is ambigous when searching\n\t"), pathstr) << endmsg;
433                         goto out;
434
435                 } else if (cnt == 0) {
436
437                         if (must_exist) {
438                                 error << string_compose(
439                                                 _("Filesource: cannot find required file (%1): while searching")
440                                                 , pathstr) << endmsg;
441                                 goto out;
442                         } else {
443                                 isnew = true;
444                         }
445                 }
446
447                 /* Current find() is unable to parse relative path names to yet non-existant
448                    sources. QuickFix(tm) */
449                 if (keeppath == "") {
450                         if (must_exist) {
451                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
452                         } else {
453                                 keeppath = pathstr;
454                         }
455                 }
456
457                 found_path = keeppath;
458
459                 ret = true;
460
461         } else {
462
463                 /* external files and/or very very old style sessions include full paths */
464
465                 /* ugh, handle ':' situation */
466
467                 if ((pos = pathstr.find_last_of (':')) != string::npos) {
468
469                         string shorter = pathstr.substr (0, pos);
470
471                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
472                                 chan = atoi (pathstr.substr (pos+1));
473                                 pathstr = shorter;
474                         }
475                 }
476
477                 found_path = pathstr;
478
479                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
480
481                         /* file does not exist or we cannot read it */
482
483                         if (must_exist) {
484                                 error << string_compose(
485                                                 _("Filesource: cannot find required file (%1): %2"),
486                                                 path, strerror (errno)) << endmsg;
487                                 goto out;
488                         }
489
490 #ifndef PLATFORM_WINDOWS
491                         if (errno != ENOENT) {
492                                 error << string_compose(
493                                                 _("Filesource: cannot check for existing file (%1): %2"),
494                                                 path, strerror (errno)) << endmsg;
495                                 goto out;
496                         }
497 #endif
498                         /* a new file */
499                         isnew = true;
500                         ret = true;
501
502                 } else {
503
504                         /* already exists */
505                         ret = true;
506                 }
507         }
508
509 out:
510         return ret;
511 }
512
513 int
514 FileSource::set_source_name (const string& newname, bool destructive)
515 {
516         Glib::Threads::Mutex::Lock lm (_lock);
517         string oldpath = _path;
518         string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
519
520         if (newpath.empty()) {
521                 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
522                 return -1;
523         }
524
525         // Test whether newpath exists, if yes notify the user but continue.
526         if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
527                 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;
528                 return -1;
529         }
530
531         if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
532                 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
533                 return -1;
534         }
535
536         _name = Glib::path_get_basename (newpath);
537         _path = newpath;
538
539         return 0;
540 }
541
542 void
543 FileSource::mark_immutable ()
544 {
545         /* destructive sources stay writable, and their other flags don't change.  */
546         if (!(_flags & Destructive)) {
547                 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
548         }
549 }
550
551 void
552 FileSource::mark_immutable_except_write ()
553 {
554         /* destructive sources stay writable, and their other flags don't change.  */
555         if (!(_flags & Destructive)) {
556                 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
557         }
558 }
559
560 void
561 FileSource::mark_nonremovable ()
562 {
563         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
564 }
565
566 void
567 FileSource::set_within_session_from_path (const std::string& path)
568 {
569         _within_session = _session.path_is_within_session (path);
570 }
571
572 void
573 FileSource::set_path (const std::string& newpath)
574 {
575         _path = newpath;
576 }
577
578 void
579 FileSource::inc_use_count ()
580 {
581         Source::inc_use_count ();
582 }
583