Strings
[vlc.git] / modules / access_filter / timeshift.c
1 /*****************************************************************************
2  * timeshift.c: access filter implementing timeshifting capabilities
3  *****************************************************************************
4  * Copyright (C) 2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *          Gildas Bazin <gbazin@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>
29
30 #include <errno.h>
31
32 #include <vlc/vlc.h>
33 #include <vlc/input.h>
34 #include "charset.h"
35
36 #include <unistd.h>
37
38 /*****************************************************************************
39  * Module descriptor
40  *****************************************************************************/
41 static int  Open ( vlc_object_t * );
42 static void Close( vlc_object_t * );
43
44 #define GRANULARITY_TEXT N_("Timeshift granularity")
45 /// \bug [String] typo
46 #define GRANULARITY_LONGTEXT N_( "This is the size of the temporary files " \
47   "tha will be used to store the timeshifted streams." )
48 #define DIR_TEXT N_("Timeshift directory")
49 #define DIR_LONGTEXT N_( "Directory used to store the timeshift temporary " \
50   "files." )
51
52 vlc_module_begin();
53     set_shortname( _("Timeshift") );
54     set_description( _("Timeshift") );
55     set_category( CAT_INPUT );
56     set_subcategory( SUBCAT_INPUT_ACCESS_FILTER );
57     set_capability( "access_filter", 0 );
58     add_shortcut( "timeshift" );
59     set_callbacks( Open, Close );
60
61     add_integer( "timeshift-granularity", 50, NULL, GRANULARITY_TEXT,
62                  GRANULARITY_LONGTEXT, VLC_TRUE );
63     add_directory( "timeshift-dir", 0, 0, DIR_TEXT, DIR_LONGTEXT, VLC_FALSE );
64 vlc_module_end();
65
66 /*****************************************************************************
67  * Local prototypes
68  *****************************************************************************/
69
70 static int      Seek( access_t *, int64_t );
71 static block_t *Block  ( access_t *p_access );
72 static int      Control( access_t *, int i_query, va_list args );
73 static void     Thread ( access_t *p_access );
74 static int      WriteBlockToFile( access_t *p_access, block_t *p_block );
75 static block_t *ReadBlockFromFile( access_t *p_access );
76 static char    *GetTmpFilePath( access_t *p_access );
77
78 #define TIMESHIFT_FIFO_MAX (10*1024*1024)
79 #define TIMESHIFT_FIFO_MIN (TIMESHIFT_FIFO_MAX/4)
80 #define TMP_FILE_MAX 256
81
82 typedef struct ts_entry_t
83 {
84     FILE *file;
85     struct ts_entry_t *p_next;
86
87 } ts_entry_t;
88
89 struct access_sys_t
90 {
91     block_fifo_t *p_fifo;
92
93     int  i_files;
94     int  i_file_size;
95     int  i_write_size;
96
97     ts_entry_t *p_read_list;
98     ts_entry_t **pp_read_last;
99     ts_entry_t *p_write_list;
100     ts_entry_t **pp_write_last;
101
102     char *psz_filename_base;
103     char *psz_filename;
104 };
105
106 /*****************************************************************************
107  * Open:
108  *****************************************************************************/
109 static int Open( vlc_object_t *p_this )
110 {
111     access_t *p_access = (access_t*)p_this;
112     access_t *p_src = p_access->p_source;
113     access_sys_t *p_sys;
114     vlc_bool_t b_bool;
115
116     /* Only work with not pace controled access */
117     if( access2_Control( p_src, ACCESS_CAN_CONTROL_PACE, &b_bool ) || b_bool )
118     {
119         msg_Dbg( p_src, "ACCESS_CAN_CONTROL_PACE" );
120         return VLC_EGENERIC;
121     }
122     /* Refuse access that can be paused */
123     if( access2_Control( p_src, ACCESS_CAN_PAUSE, &b_bool ) || b_bool )
124     {
125         msg_Dbg( p_src, "ACCESS_CAN_PAUSE: timeshift useless" );
126         return VLC_EGENERIC;
127     }
128
129     /* */
130     p_access->pf_read = NULL;
131     p_access->pf_block = Block;
132     p_access->pf_seek = Seek;
133     p_access->pf_control = Control;
134     p_access->info = p_src->info;
135
136     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
137
138     /* */
139     p_sys->p_fifo = block_FifoNew( p_access );
140     p_sys->i_write_size = 0;
141     p_sys->i_files = 0;
142
143     p_sys->p_read_list = NULL;
144     p_sys->pp_read_last = &p_sys->p_read_list;
145     p_sys->p_write_list = NULL;
146     p_sys->pp_write_last = &p_sys->p_write_list;
147
148     var_Create( p_access, "timeshift-dir",
149                 VLC_VAR_DIRECTORY | VLC_VAR_DOINHERIT );
150     var_Create( p_access, "timeshift-granularity",
151                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
152     p_sys->i_file_size = var_GetInteger( p_access, "timeshift-granularity" );
153     if( p_sys->i_file_size < 1 ) p_sys->i_file_size = 1;
154     p_sys->i_file_size *= 1024 * 1024; /* In MBytes */
155
156     p_sys->psz_filename_base = GetTmpFilePath( p_access );
157     p_sys->psz_filename = malloc( strlen( p_sys->psz_filename_base ) + 1000 );
158
159     if( vlc_thread_create( p_access, "timeshift thread", Thread,
160                            VLC_THREAD_PRIORITY_LOW, VLC_FALSE ) )
161     {
162         msg_Err( p_access, "cannot spawn timeshift access thread" );
163         return VLC_EGENERIC;
164     }
165
166     return VLC_SUCCESS;
167 }
168
169 /*****************************************************************************
170  * Close:
171  *****************************************************************************/
172 static void Close( vlc_object_t *p_this )
173 {
174     access_t     *p_access = (access_t*)p_this;
175     access_sys_t *p_sys = p_access->p_sys;
176     ts_entry_t *p_entry;
177     int i;
178
179     msg_Dbg( p_access, "timeshift close called" );
180     vlc_thread_join( p_access );
181
182     for( p_entry = p_sys->p_write_list; p_entry; )
183     {
184         ts_entry_t *p_next = p_entry->p_next;
185         fclose( p_entry->file );
186         free( p_entry );
187         p_entry = p_next;
188     }
189     for( p_entry = p_sys->p_read_list; p_entry; )
190     {
191         ts_entry_t *p_next = p_entry->p_next;
192         fclose( p_entry->file );
193         free( p_entry );
194         p_entry = p_next;
195     }
196     for( i = 0; i < p_sys->i_files; i++ )
197     {
198         sprintf( p_sys->psz_filename, "%s%i.dat",
199                  p_sys->psz_filename_base, i );
200         unlink( p_sys->psz_filename );
201     }
202
203     free( p_sys->psz_filename );
204     free( p_sys->psz_filename_base );
205     block_FifoRelease( p_sys->p_fifo );
206     free( p_sys );
207 }
208
209 /*****************************************************************************
210  *
211  *****************************************************************************/
212 static block_t *Block( access_t *p_access )
213 {
214     access_sys_t *p_sys = p_access->p_sys;
215     block_t *p_block;
216
217     if( p_access->b_die )
218     {
219         p_access->info.b_eof = VLC_TRUE;
220         return NULL;
221     }
222
223     p_block = block_FifoGet( p_sys->p_fifo );
224     //p_access->info.i_size -= p_block->i_buffer;
225     return p_block;
226 }
227
228 /*****************************************************************************
229  *
230  *****************************************************************************/
231 static void Thread( access_t *p_access )
232 {
233     access_sys_t *p_sys = p_access->p_sys;
234     access_t     *p_src = p_access->p_source;
235     int i;
236
237     while( !p_access->b_die )
238     {
239         block_t *p_block;
240
241         /* Get a new block from the source */
242         if( p_src->pf_block )
243         {
244             p_block = p_src->pf_block( p_src );
245
246             if( p_block == NULL )
247             {
248                 if( p_src->info.b_eof ) break;
249                 msleep( 1000 );
250                 continue;
251             }
252         }
253         else
254         {
255             if( ( p_block = block_New( p_access, 2048 ) ) == NULL ) break;
256
257             p_block->i_buffer =
258                 p_src->pf_read( p_src, p_block->p_buffer, 2048 );
259
260             if( p_block->i_buffer < 0 )
261             {
262                 block_Release( p_block );
263                 if( p_block->i_buffer == 0 ) break;
264                 msleep( 1000 );
265                 continue;
266             }
267         }
268
269         /* Write block */
270         if( !p_sys->p_write_list && !p_sys->p_read_list &&
271             p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MAX )
272         {
273             /* If there isn't too much timeshifted data,
274              * write directly to FIFO */
275             block_FifoPut( p_sys->p_fifo, p_block );
276
277             //p_access->info.i_size += p_block->i_buffer;
278             //p_access->info.i_update |= INPUT_UPDATE_SIZE;
279
280             /* Nothing else to do */
281             continue;
282         }
283
284         WriteBlockToFile( p_access, p_block );
285         block_Release( p_block );
286
287         /* Read from file to fill up the fifo */
288         while( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MIN &&
289                !p_access->b_die )
290         {
291             p_block = ReadBlockFromFile( p_access );
292             if( !p_block ) break;
293             block_FifoPut( p_sys->p_fifo, p_block );
294         }
295     }
296
297     msg_Dbg( p_access, "timeshift: EOF" );
298
299     /* Send dummy packet to avoid deadlock in TShiftBlock */
300     for( i = 0; i < 2; i++ )
301     {
302         block_t *p_dummy = block_New( p_access, 128 );
303         p_dummy->i_flags |= BLOCK_FLAG_DISCONTINUITY;
304         memset( p_dummy->p_buffer, 0, p_dummy->i_buffer );
305         block_FifoPut( p_sys->p_fifo, p_dummy );
306     }
307 }
308
309 /*****************************************************************************
310  * NextFileWrite:
311  *****************************************************************************/
312 static void NextFileWrite( access_t *p_access )
313 {
314     access_sys_t *p_sys = p_access->p_sys;
315     ts_entry_t   *p_next;
316
317     if( !p_sys->p_write_list )
318     {
319         p_sys->i_write_size = 0;
320         return;
321     }
322
323     p_next = p_sys->p_write_list->p_next;
324
325     /* Put written file in read list */
326     if( p_sys->i_write_size < p_sys->i_file_size )
327         ftruncate( fileno( p_sys->p_write_list->file ), p_sys->i_write_size );
328
329     fseek( p_sys->p_write_list->file, 0, SEEK_SET );
330     *p_sys->pp_read_last = p_sys->p_write_list;
331     p_sys->pp_read_last = &p_sys->p_write_list->p_next;
332     p_sys->p_write_list->p_next = 0;
333
334     /* Switch to next file to write */
335     p_sys->p_write_list = p_next;
336     if( !p_sys->p_write_list ) p_sys->pp_write_last = &p_sys->p_write_list;
337
338     p_sys->i_write_size = 0;
339 }
340
341 /*****************************************************************************
342  * NextFileRead:
343  *****************************************************************************/
344 static void NextFileRead( access_t *p_access )
345 {
346     access_sys_t *p_sys = p_access->p_sys;
347     ts_entry_t   *p_next;
348
349     if( !p_sys->p_read_list ) return;
350
351     p_next = p_sys->p_read_list->p_next;
352
353     /* Put read file in write list */
354     fseek( p_sys->p_read_list->file, 0, SEEK_SET );
355     *p_sys->pp_write_last = p_sys->p_read_list;
356     p_sys->pp_write_last = &p_sys->p_read_list->p_next;
357     p_sys->p_read_list->p_next = 0;
358
359     /* Switch to next file to read */
360     p_sys->p_read_list = p_next;
361     if( !p_sys->p_read_list ) p_sys->pp_read_last = &p_sys->p_read_list;
362 }
363
364 /*****************************************************************************
365  * WriteBlockToFile:
366  *****************************************************************************/
367 static int WriteBlockToFile( access_t *p_access, block_t *p_block )
368 {
369     access_sys_t *p_sys = p_access->p_sys;
370     int i_write, i_buffer;
371
372     if( p_sys->i_write_size == p_sys->i_file_size ) NextFileWrite( p_access );
373
374     /* Open new file if necessary */
375     if( !p_sys->p_write_list )
376     {
377         FILE *file;
378
379         sprintf( p_sys->psz_filename, "%s%i.dat",
380                  p_sys->psz_filename_base, p_sys->i_files );
381         file = utf8_fopen( p_sys->psz_filename, "w+b" );
382
383         if( !file && p_sys->i_files < 2 )
384         {
385             /* We just can't work with less than 2 buffer files */
386             msg_Err( p_access, "cannot open temporary file '%s' (%s)",
387                      p_sys->psz_filename, strerror(errno) );
388             return VLC_EGENERIC;
389         }
390         else if( !file ) return VLC_EGENERIC;
391
392         p_sys->p_write_list = malloc( sizeof(ts_entry_t) );
393         p_sys->p_write_list->p_next = 0;
394         p_sys->p_write_list->file = file;
395         p_sys->pp_write_last = &p_sys->p_write_list->p_next;
396
397         p_sys->i_files++;
398     }
399
400     /* Write to file */
401     i_buffer = __MIN( p_block->i_buffer,
402                       p_sys->i_file_size - p_sys->i_write_size );
403
404     i_write = fwrite( p_block->p_buffer, 1, i_buffer,
405                       p_sys->p_write_list->file );
406
407     if( i_write > 0 ) p_sys->i_write_size += i_write;
408
409     //p_access->info.i_size += i_write;
410     //p_access->info.i_update |= INPUT_UPDATE_SIZE;
411
412     if( i_write < i_buffer )
413     {
414         /* Looks like we're short of space */
415
416         if( !p_sys->p_write_list->p_next )
417         {
418             msg_Warn( p_access, "no more space, overwritting old data" );
419             NextFileRead( p_access );
420             NextFileRead( p_access );
421         }
422
423         /* Make sure we switch to next file in write list */
424         p_sys->i_write_size = p_sys->i_file_size;
425     }
426
427     p_block->p_buffer += i_write;
428     p_block->i_buffer -= i_write;
429
430     /* Check if we have some data left */
431     if( p_block->i_buffer ) return WriteBlockToFile( p_access, p_block );
432
433     return VLC_SUCCESS;
434 }
435
436 /*****************************************************************************
437  * ReadBlockFromFile:
438  *****************************************************************************/
439 static block_t *ReadBlockFromFile( access_t *p_access )
440 {
441     access_sys_t *p_sys = p_access->p_sys;
442     block_t *p_block;
443
444     if( !p_sys->p_read_list && p_sys->p_write_list )
445     {
446         /* Force switching to next write file, that should
447          * give us something to read */
448         NextFileWrite( p_access );
449     }
450
451     if( !p_sys->p_read_list ) return 0;
452
453     p_block = block_New( p_access, 4096 );
454     p_block->i_buffer = fread( p_block->p_buffer, 1, 4096,
455                                p_sys->p_read_list->file );
456
457     if( p_block->i_buffer == 0 ) NextFileRead( p_access );
458
459     //p_access->info.i_size -= p_block->i_buffer;
460     //p_access->info.i_update |= INPUT_UPDATE_SIZE;
461
462     return p_block;
463 }
464
465 /*****************************************************************************
466  * Seek: seek to a specific location in a file
467  *****************************************************************************/
468 static int Seek( access_t *p_access, int64_t i_pos )
469 {
470     //access_sys_t *p_sys = p_access->p_sys;
471     return VLC_SUCCESS;
472 }
473
474 /*****************************************************************************
475  *
476  *****************************************************************************/
477 static int Control( access_t *p_access, int i_query, va_list args )
478 {
479     access_t     *p_src = p_access->p_source;
480
481     vlc_bool_t   *pb_bool;
482     int          *pi_int;
483     int64_t      *pi_64;
484
485     switch( i_query )
486     {
487         case ACCESS_CAN_SEEK:
488         case ACCESS_CAN_FASTSEEK:
489             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
490             *pb_bool = VLC_TRUE;
491             break;
492
493         case ACCESS_CAN_CONTROL_PACE:   /* Not really true */
494         case ACCESS_CAN_PAUSE:
495             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
496             *pb_bool = VLC_TRUE;
497             break;
498
499         case ACCESS_GET_MTU:
500             pi_int = (int*)va_arg( args, int * );
501             *pi_int = 0;
502             break;
503
504         case ACCESS_GET_PTS_DELAY:
505             pi_64 = (int64_t*)va_arg( args, int64_t * );
506             return access2_Control( p_src, ACCESS_GET_PTS_DELAY, pi_64 );
507
508         case ACCESS_SET_PAUSE_STATE:
509             return VLC_SUCCESS;
510
511         case ACCESS_GET_TITLE_INFO:
512         case ACCESS_SET_TITLE:
513         case ACCESS_SET_SEEKPOINT:
514         case ACCESS_GET_META:
515             return VLC_EGENERIC;
516
517         case ACCESS_SET_PRIVATE_ID_STATE:
518         case ACCESS_GET_PRIVATE_ID_STATE:
519         case ACCESS_SET_PRIVATE_ID_CA:
520             return access2_vaControl( p_src, i_query, args );
521
522         default:
523             msg_Warn( p_access, "unimplemented query in control" );
524             return VLC_EGENERIC;
525
526     }
527     return VLC_SUCCESS;
528 }
529
530 /*****************************************************************************
531  * GetTmpFilePath:
532  *****************************************************************************/
533 #ifdef WIN32
534 #define getpid() GetCurrentProcessId()
535 #endif
536 static char *GetTmpFilePath( access_t *p_access )
537 {
538     char *psz_dir = var_GetString( p_access, "timeshift-dir" );
539     char *psz_filename_base;
540
541     if( ( psz_dir != NULL ) && ( psz_dir[0] == '\0' ) )
542     {
543         free( psz_dir );
544         psz_dir = NULL;
545     }
546
547     if( psz_dir == NULL )
548     {
549 #ifdef WIN32
550         char psz_local_dir[MAX_PATH];
551         int i_size;
552
553         i_size = GetTempPath( MAX_PATH, psz_local_dir );
554         if( i_size <= 0 || i_size > MAX_PATH )
555         {
556             if( !getcwd( psz_local_dir, MAX_PATH ) )
557                 strcpy( psz_local_dir, "C:" );
558         }
559
560         psz_dir = FromLocaleDup( psz_local_dir );
561
562         /* remove last \\ if any */
563         if( psz_dir[strlen(psz_dir)-1] == '\\' )
564             psz_dir[strlen(psz_dir)-1] = '\0';
565 #else
566         psz_dir = strdup( "/tmp" );
567 #endif
568     }
569
570     asprintf( &psz_filename_base, "%s/vlc-timeshift-%d-%d-",
571               psz_dir, getpid(), p_access->i_object_id );
572     free( psz_dir );
573
574     return psz_filename_base;
575 }