1 /*
2 * Copyright (C) 2002 2003 2004 2005 2006, Magnus Hjorth
3 *
4 * This file is part of mhWaveEdit.
5 *
6 * mhWaveEdit is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * mhWaveEdit is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with mhWaveEdit; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include <config.h>
22
23 #include <unistd.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26
27 #include "tempfile.h"
28 #include "ringbuf.h"
29 #include "inifile.h"
30 #include "session.h"
31
32 #define MAX_REAL_SIZE inifile_get_guint32(INI_SETTING_REALMAX,INI_SETTING_REALMAX_DEFAULT)
33
34 static int tempfile_count = 0;
35
36
copyle16(guint8 * buf,guint16 n)37 static guint8 *copyle16(guint8 *buf, guint16 n)
38 {
39 buf[0] = n&0xFF;
40 n >>= 8;
41 buf[1] = n&0xFF;
42 return buf+2;
43 }
44
copybe16(guint8 * buf,guint16 n)45 static guint8 *copybe16(guint8 *buf, guint16 n)
46 {
47 buf[1] = n&0xFF;
48 n >>= 8;
49 buf[0] = n&0xFF;
50 return buf+2;
51 }
52
copyle32(guint8 * buf,guint32 n)53 static guint8 *copyle32(guint8 *buf, guint32 n)
54 {
55 buf[0] = n&0xFF;
56 n >>= 8;
57 buf[1] = n&0xFF;
58 n >>= 8;
59 buf[2] = n&0xFF;
60 n >>= 8;
61 buf[3] = n&0xFF;
62 return buf+4;
63 }
64
copybe32(guint8 * buf,guint32 n)65 static guint8 *copybe32(guint8 *buf, guint32 n)
66 {
67 buf[3] = n&0xFF;
68 n >>= 8;
69 buf[2] = n&0xFF;
70 n >>= 8;
71 buf[1] = n&0xFF;
72 n >>= 8;
73 buf[0] = n&0xFF;
74 return buf+4;
75 }
76
77 /*
78 PCM wav:
79 'RIFF' 4
80 #size+36 4
81 'wav ' 4
82 'fmt ' 4
83 #16 4
84 #1 2
85 #channels 2
86 #srate 4
87 #bytespersec 4
88 #bytesperframe 2
89 #bitspersmp 2
90 'data' 4
91 #size 4
92 ----- total 44 bytes
93
94 IEEE wav:
95 'RIFF' 4
96 #size+50 4
97 'wav ' 4
98 'fmt ' 4
99 #18 4
100 #3 2
101 #channels 2
102 #srate 4
103 #bytespersec 4
104 #bytesperframe 2
105 #bitspersmp 2
106 #0 2
107 'fact' 4
108 #4 4
109 #samples 4
110 'data' 4
111 #size 4
112 ------ total 58 bytes
113 */
get_wav_header(Dataformat * format,off_t datasize,void * buffer)114 guint get_wav_header(Dataformat *format, off_t datasize, void *buffer)
115 {
116 gboolean bigendian=FALSE;
117 guint32 l, Bps=format->samplerate*format->channels*format->samplesize;
118 guint16 Bpsmp=format->channels*format->samplesize;
119 guint16 samplebits=(format->samplesize)<<3;
120 guint8 *c = buffer;
121
122 if (format->type == DATAFORMAT_PCM) {
123 bigendian = format->bigendian;
124 l = datasize + 36;
125 } else {
126 bigendian = FALSE;
127 l = datasize + 50;
128 }
129 if (((off_t)l) < datasize) l = 0xFFFFFFFF;
130
131 if (bigendian)
132 memcpy(c,"RIFX",4);
133 else
134 memcpy(c,"RIFF",4);
135 c += 4;
136 c = bigendian?copybe32(c,l):copyle32(c,l);
137
138 if (format->type == DATAFORMAT_PCM) {
139 if (bigendian)
140 memcpy(c,"WAVEfmt \0\0\0\20\0\1",14);
141 else
142 memcpy(c,"WAVEfmt \20\0\0\0\1\0",14);
143 } else
144 memcpy(c,"WAVEfmt \22\0\0\0\3\0",14);
145 c += 14;
146 if (bigendian) {
147 c = copybe16(c,format->channels);
148 c = copybe32(c,format->samplerate);
149 c = copybe32(c,Bps);
150 c = copybe16(c,Bpsmp);
151 c = copybe16(c,samplebits);
152 } else {
153 c = copyle16(c,format->channels);
154 c = copyle32(c,format->samplerate);
155 c = copyle32(c,Bps);
156 c = copyle16(c,Bpsmp);
157 c = copyle16(c,samplebits);
158 }
159
160 if (format->type == DATAFORMAT_FLOAT) {
161 memcpy(c,"\0\0fact\4\0\0\0",10);
162 c += 10;
163 l = datasize / format->samplebytes;
164 if (((off_t)l) < datasize/format->samplebytes) l = 0xFFFFFFFF;
165 c = copyle32(c,l);
166 }
167 memcpy(c,"data",4);
168 c += 4;
169 l = (guint32) datasize;
170 if (((off_t)l) < datasize) l = 0xFFFFFFFF;
171 c = bigendian?copybe32(c,l):copyle32(c,l);
172
173 return (guint)((c-(guint8 *)buffer));
174 }
175
176
write_wav_header(EFILE * f,Dataformat * format,off_t datasize)177 gboolean write_wav_header(EFILE *f, Dataformat *format,
178 off_t datasize)
179 {
180 gchar c[58];
181 guint i;
182 i = get_wav_header(format,datasize,c);
183 return e_fwrite(c,i,f);
184 }
185
try_tempdir(gchar * dir)186 static gchar *try_tempdir(gchar *dir)
187 {
188 gchar *c;
189 FILE *f;
190 if (!dir || dir[0]==0) return 0;
191 c = g_strjoin("/",dir,".mhwaveedit_temp_test",NULL);
192 f = fopen(c,"w");
193 if (f) {
194 fclose(f);
195 unlink(c);
196 }
197 g_free(c);
198 return f?dir:NULL;
199 }
200
201 static GList *tempdirs = NULL;
202
set_temp_directories(GList * l)203 void set_temp_directories(GList *l)
204 {
205 guint i;
206 gchar *c,*d;
207 g_list_foreach(tempdirs,(GFunc)g_free,NULL);
208 g_list_free(tempdirs);
209 tempdirs = NULL;
210 i = 1;
211 while (l != NULL) {
212 c = (gchar *)l->data;
213 d = g_strdup_printf("tempDir%d",i);
214 inifile_set(d,c);
215 g_free(d);
216 tempdirs = g_list_append(tempdirs,g_strdup(l->data));
217 l = l->next;
218 i++;
219 }
220 while (1) {
221 d = g_strdup_printf("tempDir%d",i);
222 if (inifile_get(d,NULL) == NULL) break;
223 inifile_set(d,NULL);
224 g_free(d);
225 i++;
226 }
227 }
228
get_temp_directory(guint num)229 gchar *get_temp_directory(guint num)
230 {
231 gchar *c,*d;
232 gint i=1;
233
234 if (!tempdirs) {
235 c = inifile_get("tempDir1",NULL);
236 if (!c) {
237 c = getenv("TEMP");
238 if (c==NULL) c=getenv("TMP");
239 if (try_tempdir(c))
240 tempdirs = g_list_append(tempdirs,g_strdup(c));
241 c = g_strjoin("/",get_home_directory(),".mhwaveedit",NULL);
242 mkdir(c,CONFDIR_PERMISSION);
243 if (try_tempdir(c))
244 tempdirs = g_list_append(tempdirs,g_strdup(c));
245 } else {
246 do {
247 if (try_tempdir(c))
248 tempdirs = g_list_append(tempdirs,g_strdup(c));
249 i++;
250 d = g_strdup_printf("tempDir%d",i);
251 c = inifile_get(d,NULL);
252 g_free(d);
253 } while (c!=NULL);
254 }
255 }
256 return (gchar *)g_list_nth_data(tempdirs,num);
257 }
258
259 G_LOCK_DEFINE_STATIC(tempfile);
260
get_temp_filename_d(gchar * dir)261 gchar *get_temp_filename_d(gchar *dir)
262 {
263 gchar *c;
264 /* printf("%s\n",d); */
265 G_LOCK(tempfile);
266 if (dir != NULL)
267 c = g_strdup_printf("%s/mhwaveedit-temp-%d-%04d-%d",dir,
268 (int)getpid(),++tempfile_count,
269 session_get_id());
270 else c = NULL;
271 G_UNLOCK(tempfile);
272 return c;
273 }
274
get_temp_filename(guint dirnum)275 gchar *get_temp_filename(guint dirnum)
276 {
277 return get_temp_filename_d(get_temp_directory(dirnum));
278 }
279
280 struct temp {
281 guint dirs;
282 EFILE **handles;
283 off_t *written_bytes;
284 guint current;
285 gboolean realtime;
286 Ringbuf *start_buf;
287 Dataformat format;
288 /* These two are only used when we're writing out partial frames. */
289 gchar sample_buf[64];
290 guint sample_buf_pos;
291 };
292
tempfile_open_dir(struct temp * t,guint dirnum,gboolean df)293 static void tempfile_open_dir(struct temp *t, guint dirnum, gboolean df)
294 {
295 gboolean b;
296 gchar *c;
297 b = report_write_errors;
298 report_write_errors = df;
299 c = get_temp_filename(dirnum);
300 t->handles[dirnum] = e_fopen(c,EFILE_WRITE);
301 g_free(c);
302 if (t->handles[dirnum] != NULL &&
303 write_wav_header(t->handles[dirnum],&(t->format),0x7FFFFFFF)) {
304 e_fclose_remove(t->handles[dirnum]);
305 t->handles[dirnum] = NULL;
306 }
307 report_write_errors = b;
308 }
309
tempfile_init(Dataformat * format,gboolean realtime)310 TempFile tempfile_init(Dataformat *format, gboolean realtime)
311 {
312 guint i;
313 struct temp *t;
314
315 t = g_malloc(sizeof(*t));
316 t->current = 0;
317 t->realtime = realtime;
318 memcpy(&(t->format),format,sizeof(Dataformat));
319
320 if (!tempdirs) get_temp_directory(0);
321 g_assert(tempdirs != NULL);
322 t->dirs = g_list_length(tempdirs);
323 t->handles = g_malloc0(t->dirs*sizeof(EFILE *));
324 if (realtime)
325 for (i=0; i<t->dirs; i++) tempfile_open_dir(t,i,FALSE);
326 t->written_bytes = g_malloc0(t->dirs*sizeof(off_t));
327
328
329 if (!realtime) {
330 t->start_buf = ringbuf_new(MAX_REAL_SIZE);
331 g_assert(ringbuf_available(t->start_buf) == 0);
332 } else
333 t->start_buf = NULL;
334
335 t->sample_buf_pos = 0;
336
337 return t;
338 }
339
tempfile_write_main(struct temp * t,gchar * data,guint length)340 static gboolean tempfile_write_main(struct temp *t, gchar *data, guint length)
341 {
342 gboolean b,r;
343 guint i;
344
345 /* Handle partial frame writes */
346 if (t->sample_buf_pos > 0) {
347 i = MIN(length, t->format.samplebytes - t->sample_buf_pos);
348 memcpy(t->sample_buf + t->sample_buf_pos, data, i);
349 t->sample_buf_pos += i;
350 if (t->sample_buf_pos < t->format.samplebytes)
351 return FALSE;
352 t->sample_buf_pos = 0;
353 if (tempfile_write_main(t,t->sample_buf,t->format.samplebytes))
354 return TRUE;
355 data += i;
356 length -= i;
357 }
358
359 if (length % t->format.samplebytes != 0) {
360 i = length % t->format.samplebytes;
361 memcpy(t->sample_buf,data+length-i,i);
362 t->sample_buf_pos = i;
363 length -= i;
364 }
365
366 if (length == 0) return FALSE;
367
368
369
370 while (1) {
371 /* Make sure we have an opened file in the currently used directory*/
372 while (t->handles[t->current] == NULL && t->current < t->dirs) {
373 if (!t->realtime) tempfile_open_dir(t,t->current,FALSE);
374 if (t->handles[t->current] == NULL) t->current ++;
375 }
376 if (t->current >= t->dirs) {
377 /* Try again to make sure the user sees at least one error
378 * message. */
379 t->current = t->dirs-1;
380 tempfile_open_dir(t,t->current,TRUE);
381 if (t->handles[t->current] == NULL) return TRUE;
382 }
383 /* Write data */
384 b = report_write_errors;
385 report_write_errors = (t->current == t->dirs-1);
386 r = e_fwrite(data,length,t->handles[t->current]);
387 report_write_errors = b;
388 if (!r || (t->current == t->dirs-1)) {
389 t->written_bytes[t->current] += length;
390 return r;
391 }
392 t->current ++;
393 /* Loop around */
394 }
395 }
396
tempfile_write(TempFile handle,gpointer data,guint length)397 gboolean tempfile_write(TempFile handle, gpointer data, guint length)
398 {
399 struct temp *t = (struct temp *)handle;
400 gchar *buf,*c=data;
401 guint32 u;
402 /* Just add to start buffer */
403 if (t->start_buf != NULL) {
404 u = ringbuf_enqueue(t->start_buf, c, length);
405 if (u == length) return FALSE;
406
407 /* Send buffer to file*/
408 c = c+u;
409 length = length - u;
410 buf = g_malloc(4096);
411 while (1) {
412 u = ringbuf_dequeue(t->start_buf, buf, 4096);
413 if (u == 0) break;
414 if (tempfile_write_main(t,buf,u)) return TRUE;
415 }
416 ringbuf_free(t->start_buf);
417 t->start_buf = NULL;
418 }
419
420 /* Send data to file */
421 return tempfile_write_main(t,c,length);
422 }
423
tempfile_abort(TempFile handle)424 void tempfile_abort(TempFile handle)
425 {
426 struct temp *t = (struct temp *)handle;
427 guint i;
428 for (i=0; i<t->dirs; i++) {
429 if (t->handles[i] != NULL)
430 e_fclose_remove(t->handles[i]);
431 }
432 g_free(t->handles);
433 g_free(t->written_bytes);
434 if (t->start_buf != NULL)
435 ringbuf_free(t->start_buf);
436 g_free(t);
437 }
438
439 /* The error reporting in this function could be improved, but the user gets
440 * warned and it shouldn't leak so it's not that important. */
tempfile_finished(TempFile handle)441 Chunk *tempfile_finished(TempFile handle)
442 {
443 struct temp *t = (struct temp *)handle;
444 guint32 u;
445 Datasource *ds;
446 Chunk *r=NULL,*c,*d;
447 off_t l,o;
448 guint i;
449
450 if (t->start_buf != NULL) {
451 ds = gtk_type_new(datasource_get_type());
452 ds->type = DATASOURCE_REAL;
453 memcpy(&(ds->format),&(t->format),sizeof(Dataformat));
454 u = ringbuf_available(t->start_buf);
455 ds->data.real = g_malloc(u);
456 ringbuf_dequeue(t->start_buf,ds->data.real,u);
457
458 ds->bytes = (off_t)u;
459 ds->length = (off_t)(u / ds->format.samplebytes);
460
461 tempfile_abort(t);
462 return chunk_new_from_datasource(ds);
463 }
464
465 for (i=0; i<t->dirs; i++) {
466
467 if (t->handles[i] == NULL) continue;
468
469 l = t->written_bytes[i];
470 if (l == 0) {
471 e_fclose_remove(t->handles[i]);
472 t->handles[i] = NULL;
473 continue;
474 }
475
476 if (e_fseek(t->handles[i],0,SEEK_SET) ||
477 write_wav_header(t->handles[i], &(t->format),l)) {
478 e_fclose_remove(t->handles[i]);
479 t->handles[i] = NULL;
480 continue;
481 }
482
483 o = e_ftell(t->handles[i]);
484 if (o < 0) {
485 e_fclose_remove(t->handles[i]);
486 t->handles[i] = NULL;
487 continue;
488 }
489
490 ds = gtk_type_new(datasource_get_type());
491 ds->type = DATASOURCE_TEMPFILE;
492
493 memcpy(&(ds->format),&(t->format),sizeof(Dataformat));
494 ds->length = l/(t->format.samplebytes);
495 ds->bytes = ds->length * ds->format.samplebytes;
496 ds->data.virtual.filename = g_strdup(t->handles[i]->filename);
497 ds->data.virtual.offset = o;
498
499 e_fclose(t->handles[i]);
500 t->handles[i] = NULL;
501
502 c = chunk_new_from_datasource(ds);
503 if (r == NULL)
504 r = c;
505 else {
506 d = r;
507 r = chunk_append(d,c);
508 gtk_object_sink(GTK_OBJECT(d));
509 gtk_object_sink(GTK_OBJECT(c));
510 }
511 }
512
513 tempfile_abort((TempFile)t);
514 return r;
515 }
516
517