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