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