/* * channels.c: Channel handling * * See the main source file 'vdr.c' for copyright information and * how to reach the author. * * $Id: channels.c 5.1 2021/05/21 09:38:34 kls Exp $ */ #include "channels.h" #include #include "device.h" #include "libsi/si.h" // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d' // format characters in order to allow any number of blanks after a numeric // value! // --- tChannelID ------------------------------------------------------------ const tChannelID tChannelID::InvalidID; tChannelID tChannelID::FromString(const char *s) { char *sourcebuf = NULL; int nid; int tid; int sid; int rid = 0; int fields = sscanf(s, "%m[^-]-%d-%d-%d-%d", &sourcebuf, &nid, &tid, &sid, &rid); if (fields == 4 || fields == 5) { int source = cSource::FromString(sourcebuf); free(sourcebuf); if (source >= 0) return tChannelID(source, nid, tid, sid, rid); } return tChannelID::InvalidID; } cString tChannelID::ToString(void) const { char buffer[256]; snprintf(buffer, sizeof(buffer), rid ? "%s-%d-%d-%d-%d" : "%s-%d-%d-%d", *cSource::ToString(source), nid, tid, sid, rid); return buffer; } tChannelID &tChannelID::ClrPolarization(void) { while (tid > 100000) tid -= 100000; return *this; } // --- cChannel -------------------------------------------------------------- cChannel::cChannel(void) { name = strdup(""); shortName = strdup(""); provider = strdup(""); portalName = strdup(""); memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__); parameters = ""; modification = CHANNELMOD_NONE; seen = 0; schedule = NULL; linkChannels = NULL; refChannel = NULL; } cChannel::cChannel(const cChannel &Channel) { name = NULL; shortName = NULL; provider = NULL; portalName = NULL; schedule = NULL; linkChannels = NULL; refChannel = NULL; seen = 0; *this = Channel; } cChannel::~cChannel() { delete linkChannels; // any links from other channels pointing to this one have been deleted in cChannels::Del() free(name); free(shortName); free(provider); free(portalName); } cChannel& cChannel::operator= (const cChannel &Channel) { name = strcpyrealloc(name, Channel.name); shortName = strcpyrealloc(shortName, Channel.shortName); provider = strcpyrealloc(provider, Channel.provider); portalName = strcpyrealloc(portalName, Channel.portalName); memcpy(&__BeginData__, &Channel.__BeginData__, (char *)&Channel.__EndData__ - (char *)&Channel.__BeginData__); nameSource = NULL; // these will be recalculated automatically nameSourceMode = 0; shortNameSource = NULL; parameters = Channel.parameters; return *this; } const char *cChannel::Name(void) const { if (Setup.ShowChannelNamesWithSource && !groupSep) { if (isempty(nameSource) || nameSourceMode != Setup.ShowChannelNamesWithSource) { if (Setup.ShowChannelNamesWithSource == 1) nameSource = cString::sprintf("%s (%c)", name, cSource::ToChar(source)); else nameSource = cString::sprintf("%s (%s)", name, *cSource::ToString(source)); } return nameSource; } return name; } const char *cChannel::ShortName(bool OrName) const { if (OrName && isempty(shortName)) return Name(); if (Setup.ShowChannelNamesWithSource && !groupSep) { if (isempty(shortNameSource)) shortNameSource = cString::sprintf("%s (%c)", shortName, cSource::ToChar(source)); return shortNameSource; } return shortName; } int cChannel::Transponder(int Frequency, char Polarization) { // some satellites have transponders at the same frequency, just with different polarization: switch (toupper(Polarization)) { case 'H': Frequency += 100000; break; case 'V': Frequency += 200000; break; case 'L': Frequency += 300000; break; case 'R': Frequency += 400000; break; default: esyslog("ERROR: invalid value for Polarization '%c'", Polarization); } return Frequency; } int cChannel::Transponder(void) const { if (!transponder) { int tf = frequency; while (tf > 20000) tf /= 1000; if (IsSat()) { const char *p = strpbrk(parameters, "HVLRhvlr"); // lowercase for backwards compatibility if (p) tf = Transponder(tf, *p); } transponder = tf; } return transponder; } int cChannel::Modification(int Mask) const { int Result = modification & Mask; modification = CHANNELMOD_NONE; return Result; } void cChannel::CopyTransponderData(const cChannel *Channel) { if (Channel) { frequency = Channel->frequency; transponder = Channel->transponder; source = Channel->source; srate = Channel->srate; parameters = Channel->parameters; } } bool cChannel::SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet) { if (strchr(Parameters, ':')) { esyslog("ERROR: parameter string '%s' contains ':'", Parameters); return false; } // Workarounds for broadcaster stupidity: // Some providers broadcast the transponder frequency of their channels with two different // values (like 12551 and 12552), so we need to allow for a little tolerance here if (abs(frequency - Frequency) <= 1) Frequency = frequency; // Sometimes the transponder frequency is set to 0, which is just wrong if (Frequency == 0) return false; // Sometimes the symbol rate is off by one if (abs(srate - Srate) <= 1) Srate = srate; if (source != Source || frequency != Frequency || srate != Srate || strcmp(parameters, Parameters)) { cString OldTransponderData = TransponderDataToString(); source = Source; frequency = Frequency; transponder = 0; srate = Srate; parameters = Parameters; schedule = NULL; nameSource = NULL; nameSourceMode = 0; shortNameSource = NULL; if (Number() && !Quiet) { dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString()); modification |= CHANNELMOD_TRANSP; } return true; } return false; } bool cChannel::SetSource(int Source) { if (source != Source) { if (Number()) { dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source)); modification |= CHANNELMOD_TRANSP; } source = Source; return true; } return false; } bool cChannel::SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid) { if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) { if (Channels && Number()) { dsyslog("changing id of channel %d (%s) from %d-%d-%d-%d to %d-%d-%d-%d", Number(), name, nid, tid, sid, rid, Nid, Tid, Sid, Rid); modification |= CHANNELMOD_ID; Channels->UnhashChannel(this); } nid = Nid; tid = Tid; sid = Sid; rid = Rid; if (Channels) Channels->HashChannel(this); schedule = NULL; return true; } return false; } bool cChannel::SetLcn(int Lcn) { if (lcn != Lcn) { if (Number()) dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn); lcn = Lcn; return true; } return false; } bool cChannel::SetName(const char *Name, const char *ShortName, const char *Provider) { if (!isempty(Name)) { bool nn = strcmp(name, Name) != 0; bool ns = strcmp(shortName, ShortName) != 0; bool np = strcmp(provider, Provider) != 0; if (nn || ns || np) { if (Number()) { dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider); modification |= CHANNELMOD_NAME; } if (nn) { name = strcpyrealloc(name, Name); nameSource = NULL; nameSourceMode = 0; } if (ns) { shortName = strcpyrealloc(shortName, ShortName); shortNameSource = NULL; } if (np) provider = strcpyrealloc(provider, Provider); return true; } } return false; } bool cChannel::SetPortalName(const char *PortalName) { if (!isempty(PortalName) && strcmp(portalName, PortalName) != 0) { if (Number()) { dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName); modification |= CHANNELMOD_NAME; } portalName = strcpyrealloc(portalName, PortalName); return true; } return false; } #define STRDIFF 0x01 #define VALDIFF 0x02 static int IntArraysDiffer(const int *a, const int *b, const char na[][MAXLANGCODE2] = NULL, const char nb[][MAXLANGCODE2] = NULL) { int result = 0; for (int i = 0; a[i] || b[i]; i++) { if (!a[i] || !b[i]) { result |= VALDIFF; break; } if (na && nb && strcmp(na[i], nb[i]) != 0) result |= STRDIFF; if (a[i] != b[i]) result |= VALDIFF; } return result; } static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[][MAXLANGCODE2] = NULL, const int *t = NULL) { char *q = s; int i = 0; while (a[i] || i == 0) { q += sprintf(q, Base == 16 ? "%s%X" : "%s%d", i ? "," : "", a[i]); const char *Delim = "="; if (a[i]) { if (n && *n[i]) { q += sprintf(q, "%s%s", Delim, n[i]); Delim = ""; } if (t && t[i]) q += sprintf(q, "%s@%d", Delim, t[i]); } if (!a[i]) break; i++; } *q = 0; return q - s; } bool cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid) { int mod = CHANNELMOD_NONE; if (vpid != Vpid || ppid != Ppid || vtype != Vtype) mod |= CHANNELMOD_PIDS; if (tpid != Tpid) mod |= CHANNELMOD_AUX; int m = IntArraysDiffer(apids, Apids, alangs, ALangs) | IntArraysDiffer(atypes, Atypes) | IntArraysDiffer(dpids, Dpids, dlangs, DLangs) | IntArraysDiffer(dtypes, Dtypes) | IntArraysDiffer(spids, Spids, slangs, SLangs); if (m & STRDIFF) mod |= CHANNELMOD_LANGS; if (m & VALDIFF) mod |= CHANNELMOD_PIDS; if (mod) { const int BufferSize = (MAXAPIDS + MAXDPIDS) * (5 + 1 + MAXLANGCODE2 + 5) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod+cod@type', +10: paranoia char OldApidsBuf[BufferSize]; char NewApidsBuf[BufferSize]; char *q = OldApidsBuf; q += IntArrayToString(q, apids, 10, alangs, atypes); if (dpids[0]) { *q++ = ';'; q += IntArrayToString(q, dpids, 10, dlangs, dtypes); } *q = 0; q = NewApidsBuf; q += IntArrayToString(q, Apids, 10, ALangs, Atypes); if (Dpids[0]) { *q++ = ';'; q += IntArrayToString(q, Dpids, 10, DLangs, Dtypes); } *q = 0; const int SBufferSize = MAXSPIDS * (5 + 1 + MAXLANGCODE2) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod', +10: paranoia char OldSpidsBuf[SBufferSize]; char NewSpidsBuf[SBufferSize]; q = OldSpidsBuf; q += IntArrayToString(q, spids, 10, slangs); *q = 0; q = NewSpidsBuf; q += IntArrayToString(q, Spids, 10, SLangs); *q = 0; if (Number()) dsyslog("changing pids of channel %d (%s) from %d+%d=%d:%s:%s:%d to %d+%d=%d:%s:%s:%d", Number(), name, vpid, ppid, vtype, OldApidsBuf, OldSpidsBuf, tpid, Vpid, Ppid, Vtype, NewApidsBuf, NewSpidsBuf, Tpid); vpid = Vpid; ppid = Ppid; vtype = Vtype; for (int i = 0; i < MAXAPIDS; i++) { apids[i] = Apids[i]; atypes[i] = Atypes[i]; strn0cpy(alangs[i], ALangs[i], MAXLANGCODE2); } apids[MAXAPIDS] = 0; for (int i = 0; i < MAXDPIDS; i++) { dpids[i] = Dpids[i]; dtypes[i] = Dtypes[i]; strn0cpy(dlangs[i], DLangs[i], MAXLANGCODE2); } dpids[MAXDPIDS] = 0; for (int i = 0; i < MAXSPIDS; i++) { spids[i] = Spids[i]; strn0cpy(slangs[i], SLangs[i], MAXLANGCODE2); } spids[MAXSPIDS] = 0; tpid = Tpid; modification |= mod; return true; } return false; } bool cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds) { bool Modified = false; if (SubtitlingTypes) { for (int i = 0; i < MAXSPIDS; i++) { Modified = subtitlingTypes[i] != SubtitlingTypes[i]; subtitlingTypes[i] = SubtitlingTypes[i]; } } if (CompositionPageIds) { for (int i = 0; i < MAXSPIDS; i++) { Modified = compositionPageIds[i] != CompositionPageIds[i]; compositionPageIds[i] = CompositionPageIds[i]; } } if (AncillaryPageIds) { for (int i = 0; i < MAXSPIDS; i++) { Modified = ancillaryPageIds[i] != AncillaryPageIds[i]; ancillaryPageIds[i] = AncillaryPageIds[i]; } } return Modified; } void cChannel::SetSeen(void) { seen = time(NULL); } void cChannel::DelLinkChannel(cChannel *LinkChannel) { if (linkChannels) { for (cLinkChannel *lc = linkChannels->First(); lc; lc = linkChannels->Next(lc)) { if (lc->Channel() == LinkChannel) { linkChannels->Del(lc); break; } } if (linkChannels->Count() == 0) { delete linkChannels; linkChannels = NULL; } } } bool cChannel::SetCaIds(const int *CaIds) { if (caids[0] && caids[0] <= CA_USER_MAX) return false; // special values will not be overwritten if (IntArraysDiffer(caids, CaIds)) { char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia char NewCaIdsBuf[MAXCAIDS * 5 + 10]; IntArrayToString(OldCaIdsBuf, caids, 16); IntArrayToString(NewCaIdsBuf, CaIds, 16); if (Number()) dsyslog("changing caids of channel %d (%s) from %s to %s", Number(), name, OldCaIdsBuf, NewCaIdsBuf); for (int i = 0; i <= MAXCAIDS; i++) { // <= to copy the terminating 0 caids[i] = CaIds[i]; if (!CaIds[i]) break; } modification |= CHANNELMOD_CA; return true; } return false; } bool cChannel::SetCaDescriptors(int Level) { if (Level > 0) { modification |= CHANNELMOD_CA; if (Number() && Level > 1) dsyslog("changing ca descriptors of channel %d (%s)", Number(), name); return true; } return false; } bool cChannel::SetLinkChannels(cLinkChannels *LinkChannels) { if (!linkChannels && !LinkChannels) return false; if (linkChannels && LinkChannels) { cLinkChannel *lca = linkChannels->First(); cLinkChannel *lcb = LinkChannels->First(); while (lca && lcb) { if (lca->Channel() != lcb->Channel()) { lca = NULL; break; } lca = linkChannels->Next(lca); lcb = LinkChannels->Next(lcb); } if (!lca && !lcb) { delete LinkChannels; return false; // linkage has not changed } } char buffer[((linkChannels ? linkChannels->Count() : 0) + (LinkChannels ? LinkChannels->Count() : 0)) * 6 + 256]; // 6: 5 digit channel number plus blank, 256: other texts (see below) plus reserve char *q = buffer; q += sprintf(q, "linking channel %d (%s) from", Number(), name); if (linkChannels) { for (cLinkChannel *lc = linkChannels->First(); lc; lc = linkChannels->Next(lc)) { lc->Channel()->SetRefChannel(NULL); q += sprintf(q, " %d", lc->Channel()->Number()); } delete linkChannels; } else q += sprintf(q, " none"); q += sprintf(q, " to"); linkChannels = LinkChannels; if (linkChannels) { for (cLinkChannel *lc = linkChannels->First(); lc; lc = linkChannels->Next(lc)) { lc->Channel()->SetRefChannel(this); q += sprintf(q, " %d", lc->Channel()->Number()); //dsyslog("link %4d -> %4d: %s", Number(), lc->Channel()->Number(), lc->Channel()->Name()); } } else q += sprintf(q, " none"); if (Number()) dsyslog("%s", buffer); return true; } void cChannel::SetRefChannel(cChannel *RefChannel) { refChannel = RefChannel; } cString cChannel::TransponderDataToString(void) const { if (cSource::IsTerr(source)) return cString::sprintf("%d:%s:%s", frequency, *parameters, *cSource::ToString(source)); return cString::sprintf("%d:%s:%s:%d", frequency, *parameters, *cSource::ToString(source), srate); } cString cChannel::ToText(const cChannel *Channel) { char FullName[strlen(Channel->name) + 1 + strlen(Channel->shortName) + 1 + strlen(Channel->provider) + 1 + 10]; // +10: paranoia char *q = FullName; q += sprintf(q, "%s", Channel->name); if (!Channel->groupSep) { if (!isempty(Channel->shortName)) q += sprintf(q, ",%s", Channel->shortName); else if (strchr(Channel->name, ',')) q += sprintf(q, ","); if (!isempty(Channel->provider)) q += sprintf(q, ";%s", Channel->provider); } *q = 0; strreplace(FullName, ':', '|'); cString buffer; if (Channel->groupSep) { if (Channel->number) buffer = cString::sprintf(":@%d %s", Channel->number, FullName); else buffer = cString::sprintf(":%s", FullName); } else { char vpidbuf[32]; char *q = vpidbuf; q += snprintf(q, sizeof(vpidbuf), "%d", Channel->vpid); if (Channel->ppid && Channel->ppid != Channel->vpid) q += snprintf(q, sizeof(vpidbuf) - (q - vpidbuf), "+%d", Channel->ppid); if (Channel->vpid && Channel->vtype) q += snprintf(q, sizeof(vpidbuf) - (q - vpidbuf), "=%d", Channel->vtype); *q = 0; const int ABufferSize = (MAXAPIDS + MAXDPIDS) * (5 + 1 + MAXLANGCODE2 + 5) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod+cod@type', +10: paranoia char apidbuf[ABufferSize]; q = apidbuf; q += IntArrayToString(q, Channel->apids, 10, Channel->alangs, Channel->atypes); if (Channel->dpids[0]) { *q++ = ';'; q += IntArrayToString(q, Channel->dpids, 10, Channel->dlangs, Channel->dtypes); } *q = 0; const int TBufferSize = MAXSPIDS * (5 + 1 + MAXLANGCODE2) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod+cod', +10: paranoia and tpid char tpidbuf[TBufferSize]; q = tpidbuf; q += snprintf(q, sizeof(tpidbuf), "%d", Channel->tpid); if (Channel->spids[0]) { *q++ = ';'; q += IntArrayToString(q, Channel->spids, 10, Channel->slangs); } char caidbuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia q = caidbuf; q += IntArrayToString(q, Channel->caids, 16); *q = 0; buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%s:%s:%d:%d:%d:%d", FullName, Channel->frequency, *Channel->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, tpidbuf, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid); } return buffer; } cString cChannel::ToText(void) const { return ToText(this); } bool cChannel::Parse(const char *s) { bool ok = true; if (*s == ':') { groupSep = true; if (*++s == '@' && *++s) { char *p = NULL; errno = 0; int n = strtol(s, &p, 10); if (!errno && p != s && n > 0) { number = n; s = p; } } name = strcpyrealloc(name, skipspace(s)); strreplace(name, '|', ':'); } else { groupSep = false; char *namebuf = NULL; char *sourcebuf = NULL; char *parambuf = NULL; char *vpidbuf = NULL; char *apidbuf = NULL; char *tpidbuf = NULL; char *caidbuf = NULL; int fields = sscanf(s, "%m[^:]:%d :%m[^:]:%m[^:] :%d :%m[^:]:%m[^:]:%m[^:]:%m[^:]:%d :%d :%d :%d ", &namebuf, &frequency, ¶mbuf, &sourcebuf, &srate, &vpidbuf, &apidbuf, &tpidbuf, &caidbuf, &sid, &nid, &tid, &rid); if (fields >= 9) { if (fields == 9) { // allow reading of old format sid = atoi(caidbuf); delete caidbuf; caidbuf = NULL; if (sscanf(tpidbuf, "%d", &tpid) != 1) return false; caids[0] = tpid; caids[1] = 0; tpid = 0; } vpid = ppid = 0; vtype = 0; apids[0] = 0; atypes[0] = 0; dpids[0] = 0; dtypes[0] = 0; spids[0] = 0; ok = false; if (parambuf && sourcebuf && vpidbuf && apidbuf) { parameters = parambuf; ok = (source = cSource::FromString(sourcebuf)) >= 0; transponder = 0; char *p; if ((p = strchr(vpidbuf, '=')) != NULL) { *p++ = 0; if (sscanf(p, "%d", &vtype) != 1) return false; } if ((p = strchr(vpidbuf, '+')) != NULL) { *p++ = 0; if (sscanf(p, "%d", &ppid) != 1) return false; } if (sscanf(vpidbuf, "%d", &vpid) != 1) return false; if (!ppid) ppid = vpid; if (vpid && !vtype) vtype = 2; // default is MPEG-2 char *dpidbuf = strchr(apidbuf, ';'); if (dpidbuf) *dpidbuf++ = 0; p = apidbuf; char *q; int NumApids = 0; char *strtok_next; while ((q = strtok_r(p, ",", &strtok_next)) != NULL) { if (NumApids < MAXAPIDS) { atypes[NumApids] = 4; // backwards compatibility char *l = strchr(q, '='); if (l) { *l++ = 0; char *t = strchr(l, '@'); if (t) { *t++ = 0; atypes[NumApids] = strtol(t, NULL, 10); } strn0cpy(alangs[NumApids], l, MAXLANGCODE2); } else *alangs[NumApids] = 0; if ((apids[NumApids] = strtol(q, NULL, 10)) != 0) NumApids++; } else esyslog("ERROR: too many APIDs!"); // no need to set ok to 'false' p = NULL; } apids[NumApids] = 0; atypes[NumApids] = 0; if (dpidbuf) { char *p = dpidbuf; char *q; int NumDpids = 0; char *strtok_next; while ((q = strtok_r(p, ",", &strtok_next)) != NULL) { if (NumDpids < MAXDPIDS) { dtypes[NumDpids] = SI::AC3DescriptorTag; // backwards compatibility char *l = strchr(q, '='); if (l) { *l++ = 0; char *t = strchr(l, '@'); if (t) { *t++ = 0; dtypes[NumDpids] = strtol(t, NULL, 10); } strn0cpy(dlangs[NumDpids], l, MAXLANGCODE2); } else *dlangs[NumDpids] = 0; if ((dpids[NumDpids] = strtol(q, NULL, 10)) != 0) NumDpids++; } else esyslog("ERROR: too many DPIDs!"); // no need to set ok to 'false' p = NULL; } dpids[NumDpids] = 0; dtypes[NumDpids] = 0; } int NumSpids = 0; if ((p = strchr(tpidbuf, ';')) != NULL) { *p++ = 0; char *q; char *strtok_next; while ((q = strtok_r(p, ",", &strtok_next)) != NULL) { if (NumSpids < MAXSPIDS) { char *l = strchr(q, '='); if (l) { *l++ = 0; strn0cpy(slangs[NumSpids], l, MAXLANGCODE2); } else *slangs[NumSpids] = 0; spids[NumSpids++] = strtol(q, NULL, 10); } else esyslog("ERROR: too many SPIDs!"); // no need to set ok to 'false' p = NULL; } spids[NumSpids] = 0; } if (sscanf(tpidbuf, "%d", &tpid) != 1) return false; if (caidbuf) { char *p = caidbuf; char *q; int NumCaIds = 0; char *strtok_next; while ((q = strtok_r(p, ",", &strtok_next)) != NULL) { if (NumCaIds < MAXCAIDS) { caids[NumCaIds++] = strtol(q, NULL, 16) & 0xFFFF; if (NumCaIds == 1 && caids[0] <= CA_USER_MAX) break; } else esyslog("ERROR: too many CA ids!"); // no need to set ok to 'false' p = NULL; } caids[NumCaIds] = 0; } } strreplace(namebuf, '|', ':'); char *p = strchr(namebuf, ';'); if (p) { *p++ = 0; provider = strcpyrealloc(provider, p); } p = strrchr(namebuf, ','); // long name might contain a ',', so search for the rightmost one if (p) { *p++ = 0; shortName = strcpyrealloc(shortName, p); } name = strcpyrealloc(name, namebuf); free(parambuf); free(sourcebuf); free(vpidbuf); free(apidbuf); free(tpidbuf); free(caidbuf); free(namebuf); nameSource = NULL; nameSourceMode = 0; shortNameSource = NULL; if (!GetChannelID().Valid()) { esyslog("ERROR: channel data results in invalid ID!"); return false; } } else return false; } return ok; } bool cChannel::Save(FILE *f) { return fprintf(f, "%s\n", *ToText()) > 0; } // --- cChannelSorter -------------------------------------------------------- class cChannelSorter : public cListObject { public: cChannel *channel; tChannelID channelID; cChannelSorter(cChannel *Channel) { channel = Channel; channelID = channel->GetChannelID(); } virtual int Compare(const cListObject &ListObject) const { cChannelSorter *cs = (cChannelSorter *)&ListObject; return memcmp(&channelID, &cs->channelID, sizeof(channelID)); } }; // --- cChannels ------------------------------------------------------------- cChannels cChannels::channels; int cChannels::maxNumber = 0; int cChannels::maxChannelNameLength = 0; int cChannels::maxShortChannelNameLength = 0; cChannels::cChannels(void) :cConfig("2 Channels") { modifiedByUser = 0; } const cChannels *cChannels::GetChannelsRead(cStateKey &StateKey, int TimeoutMs) { return channels.Lock(StateKey, false, TimeoutMs) ? &channels : NULL; } cChannels *cChannels::GetChannelsWrite(cStateKey &StateKey, int TimeoutMs) { return channels.Lock(StateKey, true, TimeoutMs) ? &channels : NULL; } void cChannels::DeleteDuplicateChannels(void) { cList ChannelSorter; for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { if (!Channel->GroupSep()) ChannelSorter.Add(new cChannelSorter(Channel)); } ChannelSorter.Sort(); cChannelSorter *cs = ChannelSorter.First(); while (cs) { cChannelSorter *Next = ChannelSorter.Next(cs); if (Next && cs->channelID == Next->channelID) { dsyslog("deleting duplicate channel %s", *Next->channel->ToText()); Del(Next->channel); } cs = Next; } } bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist) { LOCK_CHANNELS_WRITE; if (channels.cConfig::Load(FileName, AllowComments, MustExist)) { channels.DeleteDuplicateChannels(); channels.ReNumber(); return true; } return false; } void cChannels::HashChannel(cChannel *Channel) { channelsHashSid.Add(Channel, Channel->Sid()); } void cChannels::UnhashChannel(cChannel *Channel) { channelsHashSid.Del(Channel, Channel->Sid()); } int cChannels::GetNextGroup(int Idx) const { const cChannel *Channel = Get(++Idx); while (Channel && !(Channel->GroupSep() && *Channel->Name())) Channel = Get(++Idx); return Channel ? Idx : -1; } int cChannels::GetPrevGroup(int Idx) const { const cChannel *Channel = Get(--Idx); while (Channel && !(Channel->GroupSep() && *Channel->Name())) Channel = Get(--Idx); return Channel ? Idx : -1; } int cChannels::GetNextNormal(int Idx) const { const cChannel *Channel = Get(++Idx); while (Channel && Channel->GroupSep()) Channel = Get(++Idx); return Channel ? Idx : -1; } int cChannels::GetPrevNormal(int Idx) const { const cChannel *Channel = Get(--Idx); while (Channel && Channel->GroupSep()) Channel = Get(--Idx); return Channel ? Idx : -1; } void cChannels::ReNumber(void) { channelsHashSid.Clear(); maxNumber = 0; int Number = 1; for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { if (Channel->GroupSep()) { if (Channel->Number() > Number) Number = Channel->Number(); } else { HashChannel(Channel); maxNumber = Number; Channel->SetNumber(Number++); } } } bool cChannels::MoveNeedsDecrement(cChannel *From, cChannel *To) { int Number = From->Number(); if (Number < To->Number()) { for (cChannel *Channel = Next(From); Channel; Channel = Next(Channel)) { if (Channel == To) break; if (Channel->GroupSep()) { if (Channel->Number() > Number) Number = Channel->Number(); } else Number++; } return Number == To->Number(); } return false; } void cChannels::Del(cChannel *Channel) { UnhashChannel(Channel); for (cChannel *ch = First(); ch; ch = Next(ch)) ch->DelLinkChannel(Channel); cList::Del(Channel); } const cChannel *cChannels::GetByNumber(int Number, int SkipGap) const { const cChannel *Previous = NULL; for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { if (!Channel->GroupSep()) { if (Channel->Number() == Number) return Channel; else if (SkipGap && Channel->Number() > Number) return SkipGap > 0 ? Channel : Previous; Previous = Channel; } } return NULL; } const cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const { cList *list = channelsHashSid.GetList(ServiceID); if (list) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { cChannel *Channel = (cChannel *)hobj->Object(); if (Channel->Sid() == ServiceID && Channel->Source() == Source && ISTRANSPONDER(Channel->Transponder(), Transponder)) return Channel; } } return NULL; } const cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization) const { int sid = ChannelID.Sid(); cList *list = channelsHashSid.GetList(sid); if (list) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { cChannel *Channel = (cChannel *)hobj->Object(); if (Channel->Sid() == sid && Channel->GetChannelID() == ChannelID) return Channel; } if (TryWithoutRid) { ChannelID.ClrRid(); for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { cChannel *Channel = (cChannel *)hobj->Object(); if (Channel->Sid() == sid && Channel->GetChannelID().ClrRid() == ChannelID) return Channel; } } if (TryWithoutPolarization) { ChannelID.ClrPolarization(); for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { cChannel *Channel = (cChannel *)hobj->Object(); if (Channel->Sid() == sid && Channel->GetChannelID().ClrPolarization() == ChannelID) return Channel; } } } return NULL; } const cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) const { int source = ChannelID.Source(); int nid = ChannelID.Nid(); int tid = ChannelID.Tid(); for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { if (Channel->Tid() == tid && Channel->Nid() == nid && Channel->Source() == source) return Channel; } return NULL; } bool cChannels::HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel) const { tChannelID NewChannelID = NewChannel->GetChannelID(); for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { if (!Channel->GroupSep() && Channel != OldChannel && Channel->GetChannelID() == NewChannelID) return false; } return true; } bool cChannels::SwitchTo(int Number) const { const cChannel *Channel = GetByNumber(Number); return Channel && cDevice::PrimaryDevice()->SwitchChannel(Channel, true); } int cChannels::MaxChannelNameLength(void) { if (!maxChannelNameLength) { LOCK_CHANNELS_READ; for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep()) maxChannelNameLength = max(Utf8StrLen(Channel->Name()), maxChannelNameLength); } } return maxChannelNameLength; } int cChannels::MaxShortChannelNameLength(void) { if (!maxShortChannelNameLength) { LOCK_CHANNELS_READ; for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep()) maxShortChannelNameLength = max(Utf8StrLen(Channel->ShortName(true)), maxShortChannelNameLength); } } return maxShortChannelNameLength; } void cChannels::SetModifiedByUser(void) { modifiedByUser++; maxChannelNameLength = maxShortChannelNameLength = 0; } bool cChannels::ModifiedByUser(int &State) const { int Result = State != modifiedByUser; State = modifiedByUser; return Result; } cChannel *cChannels::NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid) { if (Transponder) { dsyslog("creating new channel '%s,%s;%s' on %s transponder %d with id %d-%d-%d-%d", Name, ShortName, Provider, *cSource::ToString(Transponder->Source()), Transponder->Transponder(), Nid, Tid, Sid, Rid); cChannel *NewChannel = new cChannel; NewChannel->CopyTransponderData(Transponder); NewChannel->SetId(this, Nid, Tid, Sid, Rid); NewChannel->SetName(Name, ShortName, Provider); NewChannel->SetSeen(); Add(NewChannel); ReNumber(); return NewChannel; } return NULL; } #define CHANNELMARKOBSOLETE "OBSOLETE" #define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before) bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid) { bool ChannelsModified = false; for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { if (time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Source() == Source && Channel->Nid() == Nid && Channel->Tid() == Tid && Channel->Rid() == 0) { int OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource; Setup.ShowChannelNamesWithSource = 0; if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE)) ChannelsModified |= Channel->SetName(cString::sprintf("%s %s", Channel->Name(), CHANNELMARKOBSOLETE), Channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, Channel->Provider())); Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource; } } return ChannelsModified; } cString ChannelString(const cChannel *Channel, int Number) { char buffer[256]; if (Channel) { if (Channel->GroupSep()) snprintf(buffer, sizeof(buffer), "%s", Channel->Name()); else snprintf(buffer, sizeof(buffer), "%d%s %s", Channel->Number(), Number ? "-" : "", Channel->Name()); } else if (Number) snprintf(buffer, sizeof(buffer), "%d-", Number); else snprintf(buffer, sizeof(buffer), "%s", tr("*** Invalid Channel ***")); return buffer; }