2020-08-08 13:58:53 +02:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2020-08-09 14:09:08 +02:00
using System.Reflection ;
2020-08-08 13:58:53 +02:00
using System.Text ;
2021-04-25 18:31:05 +02:00
using Microsoft.Data.Sqlite ;
2020-08-08 13:58:53 +02:00
using ChanSort.Api ;
2021-01-09 12:06:32 +01:00
namespace ChanSort.Loader.Philips
2020-08-08 13:58:53 +02:00
{
2020-08-08 20:37:16 +02:00
/ *
2021-01-17 15:44:45 +01:00
2023-09-17 20:07:55 +02:00
This loader handles the file format versions 1. x , 2.0 ( * Table and * . dat files ) , version 25. x - 45. x ( * Db . bin files + tv . db and list . db )
2021-04-11 12:08:47 +02:00
Version 30. x and 45. x were tested , version 25 is untested due to the lack of any sample files . Based on what I read online , the files are pretty much the
same as with Format 45.
2021-01-17 15:44:45 +01:00
2020-08-10 11:27:05 +02:00
channellib \ CableDigSrvTable :
= = = = = = = = = = = = = = = = = = = = = = = = = = =
2021-01-09 12:06:32 +01:00
Channels in this file are not physically ordered by the program number and there is no linked list with prev / next indexes .
2020-08-10 11:27:05 +02:00
When editing a channel with the Philips Channel Editor , it only updates the progNr field ( and overwrites all trailing bytes of the channel name with 0x00 ) .
There is also the CablePresetTable file which is probably used for LCN . The Philips tool also updates the progNr in that file and uses it as is primary source
for the progNr . I don ' t know if there is a direct reference from the channel to the preset , hence this code uses the combination of ONID + TSID + SID to link the two .
2020-08-08 20:37:16 +02:00
s2channellib \ service . dat :
= = = = = = = = = = = = = = = = = = = = = = = =
All observed files have a perfectly linear next / prev table . The Philips Channel Editor also keeps that list linear and physically reorders the channel records .
Also , all observed channel records have progNr either equal to the physical index + 1 or to 0xffff .
Each channel record with progNr 0xFFFF causes a gap in the progNr sequence .
It is unclear :
- if the next / prev list MUST be kept linear
- channel records MUST be physically ordered to be in - sync with the next / prev list
- channel records MUST be physically ordered by progNr ( allowing 0xFFFF for gaps )
To be on the safe side , this code keeps the list linear , physically reorders the records to match the progNr .
Since we don ' t show deleted channels in the UI , we can ' t keep the gaps caused by them in the channel list . They will be appended at the end and the gaps closed .
2020-08-08 13:58:53 +02:00
2020-08-08 20:37:16 +02:00
When swapping satellite channels 1 and 2 with the Philips Channel Editor 6.62 , it only updates a few fields and leaves the rest stale .
updated : SID , transponderIndex , channelName , providerName
2020-08-10 11:27:05 +02:00
This code here copies the whole record before updating the fields .
The favorite . dat file stores favorites as linked list which may support independent ordering from the main channel list .
The Philips editor even saves non - linear lists , but not in any particular order .
2021-04-11 12:08:47 +02:00
channellib \ CableChannelMaps . db
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Used in format version 30 ( not 45 ) as a 3 rd file containing program numbers . SQLite database containing tables "AnalogTable" and "DigSrvTable" .
2020-08-08 20:37:16 +02:00
* /
2021-08-24 19:41:31 +02:00
public class BinarySerializer : SerializerBase
2020-08-08 13:58:53 +02:00
{
2020-08-08 20:37:16 +02:00
private readonly IniFile ini ;
private readonly List < string > dataFilePaths = new List < string > ( ) ;
2021-02-08 16:57:43 +01:00
// lists for old binary formats <= 11.x
2020-08-10 11:27:05 +02:00
private readonly ChannelList dvbtChannels = new ChannelList ( SignalSource . DvbT , "DVB-T" ) ;
private readonly ChannelList dvbcChannels = new ChannelList ( SignalSource . DvbC , "DVB-C" ) ;
2021-02-08 16:57:43 +01:00
private readonly ChannelList dvbsChannels = new ChannelList ( SignalSource . DvbS , "DVB-S" ) ;
// lists for binary format >= 25.x
private readonly ChannelList antChannels = new ChannelList ( SignalSource . Antenna , "Antenna" ) ;
private readonly ChannelList cabChannels = new ChannelList ( SignalSource . Cable , "Cable" ) ;
private readonly ChannelList satChannels = new ChannelList ( SignalSource . Sat , "Satellite" ) ;
2020-11-16 20:43:56 +01:00
private ChanLstBin chanLstBin ;
private readonly StringBuilder logMessages = new StringBuilder ( ) ;
2021-01-31 14:37:58 +01:00
private readonly ChannelList favChannels = new ChannelList ( SignalSource . All , "Favorites" ) ;
2021-02-24 11:05:47 +01:00
private const int FavListCount = 8 ;
private bool mustFixFavListIds ;
2020-08-08 13:58:53 +02:00
2021-04-11 12:08:47 +02:00
private readonly Dictionary < int , Channel > channelsById = new ( ) ;
2020-08-08 13:58:53 +02:00
#region ctor ( )
2021-01-09 12:06:32 +01:00
public BinarySerializer ( string inputFile ) : base ( inputFile )
2020-08-08 13:58:53 +02:00
{
this . Features . ChannelNameEdit = ChannelNameEditMode . None ;
this . Features . CanSkipChannels = false ;
this . Features . CanLockChannels = true ;
this . Features . CanHideChannels = false ;
this . Features . DeleteMode = DeleteMode . NotSupported ;
2020-08-08 20:37:16 +02:00
this . Features . CanHaveGaps = false ;
2021-03-14 22:13:22 +01:00
this . Features . FavoritesMode = FavoritesMode . Flags ; // satellite favorites are stored in a separate file that may support independent sorting, but DVB C/T only have a flag
this . Features . MaxFavoriteLists = 1 ; // Map45 format will change this
2020-08-08 13:58:53 +02:00
this . Features . AllowGapsInFavNumbers = false ;
this . Features . CanEditFavListNames = false ;
2020-08-09 14:09:08 +02:00
string iniFile = Assembly . GetExecutingAssembly ( ) . Location . Replace ( ".dll" , ".ini" ) ;
this . ini = new IniFile ( iniFile ) ;
2020-08-08 13:58:53 +02:00
}
#endregion
2021-04-11 12:08:47 +02:00
// loading
#region Load
2020-08-08 13:58:53 +02:00
public override void Load ( )
{
2020-11-16 20:43:56 +01:00
this . chanLstBin = new ChanLstBin ( ) ;
this . chanLstBin . Load ( this . FileName , msg = > this . logMessages . AppendLine ( msg ) ) ;
2021-09-05 18:20:39 +02:00
this . TvModelName = this . chanLstBin . ModelName ;
this . FileFormatVersion = $"{chanLstBin.VersionMajor}.{chanLstBin.VersionMinor}" ;
2021-01-17 15:44:45 +01:00
this . dataFilePaths . Add ( this . FileName ) ;
2020-08-10 11:27:05 +02:00
var dir = Path . GetDirectoryName ( this . FileName ) ? ? "" ;
var channellib = Path . Combine ( dir , "channellib" ) ;
var s2channellib = Path . Combine ( dir , "s2channellib" ) ;
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
{
2021-02-08 16:57:43 +01:00
this . DataRoot . AddChannelList ( this . dvbtChannels ) ;
this . DataRoot . AddChannelList ( this . dvbcChannels ) ;
this . DataRoot . AddChannelList ( this . dvbsChannels ) ;
2021-01-17 15:44:45 +01:00
LoadDvbCT ( dvbtChannels , Path . Combine ( channellib , "AntennaDigSrvTable" ) , "CableDigSrvTable_entry" ) ;
LoadDvbCTPresets ( dvbtChannels , Path . Combine ( channellib , "AntennaPresetTable" ) ) ;
LoadDvbCT ( dvbcChannels , Path . Combine ( channellib , "CableDigSrvTable" ) , "CableDigSrvTable_entry" ) ;
LoadDvbCTPresets ( dvbcChannels , Path . Combine ( channellib , "CablePresetTable" ) ) ;
LoadDvbsSatellites ( Path . Combine ( s2channellib , "satellite.dat" ) ) ;
LoadDvbsTransponders ( Path . Combine ( s2channellib , "tuneinfo.dat" ) ) ;
2021-02-08 16:57:43 +01:00
LoadDvbS ( dvbsChannels , Path . Combine ( s2channellib , "service.dat" ) , "service.dat_entry" ) ;
2021-01-17 15:44:45 +01:00
LoadDvbsFavorites ( Path . Combine ( s2channellib , "favorite.dat" ) ) ;
var db_file_info = Path . Combine ( s2channellib , "db_file_info.dat" ) ;
if ( File . Exists ( db_file_info ) )
this . dataFilePaths . Add ( db_file_info ) ;
}
else if ( chanLstBin . VersionMajor > = 25 & & chanLstBin . VersionMajor < = 45 )
{
// version 25-45
2021-02-08 16:57:43 +01:00
this . Features . CanHaveGaps = true ;
this . DataRoot . AddChannelList ( this . antChannels ) ;
this . DataRoot . AddChannelList ( this . cabChannels ) ;
this . DataRoot . AddChannelList ( this . satChannels ) ;
2021-04-11 12:08:47 +02:00
// version 45 supports mixed source favorites, version 30 (and probably 25) have separate fav lists per input source
if ( chanLstBin . VersionMajor = = 45 )
{
this . favChannels . IsMixedSourceFavoritesList = true ;
this . DataRoot . AddChannelList ( this . favChannels ) ;
}
2021-02-08 16:57:43 +01:00
LoadDvbCT ( antChannels , Path . Combine ( channellib , "TerrestrialDb.bin" ) , "Map45_CableDb.bin_entry" ) ;
LoadDvbCT ( cabChannels , Path . Combine ( channellib , "CableDb.bin" ) , "Map45_CableDb.bin_entry" ) ;
2021-01-17 15:44:45 +01:00
LoadDvbS ( satChannels , Path . Combine ( s2channellib , "SatelliteDb.bin" ) , "Map45_SatelliteDb.bin_entry" ) ;
2021-04-11 12:08:47 +02:00
2021-01-17 15:44:45 +01:00
var tvDbFile = Path . Combine ( dir , "tv.db" ) ;
if ( File . Exists ( tvDbFile ) )
2021-01-31 14:37:58 +01:00
{
2021-01-17 15:44:45 +01:00
this . dataFilePaths . Add ( tvDbFile ) ;
2021-04-11 12:08:47 +02:00
this . LoadTvDb ( tvDbFile ) ;
2021-01-31 14:37:58 +01:00
}
2021-04-11 12:08:47 +02:00
LoadMap30ChannelMapsDb ( antChannels , Path . Combine ( channellib , "TerrestrialChannelMaps.db" ) ) ;
LoadMap30ChannelMapsDb ( cabChannels , Path . Combine ( channellib , "CableChannelMaps.db" ) ) ;
LoadMap30ChannelMapsDb ( satChannels , Path . Combine ( s2channellib , "SatelliteChannelMaps.db" ) ) ;
// favorites in "list.db" have different schema depending on version
2021-01-31 14:37:58 +01:00
var listDbFile = Path . Combine ( dir , "list.db" ) ;
if ( File . Exists ( listDbFile ) )
{
2021-04-11 12:08:47 +02:00
if ( chanLstBin . VersionMajor = = 30 )
this . LoadMap30Favorites ( listDbFile ) ;
else if ( chanLstBin . VersionMajor = = 45 )
this . LoadMap45Favorites ( listDbFile ) ;
2021-01-31 14:37:58 +01:00
this . dataFilePaths . Add ( listDbFile ) ;
}
2021-02-08 16:57:43 +01:00
satChannels . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . Polarity ) ) ;
}
else
{
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "Only Philips channel list format version 1.x and 25-45 are supported by this loader" ) ;
2021-01-17 15:44:45 +01:00
}
2021-01-09 12:06:32 +01:00
2020-08-08 20:37:16 +02:00
// for a proper ChanSort backup/restore with .bak files, the Philips _backup.dat files must also be included
2020-08-10 11:27:05 +02:00
foreach ( var file in this . dataFilePaths . ToList ( ) )
{
if ( file . Contains ( ".dat" ) )
this . dataFilePaths . Add ( file . Replace ( ".dat" , "_backup.dat" ) ) ;
}
2021-02-08 16:57:43 +01:00
dvbsChannels . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . Polarity ) ) ;
foreach ( var list in this . DataRoot . ChannelLists )
{
list . VisibleColumnFieldNames . Remove ( "Skip" ) ;
list . VisibleColumnFieldNames . Remove ( "ShortName" ) ;
if ( chanLstBin . VersionMajor < = 11 )
list . VisibleColumnFieldNames . Remove ( "ServiceTypeName" ) ;
list . VisibleColumnFieldNames . Remove ( "Hidden" ) ;
list . VisibleColumnFieldNames . Remove ( "AudioPid" ) ;
2022-01-07 22:02:35 +01:00
//list.VisibleColumnFieldNames.Remove("Encrypted");
2021-02-08 16:57:43 +01:00
}
foreach ( var list in new [ ] { dvbcChannels , dvbtChannels , antChannels , cabChannels , satChannels } )
{
list . VisibleColumnFieldNames . Remove ( "PcrPid" ) ;
list . VisibleColumnFieldNames . Remove ( "VideoPid" ) ;
list . VisibleColumnFieldNames . Remove ( "AudioPid" ) ;
list . VisibleColumnFieldNames . Remove ( "ChannelOrTransponder" ) ;
list . VisibleColumnFieldNames . Remove ( "Provider" ) ;
}
2020-08-08 13:58:53 +02:00
}
#endregion
2021-04-11 12:08:47 +02:00
2020-08-08 13:58:53 +02:00
#region LoadDvbCT
2021-01-09 12:06:32 +01:00
private void LoadDvbCT ( ChannelList list , string path , string mappingName )
2020-08-08 13:58:53 +02:00
{
2020-08-10 11:27:05 +02:00
if ( ! ReadAndValidateChannellibFile ( path , out var data , out var recordSize , out var recordCount ) )
return ;
2020-08-08 13:58:53 +02:00
2021-01-09 12:06:32 +01:00
var mapping = new DataMapping ( this . ini . GetSection ( mappingName ) ) ;
2021-01-17 15:44:45 +01:00
mapping . SetDataPtr ( data , chanLstBin . VersionMajor < = 11 ? 20 : 12 ) ;
2020-08-08 13:58:53 +02:00
2020-08-10 11:27:05 +02:00
for ( int i = 0 ; i < recordCount ; i + + , mapping . BaseOffset + = recordSize )
2020-08-08 13:58:53 +02:00
{
2020-08-10 11:27:05 +02:00
var progNr = mapping . GetWord ( "offProgNr" ) ;
var offChannelName = mapping . BaseOffset + mapping . GetConst ( "offName" , 0 ) ;
var lenName = mapping . GetConst ( "lenName" , 0 ) ;
for ( int j = 0 ; j < lenName ; j + = 2 )
{
if ( data [ offChannelName + j ] = = 0 )
{
lenName = j ;
2020-08-08 13:58:53 +02:00
break ;
2020-08-10 11:27:05 +02:00
}
}
string channelName = Encoding . Unicode . GetString ( data , offChannelName , lenName ) ;
2020-08-08 13:58:53 +02:00
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
{
var checksum = mapping . GetDword ( "offChecksum" ) ;
mapping . SetDword ( "offChecksum" , 0 ) ;
var crc = FaultyCrc32 ( data , mapping . BaseOffset + mapping . GetConst ( "offChecksum" , 0 ) , recordSize ) ;
if ( crc ! = checksum )
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( $"Invalid CRC in record {i} in {path}" ) ;
2021-01-17 15:44:45 +01:00
}
2020-08-08 13:58:53 +02:00
2023-06-03 10:38:11 +02:00
var ch = new Channel ( list . SignalSource & SignalSource . MaskBcastMedium , i , progNr , channelName ) ;
2021-01-17 15:44:45 +01:00
ch . Id = mapping . GetWord ( "offId" ) ; // only relevant for ChannelMap45
2021-02-08 16:57:43 +01:00
if ( chanLstBin . VersionMajor < = 11 )
ch . FreqInMhz = ( decimal ) mapping . GetWord ( "offFreqTimes16" ) / 16 ;
else
{
ch . FreqInMhz = mapping . GetDword ( "offFreq" ) / 1000 ;
ch . Encrypted = mapping . GetDword ( "offEncrypted" ) ! = 0 ;
2023-06-03 10:38:11 +02:00
ch . SignalSource | = mapping . GetDword ( "offIsDigital" ) = = 0 ? SignalSource . Analog : SignalSource . Dvb ;
2021-02-08 16:57:43 +01:00
if ( mapping . GetDword ( "offServiceType" ) = = 2 )
{
ch . SignalSource | = SignalSource . Radio ;
ch . ServiceTypeName = "Radio" ;
}
else
{
ch . SignalSource | = SignalSource . Tv ;
ch . ServiceTypeName = "TV" ;
}
}
2020-08-10 11:27:05 +02:00
ch . OriginalNetworkId = mapping . GetWord ( "offOnid" ) ;
ch . TransportStreamId = mapping . GetWord ( "offTsid" ) ;
ch . ServiceId = mapping . GetWord ( "offSid" ) ;
ch . SymbolRate = ( int ) mapping . GetDword ( "offSymbolRate" ) / 1000 ;
ch . Lock = mapping . GetByte ( "offLocked" ) ! = 0 ;
ch . Favorites = mapping . GetByte ( "offIsFav" ) ! = 0 ? Favorites . A : 0 ;
if ( ch . Favorites ! = 0 )
2021-03-07 16:12:21 +01:00
ch . SetOldPosition ( 1 , ch . OldProgramNr ) ;
2021-02-08 16:57:43 +01:00
2020-08-08 13:58:53 +02:00
this . DataRoot . AddChannel ( list , ch ) ;
}
2020-08-10 11:27:05 +02:00
}
#endregion
#region LoadDvbCTPresets
private void LoadDvbCTPresets ( ChannelList list , string path )
{
if ( ! ReadAndValidateChannellibFile ( path , out var data , out var recordSize , out var recordCount ) )
return ;
// build a mapping of (onid,tsid,sid) => channel
var channelById = new Dictionary < ulong , Channel > ( ) ;
foreach ( var chan in list . Channels )
{
var ch = ( Channel ) chan ;
var id = ( ( ulong ) ch . OriginalNetworkId < < 32 ) | ( ( ulong ) ch . TransportStreamId < < 16 ) | ( uint ) ch . ServiceId ;
channelById [ id ] = ch ;
}
2020-08-08 13:58:53 +02:00
2020-08-10 11:27:05 +02:00
// apply preset progNr (LCN?) to the channel and remember the preset index for it
var mapping = new DataMapping ( this . ini . GetSection ( "CablePresetTable_entry" ) ) ;
mapping . SetDataPtr ( data , 20 ) ;
for ( int i = 0 ; i < recordCount ; i + + , mapping . BaseOffset + = recordSize )
{
var onid = mapping . GetWord ( "offOnid" ) ;
var tsid = mapping . GetWord ( "offTsid" ) ;
var sid = mapping . GetWord ( "offSid" ) ;
var id = ( ( ulong ) onid < < 32 ) | ( ( ulong ) tsid < < 16 ) | sid ;
if ( ! channelById . TryGetValue ( id , out var ch ) )
continue ;
ch . PresetTableIndex = i ;
var progNr = mapping . GetWord ( "offProgNr" ) ;
if ( progNr ! = 0 & & progNr ! = 0xFFFF )
ch . OldProgramNr = progNr ;
}
2020-08-08 13:58:53 +02:00
}
#endregion
2020-08-10 11:27:05 +02:00
#region ReadAndValidateChannellibFile
private bool ReadAndValidateChannellibFile ( string path , out byte [ ] data , out int recordSize , out int recordCount )
{
data = null ;
recordSize = 0 ;
recordCount = 0 ;
if ( ! File . Exists ( path ) )
return false ;
data = File . ReadAllBytes ( path ) ;
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
{
if ( data . Length < 20 )
return false ;
recordSize = BitConverter . ToInt32 ( data , 8 ) ;
recordCount = BitConverter . ToInt32 ( data , 12 ) ;
if ( data . Length ! = 20 + recordCount * recordSize )
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "Unsupported file content: " + path ) ;
2021-01-17 15:44:45 +01:00
}
else
{
if ( data . Length < 12 )
return false ;
recordSize = 156 ; // Map45
recordCount = BitConverter . ToInt32 ( data , 8 ) ;
if ( data . Length ! = 12 + recordCount * recordSize )
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "Unsupported file content: " + path ) ;
2021-01-17 15:44:45 +01:00
}
2020-08-10 11:27:05 +02:00
this . dataFilePaths . Add ( path ) ;
return true ;
}
#endregion
2020-08-08 13:58:53 +02:00
2021-04-11 12:08:47 +02:00
#region LoadDvbsSatellites
2020-08-08 13:58:53 +02:00
private void LoadDvbsSatellites ( string path )
{
if ( ! File . Exists ( path ) )
return ;
var data = File . ReadAllBytes ( path ) ;
if ( data . Length < 4 )
return ;
var checksum = BitConverter . ToUInt32 ( data , data . Length - 4 ) ;
var crc = ~ Crc32 . Reversed . CalcCrc32 ( data , 0 , data . Length - 4 ) ;
if ( checksum ! = crc )
return ;
int recordSize = BitConverter . ToInt32 ( data , 4 ) ;
int recordCount = BitConverter . ToInt32 ( data , 8 ) ;
// 12 byte header, table of (next, prev) transponder, records, crc32
if ( data . Length ! = 12 + recordCount * 4 + recordCount * recordSize + 4 )
return ;
var baseOffset = 12 + recordCount * 4 ;
for ( int i = 0 ; i < recordCount ; i + + , baseOffset + = recordSize )
{
if ( data [ baseOffset + 0 ] = = 0 )
continue ;
var s = new Satellite ( i ) ;
var pos = ( sbyte ) data [ baseOffset + 8 ] ;
s . OrbitalPosition = pos < 0 ? - pos + "W" : pos + "E" ;
2021-01-17 15:44:45 +01:00
s . Name = this . DefaultEncoding . GetString ( data , baseOffset + 16 , 16 ) . TrimGarbage ( ) ;
2020-08-08 13:58:53 +02:00
this . DataRoot . AddSatellite ( s ) ;
}
}
#endregion
#region LoadDvbsTransponders
private void LoadDvbsTransponders ( string path )
{
if ( ! File . Exists ( path ) )
return ;
var data = File . ReadAllBytes ( path ) ;
if ( data . Length < 4 )
return ;
var checksum = BitConverter . ToUInt32 ( data , data . Length - 4 ) ;
var crc = ~ Crc32 . Reversed . CalcCrc32 ( data , 0 , data . Length - 4 ) ;
if ( checksum ! = crc )
return ;
int recordSize = BitConverter . ToInt32 ( data , 4 ) ;
int recordCount = BitConverter . ToInt32 ( data , 8 ) ;
// 12 byte header, table of (next, prev) transponder, records, crc32
if ( data . Length ! = 12 + recordCount * 4 + recordCount * recordSize + 4 )
return ;
var baseOffset = 12 + recordCount * 4 ;
for ( int i = 0 ; i < recordCount ; i + + , baseOffset + = recordSize )
{
var symRate = BitConverter . ToUInt16 ( data , baseOffset + 0 ) ;
if ( symRate = = 0xFFFF )
continue ;
var tsid = BitConverter . ToUInt16 ( data , baseOffset + 16 ) ;
var onid = BitConverter . ToUInt16 ( data , baseOffset + 18 ) ;
var t = new Transponder ( i ) ;
t . SymbolRate = symRate ;
2021-02-08 16:57:43 +01:00
t . FrequencyInMhz = BitConverter . ToUInt16 ( data , baseOffset + 2 ) & 0x3FFF ;
t . Polarity = ( BitConverter . ToUInt16 ( data , baseOffset + 2 ) & 0x4000 ) ! = 0 ? 'V' : 'H' ;
2020-08-08 13:58:53 +02:00
var satIndex = data [ baseOffset + 6 ] > > 4 ; // guesswork
t . Satellite = DataRoot . Satellites . TryGet ( satIndex ) ;
t . TransportStreamId = tsid ;
t . OriginalNetworkId = onid ;
this . DataRoot . AddTransponder ( t . Satellite , t ) ;
}
}
#endregion
#region LoadDvbS
2021-01-09 12:06:32 +01:00
private void LoadDvbS ( ChannelList list , string path , string mappingName )
2020-08-08 13:58:53 +02:00
{
if ( ! File . Exists ( path ) )
2020-08-08 20:37:16 +02:00
return ;
2020-08-08 13:58:53 +02:00
var data = File . ReadAllBytes ( path ) ;
2021-01-17 15:44:45 +01:00
if ( data . Length < 12 )
2020-08-08 20:37:16 +02:00
return ;
2020-08-08 13:58:53 +02:00
2021-01-17 15:44:45 +01:00
var version = chanLstBin . VersionMajor ;
if ( version < = 11 )
2021-01-09 12:06:32 +01:00
{
var checksum = BitConverter . ToUInt32 ( data , data . Length - 4 ) ;
var crcObj = new Crc32 ( false , Crc32 . NormalPoly ) ;
var crc = ~ crcObj . CalcCrc32 ( data , 0 , data . Length - 4 ) ;
if ( checksum ! = crc )
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "Invalid CRC32 in " + path ) ;
2021-01-09 12:06:32 +01:00
}
2020-08-08 13:58:53 +02:00
int recordSize = BitConverter . ToInt32 ( data , 4 ) ;
int recordCount = BitConverter . ToInt32 ( data , 8 ) ;
2021-01-17 15:44:45 +01:00
if ( recordSize = = 0 & & version ! = 1 )
recordSize = recordCount = = 0 ? 0 : ( data . Length - 12 ) / recordCount ;
2020-08-08 13:58:53 +02:00
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
2021-01-09 12:06:32 +01:00
{
// 12 bytes header, then a "next/prev" table, then the service records, then a CRC32
// the "next/prev" table is a ring-list, every entry consists of 2 ushorts with the next and previous channel, wrapping around on the ends
if ( data . Length ! = 12 + recordCount * 4 + recordCount * recordSize + 4 )
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "Unsupported file content: " + path ) ;
2021-01-09 12:06:32 +01:00
}
2020-08-08 13:58:53 +02:00
2023-09-17 20:07:55 +02:00
var channelCount = recordCount ;
if ( version = = 2 )
channelCount = BitConverter . ToInt16 ( data , 2 ) ; // number of used channels
2020-08-08 13:58:53 +02:00
this . dataFilePaths . Add ( path ) ;
var dvbStringDecoder = new DvbStringDecoder ( this . DefaultEncoding ) ;
2021-01-09 12:06:32 +01:00
var mapping = new DataMapping ( this . ini . GetSection ( mappingName ) ) ;
2021-01-17 15:44:45 +01:00
mapping . SetDataPtr ( data , 12 + ( chanLstBin . VersionMajor < = 11 ? recordCount * 4 : 0 ) ) ;
2023-09-17 20:07:55 +02:00
for ( int i = 0 ; i < channelCount ; i + + , mapping . BaseOffset + = recordSize )
2020-08-08 13:58:53 +02:00
{
2020-08-09 14:09:08 +02:00
var ch = LoadDvbsChannel ( list , mapping , i , dvbStringDecoder ) ;
2020-08-08 13:58:53 +02:00
this . DataRoot . AddChannel ( list , ch ) ;
}
}
#endregion
2020-08-09 14:09:08 +02:00
#region LoadDvbsChannel
private ChannelInfo LoadDvbsChannel ( ChannelList list , DataMapping mapping , int recordIndex , DvbStringDecoder dvbStringDecoder )
{
var transponderId = mapping . GetWord ( "offTransponderIndex" ) ;
var progNr = mapping . GetWord ( "offProgNr" ) ;
2025-01-28 21:26:53 +01:00
var serviceId = mapping . GetWord ( "offSid" ) ;
2023-06-03 10:38:11 +02:00
var ch = new Channel ( list . SignalSource & SignalSource . MaskBcastMedium , recordIndex , progNr , "" ) ;
2020-08-09 14:09:08 +02:00
// deleted channels must be kept in the list because their records must also be physically reordered when saving the list
2025-01-28 21:26:53 +01:00
if ( progNr = = 0xFFFF | | transponderId = = 0xFFFF | | serviceId = = 0xFFFF )
2020-08-09 14:09:08 +02:00
{
ch . IsDeleted = true ;
ch . OldProgramNr = - 1 ;
return ch ;
}
// onid, tsid, pcrpid and vpid can be 0 in some lists
ch . PcrPid = mapping . GetWord ( "offPcrPid" ) & mapping . GetMask ( "maskPcrPid" ) ;
2021-09-15 10:59:05 +02:00
ch . Lock = mapping . GetFlag ( "Locked" , false ) ;
2022-01-07 22:02:35 +01:00
ch . Encrypted = mapping . GetFlag ( "Encrypted" , false ) ;
2021-02-08 16:57:43 +01:00
ch . OriginalNetworkId = mapping . GetWord ( "offOnid" ) ;
2020-08-09 14:09:08 +02:00
ch . TransportStreamId = mapping . GetWord ( "offTsid" ) ;
ch . ServiceId = mapping . GetWord ( "offSid" ) ;
ch . VideoPid = mapping . GetWord ( "offVpid" ) & mapping . GetMask ( "maskVpid" ) ;
2021-09-15 10:59:05 +02:00
ch . Favorites = mapping . GetFlag ( "IsFav" , false ) ? Favorites . A : 0 ;
2020-08-09 14:09:08 +02:00
ch . OldProgramNr = progNr ;
2021-01-17 15:44:45 +01:00
ch . Id = mapping . GetWord ( "offId" ) ; // relevant for ChannelMap45
2020-08-09 14:09:08 +02:00
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
{
// the 0x1F as the first byte of the channel name is likely the DVB encoding indicator for UTF-8. So we use the DvbStringDecoder here
dvbStringDecoder . GetChannelNames ( mapping . Data , mapping . BaseOffset + mapping . GetConst ( "offName" , 0 ) , mapping . GetConst ( "lenName" , 0 ) , out var longName , out var shortName ) ;
ch . Name = longName ;
ch . ShortName = shortName ;
}
else
{
2023-06-03 10:38:11 +02:00
ch . SignalSource | = mapping . GetWord ( "offIsDigital" ) = = 0 ? SignalSource . Analog : SignalSource . Dvb ;
2021-01-17 15:44:45 +01:00
ch . Name = Encoding . Unicode . GetString ( mapping . Data , mapping . BaseOffset + mapping . GetConst ( "offName" , 0 ) , mapping . GetConst ( "lenName" , 0 ) ) . TrimEnd ( '\0' ) ;
2021-01-31 14:37:58 +01:00
ch . FreqInMhz = ( decimal ) mapping . GetDword ( "offFreq" ) / 1000 ;
2021-02-08 16:57:43 +01:00
ch . Encrypted = mapping . GetDword ( "offEncrypted" ) ! = 0 ;
ch . Polarity = mapping . GetDword ( "offPolarity" ) = = 0 ? 'H' : 'V' ;
2021-01-17 15:44:45 +01:00
ch . SymbolRate = ( int ) ( mapping . GetDword ( "offSymbolRate" ) / 1000 ) ;
2021-02-08 16:57:43 +01:00
if ( mapping . GetDword ( "offServiceType" ) = = 2 )
{
ch . SignalSource | = SignalSource . Radio ;
ch . ServiceTypeName = "Radio" ;
}
else
{
ch . SignalSource | = SignalSource . Tv ;
ch . ServiceTypeName = "TV" ;
}
ch . AddDebug ( ( byte ) mapping . GetDword ( "offUnk1" ) ) ;
ch . AddDebug ( ( byte ) mapping . GetDword ( "offUnk2" ) ) ;
2021-01-17 15:44:45 +01:00
}
2020-08-09 14:09:08 +02:00
dvbStringDecoder . GetChannelNames ( mapping . Data , mapping . BaseOffset + mapping . GetConst ( "offProvider" , 0 ) , mapping . GetConst ( "lenProvider" , 0 ) , out var provider , out _ ) ;
2021-01-17 15:44:45 +01:00
ch . Provider = provider ;
2020-08-09 14:09:08 +02:00
// copy values from the satellite/transponder tables to the channel
if ( this . DataRoot . Transponder . TryGetValue ( transponderId , out var t ) )
{
ch . Transponder = t ;
ch . FreqInMhz = t . FrequencyInMhz ;
2021-02-08 16:57:43 +01:00
ch . Polarity = t . Polarity ;
2020-08-09 14:09:08 +02:00
ch . SymbolRate = t . SymbolRate ;
ch . SatPosition = t . Satellite ? . OrbitalPosition ;
ch . Satellite = t . Satellite ? . Name ;
if ( t . OriginalNetworkId ! = 0 )
ch . OriginalNetworkId = t . OriginalNetworkId ;
2023-09-17 20:07:55 +02:00
if ( chanLstBin . VersionMajor ! = 2 )
{
if ( t . TransportStreamId ! = 0 ) // does not work in version 2.0
ch . TransportStreamId = t . TransportStreamId ;
}
2020-08-09 14:09:08 +02:00
}
return ch ;
}
#endregion
2020-08-08 13:58:53 +02:00
#region LoadDvbsFavorites
private void LoadDvbsFavorites ( string path )
{
if ( ! File . Exists ( path ) )
return ;
var data = File . ReadAllBytes ( path ) ;
if ( data . Length < 4 )
return ;
var checksum = BitConverter . ToUInt32 ( data , data . Length - 4 ) ;
var crc = ~ Crc32 . Reversed . CalcCrc32 ( data , 0 , data . Length - 4 ) ;
if ( checksum ! = crc )
return ;
int dataSize = BitConverter . ToInt32 ( data , 0 ) ;
var recordSize = 4 ;
var recordCount = ( dataSize - 4 ) / recordSize ;
// 4 byte header, data, crc32
if ( data . Length ! = 4 + dataSize + 4 )
return ;
2020-08-08 20:37:16 +02:00
this . dataFilePaths . Add ( path ) ;
2020-08-08 13:58:53 +02:00
int firstFavIndex = BitConverter . ToInt16 ( data , 4 ) ;
int favCount = BitConverter . ToInt16 ( data , 6 ) ;
if ( favCount > recordCount | | firstFavIndex < 0 | | firstFavIndex > = recordCount )
return ;
var baseOffset = 8 ;
for ( int i = 0 , curFav = firstFavIndex ; i < favCount ; i + + )
{
2021-02-08 16:57:43 +01:00
this . dvbsChannels . Channels [ curFav ] . SetOldPosition ( 1 , i + 1 ) ;
2020-08-08 13:58:53 +02:00
curFav = BitConverter . ToInt16 ( data , baseOffset + curFav * 4 + 2 ) ;
}
}
#endregion
2021-04-11 12:08:47 +02:00
#region LoadMap30ChannelMapsDb
private void LoadMap30ChannelMapsDb ( ChannelList list , string dbPath )
{
// map30 format keeps channel numbers in 3 redundant locations: tv.db, a .bin file and a *ChannelMaps.db file
// here we read the ChannelMaps.db file, compare the data with the .bin file and keep a reference to the records for later update
if ( ! File . Exists ( dbPath ) )
return ;
this . dataFilePaths . Add ( dbPath ) ;
2025-06-05 18:35:10 +02:00
using var conn = new SqliteConnection ( $"Data Source=\" { dbPath } \ ";Pooling=False" ) ;
2021-04-11 12:08:47 +02:00
conn . Open ( ) ;
using var cmd = conn . CreateCommand ( ) ;
var queries = new [ ]
{
2023-06-03 10:38:11 +02:00
new Tuple < SignalSource , string , string > ( SignalSource . Analog | ( list . SignalSource & SignalSource . MaskBcastMedium ) , "AnalogTable" ,
2021-04-11 12:08:47 +02:00
"select Dbindex, Frequency, 0, 0, 0, ChannelName, PresetNumber from AnalogTable order by Dbindex" ) ,
2023-06-03 10:38:11 +02:00
new Tuple < SignalSource , string , string > ( SignalSource . Dvb | ( list . SignalSource & SignalSource . MaskBcastMedium ) , "DigSrvTable" ,
2021-04-11 12:08:47 +02:00
"select Dbindex, Frequency, OriginalNetworkId, Tsid, ServiceId, ChannelName, PresetNumber from DigSrvTable order by Dbindex" )
} ;
foreach ( var entry in queries )
{
var source = entry . Item1 ;
var table = entry . Item2 ;
var query = entry . Item3 ;
// not all files contain an AnalogTable table
cmd . CommandText = $"select count(1) from sqlite_master where type='table' and name='{table}'" ;
if ( ( long ) cmd . ExecuteScalar ( ) = = 0 )
continue ;
cmd . CommandText = query ;
using var r = cmd . ExecuteReader ( ) ;
while ( r . Read ( ) )
{
var idx = r . GetInt32 ( 0 ) ;
var freq = ( decimal ) r . GetInt32 ( 1 ) / 1000 ;
var onid = r . GetInt32 ( 2 ) ;
var tsid = r . GetInt32 ( 3 ) ;
var sid = r . GetInt32 ( 4 ) ;
var name = r . GetString ( 5 ) ;
var prnr = r . GetInt32 ( 6 ) ;
var uid = ChannelInfo . GetUid ( source , freq , onid , tsid , sid , null ) ;
var chans = list . GetChannelByUid ( uid ) ;
if ( chans = = null | | chans . Count = = 0 )
this . logMessages . AppendLine ( $"{dbPath}: {table} entry with Dbindex={idx} has no corresponding channel in *.bin files" ) ;
else
{
var ch = ( Channel ) chans [ 0 ] ;
ch . Map30ChannelMapsDbindex = idx ;
if ( ch . Name ! = name )
this . logMessages . AppendLine ( $"{dbPath}: {table} entry with Dbindex={idx} has name {name}, in .bin file it is {ch.Name}" ) ;
if ( ch . OldProgramNr ! = prnr )
this . logMessages . AppendLine ( $"{dbPath}: {table} entry with Dbindex={idx} has PresetNumber {prnr}, in .bin file it is {ch.OldProgramNr}" ) ;
}
}
}
}
#endregion
#region LoadTvDb
private void LoadTvDb ( string tvDb )
2021-01-31 14:37:58 +01:00
{
2021-04-11 12:08:47 +02:00
// the only purpose of this method is to validate if numbers in the the tv.db file are the same as in CableDb.bin/Terrestrial.bin/SatelliteDb.bin
2021-01-31 14:37:58 +01:00
// differences are written to the log which can be viewed under File / File information
2021-04-11 12:08:47 +02:00
// The tv.db file exists in formats 30 and 45 (and unconfirmed in 25 too)
2021-01-31 14:37:58 +01:00
foreach ( var chanList in this . DataRoot . ChannelLists )
{
if ( chanList . IsMixedSourceFavoritesList )
continue ;
foreach ( var chan in chanList . Channels )
{
if ( ! ( chan is Channel ch ) )
continue ;
channelsById [ ch . Id ] = ch ;
}
}
2025-06-05 18:35:10 +02:00
using var conn = new SqliteConnection ( $"Data Source=\" { tvDb } \ ";Pooling=False" ) ;
2021-01-31 14:37:58 +01:00
conn . Open ( ) ;
using var cmd = conn . CreateCommand ( ) ;
2021-02-08 16:57:43 +01:00
cmd . CommandText = "select _id, display_number, display_name, original_network_id, transport_stream_id, service_id, service_type from channels" ;
2021-01-31 14:37:58 +01:00
var r = cmd . ExecuteReader ( ) ;
while ( r . Read ( ) )
{
if ( r . IsDBNull ( 1 ) )
continue ;
var prNr = r . GetString ( 1 ) ;
if ( ! int . TryParse ( prNr , out var nr ) )
continue ;
var id = r . GetInt32 ( 0 ) ;
if ( ! channelsById . TryGetValue ( id , out var ch ) )
{
this . logMessages . AppendLine ( $"Could not find channel with id {id} in tv.db" ) ;
continue ;
}
if ( ch . OldProgramNr ! = nr )
2021-04-25 18:31:05 +02:00
this . logMessages . AppendLine ( $"channel with id {id}: prNum {ch.OldProgramNr} in bin file and {r.GetString(1)} in tv.db" ) ;
2021-01-31 14:37:58 +01:00
if ( ch . Name ! = r . GetString ( 2 ) )
2021-02-08 16:57:43 +01:00
this . logMessages . AppendLine ( $"channel with id {id}: Name {ch.Name} in bin file and {r.GetString(2)} in tv.db" ) ;
2021-01-31 14:37:58 +01:00
if ( ch . OriginalNetworkId ! = r . GetInt32 ( 3 ) )
2021-02-08 16:57:43 +01:00
this . logMessages . AppendLine ( $"channel with id {id}: ONID {ch.OriginalNetworkId} in bin file and {r.GetInt32(3)} in tv.db" ) ;
2021-01-31 14:37:58 +01:00
if ( ch . TransportStreamId ! = r . GetInt32 ( 4 ) )
2021-02-08 16:57:43 +01:00
this . logMessages . AppendLine ( $"channel with id {id}: TSID {ch.TransportStreamId} in bin file and {r.GetInt32(4)} in tv.db" ) ;
2021-01-31 14:37:58 +01:00
if ( ch . ServiceId ! = r . GetInt32 ( 5 ) )
2021-02-08 16:57:43 +01:00
this . logMessages . AppendLine ( $"channel with id {id}: SID {ch.ServiceId} in bin file and {r.GetInt32(5)} in tv.db" ) ;
var stype = r . GetString ( 6 ) ;
if ( stype = = "SERVICE_TYPE_AUDIO" )
{
if ( ( ch . SignalSource & SignalSource . Radio ) = = 0 )
this . logMessages . AppendLine ( $"channel with id {id}: service type TV in bin file and RADIO in tv.db" ) ;
}
else // if (stype == "SERVICE_TYPE_AUDIO_VIDEO" || stype == "SERVICE_TYPE_OTHER") // Sky option channels are OTHER
{
if ( ( ch . SignalSource & SignalSource . Tv ) = = 0 )
this . logMessages . AppendLine ( $"channel with id {id}: service type RADIO in bin file and TV in tv.db" ) ;
}
2021-01-31 14:37:58 +01:00
}
}
#endregion
2021-04-11 12:08:47 +02:00
#region LoadMap30Favorites
private void LoadMap30Favorites ( string listDb )
{
// The "list.db" file in format 30 contains tables TList1-4, CList1-4 and SList1-4 for 4 favorite favorite lists for cable, satellite and terrestrial
// It also contains a table "List" with 12 entries holding names for the 3*4 favorite lists and SatFrequency and TCFrequency for satellite/terrestrial/cable frequencies
// The list.db:xListN.channel_id field references the tv.db:channels._id field
if ( ! File . Exists ( listDb ) | | this . channelsById . Count = = 0 )
return ;
this . Features . FavoritesMode = FavoritesMode . OrderedPerSource ;
this . Features . MaxFavoriteLists = 4 ;
2025-06-05 18:35:10 +02:00
using var conn = new SqliteConnection ( $"Data Source=\" { listDb } \ ";Pooling=False" ) ;
2021-04-11 12:08:47 +02:00
conn . Open ( ) ;
using var cmd = conn . CreateCommand ( ) ;
2021-04-11 13:17:53 +02:00
// read favorite list names
cmd . CommandText = "select list_id, list_name from List order by list_id" ;
using ( var r = cmd . ExecuteReader ( ) )
{
while ( r . Read ( ) )
{
var id = r . GetInt32 ( 0 ) ;
var name = r . GetString ( 1 ) ;
var list = id < = 4 ? this . antChannels : id < = 8 ? this . cabChannels : this . satChannels ;
list . SetFavListCaption ( ( id - 1 ) % 4 , name ) ;
}
}
2021-04-11 12:08:47 +02:00
// read favorite channels
for ( int listIdx = 0 ; listIdx < 12 ; listIdx + + )
{
var table = "TCS" [ listIdx / 4 ] + "List" + ( listIdx % 4 + 1 ) ; // TList1-4, CList1-4, SList1-4
cmd . CommandText = $"select count(1) from sqlite_master where type='table' and name='{table}'" ;
if ( ( long ) cmd . ExecuteScalar ( ) = = 0 )
continue ;
cmd . CommandText = $"select _id, channel_id from {table} order by rank" ;
using var r = cmd . ExecuteReader ( ) ;
int order = 0 ;
while ( r . Read ( ) )
{
int channelId = r . GetInt32 ( 1 ) ;
if ( this . channelsById . TryGetValue ( channelId , out var ch ) )
ch . SetOldPosition ( 1 + listIdx % 4 , + + order ) ;
else
this . logMessages . AppendLine ( $"list.db: {table} _id {r.GetInt32(0)} references non-existing channel with channel_id {channelId}" ) ;
}
}
}
#endregion
2021-01-31 14:37:58 +01:00
#region LoadMap45Favorites
private void LoadMap45Favorites ( string listDb )
{
foreach ( var chanList in this . DataRoot . ChannelLists )
{
if ( chanList . IsMixedSourceFavoritesList )
continue ;
foreach ( var chan in chanList . Channels )
{
chan . Source = chanList . ShortCaption ;
this . favChannels . AddChannel ( chan ) ;
}
}
2021-03-14 22:13:22 +01:00
this . Features . FavoritesMode = FavoritesMode . MixedSource ;
this . Features . MaxFavoriteLists = 8 ;
2021-01-31 14:37:58 +01:00
this . Features . AllowGapsInFavNumbers = false ;
2025-06-05 18:35:10 +02:00
using var conn = new SqliteConnection ( $"Data Source=\" { listDb } \ ";Pooling=False" ) ;
2021-01-31 14:37:58 +01:00
conn . Open ( ) ;
2021-02-24 11:05:47 +01:00
// older versions of ChanSort wrote invalid "list_id" values starting at 0 instead of 1 and going past 8.
// if everything is in the range of 1-8, this code keeps the current ids. otherwise it remaps them to 1-8.
2021-01-31 14:37:58 +01:00
using var cmd = conn . CreateCommand ( ) ;
2021-02-24 11:05:47 +01:00
cmd . CommandText = "select min(list_id), max(list_id) from List" ;
2021-01-31 14:37:58 +01:00
using ( var r = cmd . ExecuteReader ( ) )
{
2021-02-24 11:05:47 +01:00
r . Read ( ) ;
mustFixFavListIds = ! r . IsDBNull ( 0 ) & & ( r . GetInt16 ( 0 ) < 1 | | r . GetInt16 ( 1 ) > 8 ) ;
if ( mustFixFavListIds )
logMessages . AppendLine ( "invalid list_id values in list.db will be corrected" ) ;
}
2021-02-08 16:57:43 +01:00
2021-02-24 11:05:47 +01:00
cmd . CommandText = "select list_id, list_name from List order by list_id" ;
var listIds = new List < int > ( ) ;
using ( var r = cmd . ExecuteReader ( ) )
{
var listIndex = 0 ;
while ( r . Read ( ) )
2021-02-08 16:57:43 +01:00
{
2021-02-24 11:05:47 +01:00
var listId = r . GetInt16 ( 0 ) ;
listIds . Add ( listId ) ;
if ( ! this . mustFixFavListIds )
listIndex = listId - 1 ;
2021-04-11 13:17:53 +02:00
this . favChannels . SetFavListCaption ( listIndex , r . GetString ( 1 ) ) ;
2021-02-24 11:05:47 +01:00
+ + listIndex ;
2021-02-08 16:57:43 +01:00
}
2021-01-31 14:37:58 +01:00
}
2021-02-24 11:05:47 +01:00
for ( int listIndex = 0 ; listIndex < listIds . Count ; listIndex + + )
2021-01-31 14:37:58 +01:00
{
2021-02-24 11:05:47 +01:00
cmd . CommandText = $"select channel_id from FavoriteChannels where fav_list_id={listIds[listIndex]} order by rank" ;
2021-01-31 14:37:58 +01:00
using var r = cmd . ExecuteReader ( ) ;
int seq = 0 ;
while ( r . Read ( ) )
2021-04-11 12:08:47 +02:00
{
2021-01-31 14:37:58 +01:00
var channelId = r . GetInt32 ( 0 ) ;
var chan = this . favChannels . Channels . FirstOrDefault ( c = > ( ( Channel ) c ) . Id = = channelId ) ;
if ( chan = = null )
{
this . logMessages . AppendLine ( $"Could not find favorite channel with id {channelId}" ) ;
continue ;
}
chan . SetOldPosition ( listIndex + 1 , + + seq ) ;
}
}
}
#endregion
2021-04-11 12:08:47 +02:00
// saving
2020-08-08 13:58:53 +02:00
#region Save ( )
2022-11-29 22:00:16 +01:00
public override void Save ( )
2020-08-08 13:58:53 +02:00
{
2020-08-10 11:27:05 +02:00
var dir = Path . GetDirectoryName ( this . FileName ) ? ? "" ;
var channellib = Path . Combine ( dir , "channellib" ) ;
var s2channellib = Path . Combine ( dir , "s2channellib" ) ;
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
{
SaveDvbCTChannels ( this . dvbtChannels , Path . Combine ( channellib , "AntennaDigSrvTable" ) ) ;
SaveDvbCTPresets ( this . dvbtChannels , Path . Combine ( channellib , "AntennaPresetTable" ) ) ;
SaveDvbCTChannels ( this . dvbcChannels , Path . Combine ( channellib , "CableDigSrvTable" ) ) ;
SaveDvbCTPresets ( this . dvbcChannels , Path . Combine ( channellib , "CablePresetTable" ) ) ;
2021-02-08 16:57:43 +01:00
SaveDvbsChannels ( this . dvbsChannels , Path . Combine ( s2channellib , "service.dat" ) ) ;
2021-01-17 15:44:45 +01:00
SaveDvbsFavorites ( Path . Combine ( s2channellib , "favorite.dat" ) ) ;
SaveDvbsDbFileInfo ( Path . Combine ( s2channellib , "db_file_info.dat" ) ) ;
}
else if ( chanLstBin . VersionMajor > = 25 & & chanLstBin . VersionMajor < = 45 )
{
2021-02-08 16:57:43 +01:00
SaveDvbCTChannels ( this . antChannels , Path . Combine ( channellib , "TerrestrialDb.bin" ) ) ;
SaveDvbCTChannels ( this . cabChannels , Path . Combine ( channellib , "CableDb.bin" ) ) ;
SaveDvbsChannels ( this . satChannels , Path . Combine ( s2channellib , "SatelliteDb.bin" ) ) ;
2020-08-10 11:27:05 +02:00
2021-04-11 12:08:47 +02:00
SaveMap30ChannelMapsDb ( this . antChannels , Path . Combine ( channellib , "TerrestrialChannelMaps.db" ) ) ;
SaveMap30ChannelMapsDb ( this . cabChannels , Path . Combine ( channellib , "CableChannelMaps.db" ) ) ;
SaveMap30ChannelMapsDb ( this . satChannels , Path . Combine ( s2channellib , "SatelliteChannelMaps.db" ) ) ;
SaveTvDb ( ) ;
// favorite lists have different DB schema depending on version
var listDb = Path . Combine ( dir , "list.db" ) ;
if ( chanLstBin . VersionMajor = = 30 )
SaveMap30Favorites ( listDb ) ;
else if ( chanLstBin . VersionMajor = = 45 )
SaveMap45Favorites ( listDb ) ;
2021-01-17 15:44:45 +01:00
}
2020-11-16 20:43:56 +01:00
this . chanLstBin . Save ( this . FileName ) ;
2020-08-10 11:27:05 +02:00
}
#endregion
#region SaveDvbCTChannels
private void SaveDvbCTChannels ( ChannelList list , string path )
{
if ( ! ReadAndValidateChannellibFile ( path , out var data , out var recordSize , out _ ) )
return ;
2021-01-17 15:44:45 +01:00
int baseOffset ;
DataMapping mapping ;
if ( chanLstBin . VersionMajor < = 11 )
{
mapping = new DataMapping ( this . ini . GetSection ( "CableDigSrvTable_entry" ) ) ;
baseOffset = 20 ;
}
else
{
mapping = new DataMapping ( this . ini . GetSection ( "Map45_CableDb.bin_entry" ) ) ;
baseOffset = 12 ;
}
mapping . SetDataPtr ( data , baseOffset ) ;
2020-08-10 11:27:05 +02:00
foreach ( var ch in list . Channels )
{
2021-01-17 15:44:45 +01:00
if ( ch . IsProxy ) continue ;
mapping . BaseOffset = baseOffset + ( int ) ch . RecordIndex * recordSize ;
2020-08-10 11:27:05 +02:00
mapping . SetWord ( "offProgNr" , ch . NewProgramNr ) ;
mapping . SetByte ( "offLocked" , ch . Lock ? 1 : 0 ) ;
mapping . SetByte ( "offIsFav" , ch . Favorites = = 0 ? 0 : 1 ) ;
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
{
mapping . SetDword ( "offChecksum" , 0 ) ;
var crc = FaultyCrc32 ( data , mapping . BaseOffset , recordSize ) ;
mapping . SetDword ( "offChecksum" , crc ) ;
}
else if ( chanLstBin . VersionMajor > = 25 & & chanLstBin . VersionMajor < = 45 )
{
mapping . SetWord ( "offServiceEdit" , 1 ) ;
}
2020-08-10 11:27:05 +02:00
}
File . WriteAllBytes ( path , data ) ;
}
#endregion
#region SaveDvbCTPresets
private void SaveDvbCTPresets ( ChannelList list , string path )
{
if ( ! ReadAndValidateChannellibFile ( path , out var data , out var recordSize , out _ ) )
return ;
var mapping = new DataMapping ( this . ini . GetSection ( "CablePresetTable_entry" ) ) ;
mapping . SetDataPtr ( data , 20 ) ;
2020-08-08 13:58:53 +02:00
2020-08-10 11:27:05 +02:00
// update the preset records with new channel numbers
foreach ( var chan in list . Channels )
{
if ( ! ( chan is Channel ch ) | | ch . PresetTableIndex < 0 )
continue ;
mapping . BaseOffset = 20 + ch . PresetTableIndex * recordSize ;
mapping . SetWord ( "offProgNr" , ch . NewProgramNr ) ;
mapping . SetDword ( "offChecksum" , 0 ) ;
var crc = FaultyCrc32 ( data , mapping . BaseOffset , recordSize ) ;
mapping . SetDword ( "offChecksum" , crc ) ;
}
2020-08-08 13:58:53 +02:00
2020-08-10 11:27:05 +02:00
File . WriteAllBytes ( path , data ) ;
2020-08-08 13:58:53 +02:00
}
#endregion
#region SaveDvbsChannels
2021-02-08 16:57:43 +01:00
private void SaveDvbsChannels ( ChannelList list , string path )
2020-08-08 13:58:53 +02:00
{
2020-08-08 20:37:16 +02:00
var orig = File . ReadAllBytes ( path ) ;
2021-01-17 15:44:45 +01:00
2020-08-08 20:37:16 +02:00
// create a new array for the modified data, copying the header and next/prev table
var data = new byte [ orig . Length ] ;
2021-01-17 15:44:45 +01:00
int recordCount = BitConverter . ToInt32 ( orig , 8 ) ;
int recordSize ;
int baseOffset ;
DataMapping mapping ;
2020-08-08 13:58:53 +02:00
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
{
recordSize = BitConverter . ToInt32 ( orig , 4 ) ;
baseOffset = 12 + recordCount * 4 ;
mapping = new DataMapping ( this . ini . GetSection ( "service.dat_entry" ) ) ;
}
else
{
recordSize = recordCount = = 0 ? 0 : ( orig . Length - 12 ) / recordCount ;
baseOffset = 12 ;
mapping = new DataMapping ( this . ini . GetSection ( "Map45_SatelliteDb.bin_entry" ) ) ;
}
if ( recordCount = = 0 )
return ;
Array . Copy ( orig , data , baseOffset ) ;
2020-08-08 20:37:16 +02:00
mapping . SetDataPtr ( data , baseOffset ) ;
2020-08-08 13:58:53 +02:00
2020-08-08 20:37:16 +02:00
// copy physical records to bring them in the new order and update fields like progNr
// this way the linked next/prev list remains in-sync with the channel order
int i = 0 ;
2021-01-31 14:37:58 +01:00
var channels = chanLstBin . VersionMajor < 25
2021-02-08 16:57:43 +01:00
? list . Channels . OrderBy ( c = > c . NewProgramNr < = 0 ? int . MaxValue : c . NewProgramNr ) . ThenBy ( c = > c . OldProgramNr )
: list . Channels . OrderBy ( c = > c . RecordIndex ) ;
2021-01-31 14:37:58 +01:00
foreach ( var ch in channels )
2020-08-08 13:58:53 +02:00
{
2025-01-28 21:26:53 +01:00
if ( ch . IsProxy )
continue ;
2020-08-08 20:37:16 +02:00
mapping . BaseOffset = baseOffset + i * recordSize ;
Array . Copy ( orig , baseOffset + ( int ) ch . RecordIndex * recordSize , data , mapping . BaseOffset , recordSize ) ;
2020-08-08 13:58:53 +02:00
if ( ch . IsDeleted )
{
2020-08-08 20:37:16 +02:00
mapping . SetWord ( "offSid" , 0xFFFF ) ;
mapping . SetWord ( "offTransponderIndex" , 0xFFFF ) ;
mapping . SetWord ( "offProgNr" , 0xFFFF ) ;
2020-08-08 13:58:53 +02:00
}
2020-08-08 20:37:16 +02:00
else
{
mapping . SetWord ( "offProgNr" , ch . NewProgramNr ) ;
mapping . SetFlag ( "IsFav" , ch . Favorites ! = 0 ) ;
mapping . SetFlag ( "Locked" , ch . Lock ) ;
2021-01-31 14:37:58 +01:00
if ( mapping . GetWord ( "offWrongServiceEdit" ) = = 1 ) // ChanSort versions before 2021-01-31 accidentally set a byte at the wrong offset
mapping . SetWord ( "offWrongServiceEdit" , 0 ) ;
2021-01-17 15:44:45 +01:00
mapping . SetWord ( "offServiceEdit" , 1 ) ;
2020-08-08 20:37:16 +02:00
}
ch . RecordIndex = i + + ; // required so that subsequent saves don't reshuffle the records
2020-08-08 13:58:53 +02:00
}
2021-01-17 15:44:45 +01:00
if ( chanLstBin . VersionMajor < = 11 )
{
var crc32 = ~ Crc32 . Reversed . CalcCrc32 ( data , 0 , data . Length - 4 ) ;
data . SetInt32 ( data . Length - 4 , ( int ) crc32 ) ;
2020-08-08 20:37:16 +02:00
2021-01-17 15:44:45 +01:00
var backupFile = path . Replace ( ".dat" , "_backup.dat" ) ;
File . WriteAllBytes ( backupFile , data ) ;
}
2020-08-08 13:58:53 +02:00
2021-01-17 15:44:45 +01:00
File . WriteAllBytes ( path , data ) ;
2020-08-08 13:58:53 +02:00
}
#endregion
#region SaveDvbsFavorites
private void SaveDvbsFavorites ( string path )
{
var data = File . ReadAllBytes ( path ) ;
int dataSize = BitConverter . ToInt32 ( data , 0 ) ;
var recordSize = 4 ;
var recordCount = ( dataSize - 4 ) / recordSize ;
2021-03-13 18:11:30 +01:00
var favList = this . dvbsChannels . Channels . Where ( c = > c . GetPosition ( 1 ) ! = - 1 ) . OrderBy ( c = > c . GetPosition ( 1 ) ) . ToList ( ) ;
2020-08-08 13:58:53 +02:00
var favCount = favList . Count ;
var firstFavIndex = favCount = = 0 ? - 1 : ( int ) favList [ 0 ] . RecordIndex ;
data . SetInt16 ( 4 , firstFavIndex ) ;
data . SetInt16 ( 6 , favCount ) ;
data . MemSet ( 8 , 0xFF , recordCount * 4 ) ;
if ( favCount > 0 )
{
var prevFav = ( int ) favList [ favList . Count - 1 ] . RecordIndex ;
var curFav = firstFavIndex ;
var nextFav = ( int ) favList [ 1 % favCount ] . RecordIndex ;
for ( int i = 0 ; i < favCount ; i + + )
{
var ch = favList [ i ] ;
var off = 8 + ( int ) ch . RecordIndex * 4 ;
data . SetInt16 ( off + 0 , prevFav ) ;
data . SetInt16 ( off + 2 , nextFav ) ;
prevFav = curFav ;
curFav = nextFav ;
nextFav = ( int ) favList [ ( i + 2 ) % favCount ] . RecordIndex ;
}
}
var crc32 = ~ Crc32 . Reversed . CalcCrc32 ( data , 0 , data . Length - 4 ) ;
data . SetInt32 ( data . Length - 4 , ( int ) crc32 ) ;
File . WriteAllBytes ( path , data ) ;
var backupFile = path . Replace ( ".dat" , "_backup.dat" ) ;
File . WriteAllBytes ( backupFile , data ) ;
}
#endregion
#region SaveDvbsDbFileInfo
private void SaveDvbsDbFileInfo ( string path )
{
var data = File . ReadAllBytes ( path ) ;
// the ushort at offset 10 is incremented by 4 every time a change is made to the list (maybe the lower 2 bits of that fields are used for something else)
var offset = 10 ;
data . SetInt16 ( offset , data . GetInt16 ( offset ) + 4 ) ;
var crc32 = ~ Crc32 . Reversed . CalcCrc32 ( data , 0 , data . Length - 4 ) ;
data . SetInt32 ( data . Length - 4 , ( int ) crc32 ) ;
File . WriteAllBytes ( path , data ) ;
var backupFile = path . Replace ( ".dat" , "_backup.dat" ) ;
File . WriteAllBytes ( backupFile , data ) ;
}
#endregion
2021-04-11 12:08:47 +02:00
#region SaveMap30ChannelMapsDb
private void SaveMap30ChannelMapsDb ( ChannelList list , string dbPath )
{
// map30 format keeps channel numbers in 3 redundant locations: tv.db, a .bin file and a *ChannelMaps.db file
// here we save the ChannelMaps.db file
if ( ! File . Exists ( dbPath ) )
return ;
2025-06-05 18:35:10 +02:00
using var conn = new SqliteConnection ( $"Data Source=\" { dbPath } \ ";Pooling=False" ) ;
2021-04-11 12:08:47 +02:00
conn . Open ( ) ;
using var trans = conn . BeginTransaction ( ) ;
using var cmd = conn . CreateCommand ( ) ;
var tables = new [ ] { "AnalogTable" , "DigSrvTable" } ;
foreach ( var table in tables )
{
// not all files contain an AnalogTable table
cmd . CommandText = $"select count(1) from sqlite_master where type='table' and name='{table}'" ;
if ( ( long ) cmd . ExecuteScalar ( ) = = 0 )
continue ;
cmd . CommandText = $"update {table} set PresetNumber = @prNum where Dbindex = @dbindex" ;
2021-04-25 18:31:05 +02:00
cmd . Parameters . Add ( "@prNum" , SqliteType . Text ) ;
cmd . Parameters . Add ( "@dbindex" , SqliteType . Integer ) ;
2021-04-11 12:08:47 +02:00
foreach ( var channel in list . Channels )
{
if ( ! ( channel is Channel ch ) | | ch . Map30ChannelMapsDbindex < 0 )
continue ;
cmd . Parameters [ "@dbindex" ] . Value = ch . Map30ChannelMapsDbindex ;
cmd . Parameters [ "@prNum" ] . Value = ch . NewProgramNr . ToString ( ) ;
cmd . ExecuteNonQuery ( ) ;
}
}
trans . Commit ( ) ;
}
#endregion
#region SaveTvDb
/// <summary>
/// The "tv.db" file was reported to exist as early as in ChannelMap_25 format and has been seen in formats 30 and 45 too
/// </summary>
private void SaveTvDb ( )
2021-01-17 15:44:45 +01:00
{
var tvDb = Path . Combine ( Path . GetDirectoryName ( this . FileName ) ? ? "" , "tv.db" ) ;
if ( ! File . Exists ( tvDb ) )
return ;
2025-06-05 18:35:10 +02:00
using var conn = new SqliteConnection ( $"Data Source=\" { tvDb } \ ";Pooling=False" ) ;
2021-01-17 15:44:45 +01:00
conn . Open ( ) ;
using var trans = conn . BeginTransaction ( ) ;
using var cmd = conn . CreateCommand ( ) ;
cmd . CommandText = "update channels set display_number=@prNum, display_name=@name, browsable=@browsable, locked=@locked where _id=@id" ;
cmd . Parameters . Clear ( ) ;
2021-04-25 18:31:05 +02:00
cmd . Parameters . Add ( "@id" , SqliteType . Integer ) ;
cmd . Parameters . Add ( "@prNum" , SqliteType . Text ) ;
cmd . Parameters . Add ( "@name" , SqliteType . Text ) ;
cmd . Parameters . Add ( "@browsable" , SqliteType . Integer ) ;
cmd . Parameters . Add ( "@locked" , SqliteType . Integer ) ;
2021-01-17 15:44:45 +01:00
cmd . Prepare ( ) ;
foreach ( var list in this . DataRoot . ChannelLists )
{
foreach ( var chan in list . Channels )
{
if ( ! ( chan is Channel ch ) )
continue ;
cmd . Parameters [ "@id" ] . Value = ch . Id ;
2021-01-31 14:37:58 +01:00
cmd . Parameters [ "@prNum" ] . Value = ch . NewProgramNr . ToString ( ) ;
2021-01-17 15:44:45 +01:00
cmd . Parameters [ "@name" ] . Value = ch . Name ;
cmd . Parameters [ "@browsable" ] . Value = ch . Skip ? 0 : 1 ;
cmd . Parameters [ "@locked" ] . Value = ch . Lock ? 1 : 0 ;
var res = cmd . ExecuteNonQuery ( ) ;
if ( res = = 0 )
this . logMessages . AppendFormat ( $"Could not update record with id {ch.Id} in tv.db service table" ) ;
}
}
trans . Commit ( ) ;
conn . Close ( ) ;
}
#endregion
2021-04-11 12:08:47 +02:00
#region SaveMap30Favorites
private void SaveMap30Favorites ( string listDb )
{
if ( ! File . Exists ( listDb ) | | this . channelsById . Count = = 0 )
return ;
2025-06-05 18:35:10 +02:00
using var conn = new SqliteConnection ( $"Data Source=\" { listDb } \ ";Pooling=False" ) ;
2021-04-11 12:08:47 +02:00
conn . Open ( ) ;
using var trans = conn . BeginTransaction ( ) ;
2021-05-01 11:41:33 +02:00
using var cmd = conn . CreateCommand ( ) ;
2021-04-11 12:08:47 +02:00
// save favorite channels
for ( int listIdx = 0 ; listIdx < 12 ; listIdx + + )
{
var table = "TCS" [ listIdx / 4 ] + "List" + ( listIdx % 4 + 1 ) ; // TList1-4, CList1-4, SList1-4
cmd . CommandText = $"select count(1) from sqlite_master where type='table' and name='{table}'" ;
if ( ( long ) cmd . ExecuteScalar ( ) = = 0 )
continue ;
2021-04-11 13:17:53 +02:00
var incFavList = ( ini . GetSection ( "Map" + chanLstBin . VersionMajor ) ? . GetBool ( "incrementFavListVersion" , true ) ? ? true )
? ", list_version=list_version+1"
: "" ;
var list = listIdx < 4 ? this . antChannels : listIdx < 8 ? this . cabChannels : this . satChannels ;
cmd . CommandText = "update List set list_name=@name" + incFavList + " where list_id=@listId" ;
2021-04-25 18:31:05 +02:00
cmd . Parameters . Add ( "@listId" , SqliteType . Integer ) ;
cmd . Parameters . Add ( "@name" , SqliteType . Text ) ;
2021-04-11 13:17:53 +02:00
cmd . Parameters [ "@listId" ] . Value = listIdx + 1 ;
cmd . Parameters [ "@name" ] . Value = list . GetFavListCaption ( listIdx % 4 , false ) ;
cmd . ExecuteNonQuery ( ) ;
2021-04-11 12:08:47 +02:00
cmd . CommandText = $"delete from {table}" ;
2021-04-11 13:17:53 +02:00
cmd . Parameters . Clear ( ) ;
2021-04-11 12:08:47 +02:00
cmd . ExecuteNonQuery ( ) ;
cmd . CommandText = $"insert into {table} (_id, channel_id, rank) values (@id, @channelId, @rank)" ;
2021-04-25 18:31:05 +02:00
cmd . Parameters . Add ( "@id" , SqliteType . Integer ) ;
cmd . Parameters . Add ( "@channelId" , SqliteType . Integer ) ;
cmd . Parameters . Add ( "@rank" , SqliteType . Integer ) ;
2021-04-11 12:08:47 +02:00
int order = 0 ;
foreach ( var channel in list . Channels )
{
if ( ! ( channel is Channel ch ) )
continue ;
var favPos = ch . GetPosition ( 1 + listIdx % 4 ) ;
if ( favPos < 0 )
continue ;
+ + order ;
cmd . Parameters [ "@id" ] . Value = order ;
cmd . Parameters [ "@channelId" ] . Value = ch . Id ;
2021-04-11 21:02:10 +02:00
cmd . Parameters [ "@rank" ] . Value = favPos ;
2021-04-11 12:08:47 +02:00
cmd . ExecuteNonQuery ( ) ;
}
}
trans . Commit ( ) ;
}
#endregion
#region SaveMap45Favorites
private void SaveMap45Favorites ( string listDb )
2021-01-31 14:37:58 +01:00
{
if ( ! File . Exists ( listDb ) )
return ;
2025-06-05 18:35:10 +02:00
using var conn = new SqliteConnection ( $"Data Source=\" { listDb } \ ";Pooling=False" ) ;
2021-01-31 14:37:58 +01:00
conn . Open ( ) ;
using var trans = conn . BeginTransaction ( ) ;
using var cmd = conn . CreateCommand ( ) ;
2021-02-24 11:05:47 +01:00
cmd . CommandText = "delete from FavoriteChannels" ;
cmd . ExecuteNonQuery ( ) ;
if ( this . mustFixFavListIds )
2021-01-31 14:37:58 +01:00
{
2021-02-24 11:05:47 +01:00
cmd . CommandText = "delete from List" ;
cmd . ExecuteNonQuery ( ) ;
}
var incFavList = ( ini . GetSection ( "Map" + chanLstBin . VersionMajor ) ? . GetBool ( "incrementFavListVersion" , true ) ? ? true )
? ", list_version=list_version+1"
: "" ;
for ( int favListIndex = 0 ; favListIndex < FavListCount ; favListIndex + + )
{
var favListId = favListIndex + 1 ;
cmd . CommandText = $"select count(1) from List where list_id={favListId}" ;
cmd . CommandText = ( long ) cmd . ExecuteScalar ( ) = = 0 ?
"insert into List (list_id, list_name, list_version) values (@id,@name,1)" :
"update List set list_name=@name" + incFavList + " where list_id=@id" ;
2021-02-08 16:57:43 +01:00
2021-04-25 18:31:05 +02:00
cmd . Parameters . Add ( "@id" , SqliteType . Integer ) ;
cmd . Parameters . Add ( "@name" , SqliteType . Text ) ;
2021-02-08 16:57:43 +01:00
cmd . Parameters [ "@id" ] . Value = favListId ;
2021-04-11 13:17:53 +02:00
cmd . Parameters [ "@name" ] . Value = this . favChannels . GetFavListCaption ( favListIndex ) ? ? "Fav " + ( favListIndex + 1 ) ;
2021-01-31 14:37:58 +01:00
cmd . ExecuteNonQuery ( ) ;
cmd . CommandText = "insert into FavoriteChannels(fav_list_id, channel_id, rank) values (@listId,@channelId,@rank)" ;
cmd . Parameters . Clear ( ) ;
2021-04-25 18:31:05 +02:00
cmd . Parameters . Add ( "@listId" , SqliteType . Integer ) ;
cmd . Parameters . Add ( "@channelId" , SqliteType . Integer ) ;
cmd . Parameters . Add ( "@rank" , SqliteType . Real ) ;
2021-01-31 14:37:58 +01:00
cmd . Prepare ( ) ;
foreach ( var chan in favChannels . Channels )
{
if ( ! ( chan is Channel ch ) )
continue ;
var rank = chan . GetPosition ( favListIndex + 1 ) ;
if ( rank < = 0 )
continue ;
cmd . Parameters [ "@listId" ] . Value = favListId ;
cmd . Parameters [ "@channelId" ] . Value = ch . Id ;
cmd . Parameters [ "@rank" ] . Value = ( double ) rank ;
cmd . ExecuteNonQuery ( ) ;
}
}
2021-02-08 16:57:43 +01:00
// delete empty fav lists
cmd . Parameters . Clear ( ) ;
cmd . CommandText = "delete from List where list_id not in (select fav_list_id from FavoriteChannels)" ;
cmd . ExecuteNonQuery ( ) ;
2021-02-17 09:41:06 +01:00
// make sure the last_watched_channel_id is valid in the list
2021-02-24 11:05:47 +01:00
cmd . CommandText = @"update List set last_watched_channel_id=(select channel_id from FavoriteChannels f where f.fav_list_id=List.list_id order by rank limit 1) where last_watched_channel_id not in (select channel_id from FavoriteChannels f where f.fav_list_id=List.list_id)" ;
2021-02-17 09:41:06 +01:00
cmd . ExecuteNonQuery ( ) ;
2021-01-31 14:37:58 +01:00
trans . Commit ( ) ;
conn . Close ( ) ;
}
#endregion
2021-04-11 12:08:47 +02:00
// common
2020-08-08 13:58:53 +02:00
#region FaultyCrc32
2021-04-11 12:08:47 +02:00
/// <summary>
/// Philips uses a broken CRC32 implementation, so we can't use the ChanSort.Api.Utils.Crc32 code
/// </summary>
2020-11-16 20:43:56 +01:00
public static uint FaultyCrc32 ( byte [ ] bytes , int start , int count )
2020-08-08 13:58:53 +02:00
{
var crc = 0xFFFFFFFF ;
var off = start ;
for ( int i = 0 ; i < count ; i + + , off + + )
{
var b = bytes [ off ] ;
for ( int j = 0 ; j < 8 ; j + + )
{
crc < < = 1 ;
var b1 = ( uint ) b > > 7 ;
var b2 = crc > > 31 ;
if ( b1 ! = b2 )
crc ^ = 0x04C11DB7 ;
b < < = 1 ;
}
}
return ~ crc ;
}
#endregion
2020-11-16 20:43:56 +01:00
2021-04-11 12:08:47 +02:00
// framework support methods
#region GetDataFilePaths
/// <summary>
/// List of files for backup/restore
/// </summary>
public override IEnumerable < string > GetDataFilePaths ( ) = > this . dataFilePaths ;
#endregion
#region GetFileInformation
2020-11-16 20:43:56 +01:00
public override string GetFileInformation ( )
{
return base . GetFileInformation ( ) + this . logMessages . Replace ( "\n" , "\r\n" ) ;
}
2021-04-11 12:08:47 +02:00
#endregion
2020-08-08 13:58:53 +02:00
}
}