//#define LOCK_LCN_LISTS using System; using System.Collections.Generic; using System.Data; using ChanSort.Api; using System.Data.SQLite; using System.IO; using System.Text.RegularExpressions; using System.Windows.Forms; namespace ChanSort.Loader.Hisense { public class HisDbSerializer : SerializerBase { public override string DisplayName => "Hisense channel.db Loader"; #region enums and bitmasks internal enum BroadcastType { Analog = 1, Dvb = 2 } internal enum BroadcastMedium { DigTer = 1, DigCab = 2, DigSat = 3, AnaTer = 4, AnaCab = 5, AnaSat = 6 } internal enum ServiceType { Tv = 1, Radio = 2, App = 3 } [Flags] internal enum NwMask { Active = 1 << 1, Visible = 1 << 3, Fav1 = 1 << 4, Fav2 = 1 << 5, Fav3 = 1 << 6, Fav4 = 1 << 7, Lock = 1 << 8 } [Flags] internal enum OptionMask { NameEdited = 1 << 3, ChNumEdited = 1 << 10, DeletedByUser = 1 << 13 } [Flags] internal enum HashCode { Name = 1 << 0, ChannelId = 1 << 1, BroadcastType = 1 << 2, TsRecId = 1 << 3, ProgNum = 1 << 4, DvbShortName = 1 << 5, Radio = 1 << 10, Encrypted = 1 << 11, Tv = 1 << 13 } [Flags] internal enum DvbLinkageMask { Ts = 1 << 2 } #endregion private readonly List channelLists = new List(); private readonly Dictionary channelsById = new Dictionary(); private List tableNames; #region ctor() public HisDbSerializer(string inputFile) : base(inputFile) { DepencencyChecker.AssertVc2010RedistPackageX86Installed(); this.Features.ChannelNameEdit = ChannelNameEditMode.All; this.Features.CanDeleteChannels = false; this.Features.CanSkipChannels = false; this.Features.CanHaveGaps = true; this.DataRoot.SortedFavorites = true; channelLists.Add(new ChannelList(SignalSource.Antenna | SignalSource.Analog | SignalSource.Digital | SignalSource.Radio | SignalSource.Tv, "Antenna")); channelLists.Add(new ChannelList(SignalSource.Cable | SignalSource.Analog | SignalSource.Digital | SignalSource.Radio | SignalSource.Tv, "Cable")); channelLists.Add(new ChannelList(SignalSource.Sat | SignalSource.Analog | SignalSource.Digital | SignalSource.Radio | SignalSource.Tv, "Sat")); channelLists.Add(new ChannelList(SignalSource.Sat | SignalSource.Analog | SignalSource.Digital | SignalSource.Radio | SignalSource.Tv, "Prefered Sat")); channelLists.Add(new ChannelList(SignalSource.Antenna | SignalSource.Cable | SignalSource.Sat | SignalSource.Analog | SignalSource.Digital | SignalSource.Radio | SignalSource.Tv, "CI 1")); channelLists.Add(new ChannelList(SignalSource.Antenna | SignalSource.Cable | SignalSource.Sat | SignalSource.Analog | SignalSource.Digital | SignalSource.Radio | SignalSource.Tv, "CI 2")); channelLists.Add(new ChannelList(SignalSource.AnalogCT | SignalSource.DvbCT | SignalSource.DvbS | SignalSource.TvAndRadio, "Favorites")); channelLists[channelLists.Count - 1].IsMixedSourceFavoritesList = true; foreach (var list in this.channelLists) { this.DataRoot.AddChannelList(list); list.VisibleColumnFieldNames = new List { "OldPosition", "Position", "Source", "NewProgramNr", "Name", "ShortName", "Favorites", "Lock", "Hidden", "Encrypted", "FreqInMhz", "OriginalNetworkId", "TransportStreamId", "ServiceId", "ServiceType", "ServiceTypeName", "NetworkName", "Satellite", "SymbolRate" }; } } #endregion #region Load() public override void Load() { using (var conn = new SQLiteConnection("Data Source=" + this.FileName)) { conn.Open(); using (var cmd = conn.CreateCommand()) { this.RepairCorruptedDatabaseImage(cmd); this.LoadTableNames(cmd); if (!tableNames.Contains("svl_1") && !tableNames.Contains("svl_2") && !tableNames.Contains("svl_3")) throw new FileLoadException("File doesn't contain svl_* tables"); this.LoadSatelliteData(cmd); this.LoadTslData(cmd); this.LoadSvlData(cmd); this.LoadFavorites(cmd); } } int totalCount = 0; foreach (var list in this.channelLists) totalCount += list.Count; if (totalCount == 0) MessageBox.Show(Resources.Load_NoChannelsMsg, Resources.Load_NoChannelsCaption, MessageBoxButtons.OK); } #endregion #region RepairCorruptedDatabaseImage() private void RepairCorruptedDatabaseImage(SQLiteCommand cmd) { cmd.CommandText = "REINDEX"; cmd.ExecuteNonQuery(); } #endregion #region LoadTableNames() private void LoadTableNames(SQLiteCommand cmd) { this.tableNames = new List(); cmd.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table' order by name"; using (var r = cmd.ExecuteReader()) { while (r.Read()) tableNames.Add(r.GetString(0)); } } #endregion #region LoadSatelliteData() private void LoadSatelliteData(SQLiteCommand cmd) { var regex = new Regex(@"^satl_\d$"); foreach (var tableName in this.tableNames) { if (!regex.IsMatch(tableName)) continue; cmd.CommandText = "select satl_rec_id, i2_orb_pos, ac_sat_name from " + tableName; using (var r = cmd.ExecuteReader()) { while (r.Read()) { var sat = new Satellite(r.GetInt32(0)); var pos = r.GetInt32(1); sat.OrbitalPosition = $"{(decimal) Math.Abs(pos)/10:n1}{(pos < 0 ? 'W' : 'E')}"; sat.Name = r.GetString(2); this.DataRoot.AddSatellite(sat); } } } } #endregion #region LoadTslData() private void LoadTslData(SQLiteCommand cmd) { var regex = new Regex(@"^tsl_(\d)$"); foreach (var table in this.tableNames) { var match = regex.Match(table); if (!match.Success) continue; int x = int.Parse(match.Groups[1].Value); this.LoadTslData(cmd, x, "tsl_#_data_ter_dig", ", freq", (t, r, i0) => { t.FrequencyInMhz = (decimal) r.GetInt32(i0 + 0)/1000000; }); this.LoadTslData(cmd, x, "tsl_#_data_ter_ana", ", freq", (t, r, i0) => { t.FrequencyInMhz = (decimal) r.GetInt32(i0 + 0)/1000000; }); this.LoadTslData(cmd, x, "tsl_#_data_cab_dig", ", freq, sym_rate", (t, r, i0) => { t.FrequencyInMhz = (decimal) r.GetInt32(i0 + 0)/1000000; t.SymbolRate = r.GetInt32(i0 + 1); }); this.LoadTslData(cmd, x, "tsl_#_data_cab_ana", ", freq", (t, r, i0) => { t.FrequencyInMhz = (decimal) r.GetInt32(i0 + 0)/1000000; }); this.LoadTslData(cmd, x, "tsl_#_data_sat_dig", ", freq, sym_rate, orb_pos", (t, r, i0) => { t.FrequencyInMhz = r.GetInt32(i0 + 0); t.SymbolRate = r.GetInt32(i0 + 1); // satellite information may or may not be available in the database. if there is none, create a proxy sat records from the orbital position in the TSL data if (t.Satellite == null) { var opos = r.GetInt32(i0 + 2); var sat = this.DataRoot.Satellites.TryGet(opos); if (sat == null) { sat = new Satellite(opos); var pos = (decimal) opos/10; sat.Name = pos < 0 ? (-pos).ToString("n1") + "W" : pos.ToString("n1") + "E"; } t.Satellite = sat; } }); } } private void LoadTslData(SQLiteCommand cmd, int tableNr, string joinTable, string joinFields, Action enhanceTransponderInfo) { if (!this.tableNames.Contains(joinTable.Replace("#", tableNr.ToString()))) return; cmd.CommandText = $"select tsl_#.tsl_rec_id, `t_desc.on_id`, `t_desc.ts_id`, `t_ref.satl_rec_id`, `t_desc.e_bcst_medium` {joinFields} " + $" from tsl_# inner join {joinTable} on {joinTable}.tsl_rec_id=tsl_#.tsl_rec_id"; cmd.CommandText = cmd.CommandText.Replace("#", tableNr.ToString()); using (var r = cmd.ExecuteReader()) { while (r.Read()) { int id = (tableNr << 16) | r.GetInt32(0); var trans = new Transponder(id); trans.OriginalNetworkId = r.GetInt32(1); trans.TransportStreamId = r.GetInt32(2); trans.Satellite = this.DataRoot.Satellites.TryGet(r.GetInt32(3)); enhanceTransponderInfo(trans, r, 5); this.DataRoot.AddTransponder(trans.Satellite, trans); } } } #endregion #region LoadSvlData() private void LoadSvlData(SQLiteCommand cmd) { var regex = new Regex(@"^svl_(\d)$"); foreach (var table in this.tableNames) { var match = regex.Match(table); if (!match.Success) continue; int x = int.Parse(match.Groups[1].Value); if (x < 1 || x > 6) { this.DataRoot.Warnings.AppendLine("Skipping unknown channel list with number " + x); return; } this.LoadSvlData(cmd, x, "svl_#_data_analog", "", (ci, r, i0) => { }); this.LoadSvlData(cmd, x, "svl_#_data_dvb", ", b_free_ca_mode, s_svc_name, sdt_service_type, cur_lcn", (ci, r, i0) => { ci.Encrypted = r.GetBoolean(i0 + 0); ci.ShortName = r.GetString(i0 + 1); ci.ServiceType = r.GetInt32(i0 + 2); if (ci.ServiceType != 0) ci.ServiceTypeName = LookupData.Instance.GetServiceTypeDescription(ci.ServiceType); if ((ci.SignalSource & SignalSource.DvbT) == SignalSource.DvbT) ci.ChannelOrTransponder = LookupData.Instance.GetDvbtTransponder(ci.FreqInMhz).ToString(); #if LOCK_LCN_LISTS // make the current list read-only if LCN is used if (r.GetInt32(i0 + 3) != 0) { this.channelLists[x - 1].ReadOnly = true; } #endif }); } } private void LoadSvlData(SQLiteCommand cmd, int tableNr, string joinTable, string joinFields, Action enhanceChannelInfo) { if (!this.tableNames.Contains(joinTable.Replace("#", tableNr.ToString()))) return; cmd.CommandText = $"select svl_#.svl_rec_id, channel_id, svl_#.tsl_id, svl_#.tsl_rec_id, e_serv_type, ac_name, nw_mask, prog_id, `t_desc.e_bcst_medium` {joinFields}" + $" from svl_# inner join {joinTable} on {joinTable}.svl_rec_id=svl_#.svl_rec_id inner join tsl_# on tsl_#.tsl_rec_id=svl_#.tsl_rec_id"; cmd.CommandText = cmd.CommandText.Replace("#", tableNr.ToString()); using (var r = cmd.ExecuteReader()) { while (r.Read()) { var id = ((long) tableNr << 32) | (uint) r.GetInt32(0); var prNr = (int) ((uint) r.GetInt32(1)) >> 18; var trans = this.DataRoot.Transponder.TryGet((r.GetInt32(2) << 16) | r.GetInt32(3)); var stype = (ServiceType) r.GetInt32(4); var name = r.GetString(5); var nwMask = (NwMask) r.GetInt32(6); var sid = r.GetInt32(7); var bmedium = (BroadcastMedium) r.GetInt32(8); var ssource = DetermineSignalSource(bmedium, stype); var ci = new ChannelInfo(ssource, id, prNr, name); if (trans != null) { ci.Transponder = trans; ci.OriginalNetworkId = trans.OriginalNetworkId; ci.TransportStreamId = trans.TransportStreamId; ci.SymbolRate = trans.SymbolRate; ci.FreqInMhz = trans.FrequencyInMhz; ci.Satellite = trans.Satellite?.ToString(); } ci.ServiceId = sid; //ci.Skip = (nwMask & NwMask.Active) == 0; ci.Lock = (nwMask & NwMask.Lock) != 0; ci.Hidden = (nwMask & NwMask.Visible) == 0; ci.Favorites |= (Favorites) ((int) (nwMask & (NwMask.Fav1 | NwMask.Fav2 | NwMask.Fav3 | NwMask.Fav4)) >> 4); if (stype == ServiceType.Radio) ci.ServiceTypeName = "Radio"; else if (stype == ServiceType.Tv) ci.ServiceTypeName = "TV"; else if (stype == ServiceType.App) ci.ServiceTypeName = "Data"; enhanceChannelInfo(ci, r, 9); var list = this.channelLists[tableNr - 1]; ci.Source = list.ShortCaption; this.DataRoot.AddChannel(list, ci); // add the channel to all favorites lists this.DataRoot.AddChannel(this.channelLists[6], ci); this.channelsById[ci.RecordIndex] = ci; } } } #endregion #region LoadFavorites() private void LoadFavorites(SQLiteCommand cmd) { for (int i = 1; i <= 4; i++) { if (!this.tableNames.Contains($"fav_{i}")) continue; cmd.CommandText = $"select ui2_svc_id, ui2_svc_rec_id, user_defined_ch_num from fav_{i}"; using (var r = cmd.ExecuteReader()) { while (r.Read()) { var id = ((long) r.GetInt32(0) << 32) | (uint) r.GetInt32(1); var ci = this.channelsById.TryGet(id); if (ci != null) ci.FavIndex[i - 1] = int.Parse(r.GetString(2)); } } } } #endregion #region DetermineSignalSource() private static SignalSource DetermineSignalSource(BroadcastMedium bmedium, ServiceType stype) { SignalSource ssource = 0; if (bmedium == BroadcastMedium.AnaCab) ssource = SignalSource.AnalogC; else if (bmedium == BroadcastMedium.AnaSat) ssource = SignalSource.Analog | SignalSource.Sat; else if (bmedium == BroadcastMedium.AnaTer) ssource = SignalSource.AnalogT; else if (bmedium == BroadcastMedium.DigCab) ssource = SignalSource.DvbC; else if (bmedium == BroadcastMedium.DigSat) ssource = SignalSource.DvbS; else if (bmedium == BroadcastMedium.DigTer) ssource = SignalSource.DvbT; ssource |= stype == ServiceType.Radio ? SignalSource.Radio : SignalSource.Tv; return ssource; } #endregion // Saving ==================================== #region Save() public override void Save(string tvOutputFile) { Editor.SequentializeFavPos(this.channelLists[6], 4); if (tvOutputFile != this.FileName) File.Copy(this.FileName, tvOutputFile, true); using (var conn = new SQLiteConnection("Data Source=" + tvOutputFile)) { conn.Open(); using (var trans = conn.BeginTransaction()) using (var cmd = conn.CreateCommand()) { cmd.Transaction = trans; try { this.CreateFavTables(cmd); #if !LOCK_LCN_LISTS this.ResetLcn(cmd); #endif foreach (var list in this.DataRoot.ChannelLists) { if (list.ReadOnly) continue; foreach (var ci in list.Channels) this.UpdateChannel(cmd, ci); } trans.Commit(); this.FileName = tvOutputFile; } catch { trans.Rollback(); throw; } } } } #endregion #region CreateFavTables() private void CreateFavTables(SQLiteCommand cmd) { for (int i = 1; i <= 4; i++) { if (!this.tableNames.Contains("fav_" + i)) { cmd.CommandText = $"CREATE TABLE fav_{i} (ui2_svc_id INTEGER, ui2_svc_rec_id INTEGER, user_defined_ch_num VARCHAR, user_defined_ch_name VARCHAR)"; cmd.ExecuteNonQuery(); this.tableNames.Add($"fav_{i}"); } } } #endregion #region ResetLcn() private void ResetLcn(SQLiteCommand cmd) { var regex = new Regex(@"^svl_\d_data_dvb$"); foreach (var table in this.tableNames) { if (!regex.IsMatch(table)) continue; cmd.CommandText = "update " + table + " set cur_lcn=0, original_lcn=0, lcn_idx=0"; cmd.ExecuteNonQuery(); } } #endregion #region UpdateChannel() private void UpdateChannel(SQLiteCommand cmd, ChannelInfo ci) { if (ci.RecordIndex < 0) // skip reference list proxy channels return; int x = (int) ((ulong) ci.RecordIndex >> 32); // the table number is kept in the higher 32 bits int id = (int) (ci.RecordIndex & 0xFFFFFFFF); // the record id is kept in the lower 32 bits var resetFlags = NwMask.Fav1 | NwMask.Fav2 | NwMask.Fav3 | NwMask.Fav4 | NwMask.Lock | NwMask.Visible; var setFlags = (NwMask) (((int) ci.Favorites & 0x0F) << 4); if (ci.Lock) setFlags |= NwMask.Lock; if (!ci.Hidden && ci.NewProgramNr >= 0) setFlags |= NwMask.Visible; cmd.CommandText = $"update svl_{x} set channel_id=(channel_id&{0x3FFFF})|(@chnr << 18)" + $", ch_id_txt=@chnr || ' 0'" + $", ac_name=@name" + $", option_mask=option_mask|{(int) (OptionMask.ChNumEdited | OptionMask.NameEdited)}" + $", nw_mask=(nw_mask&@resetFlags)|@setFlags" + $" where svl_rec_id=@id"; cmd.Parameters.Clear(); cmd.Parameters.Add("@id", DbType.Int32); cmd.Parameters.Add("@chnr", DbType.Int32); cmd.Parameters.Add("@name", DbType.String); cmd.Parameters.Add("@resetFlags", DbType.Int32); cmd.Parameters.Add("@setFlags", DbType.Int32); cmd.Parameters["@id"].Value = id; cmd.Parameters["@chnr"].Value = ci.NewProgramNr; cmd.Parameters["@name"].Value = ci.Name; cmd.Parameters["@resetFlags"].Value = ~(int) resetFlags; cmd.Parameters["@setFlags"].Value = (int) setFlags; cmd.ExecuteNonQuery(); for (int i = 0; i < 4; i++) { if (ci.FavIndex[i] <= 0) { cmd.CommandText = $"delete from fav_{i + 1} where ui2_svc_id={ci.RecordIndex >> 32} and ui2_svc_rec_id={ci.RecordIndex & 0xFFFF}"; cmd.ExecuteNonQuery(); } else { cmd.CommandText = $"update fav_{i + 1} set user_defined_ch_num=@chnr, user_defined_ch_name=@name where ui2_svc_id=@svcid and ui2_svc_rec_id=@recid"; cmd.Parameters.Clear(); cmd.Parameters.Add("@chnr", DbType.String); // for some reason this is a VARCHAR in the database cmd.Parameters.Add("@name", DbType.String); cmd.Parameters.Add("@svcid", DbType.Int32); cmd.Parameters.Add("@recid", DbType.Int32); cmd.Parameters["@chnr"].Value = ci.FavIndex[i].ToString(); cmd.Parameters["@name"].Value = ci.Name; cmd.Parameters["@svcid"].Value = ci.RecordIndex >> 32; cmd.Parameters["@recid"].Value = ci.RecordIndex & 0xFFFF; if (cmd.ExecuteNonQuery() == 0) { cmd.CommandText = $"insert into fav_{i + 1} (ui2_svc_id, ui2_svc_rec_id, user_defined_ch_num, user_defined_ch_name) values (@svcid,@recid,@chnr,@name)"; cmd.ExecuteNonQuery(); } } } } #endregion } }