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