More solid "fake" recording and serialization
[ardour.git] / libs / ardour / smf_source.cc
1 /*
2     Copyright (C) 2006 Paul Davis 
3         Written by Dave Robillard, 2006
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <vector>
22
23 #include <sys/time.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <errno.h>
27
28 #include <pbd/mountpoint.h>
29 #include <pbd/pathscanner.h>
30 #include <pbd/stl_delete.h>
31 #include <pbd/strsplit.h>
32
33 #include <glibmm/miscutils.h>
34
35 #include <ardour/smf_source.h>
36 #include <ardour/session.h>
37
38 #include "i18n.h"
39
40 using namespace ARDOUR;
41
42 string SMFSource::_search_path;
43
44 /*sigc::signal<void,struct tm*, time_t> SMFSource::HeaderPositionOffsetChanged;
45 bool                                  SMFSource::header_position_negative;
46 uint64_t                              SMFSource::header_position_offset;
47 */
48
49 SMFSource::SMFSource (std::string path, Flag flags)
50         : MidiSource (region_name_from_path(path)), _flags (flags)
51 {
52         /* constructor used for new internal-to-session files. file cannot exist */
53
54         if (init (path, false)) {
55                 throw failed_constructor ();
56         }
57         
58         assert(_name.find("/") == string::npos);
59
60         SourceCreated (this); /* EMIT SIGNAL */
61 }
62
63 SMFSource::SMFSource (const XMLNode& node)
64         : MidiSource (node), _flags (Flag (Writable|CanRename))
65 {
66         /* constructor used for existing internal-to-session files. file must exist */
67
68         if (set_state (node)) {
69                 throw failed_constructor ();
70         }
71         
72         if (init (_name, true)) {
73                 throw failed_constructor ();
74         }
75         
76         assert(_name.find("/") == string::npos);
77         
78         SourceCreated (this); /* EMIT SIGNAL */
79 }
80
81 SMFSource::~SMFSource ()
82 {
83         if (removable()) {
84                 unlink (_path.c_str());
85         }
86 }
87
88 bool
89 SMFSource::removable () const
90 {
91         return (_flags & Removable) && ((_flags & RemoveAtDestroy) || 
92                                       ((_flags & RemovableIfEmpty) && is_empty (_path)));
93 }
94
95 int
96 SMFSource::init (string pathstr, bool must_exist)
97 {
98         //bool is_new = false;
99
100         /*
101         if (!find (pathstr, must_exist, is_new)) {
102                 cerr << "cannot find " << pathstr << " with me = " << must_exist << endl;
103                 return -1;
104         }
105
106         if (is_new && must_exist) {
107                 return -1;
108         }
109         */
110
111         // Yeah, we found it.  Swear.
112
113         assert(_name.find("/") == string::npos);
114         return 0;
115 }
116
117 int
118 SMFSource::update_header (jack_nframes_t when, struct tm&, time_t)
119 {
120         return 0;
121 }
122
123 int
124 SMFSource::flush_header ()
125 {
126         return 0;
127 }
128
129 jack_nframes_t
130 SMFSource::read_unlocked (MidiBuffer& dst, jack_nframes_t start, jack_nframes_t cnt) const
131 {
132         dst.clear();
133         return cnt;
134 }
135
136 jack_nframes_t
137 SMFSource::write_unlocked (MidiBuffer& src, jack_nframes_t cnt)
138 {
139         ViewDataRangeReady (_length, cnt); /* EMIT SIGNAL */
140         _length += cnt;
141         return cnt;
142 }
143
144 XMLNode&
145 SMFSource::get_state ()
146 {
147         XMLNode& root (MidiSource::get_state());
148         char buf[16];
149         snprintf (buf, sizeof (buf), "0x%x", (int)_flags);
150         root.add_property ("flags", buf);
151         return root;
152 }
153
154 int
155 SMFSource::set_state (const XMLNode& node)
156 {
157         const XMLProperty* prop;
158
159         if (MidiSource::set_state (node)) {
160                 return -1;
161         }
162
163         if ((prop = node.property (X_("flags"))) != 0) {
164
165                 int ival;
166                 sscanf (prop->value().c_str(), "0x%x", &ival);
167                 _flags = Flag (ival);
168
169         } else {
170
171                 _flags = Flag (0);
172
173         }
174
175         assert(_name.find("/") == string::npos);
176
177         return 0;
178 }
179
180 void
181 SMFSource::mark_for_remove ()
182 {
183         if (!writable()) {
184                 return;
185         }
186         _flags = Flag (_flags | RemoveAtDestroy);
187 }
188
189 void
190 SMFSource::mark_streaming_write_completed ()
191 {
192         if (!writable()) {
193                 return;
194         }
195 #if 0
196         Glib::Mutex::Lock lm (_lock);
197
198
199         next_peak_clear_should_notify = true;
200
201         if (_peaks_built || pending_peak_builds.empty()) {
202                 _peaks_built = true;
203                  PeaksReady (); /* EMIT SIGNAL */
204         }
205 #endif
206 }
207
208 void
209 SMFSource::mark_take (string id)
210 {
211         if (writable()) {
212                 _take_id = id;
213         }
214 }
215
216 int
217 SMFSource::move_to_trash (const string trash_dir_name)
218 {
219         string newpath;
220
221         if (!writable()) {
222                 return -1;
223         }
224
225         /* don't move the file across filesystems, just
226            stick it in the `trash_dir_name' directory
227            on whichever filesystem it was already on.
228         */
229
230         newpath = Glib::path_get_dirname (_path);
231         newpath = Glib::path_get_dirname (newpath);
232
233         newpath += '/';
234         newpath += trash_dir_name;
235         newpath += '/';
236         newpath += Glib::path_get_basename (_path);
237
238         if (access (newpath.c_str(), F_OK) == 0) {
239
240                 /* the new path already exists, try versioning */
241                 
242                 char buf[PATH_MAX+1];
243                 int version = 1;
244                 string newpath_v;
245
246                 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
247                 newpath_v = buf;
248
249                 while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
250                         snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
251                         newpath_v = buf;
252                 }
253                 
254                 if (version == 999) {
255                         PBD::error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
256                                           newpath)
257                               << endmsg;
258                 } else {
259                         newpath = newpath_v;
260                 }
261
262         } else {
263
264                 /* it doesn't exist, or we can't read it or something */
265
266         }
267
268         if (::rename (_path.c_str(), newpath.c_str()) != 0) {
269                 PBD::error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"),
270                                   _path, newpath, strerror (errno))
271                       << endmsg;
272                 return -1;
273         }
274 #if 0
275         if (::unlink (peakpath.c_str()) != 0) {
276                 PBD::error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
277                                   peakpath, _path, strerror (errno))
278                       << endmsg;
279                 /* try to back out */
280                 rename (newpath.c_str(), _path.c_str());
281                 return -1;
282         }
283             
284         _path = newpath;
285         peakpath = "";
286 #endif  
287         /* file can not be removed twice, since the operation is not idempotent */
288
289         _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
290
291         return 0;
292 }
293
294 // FIXME: Merge this with audiofilesource somehow (make a generic filesource?)
295 bool
296 SMFSource::find (string pathstr, bool must_exist, bool& isnew)
297 {
298         string::size_type pos;
299         bool ret = false;
300
301         isnew = false;
302
303         /* clean up PATH:CHANNEL notation so that we are looking for the correct path */
304
305         if ((pos = pathstr.find_last_of (':')) == string::npos) {
306                 pathstr = pathstr;
307         } else {
308                 pathstr = pathstr.substr (0, pos);
309         }
310
311         if (pathstr[0] != '/') {
312
313                 /* non-absolute pathname: find pathstr in search path */
314
315                 vector<string> dirs;
316                 int cnt;
317                 string fullpath;
318                 string keeppath;
319
320                 if (_search_path.length() == 0) {
321                         PBD::error << _("FileSource: search path not set") << endmsg;
322                         goto out;
323                 }
324
325                 split (_search_path, dirs, ':');
326
327                 cnt = 0;
328                 
329                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
330
331                         fullpath = *i;
332                         if (fullpath[fullpath.length()-1] != '/') {
333                                 fullpath += '/';
334                         }
335                         fullpath += pathstr;
336                         
337                         if (access (fullpath.c_str(), R_OK) == 0) {
338                                 keeppath = fullpath;
339                                 ++cnt;
340                         } 
341                 }
342
343                 if (cnt > 1) {
344
345                         PBD::error << string_compose (_("FileSource: \"%1\" is ambigous when searching %2\n\t"), pathstr, _search_path) << endmsg;
346                         goto out;
347
348                 } else if (cnt == 0) {
349
350                         if (must_exist) {
351                                 PBD::error << string_compose(_("Filesource: cannot find required file (%1): while searching %2"), pathstr, _search_path) << endmsg;
352                                 goto out;
353                         } else {
354                                 isnew = true;
355                         }
356                 }
357                 
358                 _name = pathstr;
359                 _path = keeppath;
360                 ret = true;
361
362         } else {
363                 
364                 /* external files and/or very very old style sessions include full paths */
365                 
366                 _path = pathstr;
367                 _name = pathstr.substr (pathstr.find_last_of ('/') + 1);
368                 
369                 if (access (_path.c_str(), R_OK) != 0) {
370
371                         /* file does not exist or we cannot read it */
372
373                         if (must_exist) {
374                                 PBD::error << string_compose(_("Filesource: cannot find required file (%1): %2"), _path, strerror (errno)) << endmsg;
375                                 goto out;
376                         }
377                         
378                         if (errno != ENOENT) {
379                                 PBD::error << string_compose(_("Filesource: cannot check for existing file (%1): %2"), _path, strerror (errno)) << endmsg;
380                                 goto out;
381                         }
382                         
383                         /* a new file */
384
385                         isnew = true;
386                         ret = true;
387
388                 } else {
389                         
390                         /* already exists */
391
392                         ret = true;
393                 }
394         }
395         
396   out:
397         return ret;
398 }
399
400 void
401 SMFSource::set_search_path (string p)
402 {
403         _search_path = p;
404 }
405
406
407 void
408 SMFSource::set_allow_remove_if_empty (bool yn)
409 {
410         if (writable()) {
411                 _allow_remove_if_empty = yn;
412         }
413 }
414
415 int
416 SMFSource::set_name (string newname, bool destructive)
417 {
418         //Glib::Mutex::Lock lm (_lock); FIXME
419         string oldpath = _path;
420         string newpath = Session::change_audio_path_by_name (oldpath, _name, newname, destructive);
421
422         if (newpath.empty()) {
423                 PBD::error << string_compose (_("programming error: %1"), "cannot generate a changed audio path") << endmsg;
424                 return -1;
425         }
426
427         if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
428                 PBD::error << string_compose (_("cannot rename audio file for %1 to %2"), _name, newpath) << endmsg;
429                 return -1;
430         }
431
432         _name = Glib::path_get_basename (newpath);
433         _path = newpath;
434
435         return 0;//rename_peakfile (peak_path (_path));
436 }
437
438 bool
439 SMFSource::is_empty (string path)
440 {
441         /* XXX fix me */
442
443         return false;
444 }
445