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