79a6e354f46e16c1a23fd616ce68e6effa0bf9ab
[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())));
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"), pathstr) << endmsg;
440                                 goto out;
441                         } else {
442                                 isnew = true;
443                         }
444                 }
445
446                 /* Current find() is unable to parse relative path names to yet non-existant
447                    sources. QuickFix(tm) */
448                 if (keeppath == "") {
449                         if (must_exist) {
450                                 error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl;
451                         } else {
452                                 keeppath = pathstr;
453                         }
454                 }
455
456                 found_path = keeppath;
457
458                 ret = true;
459
460         } else {
461
462                 /* external files and/or very very old style sessions include full paths */
463
464                 /* ugh, handle ':' situation */
465
466                 if ((pos = pathstr.find_last_of (':')) != string::npos) {
467
468                         string shorter = pathstr.substr (0, pos);
469
470                         if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
471                                 chan = atoi (pathstr.substr (pos+1));
472                                 pathstr = shorter;
473                         }
474                 }
475
476                 found_path = pathstr;
477
478                 if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
479
480                         /* file does not exist or we cannot read it */
481
482                         if (must_exist) {
483                                 error << string_compose(
484                                                 _("Filesource: cannot find required file (%1): %2"),
485                                                 path, strerror (errno)) << endmsg;
486                                 goto out;
487                         }
488
489 #ifndef PLATFORM_WINDOWS
490                         if (errno != ENOENT) {
491                                 error << string_compose(
492                                                 _("Filesource: cannot check for existing file (%1): %2"),
493                                                 path, strerror (errno)) << endmsg;
494                                 goto out;
495                         }
496 #endif
497                         /* a new file */
498                         isnew = true;
499                         ret = true;
500
501                 } else {
502
503                         /* already exists */
504                         ret = true;
505                 }
506         }
507
508 out:
509         return ret;
510 }
511
512 int
513 FileSource::set_source_name (const string& newname, bool destructive)
514 {
515         Glib::Threads::Mutex::Lock lm (_lock);
516         string oldpath = _path;
517         string newpath = _session.change_source_path_by_name (oldpath, _name, newname, destructive);
518
519         if (newpath.empty()) {
520                 error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg;
521                 return -1;
522         }
523
524         // Test whether newpath exists, if yes notify the user but continue.
525         if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
526                 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;
527                 return -1;
528         }
529
530         if (::rename (oldpath.c_str(), newpath.c_str()) != 0) {
531                 error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg;
532                 return -1;
533         }
534
535         _name = Glib::path_get_basename (newpath);
536         _path = newpath;
537
538         return 0;
539 }
540
541 void
542 FileSource::mark_immutable ()
543 {
544         /* destructive sources stay writable, and their other flags don't change.  */
545         if (!(_flags & Destructive)) {
546                 _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
547         }
548 }
549
550 void
551 FileSource::mark_immutable_except_write ()
552 {
553         /* destructive sources stay writable, and their other flags don't change.  */
554         if (!(_flags & Destructive)) {
555                 _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
556         }
557 }
558
559 void
560 FileSource::mark_nonremovable ()
561 {
562         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
563 }
564
565 void
566 FileSource::set_within_session_from_path (const std::string& path)
567 {
568         _within_session = _session.path_is_within_session (path);
569 }
570
571 void
572 FileSource::set_path (const std::string& newpath)
573 {
574         _path = newpath;
575 }
576
577 void
578 FileSource::inc_use_count ()
579 {
580         Source::inc_use_count ();
581 }
582
583 bool
584 FileSource::is_stub () const
585 {
586         if (!empty()) {
587                 return false;
588         }
589         
590         if (!removable()) {
591                 return false;
592         }
593
594         if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)) {
595                 return false;
596         }
597
598         return true;
599 }
600