/* * Private Video Recorder * Copyright (C) 2007 Andreas Öman * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tvhead.h" #include "channels.h" #include "transports.h" #include "htsclient.h" #include "pvr.h" #include "pvr_rec.h" #include "epg.h" pthread_mutex_t pvr_mutex = PTHREAD_MUTEX_INITIALIZER; struct pvr_rec_list pvrr_work_list[PVRR_WORK_MAX]; struct pvr_rec_list pvrr_global_list; static void pvr_database_load(void); static void *pvr_main_thread(void *aux); static void pvr_unrecord(pvr_rec_t *pvrr); /**************************************************************************** * * Externally visible functions * */ void pvr_init(void) { pthread_t ptid; pthread_attr_t attr; pvr_database_load(); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&ptid, &attr, pvr_main_thread, NULL); } char pvr_prog_status(event_t *e) { pvr_rec_t *pvrr; LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link) { if(pvrr->pvrr_start >= e->e_start && pvrr->pvrr_stop <= e->e_start + e->e_duration && pvrr->pvrr_channel == e->e_ch) { return pvrr->pvrr_status; } } return HTSTV_PVR_STATUS_NONE; } pvr_rec_t * pvr_get_log_entry(int e) { pvr_rec_t *pvrr = LIST_FIRST(&pvrr_global_list); while(pvrr) { if(e == 0) return pvrr; e--; pvrr = LIST_NEXT(pvrr, pvrr_global_link); } return NULL; } pvr_rec_t * pvr_get_tag_entry(int e) { pvr_rec_t *pvrr; LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link) if(pvrr->pvrr_ref == e) return pvrr; return NULL; } /**************************************************************************** * * work queue * */ static void pvrr_launch(pvr_rec_t *pvrr) { pthread_t ptid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&ptid, &attr, pvr_recorder_thread, pvrr); } static void * pvr_main_thread(void *aux) { pvr_rec_t *pvrr; time_t now; pthread_mutex_lock(&pvr_mutex); while(1) { pthread_mutex_unlock(&pvr_mutex); sleep(1); pthread_mutex_lock(&pvr_mutex); time(&now); LIST_FOREACH(pvrr, &pvrr_work_list[PVRR_WORK_SCHEDULED], pvrr_work_link) { /* We start 30 seconds early to allow * - Transponder to lock * - MPEG I-frame lock * - Acquire additional info from TV network (if program is delayed * and such things) */ if(pvrr->pvrr_start - 30 < now) { LIST_REMOVE(pvrr, pvrr_work_link); pvrr_launch(pvrr); } } } } void pvr_inform_status_change(pvr_rec_t *pvrr) { event_t *e; clients_enq_ref(pvrr->pvrr_ref); e = epg_event_find_by_time(pvrr->pvrr_channel, pvrr->pvrr_start); if(e != NULL) clients_enq_ref(e->e_tag); } static void pvr_free(pvr_rec_t *pvrr) { LIST_REMOVE(pvrr, pvrr_work_link); LIST_REMOVE(pvrr, pvrr_global_link); free(pvrr->pvrr_title); free(pvrr->pvrr_desc); free(pvrr->pvrr_printname); free(pvrr->pvrr_filename); free(pvrr->pvrr_format); free(pvrr); } static void pvr_unrecord(pvr_rec_t *pvrr) { pvr_rec_t *x; pvr_inform_status_change(pvrr); if(pvrr->pvrr_status == HTSTV_PVR_STATUS_SCHEDULED) { x = LIST_NEXT(pvrr, pvrr_work_link); pvr_free(pvrr); } else { pvrr->pvrr_status = HTSTV_PVR_STATUS_ABORTED; } clients_enq_ref(-1); pvr_database_save(); } static int pvr_rec_cmp(pvr_rec_t *a, pvr_rec_t *b) { return a->pvrr_start - b->pvrr_start; } static int pvr_glob_cmp(pvr_rec_t *a, pvr_rec_t *b) { return b->pvrr_start - a->pvrr_start; } static void pvr_link_pvrr(pvr_rec_t *pvrr) { struct pvr_rec_list *l; switch(pvrr->pvrr_status) { case HTSTV_PVR_STATUS_SCHEDULED: l = &pvrr_work_list[PVRR_WORK_SCHEDULED]; break; case HTSTV_PVR_STATUS_RECORDING: l = &pvrr_work_list[PVRR_WORK_RECORDING]; break; default: l = &pvrr_work_list[PVRR_WORK_DONE]; break; } pvrr->pvrr_ref = tag_get(); LIST_INSERT_SORTED(&pvrr_global_list, pvrr, pvrr_global_link, pvr_glob_cmp); LIST_INSERT_SORTED(l, pvrr, pvrr_work_link, pvr_rec_cmp); clients_enq_ref(-1); pvr_inform_status_change(pvrr); } void pvr_event_record_op(th_channel_t *ch, event_t *e, recop_t op) { time_t start = e->e_start; time_t stop = e->e_start + e->e_duration; time_t now; pvr_rec_t *pvrr; char buf[100]; time(&now); event_time_txt(start, e->e_duration, buf, sizeof(buf)); syslog(LOG_NOTICE, "Got recording command %d for %s", op, buf); if(stop < now) return; /* Try to see if we already have a scheduled or active recording */ LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link) { if(pvrr->pvrr_channel == ch && pvrr->pvrr_start == start && pvrr->pvrr_stop == stop) break; } switch(op) { case RECOP_TOGGLE: if(pvrr != NULL) { pvr_unrecord(pvrr); return; } break; case RECOP_CANCEL: if(pvrr != NULL) pvr_unrecord(pvrr); return; case RECOP_ONCE: if(pvrr != NULL) return; break; case RECOP_DAILY: case RECOP_WEEKLY: syslog(LOG_ERR,"Recording type not supported yet"); return; } pvrr = calloc(1, sizeof(pvr_rec_t)); pvrr->pvrr_status = HTSTV_PVR_STATUS_SCHEDULED; pvrr->pvrr_channel = ch; pvrr->pvrr_start = start; pvrr->pvrr_stop = stop; pvrr->pvrr_title = e->e_title ? strdup(e->e_title) : NULL; pvrr->pvrr_desc = e->e_desc ? strdup(e->e_desc) : NULL; pvr_link_pvrr(pvrr); pvr_database_save(); } /***************************************************************************** * * Plain text "database" of pvr programmes * */ static char * pvr_schedule_path_get(void) { static char path[400]; snprintf(path, sizeof(path), "%s/.pvrschedule.sav", config_get_str("pvrdir", ".")); return path; } void pvr_database_save(void) { pvr_rec_t *pvrr, *next; FILE *fp; struct stat st; fp = fopen(pvr_schedule_path_get(), "w"); if(fp == NULL) return; for(pvrr = LIST_FIRST(&pvrr_global_list); pvrr ; pvrr = next) { next = LIST_NEXT(pvrr, pvrr_global_link); if(pvrr->pvrr_filename) { /* Check if file is still around */ if(stat(pvrr->pvrr_filename, &st) == -1) { /* Nope, dont save this entry, instead remove it from memory too */ pvr_free(pvrr); break; } fprintf(fp, "filename %s\n", pvrr->pvrr_filename); } fprintf(fp, "channel %s\n", pvrr->pvrr_channel->ch_name); fprintf(fp, "start %ld\n", pvrr->pvrr_start); fprintf(fp, "stop %ld\n", pvrr->pvrr_stop); if(pvrr->pvrr_title) fprintf(fp, "title %s\n", pvrr->pvrr_title); if(pvrr->pvrr_desc) fprintf(fp, "desc %s\n", pvrr->pvrr_desc); fprintf(fp, "status %c\n", pvrr->pvrr_status); fprintf(fp, "end of record ------------------------------\n"); } fclose(fp); } static void pvr_database_load(void) { char line[4000]; pvr_rec_t *pvrr = NULL; th_channel_t *ch; FILE *fp; int l; char *key, *val; fp = fopen(pvr_schedule_path_get(), "r"); if(fp == NULL) { perror("Cant open schedule"); return; } while(!feof(fp)) { if(pvrr == NULL) pvrr = calloc(1, sizeof(pvr_rec_t)); if(fgets(line, sizeof(line), fp) == NULL) break; l = strlen(line) - 1; while(l > 0 && line[l] < 32) line[l--] = 0; key = line; val = strchr(line, ' '); if(val == NULL) continue; *val++ = 0; if(!strcmp(key, "channel")) { TAILQ_FOREACH(ch, &channels, ch_global_link) if(!strcmp(ch->ch_name, val)) break; pvrr->pvrr_channel = ch; } else if(!strcmp(key, "start")) pvrr->pvrr_start = atoi(val); else if(!strcmp(key, "stop")) pvrr->pvrr_stop = atoi(val); else if(!strcmp(key, "title")) pvrr->pvrr_title = strdup(val); else if(!strcmp(key, "desc")) pvrr->pvrr_desc = strdup(val); else if(!strcmp(key, "filename")) pvrr->pvrr_filename = strdup(val); else if(!strcmp(key, "status")) pvrr->pvrr_status = *val; else if(!strcmp(key, "end")) { if(pvrr->pvrr_channel == NULL || pvrr->pvrr_start == 0 || pvrr->pvrr_stop == 0 || pvrr->pvrr_status == 0) { memset(pvrr, 0, sizeof(pvr_rec_t)); continue; } pvr_link_pvrr(pvrr); pvrr = NULL; } } if(pvrr) free(pvrr); pvr_database_save(); fclose(fp); }