/* * Humax EPG Tool * by af123, 2011 */ #include #include #include #include #include #include #include #include #include #include #include #include "lint.h" #include #include "epgsql.h" #include "sys/stat.h" #include int debug = 0; const char *version = "1.8"; unsigned long sysopts = 0; unsigned long filterflags = 0; int syntax() { fprintf(stderr, "Humax Foxsat EPG Tool v%s, by adrianf36, 2011-2016, MofTot 2018-20.\n", version); fprintf(stderr, "based on the Humax (T2) EPG Tool from af123 v1.0.2, 2011.\n\n"); fprintf(stderr, "Syntax: epg [options] [filters] ...\n\n"); fprintf(stderr, " Options:\n" " -b Brief output.\n" " -d[level] Set debug level.\n" " -f Specify alternate EPG data file.\n" " -h Show help text.\n" " -p Parsable output.\n" "\n" ); fprintf(stderr, " Filters: (can be specified multiple times, all must be true)\n" " -C Show only events with this CRID.\n" " -D Show only selected descriptor type.\n" " -E Show only selected event.\n" " -R Show only events with this Series ID.\n" " -S Show only selected service.\n" " -T Show only selected content types.\n" " -@ Show only programmes at time.\n" "\n" ); fprintf(stderr, " Commands:\n" " dump Show a parsed summary of the EPG.\n" " dumpraw Show raw data from the EPG.\n" " dumpsql Process the EPG file into a Sqlite Database\n" " dumpsqlcont As 'dumpsql' but run continuously\n" " Check for updated data every hour and process if necessary\n" ); fprintf(stderr, " now Show what is currently on.\n" " parse Parse the EPG, no output.\n" " search Search programme names for text.\n" " searchall " "Search programme names/descriptions for text.\n" ); fprintf(stderr, "\n"); return 0; } #define DECOMPRESS(str, len) \ if (str && *(str) == 0x1f) uncompress_epg(&(str), &(len)) unsigned int NeedToProcess (char * epgFile) { struct stat epgstat; unsigned int FileTS; unsigned int DBTS; FILE * fp; /* Get the timestamp of the epg file to be processed */ if ((fp = fopen(epgFile, "r"))) { if (!stat(epgFile, &epgstat)) { FileTS = (unsigned int) (epgstat.st_mtime); } else { fprintf(stderr, "Error getting epg file modified date\n"); return 0; } } else { fprintf(stderr, "Input file (%s) does not exist!\n", epgFile); return 0; } /* Get the timestamp of the last file processed from the DB */ GetFileTimestamp(&DBTS); /* printf("File Timestamp : %i\n", FileTS); printf("DB Timestamp : %i\n", DBTS); */ if (FileTS==DBTS) return 0; else return FileTS; } void SwapEPGFiles() { /* Having built the new EPG file as epgnew.db delete the old one and replace it with the new one. Handle the case where the old one can't be deleted because it's being accessed First thing make sure that the size of the new file looks sensible. Expect this to be > 10MB ..... usually it's about 12MB */ struct stat epgstat; off_t fsize; FILE * fp; if ((fp = fopen("/opt/epg/epgnew.db", "r"))) { if (!stat("/opt/epg/epgnew.db", &epgstat)) { fsize = epgstat.st_size; if (fsize > (10 * 1024 * 1024)) { while (remove("/opt/epg/epg.db") == -1) { sleep(5); } while (rename("/opt/epg/epgnew.db", "/opt/epg/epg.db") != 0) { sleep(5); } } else { /* epgnew.db is smaller than we expected and is probably incomplete. Delete it and output a warning. */ fprintf(stderr, "Error. epgnew.db file size too small. Probably corrupt.\n"); remove("/opt/epg/epgnew.db"); } } else { fprintf(stderr, "Error getting epgnew.db file size\n"); } } else { fprintf(stderr, "Can't open epgnew.db\n"); } } void pass(struct epg *epg __attribute__((unused)), struct section *s __attribute__((unused)), struct data *d __attribute__((unused)), struct descriptor **ds __attribute__((unused)), void *var __attribute__((unused))) { /* Do nothing. */ } void dumpraw(struct epg *epg __attribute__((unused)), struct section *s, struct data *d, struct descriptor **ds, void *var __attribute__((unused))) { dump_section(s); dump_data(d); if (ds[PARSER_SHORT_EVENT]) dump_descriptor(ds[PARSER_SHORT_EVENT], 1); if (ds[PARSER_USER_DEFINED]) dump_descriptor(ds[PARSER_USER_DEFINED], 1); if (ds[PARSER_CONTENT]) dump_descriptor(ds[PARSER_CONTENT], 1); if (ds[PARSER_CRID_EVENT]) dump_descriptor(ds[PARSER_CRID_EVENT], 1); if (ds[PARSER_CRID_SERIES]) dump_descriptor(ds[PARSER_CRID_SERIES], 1); if (ds[PARSER_CRID_REC]) dump_descriptor(ds[PARSER_CRID_REC], 1); } /* Strings should all be safe now the huffman module is in place... */ #define safeprintf printf void EscapeChars(char ** InString, unsigned int *Len) { /* Escape String for inserting into SQLite */ /* Replace single quote ' with two single quotes '' */ char * new; unsigned int i,j; int charcount=0; char * originalstring; originalstring=*InString; if (strchr(*InString, '\'')!=NULL) { /* at least one ' character - process string */ /* count them so we know how much space to allocate */ for (i=0; i<*Len; i++) { if (originalstring[i]==0x27) { charcount++; } } new = (char *) malloc(sizeof(char) * (*Len + charcount + 1)); j=0; for (i=0; i<*Len; i++) { new[j++]=originalstring[i]; if (originalstring[i]==0x27) new[j++]=0x27; } new[j]='\0'; free(*InString); *InString = new; *Len = strlen(new); } } void dumpsql(struct epg *epg __attribute__((unused)), struct section *s, struct data *d, struct descriptor **ds, void *var) { time_t tm; char SQLStatement[5000]; sqlite3 * db = (sqlite3 *)var; int rc; tm = mjd(d->start_date, d->start_hour, d->start_min, d->start_sec); if (ds[PARSER_SHORT_EVENT]) { struct descriptor *d77 = ds[PARSER_SHORT_EVENT]; DECOMPRESS(d77->content.d77.name, d77->content.d77.namelen); DECOMPRESS(d77->content.d77.text, d77->content.d77.textlen); EscapeChars(&d77->content.d77.name, &d77->content.d77.namelen); EscapeChars(&d77->content.d77.text, &d77->content.d77.textlen); } if (ds[PARSER_USER_DEFINED]) { struct descriptor *d = ds[PARSER_USER_DEFINED]; DECOMPRESS(d->content.d137.warning, d->content.d137.warninglen); EscapeChars(&d->content.d137.warning, &d->content.d137.warninglen); } /* service_id, event_id, start, duration, encrypted, name, text * warning, content code, content type, * event CRID, series CRID, rec CRID */ sprintf(SQLStatement,"insert into epg (serviceid, eventid, starttime, duration, encrypted, name,"\ "descr, warning, contentcode, contenttype, ECRID, SCRID, RCRID) values ("); sprintf(SQLStatement, "%s%d,%d,%ld,%d,%d,", SQLStatement, s->service_id, d->event_id, tm, d->dur_hour * 3600 + d->dur_min * 60 + d->dur_sec, d->u1.u.free_CA_mode); if (ds[PARSER_SHORT_EVENT]) { struct descriptor *d77 = ds[PARSER_SHORT_EVENT]; sprintf(SQLStatement,"%s'%.*s',", SQLStatement, d77->content.d77.namelen, d77->content.d77.name); sprintf(SQLStatement,"%s'%.*s',", SQLStatement, d77->content.d77.textlen, d77->content.d77.text); } else sprintf(SQLStatement,"%s'','',",SQLStatement); if (ds[PARSER_USER_DEFINED]) { struct descriptor *d137 = ds[PARSER_USER_DEFINED]; EscapeChars(&d137->content.d137.warning, &d137->content.d137.warninglen); sprintf(SQLStatement,"%s'%.*s',", SQLStatement, d137->content.d137.warninglen, d137->content.d137.warning); } else sprintf(SQLStatement,"%s'',",SQLStatement); if (ds[PARSER_CONTENT]) { sprintf(SQLStatement,"%s%d,", SQLStatement, ds[PARSER_CONTENT]->content.d84.level1); sprintf(SQLStatement,"%s'%s',", SQLStatement, content_type(ds[PARSER_CONTENT])); } else sprintf(SQLStatement,"%s0,'',",SQLStatement); if (ds[PARSER_CRID_EVENT]) { struct descriptor *d = ds[PARSER_CRID_EVENT]; EscapeChars(&d->content.d118.crids[0].crid, &d->content.d118.crids[0].cridlen); sprintf(SQLStatement,"%s'%.*s',", SQLStatement, d->content.d118.crids[0].cridlen, d->content.d118.crids[0].crid); } else sprintf(SQLStatement,"%s'',",SQLStatement); if (ds[PARSER_CRID_SERIES]) { struct descriptor *d = ds[PARSER_CRID_SERIES]; EscapeChars(&d->content.d118.crids[0].crid, &d->content.d118.crids[0].cridlen); sprintf(SQLStatement,"%s'%.*s',", SQLStatement, d->content.d118.crids[0].cridlen, d->content.d118.crids[0].crid); } else sprintf(SQLStatement,"%s'',",SQLStatement); if (ds[PARSER_CRID_REC]) { struct descriptor *d = ds[PARSER_CRID_REC]; EscapeChars(&d->content.d118.crids[0].crid, &d->content.d118.crids[0].cridlen); sprintf(SQLStatement,"%s'%.*s'", SQLStatement, d->content.d118.crids[0].cridlen, d->content.d118.crids[0].crid); } else sprintf(SQLStatement,"%s''",SQLStatement); sprintf(SQLStatement,"%s)",SQLStatement); rc = ExecSQLStatement(SQLStatement, db); return; } void dosqldump(char * epgpath,struct epgfilter *filter, int Continuous) { sqlite3 * db; int rc; unsigned int FileTS; char SQL [500] = {0}; if (Continuous==1) { while (1) { FileTS = NeedToProcess(epgpath); if (FileTS !=0) { /* Delete any old epgnew SQLite files before trying to open a new one */ /* Don't worry about error handling - just need to make sure they're gone! */ remove("/opt/epg/epgnew.db"); remove("/opt/epg/epgnew.db-journal"); rc = OpenCreateDB("/opt/epg/epgnew.db", &db); rc = ExecSQLStatement("pragma journal_mode = off;", db); rc = ExecSQLStatement("pragma synchronous = off;", db); InitDB("/opt/epg/epgDBinit.sql", db); rc = ExecSQLStatement("BEGIN;", db); parse(epgpath, dumpsql, (void *)db, filter); rc = ExecSQLStatement("COMMIT;", db); copy_freesat_to_non_freesat(db); sprintf(SQL, "replace into epgtimestamp (lastfileprocessed) values (%i);", FileTS); rc = ExecSQLStatement("Delete from epgtimestamp;", db); rc = ExecSQLStatement(SQL, db); if (db != NULL) CloseDB(db); SwapEPGFiles(); } sleep(3600); } } else { FileTS = NeedToProcess(epgpath); if (FileTS != 0) { /* Delete any old epgnew SQLite files before trying to open a new one */ /* Don't worry about error handling - just need to make sure they're gone! */ remove("/opt/epg/epgnew.db"); remove("/opt/epg/epgnew.db-journal"); rc = OpenCreateDB("/opt/epg/epgnew.db", &db); InitDB("/opt/epg/epgDBinit.sql", db); rc = ExecSQLStatement("BEGIN;", db); parse(epgpath, dumpsql, (void *)db, filter); rc = ExecSQLStatement("COMMIT;", db); copy_freesat_to_non_freesat(db); sprintf(SQL, "replace into epgtimestamp (lastfileprocessed) values (%i);", FileTS); rc = ExecSQLStatement("Delete from epgtimestamp;", db); rc = ExecSQLStatement(SQL, db); if (db != NULL) CloseDB(db); SwapEPGFiles(); } } } void dump(struct epg *epg __attribute__((unused)), struct section *s, struct data *d, struct descriptor **ds, void *var __attribute__((unused))) { time_t tm; tm = mjd(d->start_date, d->start_hour, d->start_min, d->start_sec); if (ds[PARSER_SHORT_EVENT]) { struct descriptor *d77 = ds[PARSER_SHORT_EVENT]; DECOMPRESS(d77->content.d77.name, d77->content.d77.namelen); DECOMPRESS(d77->content.d77.text, d77->content.d77.textlen); } if (ds[PARSER_USER_DEFINED]) { struct descriptor *d = ds[PARSER_USER_DEFINED]; DECOMPRESS(d->content.d137.warning, d->content.d137.warninglen); } if (sysopts & SYSOPT_PARSABLE) { /* service_id, event_id, start, duration, encrypted, name, text * warning, content code, content type, * event CRID, series CRID, rec CRID */ printf("%d\t%d\t%ld\t%d\t%d\t", s->service_id, d->event_id, tm, d->dur_hour * 3600 + d->dur_min * 60 + d->dur_sec, d->u1.u.free_CA_mode); if (ds[PARSER_SHORT_EVENT]) { struct descriptor *d77 = ds[PARSER_SHORT_EVENT]; safeprintf("%.*s\t", d77->content.d77.namelen, d77->content.d77.name); safeprintf("%.*s\t", d77->content.d77.textlen, d77->content.d77.text); } else printf("\t\t"); if (ds[PARSER_USER_DEFINED]) { struct descriptor *d137 = ds[PARSER_USER_DEFINED]; safeprintf("%.*s\t", d137->content.d137.warninglen, d137->content.d137.warning); } else printf("\t"); if (ds[PARSER_CONTENT]) { printf("%d\t", ds[PARSER_CONTENT]->content.d84.level1); printf("%s\t", content_type(ds[PARSER_CONTENT])); } else printf("\t\t"); if (ds[PARSER_CRID_EVENT]) { struct descriptor *d = ds[PARSER_CRID_EVENT]; printf("%.*s\t", d->content.d118.crids[0].cridlen, d->content.d118.crids[0].crid); } else printf("\t"); if (ds[PARSER_CRID_SERIES]) { struct descriptor *d = ds[PARSER_CRID_SERIES]; printf("%.*s\t", d->content.d118.crids[0].cridlen, d->content.d118.crids[0].crid); } else printf("\t"); if (ds[PARSER_CRID_REC]) { struct descriptor *d = ds[PARSER_CRID_REC]; printf("%.*s\t", d->content.d118.crids[0].cridlen, d->content.d118.crids[0].crid); } else printf("\t"); printf("\n"); return; } printf("----------------------------------------------------------\n"); if (sysopts & SYSOPT_BRIEF) { safeprintf("%d/%d: %s+%d\n", s->service_id, d->event_id, ctime_nl(&tm), d->dur_hour * 3600 + d->dur_min * 60 + d->dur_sec); if (ds[PARSER_SHORT_EVENT]) { struct descriptor *d77 = ds[PARSER_SHORT_EVENT]; safeprintf("Name:%.*s\n", d77->content.d77.namelen, d77->content.d77.name); safeprintf("Text:%.*s\n", d77->content.d77.textlen, d77->content.d77.text); } if (ds[PARSER_USER_DEFINED]) { struct descriptor *d137 = ds[PARSER_USER_DEFINED]; safeprintf("Warning:%.*s\n", d137->content.d137.warninglen, d137->content.d137.warning); } return; } printf("%30s: %d\n", "Service ID", s->service_id); printf("%30s: %d\n", "Event ID", d->event_id); printf("%30s: %#x %d:%02d:%02d (%s)\n", "start_date", d->start_date, d->start_hour, d->start_min, d->start_sec, ctime_nl(&tm)); printf("%30s: %d:%02d:%02d\n", "duration", d->dur_hour, d->dur_min, d->dur_sec); printf("%30s: %d\n", "encrypted", d->u1.u.free_CA_mode); if (ds[PARSER_SHORT_EVENT]) { struct descriptor *d77 = ds[PARSER_SHORT_EVENT]; safeprintf("%30s: %.*s\n", "Name", d77->content.d77.namelen, d77->content.d77.name); safeprintf("%30s: %.*s\n", "Text", d77->content.d77.textlen, d77->content.d77.text); } if (ds[PARSER_USER_DEFINED]) { struct descriptor *d137 = ds[PARSER_USER_DEFINED]; safeprintf("%30s: %.*s\n", "Warning", d137->content.d137.warninglen, d137->content.d137.warning); } if (ds[PARSER_CONTENT]) printf("%30s: %s (%d)\n", "Content Type", content_type(ds[PARSER_CONTENT]), ds[PARSER_CONTENT]->content.d84.level1); if (ds[PARSER_CRID_EVENT]) printf("%30s: %.*s\n", "Event CRID", ds[PARSER_CRID_EVENT]->content.d118.crids[0].cridlen, ds[PARSER_CRID_EVENT]->content.d118.crids[0].crid); if (ds[PARSER_CRID_SERIES]) printf("%30s: %.*s\n", "Series CRID", ds[PARSER_CRID_SERIES]->content.d118.crids[0].cridlen, ds[PARSER_CRID_SERIES]->content.d118.crids[0].crid); if (ds[PARSER_CRID_REC]) { struct descriptor *d118 = ds[PARSER_CRID_REC]; int i; for (i = 0; i < d118->content.d118.i; i++) { struct crid *crid = &d118->content.d118.crids[i]; printf("%30s: %.*s\n", "Recommended CRID", crid->cridlen, crid->crid); } } } int copy_freesat_to_non_freesat(sqlite3 * db) { int rc; ExecSQLStatement( "attach database '/opt/webif/plugin/epg/epgsettings.db' as epgsettings", db ); ExecSQLStatement("BEGIN;", db); rc = ExecSQLStatementRowCount( "insert into epg " "select epgmappings.nonfreesatserviceid, " "eventid, " "starttime, " "duration, " "encrypted, " "name, " "descr, " "warning, " "contentcode, " "contenttype, " "ECRID, " "SCRID, " "RCRID " "from epgsettings.epgmappings join epg on epg.serviceid = epgsettings.epgmappings.freesatserviceid " "except " "select epg.* " "from epgsettings.epgmappings join epg on epg.serviceid = epgsettings.epgmappings.freesatserviceid", db ); ExecSQLStatement("COMMIT;", db); printf("%d EPG event(s) copied from Freesat to non-Freesat\n", rc); return rc; } void search(struct epg *epg __attribute__((unused)), struct section *s, struct data *d, struct descriptor **ds, void *var) { if (ds[PARSER_SHORT_EVENT]) { struct descriptor *d77 = ds[PARSER_SHORT_EVENT]; DECOMPRESS(d77->content.d77.name, d77->content.d77.namelen); if (strcasestr(ds[PARSER_SHORT_EVENT]->content.d77.name, (char *)var)) dump(epg, s, d, ds, NULL); } } void searchall(struct epg *epg __attribute__((unused)), struct section *s, struct data *d, struct descriptor **ds, void *var) { if (ds[PARSER_SHORT_EVENT]) { struct descriptor *d77 = ds[PARSER_SHORT_EVENT]; DECOMPRESS(d77->content.d77.name, d77->content.d77.namelen); DECOMPRESS(d77->content.d77.text, d77->content.d77.textlen); if (strcasestr(ds[PARSER_SHORT_EVENT]->content.d77.name, (char *)var) || strcasestr(ds[PARSER_SHORT_EVENT]->content.d77.text, (char *)var)) dump(epg, s, d, ds, NULL); } } #define GETOPTOPT \ do { \ if (*++cp == '\0' && argc > 1) \ { \ argc--, argv++; \ cp = argv[0]; \ } \ while (*cp != '\0' && isspace((int)*cp)) \ cp++; \ } while (0) #define GETOPTINTOPT \ do { \ if (*++cp == '\0' && argc > 1 && atoi(argv[1]) > 0) \ { \ argc--, argv++; \ cp = argv[0]; \ } \ while (*cp != '\0' && isspace((int)*cp)) \ cp++; \ } while (0) #define GETOPT \ do { \ if (*++cp == '\0' && argc < 2) \ { \ fprintf(stderr, \ "No argument supplied for -%c\n", opt); \ exit(1); \ } \ else if (*cp == '\0') \ { \ argc--, argv++; \ cp = argv[0]; \ } \ while (isspace((int)*cp)) \ cp++; \ } while (0) int main(int argc, char **argv) { char *epgpath = DEFAULT_EPG_FILE; struct epgfilter *filter = NULL; char *cp; argc--, argv++; while (argc > 0 && *argv[0] == '-') { for (cp = &argv[0][1]; *cp; cp++) { char opt; switch (opt = *cp) { case 'b': sysopts |= SYSOPT_BRIEF; break; case 'd': GETOPTINTOPT; if (*cp == '\0') debug = 1; else debug = atoi(cp); goto nextopt; case 'f': GETOPT; epgpath = strdup(cp); goto nextopt; case 'h': return syntax(); case 'p': sysopts |= SYSOPT_PARSABLE; break; /* Filters */ case '@': GETOPT; add_epgfilter(&filter, FILTER_TIMESTAMP, atoi(cp), 0, NULL, FT_EQUAL); goto nextopt; case 'C': GETOPT; add_epgfilter(&filter, FILTER_CRID, 0, 0, cp, FT_EQUAL); goto nextopt; case 'D': GETOPT; add_epgfilter(&filter, FILTER_DESCRIPTOR, atoi(cp), 0, NULL, FT_EQUAL); goto nextopt; case 'E': GETOPT; add_epgfilter(&filter, FILTER_EVENT, atoi(cp), 0, NULL, FT_EQUAL); goto nextopt; case 'S': GETOPT; add_epgfilter(&filter, FILTER_SERVICE, atoi(cp), 0, NULL, FT_EQUAL); goto nextopt; case 'R': GETOPT; add_epgfilter(&filter, FILTER_SCRID, 0, 0, cp, FT_EQUAL); goto nextopt; case 'T': GETOPT; add_epgfilter(&filter, FILTER_CONTENT, atoi(cp), 0, NULL, FT_EQUAL); goto nextopt; } } nextopt: argc--, argv++; } if (argc < 1) return syntax(); if (!strcmp(argv[0], "dump")) parse(epgpath, dump, NULL, filter); else if (!strcmp(argv[0], "parse")) parse(epgpath, pass, NULL, filter); else if (!strcmp(argv[0], "dumpraw")) parse(epgpath, dumpraw, NULL, filter); else if (!strcmp(argv[0], "dumpsql")) dosqldump(epgpath, filter, 0); else if (!strcmp(argv[0], "now")) { time_t tm; time(&tm); add_epgfilter(&filter, FILTER_TIMESTAMP, tm, 0, NULL, FT_EQUAL); parse(epgpath, dump, NULL, filter); } else if (!strcmp(argv[0], "search") && argc > 1) parse(epgpath, search, (void *)argv[1], filter); else if (!strcmp(argv[0], "searchall") && argc > 1) parse(epgpath, searchall, (void *)argv[1], filter); else if (!strcmp(argv[0], "dumpsqlcont")) { dosqldump(epgpath, filter, 1); } else syntax(); return 0; }