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