diff --git a/source/ChanSort.Api/Controller/Editor.cs b/source/ChanSort.Api/Controller/Editor.cs index 6d80534..4fa1dd2 100644 --- a/source/ChanSort.Api/Controller/Editor.cs +++ b/source/ChanSort.Api/Controller/Editor.cs @@ -351,6 +351,8 @@ namespace ChanSort.Api this.unsortedChannelMode = mode; foreach (var list in DataRoot.ChannelLists) { + if (list.IsMixedSourceFavoritesList) + continue; var sortedChannels = list.Channels.OrderBy(ChanSortCriteria).ToList(); int maxProgNr = 0; diff --git a/source/ChanSort.Api/Model/ChannelInfo.cs b/source/ChanSort.Api/Model/ChannelInfo.cs index 74123fb..724e7ae 100644 --- a/source/ChanSort.Api/Model/ChannelInfo.cs +++ b/source/ChanSort.Api/Model/ChannelInfo.cs @@ -50,6 +50,7 @@ namespace ChanSort.Api public int ProgramNrPreset { get; set; } public bool IsNameModified { get; set; } + public bool IsProxy => this.RecordIndex < 0; #region ctor() protected ChannelInfo() diff --git a/source/ChanSort.Api/Model/ChannelList.cs b/source/ChanSort.Api/Model/ChannelList.cs index a60db98..207581b 100644 --- a/source/ChanSort.Api/Model/ChannelList.cs +++ b/source/ChanSort.Api/Model/ChannelList.cs @@ -30,6 +30,11 @@ namespace ChanSort.Api public int MaxChannelNameLength { get; set; } public int PresetProgramNrCount { get; private set; } public IList VisibleColumnFieldNames; + + /// + /// Set for helper lists used to manage favorites mixed from all input sources. + /// When true, the UI won't show the "Pr#" tab but will show "Fav A-D" tabs and a "Source" column. + /// public bool IsMixedSourceFavoritesList { get; set; } #region Caption diff --git a/source/ChanSort.Api/Model/DataRoot.cs b/source/ChanSort.Api/Model/DataRoot.cs index 0d3cc34..65e902e 100644 --- a/source/ChanSort.Api/Model/DataRoot.cs +++ b/source/ChanSort.Api/Model/DataRoot.cs @@ -22,6 +22,7 @@ namespace ChanSort.Api public bool SortedFavorites { get; set; } public bool MixedSourceFavorites { get; set; } public bool AllowGapsInFavNumbers { get; set; } + public bool ShowDeletedChannels { get; set; } public DataRoot() { @@ -105,10 +106,20 @@ namespace ChanSort.Api #region ApplyCurrentProgramNumbers() public void ApplyCurrentProgramNumbers() { + int c = 0; + if (this.MixedSourceFavorites || this.SortedFavorites) + { + for (int m = (int) this.SupportedFavorites; m != 0; m >>= 1) + ++c; + } + foreach (var list in this.ChannelLists) { foreach (var channel in list.Channels) - channel.NewProgramNr = channel.OldProgramNr; + { + for (int i=0; i<=c; i++) + channel.SetPosition(i, channel.GetOldPosition(i)); + } } } #endregion diff --git a/source/ChanSort.Api/Model/Enums.cs b/source/ChanSort.Api/Model/Enums.cs index d95738a..69466f5 100644 --- a/source/ChanSort.Api/Model/Enums.cs +++ b/source/ChanSort.Api/Model/Enums.cs @@ -58,7 +58,9 @@ namespace ChanSort.Api TivuSatD = Digital + Sat + TivuSat, CanalDigitalSatD = Digital + Sat + CanalDigital, DigitalPlusD = Digital + Sat + DigitalPlus, - CyfraPlusD = Digital + Sat + CyfraPlus + CyfraPlusD = Digital + Sat + CyfraPlus, + + All = MaskAnalogDigital | MaskAntennaCableSat | MaskTvRadio } #endregion diff --git a/source/ChanSort.Api/Utils/Tools.cs b/source/ChanSort.Api/Utils/Tools.cs index 62434a1..5f9fb0c 100644 --- a/source/ChanSort.Api/Utils/Tools.cs +++ b/source/ChanSort.Api/Utils/Tools.cs @@ -6,11 +6,10 @@ namespace ChanSort.Api { public static class Tools { - public static V TryGet(this IDictionary dict, K key) + public static V TryGet(this IDictionary dict, K key, V defaultValue = default(V)) { V val; - dict.TryGetValue(key, out val); - return val; + return dict.TryGetValue(key, out val) ? val : defaultValue; } #region GetAnalogChannelNumber() diff --git a/source/ChanSort.Loader.Hisense/HisDbSerializer.cs b/source/ChanSort.Loader.Hisense/HisDbSerializer.cs index 331ee8b..45b01ce 100644 --- a/source/ChanSort.Loader.Hisense/HisDbSerializer.cs +++ b/source/ChanSort.Loader.Hisense/HisDbSerializer.cs @@ -151,6 +151,10 @@ namespace ChanSort.Loader.Hisense { 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); diff --git a/source/ChanSort.Loader.Hisense/HisDbSerializerPlugin.cs b/source/ChanSort.Loader.Hisense/HisDbSerializerPlugin.cs index 5a7e5e7..a771c11 100644 --- a/source/ChanSort.Loader.Hisense/HisDbSerializerPlugin.cs +++ b/source/ChanSort.Loader.Hisense/HisDbSerializerPlugin.cs @@ -1,26 +1,15 @@ -#define HISENSE_ENABLED - -/* -Support for the Hisense file format (Sep 2015) is currently disabled due to the risk of damaging the TV when -users import files in an older/newer format than the currently installed firmware expects. -*/ - -using ChanSort.Api; +using ChanSort.Api; namespace ChanSort.Loader.Hisense { -#if HISENSE_ENABLED public class HisDbSerializerPlugin : ISerializerPlugin { public string PluginName => "Hisense channel.db"; - public string FileFilter => "channel*.db"; + public string FileFilter => "*.db"; -#region CreateSerializer() public SerializerBase CreateSerializer(string inputFile) { return new HisDbSerializer(inputFile); } -#endregion } -#endif -} +} \ No newline at end of file diff --git a/source/ChanSort.Loader.Hisense2017/HisDbSerializer.cs b/source/ChanSort.Loader.Hisense2017/HisDbSerializer.cs index 2cf3deb..3af1cdb 100644 --- a/source/ChanSort.Loader.Hisense2017/HisDbSerializer.cs +++ b/source/ChanSort.Loader.Hisense2017/HisDbSerializer.cs @@ -1,24 +1,78 @@ -//#define LOCK_LCN_LISTS +#define LOCK_LCN_LISTS using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; using System.IO; -using System.Text.RegularExpressions; using System.Windows.Forms; using ChanSort.Api; -namespace ChanSort.Loader.Hisense +namespace ChanSort.Loader.Hisense2017 { public class HisDbSerializer : SerializerBase { - private readonly Dictionary channelsById = new Dictionary(); - private readonly Dictionary channelLists = new Dictionary(); - private ChannelList favlist; - private readonly Dictionary favListIdToFavIndex = new Dictionary(); - private List tableNames; + /* + * The 2017 Hisense / Loewe data model for channel lists is a bit different than all other supported models and need some workarounds to be supported. + * It is based on a flat "Services" table which doesn't hold program numbers and a FavoritesList/FavoritesItem table to assign numbers + * to physical tuner lists and user favorite lists alike. + * + * Physical channel lists (e.g. for $av, Astra, Hot Bird) have their own ChannelList in the channelList dictionary and use + * ChannelInfo.NewProgramNr to hold the program number. This doesn't allow the user to add services from other lists. + * + * The user favorite lists (FAV1-FAV4) use the separate favList ChannelList filled with all services from all physical lists. + * ChannelInfo.FavIndex[0-3] holds the information for the program numbers in FAV1-4. The value -1 is used to indicate "not included". + * + * The $all list is hidden from the user and automatically updated to match the contents of all other lists (except $av and FAV1-4). + * + * The $av list is hidden from the user and not updated at all. + * + * This loader poses the following restrictions on the database: + * - a service must not appear in more than one physical channel list ($all and FAV1-4 are not part of this restriction) + * - a service can't appear more than once in any list + * + */ + /// + /// list of all table names in the database + /// + private readonly List tableNames = new List(); + + /// + /// mapping of Service.Pid => ChannelInfo + /// + private readonly Dictionary channelsById = new Dictionary(); + + /// + /// mapping of FavoriteList.Pid => ChannelList. + /// This dict does not include real user favorite lists (FAV1-FAV4). + /// + private readonly Dictionary channelLists = new Dictionary(); + + /// + /// This list is filled with all channels/services and serves as a holder for favorite lists 1-4 + /// + private readonly ChannelList userFavList = new ChannelList(SignalSource.All, "Favorites"); + + /// + /// mapping of FavoriteList.Pid for FAV1-4 => index of the internal favorite list within userFavList (0-3) + /// Pids that don't belong to the FAV1-4 are not included in this dictionary. + /// + private readonly Dictionary favListIdToFavIndex = new Dictionary(); + + /// + /// FavoriteList.Pid of the $all list + /// + private int pidAll; + + /// + /// FavoriteList.Pid of the $av list + /// + private int pidAv; + + /// + /// Fields of the ChannelInfo that will be shown in the UI + /// private static readonly List ColumnNames = new List { "OldPosition", @@ -28,6 +82,7 @@ namespace ChanSort.Loader.Hisense "Name", "ShortName", "Favorites", + "Skip", "Lock", "Hidden", "Encrypted", @@ -38,10 +93,14 @@ namespace ChanSort.Loader.Hisense "ServiceType", "ServiceTypeName", "NetworkName", - "Satellite", - "SymbolRate" + "Satellite" +// "SymbolRate" }; + #region class HisTransponder + /// + /// This class holds information from the Tuner table + /// public class HisTransponder : Transponder { public SignalSource SignalSource { get; set; } @@ -51,6 +110,7 @@ namespace ChanSort.Loader.Hisense { } } + #endregion #region ctor() @@ -59,10 +119,12 @@ namespace ChanSort.Loader.Hisense DepencencyChecker.AssertVc2010RedistPackageX86Installed(); Features.ChannelNameEdit = ChannelNameEditMode.All; - Features.CanDeleteChannels = false; - Features.CanSkipChannels = false; + Features.CanDeleteChannels = true; + Features.CanSkipChannels = true; Features.CanHaveGaps = true; + DataRoot.MixedSourceFavorites = true; DataRoot.SortedFavorites = true; + DataRoot.ShowDeletedChannels = true; } #endregion @@ -79,9 +141,13 @@ namespace ChanSort.Loader.Hisense using (var cmd = conn.CreateCommand()) { RepairCorruptedDatabaseImage(cmd); - LoadLists(cmd); LoadTableNames(cmd); - LoadSatelliteData(cmd); + + // make sure this .db file contains the required tables + if (!tableNames.Contains("service") || !tableNames.Contains("tuner") || !tableNames.Contains("favoriteitem")) + throw new FileLoadException("File doesn't contain service/tuner/favoriteitem tables"); + + LoadLists(cmd); LoadTunerData(cmd); LoadServiceData(cmd); LoadFavorites(cmd); @@ -108,7 +174,6 @@ namespace ChanSort.Loader.Hisense private void LoadTableNames(SQLiteCommand cmd) { - tableNames = new List(); cmd.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table' order by name"; using (var r = cmd.ExecuteReader()) { @@ -129,62 +194,37 @@ namespace ChanSort.Loader.Hisense { int listId = r.GetInt32(0); string name = r.GetString(1); - if (name.StartsWith("FAV")) + + if (name == "$all") + pidAll = listId; + else if (name == "$av") + pidAv = listId; + else if (name.StartsWith("FAV")) { - favListIdToFavIndex.Add(listId, int.Parse(name.Substring(3))); + // all real user favorite lists are using the "userFavList" + favListIdToFavIndex.Add(listId, int.Parse(name.Substring(3)) - 1); continue; } - var list = new ChannelList(SignalSource.Analog | SignalSource.AvInput | SignalSource.DvbCT | SignalSource.DvbS | SignalSource.TvAndRadio, name); + // lists for physical channel sources + var list = new ChannelList(SignalSource.All, name); list.VisibleColumnFieldNames = ColumnNames; - list.IsMixedSourceFavoritesList = list.Caption.StartsWith("FAV"); - channelLists.Add(listId, list); - DataRoot.AddChannelList(list); - } - - - favlist = new ChannelList(SignalSource.Analog | SignalSource.AvInput | SignalSource.DvbCT | SignalSource.DvbS | SignalSource.TvAndRadio, "Favorites"); - favlist.VisibleColumnFieldNames = ColumnNames; - favlist.IsMixedSourceFavoritesList = true; - channelLists.Add(0, favlist); - DataRoot.AddChannelList(favlist); - } - } - #endregion - - - #region LoadSatelliteData() - - - private void LoadSatelliteData(SQLiteCommand cmd) - { - // sample data file doesn't contain any satellite information -#if false - var regex = new Regex(@"^satellite$"); - 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); - } + if (name.StartsWith("$")) + list.ReadOnly = true; + else + DataRoot.AddChannelList(list); // only lists in the DataRoot will be visible in the UI } } -#endif + + // add the special list for the user favorites 1-4 + userFavList.VisibleColumnFieldNames = ColumnNames; + userFavList.IsMixedSourceFavoritesList = true; + channelLists.Add(0, userFavList); + DataRoot.AddChannelList(userFavList); } - #endregion - #region LoadTunerData() private void LoadTunerData(SQLiteCommand cmd) @@ -198,6 +238,7 @@ namespace ChanSort.Loader.Hisense Tuple.Create("T", SignalSource.DvbT, "bandwidth"), Tuple.Create("T2", SignalSource.DvbT, "bandwidth"), }; + foreach (var input in inputs) { var table = input.Item1; @@ -210,28 +251,6 @@ namespace ChanSort.Loader.Hisense t.SymbolRate = r.GetInt32(i0 + 1); }); } - -#if false - this.LoadTunerData(cmd, "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; - } - }); -#endif } private void LoadTunerData(SQLiteCommand cmd, string joinTable, string joinFields, Action enhanceTransponderInfo) @@ -266,7 +285,7 @@ namespace ChanSort.Loader.Hisense private void LoadServiceData(SQLiteCommand cmd) { cmd.CommandText = @" -select s.pid, s.type, anls.Frequency, digs.TunerId, digs.Sid, Name, ShortName, Encrypted, Visible, Selectable, ParentalLock +select s.pid, s.type, anls.Frequency, digs.TunerId, digs.Sid, Name, ShortName, Encrypted, Visible, Selectable, ParentalLock, MediaType from service s left outer join AnalogService anls on anls.ServiceId=s.Pid left outer join DVBService digs on digs.ServiceId=s.Pid @@ -277,11 +296,11 @@ left outer join DVBService digs on digs.ServiceId=s.Pid while (r.Read()) { ChannelInfo ci = null; - if (!r.IsDBNull(2)) + if (!r.IsDBNull(2)) // AnalogService ci = new ChannelInfo(SignalSource.Analog, r.GetInt32(0), -1, r.GetString(5)); - else if (!r.IsDBNull(3)) + else if (!r.IsDBNull(3)) // DvbService { - var trans = (HisTransponder)DataRoot.Transponder.TryGet(r.GetInt32(3)); + var trans = (HisTransponder) DataRoot.Transponder.TryGet(r.GetInt32(3)); ci = new ChannelInfo(trans.SignalSource, r.GetInt32(0), -1, r.GetString(5)); ci.Transponder = trans; ci.FreqInMhz = trans.FrequencyInMhz; @@ -294,113 +313,89 @@ left outer join DVBService digs on digs.ServiceId=s.Pid ci.Hidden = r.GetInt32(8) == 0; ci.Skip = r.GetInt32(9) == 0; ci.Lock = r.GetInt32(10) != 0; + var mediaType = r.GetInt32(11); + if (mediaType == 1) + { + ci.SignalSource |= SignalSource.Tv; + ci.ServiceTypeName = "TV"; + } + else if (mediaType == 2) + { + ci.SignalSource |= SignalSource.Radio; + ci.ServiceTypeName = "Radio"; + } + else + ci.ServiceTypeName = mediaType.ToString(); } - else if (r.GetInt32(1) == 0) + else if (r.GetInt32(1) == 0) // A/V input + { ci = new ChannelInfo(SignalSource.AvInput, r.GetInt32(0), -1, r.GetString(5)); + ci.ServiceTypeName = "A/V"; + } if (ci != null) channelsById.Add(ci.RecordIndex, ci); } } -#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 - } -#if false - private void LoadServiceData(SQLiteCommand cmd, string joinTable, string joinFields, Action enhanceChannelInfo) - { - if (!tableNames.Contains(joinTable)) - return; - - cmd.CommandText = $"select service.pid, -1, {joinFields}" - + $" from service inner join {joinTable} on {joinTable}.ServiceId="; - using (var r = cmd.ExecuteReader()) - { - while (r.Read()) - { - var id = (uint)r.GetInt32(0); - var prNr = (int)(uint)r.GetInt32(1) >> 18; - var trans = 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 = channelLists[tableNr - 1]; - ci.Source = list.ShortCaption; - DataRoot.AddChannel(list, ci); - - // add the channel to all favorites lists - DataRoot.AddChannel(channelLists[6], ci); - channelsById[ci.RecordIndex] = ci; - } - } - } -#endif #endregion #region LoadFavorites() private void LoadFavorites(SQLiteCommand cmd) { - cmd.CommandText = "select FavoriteId, ServiceId, ChannelNum from FavoriteItem fi"; + cmd.CommandText = @" +select fi.FavoriteId, fi.ServiceId, fi.ChannelNum, fi.Selectable, fi.Visible, fi.isDeleted, fi.Protected, l.Lcn +from FavoriteItem fi +left outer join Lcn l on l.ServiceId=fi.ServiceId and l.FavoriteId=fi.FavoriteId +"; using (var r = cmd.ExecuteReader()) { while (r.Read()) { int favListId = r.GetInt32(0); var ci = channelsById.TryGet(r.GetInt32(1)); - int favListIdx = favListIdToFavIndex.TryGet(favListId); - if (favListIdx != 0) + if (ci == null) + continue; + + int favListIdx = favListIdToFavIndex.TryGet(favListId, -1); + if (favListIdx >= 0) { - ci?.SetOldPosition(favListIdx, r.GetInt32(1)); + // NOTE: we need to set the NEW fav index here because AddChannel will use the new value to initialize the old value + ci.FavIndex[favListIdx] = r.GetInt32(2); } - else + + ci.SetOldPosition(favListIdx + 1, r.GetInt32(2)); // 0=main nr, 1-4=fav 1-4 + if (favListIdx < 0) { + // physical channel list (specific satellite, $av, ...) var list = channelLists.TryGet(favListId); - // TODO create copy of channel for each channel list so that it can have an independant number - ci?.SetOldPosition(0, r.GetInt32(1)); + + if (!r.IsDBNull(7)) // LCN + { + ci.ProgramNrPreset = r.GetInt32(7); +#if LOCK_LCN_LISTS + list.ReadOnly = true; +#endif + } + + ci.Skip = r.GetInt32(3) == 0; + ci.Lock = r.GetInt32(6) != 0; + ci.Hidden = r.GetInt32(4) == 0; + ci.IsDeleted = r.GetInt32(5) != 0; + ci.Source = list.ShortCaption; + if (ci.IsDeleted) + ci.OldProgramNr = -1; + if ((ci.SignalSource & (SignalSource.MaskAntennaCableSat | SignalSource.MaskAnalogDigital)) == SignalSource.DvbS) + ci.Satellite = list.ShortCaption; + DataRoot.AddChannel(list, ci); } } } foreach(var ci in channelsById.Values) - DataRoot.AddChannel(favlist, ci); + DataRoot.AddChannel(userFavList, ci); } #endregion @@ -410,8 +405,6 @@ left outer join DVBService digs on digs.ServiceId=s.Pid public override void Save(string tvOutputFile) { - //Editor.SequentializeFavPos(channelLists[6], 4); - if (tvOutputFile != FileName) File.Copy(FileName, tvOutputFile, true); @@ -424,17 +417,13 @@ left outer join DVBService digs on digs.ServiceId=s.Pid cmd.Transaction = trans; try { - CreateFavTables(cmd); #if !LOCK_LCN_LISTS ResetLcn(cmd); #endif - foreach (var list in DataRoot.ChannelLists) - { - if (list.ReadOnly) - continue; - foreach (var ci in list.Channels) - UpdateChannel(cmd, ci); - } + UpdateServices(cmd); + UpdatePhysicalChannelLists(cmd); + UpdateUserFavoriteLists(cmd); + trans.Commit(); FileName = tvOutputFile; } @@ -446,165 +435,134 @@ left outer join DVBService digs on digs.ServiceId=s.Pid } } } - - #endregion - - #region CreateFavTables() - - private void CreateFavTables(SQLiteCommand cmd) - { - for (var i = 1; i <= 4; i++) - if (!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(); - tableNames.Add($"fav_{i}"); - } - } - #endregion #region ResetLcn() private void ResetLcn(SQLiteCommand cmd) { - var regex = new Regex(@"^svl_\d_data_dvb$"); - foreach (var table in tableNames) + cmd.CommandText = "delete from Lcn where FavoriteId<>" + pidAv; + cmd.ExecuteNonQuery(); + } + + #endregion + + #region UpdateServices() + private void UpdateServices(SQLiteCommand cmd) + { + cmd.CommandText = "update Service set Name=@name, ShortName=@sname, ParentalLock=@lock, Visible=@vis, Selectable=@sel, FavTag=@fav1, FavTag2=@fav1, FavTag3=@fav3, FavTag4=@fav4 where Pid=@servId"; + cmd.Parameters.Clear(); + cmd.Parameters.Add("@servId", DbType.Int32); + cmd.Parameters.Add("@name", DbType.String); + cmd.Parameters.Add("@sname", DbType.String); + cmd.Parameters.Add("@lock", DbType.Int32); + cmd.Parameters.Add("@vis", DbType.Int32); + cmd.Parameters.Add("@sel", DbType.Int32); + cmd.Parameters.Add("@fav1", DbType.Int32); + cmd.Parameters.Add("@fav2", DbType.Int32); + cmd.Parameters.Add("@fav3", DbType.Int32); + cmd.Parameters.Add("@fav4", DbType.Int32); + cmd.Prepare(); + + foreach (var ci in channelsById.Values) { - if (!regex.IsMatch(table)) - continue; - cmd.CommandText = "update " + table + " set cur_lcn=0, original_lcn=0, lcn_idx=0"; + cmd.Parameters["@servId"].Value = ci.RecordIndex; + cmd.Parameters["@name"].Value = ci.Name; + cmd.Parameters["@sname"].Value = ci.ShortName; + cmd.Parameters["@lock"].Value = ci.Lock ? 1 : 0; + cmd.Parameters["@vis"].Value = ci.Hidden ? 0 : 1; + cmd.Parameters["@sel"].Value = ci.Skip ? 0 : 1; + cmd.Parameters["@fav1"].Value = (ci.Favorites & Favorites.A) != 0 ? 1 : 0; + cmd.Parameters["@fav2"].Value = (ci.Favorites & Favorites.B) != 0 ? 1 : 0; + cmd.Parameters["@fav3"].Value = (ci.Favorites & Favorites.C) != 0 ? 1 : 0; + cmd.Parameters["@fav4"].Value = (ci.Favorites & Favorites.D) != 0 ? 1 : 0; cmd.ExecuteNonQuery(); } } - #endregion - #region UpdateChannel() - - private void UpdateChannel(SQLiteCommand cmd, ChannelInfo ci) + #region UpdatePhysicalChannelLists() + private void UpdatePhysicalChannelLists(SQLiteCommand cmd) { - if (ci.RecordIndex < 0) // skip reference list proxy channels - return; - - var x = (int) ((ulong) ci.RecordIndex >> 32); // the table number is kept in the higher 32 bits - var 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.CommandText = "update FavoriteItem set ChannelNum=@ch, isDeleted=@del, Protected=@prot, Selectable=@sel, Visible=@vis where FavoriteId=@favId and ServiceId=@servId"; 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(); + cmd.Parameters.Add("@favId", DbType.Int32); + cmd.Parameters.Add("@servId", DbType.Int32); + cmd.Parameters.Add("@ch", DbType.Int32); + cmd.Parameters.Add("@del", DbType.Int32); + cmd.Parameters.Add("@prot", DbType.Int32); + cmd.Parameters.Add("@sel", DbType.Int32); + cmd.Parameters.Add("@vis", DbType.Int32); + cmd.Prepare(); - for (var i = 0; i < 4; i++) - if (ci.FavIndex[i] <= 0) + foreach (var entry in channelLists) + { + var list = entry.Value; + if (list.ReadOnly) // don't update read-only lists (i.e. containing LCNs) + continue; + + // don't update the $all list directly. It will be updated while iterating all other lists + var favId = entry.Key; + if (favId == pidAll) + continue; + + foreach (var ci in list.Channels) { - cmd.CommandText = $"delete from fav_{i + 1} where ui2_svc_id={ci.RecordIndex >> 32} and ui2_svc_rec_id={ci.RecordIndex & 0xFFFF}"; + if (ci.IsProxy) // ignore proxies for missing channels that might have been added by applying a reference list + continue; + + cmd.Parameters["@favId"].Value = favId; + cmd.Parameters["@servId"].Value = ci.RecordIndex; + cmd.Parameters["@ch"].Value = ci.NewProgramNr <= 0 ? 9999 : ci.NewProgramNr; + cmd.Parameters["@del"].Value = ci.NewProgramNr <= 0 ? 1 : 0; // 1 or -1 ? + // not sure if the following columns are used at all. they also exist in the Services table + cmd.Parameters["@prot"].Value = ci.Lock ? -1 : 0; + cmd.Parameters["@sel"].Value = ci.Skip ? 0 : -1; + cmd.Parameters["@vis"].Value = ci.Hidden ? 0 : -1; 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) + + // update the $all list with the same values + if (pidAll != 0 && favId != pidAv) { - 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.Parameters["@favId"].Value = pidAll; cmd.ExecuteNonQuery(); } } + } } - #endregion - #region enums and bitmasks - - internal enum BroadcastType + #region UpdateUserFavoriteLists() + private void UpdateUserFavoriteLists(SQLiteCommand cmd) { - Analog = 1, - Dvb = 2 - } + // delete all FavoriteItem records that belong to the FAV1-4 lists + cmd.Parameters.Clear(); + cmd.CommandText = "delete from FavoriteItem where FavoriteId in (select Pid from FavoriteList where name like 'FAV_')"; + cmd.ExecuteNonQuery(); - internal enum BroadcastMedium - { - DigTer = 1, - DigCab = 2, - DigSat = 3, - AnaTer = 4, - AnaCab = 5, - AnaSat = 6 - } + // (re-)insert the user's new favorites + cmd.CommandText = "insert into FavoriteItem (FavoriteId, ServiceId, ChannelNum) values (@favId, @servId, @ch)"; + cmd.Parameters.Add("@favId", DbType.Int32); + cmd.Parameters.Add("@servId", DbType.Int32); + cmd.Parameters.Add("@ch", DbType.Int32); + foreach (var entry in favListIdToFavIndex) + { + var favIndex = entry.Value; + cmd.Parameters["@favId"].Value = entry.Key; + foreach (var ci in userFavList.Channels) + { + if (ci.IsProxy) // ignore proxies for missing channels that might have been added by applying a reference list + continue; - 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 + var num = ci.GetPosition(favIndex + 1); + if (num > 0) + { + cmd.Parameters["@servId"].Value = ci.RecordIndex; + cmd.Parameters["@ch"].Value = num; + cmd.ExecuteNonQuery(); + } + } + } } #endregion diff --git a/source/ChanSort.Loader.Hisense2017/HisDbSerializerPlugin.cs b/source/ChanSort.Loader.Hisense2017/HisDbSerializerPlugin.cs index 413aa56..ca2de6b 100644 --- a/source/ChanSort.Loader.Hisense2017/HisDbSerializerPlugin.cs +++ b/source/ChanSort.Loader.Hisense2017/HisDbSerializerPlugin.cs @@ -1,26 +1,15 @@ -#define HISENSE_ENABLED +using ChanSort.Api; -/* -Support for the Hisense file format (Sep 2015) is currently disabled due to the risk of damaging the TV when -users import files in an older/newer format than the currently installed firmware expects. -*/ - -using ChanSort.Api; - -namespace ChanSort.Loader.Hisense +namespace ChanSort.Loader.Hisense2017 { -#if HISENSE_ENABLED public class HisDbSerializerPlugin : ISerializerPlugin { public string PluginName => "Hisense servicelist.db"; - public string FileFilter => "servicelist*.db"; + public string FileFilter => "*.db"; -#region CreateSerializer() public SerializerBase CreateSerializer(string inputFile) { return new HisDbSerializer(inputFile); } -#endregion } -#endif -} +} \ No newline at end of file diff --git a/source/ChanSort.Loader.Hisense2017/Resources.Designer.cs b/source/ChanSort.Loader.Hisense2017/Resources.Designer.cs index 2659a4e..c5c909a 100644 --- a/source/ChanSort.Loader.Hisense2017/Resources.Designer.cs +++ b/source/ChanSort.Loader.Hisense2017/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace ChanSort.Loader.Hisense { +namespace ChanSort.Loader.Hisense2017 { using System; diff --git a/source/ChanSort.Loader.LG/TllFileSerializerPlugin.cs b/source/ChanSort.Loader.LG/TllFileSerializerPlugin.cs index 11da36f..2e6603b 100644 --- a/source/ChanSort.Loader.LG/TllFileSerializerPlugin.cs +++ b/source/ChanSort.Loader.LG/TllFileSerializerPlugin.cs @@ -4,7 +4,7 @@ namespace ChanSort.Loader.LG { public class TllFileSerializerPlugin : ISerializerPlugin { - public string PluginName { get { return "LG-Electronics xx*.tll"; } } + public string PluginName { get { return "LG model specific (xx*.tll)"; } } public string FileFilter { get { return "xx*.TLL"; } } #region CreateSerializer() diff --git a/source/ChanSort.Loader.Panasonic/Serializer.cs b/source/ChanSort.Loader.Panasonic/Serializer.cs index bbc591c..ca250cd 100644 --- a/source/ChanSort.Loader.Panasonic/Serializer.cs +++ b/source/ChanSort.Loader.Panasonic/Serializer.cs @@ -339,6 +339,11 @@ namespace ChanSort.Loader.Panasonic { RepairCorruptedDatabaseImage(cmd); InitCharacterEncoding(cmd); + + cmd.CommandText = "SELECT count(1) FROM sqlite_master WHERE type = 'table' and name in ('svl', 'tsl')"; + if (Convert.ToInt32(cmd.ExecuteScalar()) != 2) + throw new FileLoadException("File doesn't contain the expected TSL/SVL tables"); + this.ReadChannels(cmd); } } diff --git a/source/ChanSort.Loader.Panasonic/SerializerPlugin.cs b/source/ChanSort.Loader.Panasonic/SerializerPlugin.cs index 1f879cc..4d9550a 100644 --- a/source/ChanSort.Loader.Panasonic/SerializerPlugin.cs +++ b/source/ChanSort.Loader.Panasonic/SerializerPlugin.cs @@ -4,7 +4,7 @@ namespace ChanSort.Loader.Panasonic { public class SerializerPlugin : ISerializerPlugin { - public string PluginName { get { return "Panasonic *.db,*.bin"; } } + public string PluginName { get { return "Panasonic"; } } public string FileFilter { get { return "*.db;*.bin"; } } public SerializerBase CreateSerializer(string inputFile) diff --git a/source/ChanSort.Loader.Samsung/ScmChannelBase.cs b/source/ChanSort.Loader.Samsung/ScmChannelBase.cs index 8a23a9d..7fc8ef4 100644 --- a/source/ChanSort.Loader.Samsung/ScmChannelBase.cs +++ b/source/ChanSort.Loader.Samsung/ScmChannelBase.cs @@ -144,6 +144,8 @@ namespace ChanSort.Loader.Samsung mapping.SetFlag(_Lock, this.Lock); mapping.SetFlag(_Deleted, this.NewProgramNr < 0); mapping.SetFlag(_IsActive, this.NewProgramNr >= 0); + if (this.Encrypted != null) + mapping.SetFlag(_Encrypted, this.Encrypted.Value); this.UpdateChecksum(); } #endregion diff --git a/source/ChanSort.Loader.Samsung/ScmSerializer.cs b/source/ChanSort.Loader.Samsung/ScmSerializer.cs index 5cf6b09..d332d37 100644 --- a/source/ChanSort.Loader.Samsung/ScmSerializer.cs +++ b/source/ChanSort.Loader.Samsung/ScmSerializer.cs @@ -63,6 +63,7 @@ namespace ChanSort.Loader.Samsung this.ReadConfigurationFromIniFile(); this.Features.ChannelNameEdit = ChannelNameEditMode.All; this.Features.CleanUpChannelData = true; + this.Features.EncryptedFlagEdit = true; } #endregion diff --git a/source/ChanSort.Loader.Samsung/ScmSerializerPlugin.cs b/source/ChanSort.Loader.Samsung/ScmSerializerPlugin.cs index 6908da4..69eee03 100644 --- a/source/ChanSort.Loader.Samsung/ScmSerializerPlugin.cs +++ b/source/ChanSort.Loader.Samsung/ScmSerializerPlugin.cs @@ -4,7 +4,7 @@ namespace ChanSort.Loader.Samsung { public class ScmSerializerPlugin : ISerializerPlugin { - public string PluginName { get { return "Samsung *.scm"; } } + public string PluginName { get { return "Samsung B-H series"; } } public string FileFilter { get { return "*.scm"; } } public SerializerBase CreateSerializer(string inputFile) diff --git a/source/ChanSort.Loader.SamsungJ/ChanSort.Loader.SamsungJ.csproj b/source/ChanSort.Loader.SamsungJ/ChanSort.Loader.SamsungJ.csproj index af361b4..bad2901 100644 --- a/source/ChanSort.Loader.SamsungJ/ChanSort.Loader.SamsungJ.csproj +++ b/source/ChanSort.Loader.SamsungJ/ChanSort.Loader.SamsungJ.csproj @@ -9,8 +9,9 @@ Properties ChanSort.Loader.SamsungJ ChanSort.Loader.SamsungJ - v4.5 + v4.0 512 + Client true diff --git a/source/ChanSort.Loader.SamsungJ/DbSerializer.cs b/source/ChanSort.Loader.SamsungJ/DbSerializer.cs index ea0e3a8..7879239 100644 --- a/source/ChanSort.Loader.SamsungJ/DbSerializer.cs +++ b/source/ChanSort.Loader.SamsungJ/DbSerializer.cs @@ -55,7 +55,11 @@ namespace ChanSort.Loader.SamsungJ catch { } } - foreach (var filePath in Directory.GetFiles(tempDir, "*.")) + var files = Directory.GetFiles(tempDir, "*."); + if (files.Length == 0) + throw new FileLoadException("The Samsung .zip channel list archive does not contain any supported files."); + + foreach (var filePath in files) { var filename = Path.GetFileName(filePath) ?? ""; if (filename.StartsWith("vconf_")) diff --git a/source/ChanSort.Loader.SamsungJ/DbSerializerPlugin.cs b/source/ChanSort.Loader.SamsungJ/DbSerializerPlugin.cs index 2fa4d0b..9174ed6 100644 --- a/source/ChanSort.Loader.SamsungJ/DbSerializerPlugin.cs +++ b/source/ChanSort.Loader.SamsungJ/DbSerializerPlugin.cs @@ -4,7 +4,7 @@ namespace ChanSort.Loader.SamsungJ { public class DbSerializerPlugin : ISerializerPlugin { - public string PluginName => "Samsung J-Series (*.zip)"; + public string PluginName => "Samsung J-K series"; public string FileFilter => "*.zip"; // "channel_list_t*.zip"; public SerializerBase CreateSerializer(string inputFile) diff --git a/source/ChanSort.Loader.Toshiba/DbSerializerPlugin.cs b/source/ChanSort.Loader.Toshiba/DbSerializerPlugin.cs index fd50371..8ef51ac 100644 --- a/source/ChanSort.Loader.Toshiba/DbSerializerPlugin.cs +++ b/source/ChanSort.Loader.Toshiba/DbSerializerPlugin.cs @@ -4,8 +4,8 @@ namespace ChanSort.Loader.Toshiba { public class DbSerializerPlugin : ISerializerPlugin { - public string PluginName => "Toshiba *.zip"; - public string FileFilter => "Hotel*.zip"; + public string PluginName => "Toshiba"; + public string FileFilter => "*.zip"; public SerializerBase CreateSerializer(string inputFile) { diff --git a/source/ChanSort.Loader.VDR/SerializerPlugin.cs b/source/ChanSort.Loader.VDR/SerializerPlugin.cs index e7d93d4..b090cdf 100644 --- a/source/ChanSort.Loader.VDR/SerializerPlugin.cs +++ b/source/ChanSort.Loader.VDR/SerializerPlugin.cs @@ -4,7 +4,7 @@ namespace ChanSort.Loader.VDR { public class SerializerPlugin : ISerializerPlugin { - public string PluginName { get { return "VDR Channels *.conf"; } } + public string PluginName { get { return "Linux VDR"; } } public string FileFilter { get { return "*.conf"; } } public SerializerBase CreateSerializer(string inputFile) diff --git a/source/ChanSort/ChanSort.csproj b/source/ChanSort/ChanSort.csproj index 6606368..04df4e6 100644 --- a/source/ChanSort/ChanSort.csproj +++ b/source/ChanSort/ChanSort.csproj @@ -332,6 +332,42 @@ {DCCFFA08-472B-4D17-BB90-8F513FC01392} ChanSort.Api + + {5361c8cb-f737-4709-af8c-e1f0456f3c5b} + ChanSort.Loader.GlobalClone + + + {9282e1db-cd1f-400a-aca1-17e0c4562acf} + ChanSort.Loader.Hisense2017 + + + {d093e7ee-d3ad-4e7b-af82-c6918ca017fb} + ChanSort.Loader.Hisense + + + {e972d8a1-2f5f-421c-ac91-cff45e5191be} + ChanSort.Loader.LG + + + {68da8072-3a29-4076-9f64-d66f38349585} + ChanSort.Loader.Panasonic + + + {33897002-0537-49a4-b963-a18d17311b3d} + ChanSort.Loader.SamsungJ + + + {a1c9a98d-368a-44e8-9b7f-7eaca46c9ec5} + ChanSort.Loader.Samsung + + + {f6f02792-07f1-48d5-9af3-f945ca5e3931} + ChanSort.Loader.Toshiba + + + {74a18c6f-09ff-413e-90d9-827066fa5b36} + ChanSort.Loader.VDR + diff --git a/source/ChanSort/MainForm.Designer.cs b/source/ChanSort/MainForm.Designer.cs index 409a649..97f33e7 100644 --- a/source/ChanSort/MainForm.Designer.cs +++ b/source/ChanSort/MainForm.Designer.cs @@ -614,6 +614,7 @@ this.gviewRight.ShownEditor += new System.EventHandler(this.gview_ShownEditor); this.gviewRight.FocusedRowChanged += new DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventHandler(this.gviewRight_FocusedRowChanged); this.gviewRight.CellValueChanged += new DevExpress.XtraGrid.Views.Base.CellValueChangedEventHandler(this.gviewRight_CellValueChanged); + this.gviewRight.CustomColumnSort += new DevExpress.XtraGrid.Views.Base.CustomColumnSortEventHandler(this.gviewRight_CustomColumnSort); this.gviewRight.CustomUnboundColumnData += new DevExpress.XtraGrid.Views.Base.CustomColumnDataEventHandler(this.gview_CustomUnboundColumnData); this.gviewRight.CustomColumnDisplayText += new DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventHandler(this.gviewRight_CustomColumnDisplayText); this.gviewRight.LayoutUpgrade += new DevExpress.Utils.LayoutUpgradeEventHandler(this.gviewRight_LayoutUpgrade); @@ -639,6 +640,7 @@ this.colSlotOld.Name = "colSlotOld"; this.colSlotOld.OptionsColumn.AllowEdit = false; this.colSlotOld.OptionsFilter.AllowAutoFilter = false; + this.colSlotOld.SortMode = DevExpress.XtraGrid.ColumnSortMode.Custom; this.colSlotOld.UnboundType = DevExpress.Data.UnboundColumnType.Integer; // // colSlotNew diff --git a/source/ChanSort/MainForm.cs b/source/ChanSort/MainForm.cs index 146af0a..5dbf98e 100644 --- a/source/ChanSort/MainForm.cs +++ b/source/ChanSort/MainForm.cs @@ -31,7 +31,7 @@ namespace ChanSort.Ui { public partial class MainForm : XtraForm { - public const string AppVersion = "v2017-03-29"; + public const string AppVersion = "v2017-06-08"; private const int MaxMruEntries = 10; private readonly List isoEncodings = new List(); @@ -186,12 +186,17 @@ namespace ChanSort.Ui numberOfFilters = 0; var filter = new StringBuilder(); var extension = new StringBuilder(); - foreach (var plugin in this.Plugins) + var sortedPlugins = this.Plugins.ToList(); + sortedPlugins.Sort((a, b) => a.PluginName.CompareTo(b.PluginName)); + foreach (var plugin in sortedPlugins) { filter.Append(plugin.PluginName).Append("|").Append(plugin.FileFilter); - extension.Append(plugin.FileFilter); filter.Append("|"); - extension.Append(";"); + if (!(";" + extension + ";").Contains(plugin.FileFilter)) + { + extension.Append(plugin.FileFilter); + extension.Append(";"); + } ++numberOfFilters; } if (extension.Length > 0) @@ -252,7 +257,7 @@ namespace ChanSort.Ui this.Editor.ChannelList = null; this.gridRight.DataSource = null; this.gridLeft.DataSource = null; - this.FillChannelListCombo(); + this.FillChannelListTabs(); //this.SetControlsEnabled(!this.dataRoot.IsEmpty); this.UpdateFavoritesEditor(this.DataRoot.SupportedFavorites); @@ -314,9 +319,9 @@ namespace ChanSort.Ui #endregion - #region FillChannelListCombo() + #region FillChannelListTabs() - private void FillChannelListCombo() + private void FillChannelListTabs() { this.tabChannelList.TabPages.Clear(); @@ -408,23 +413,60 @@ namespace ChanSort.Ui #endregion - #region GetTvFileSerializer() + #region GetSerializerForFile() - internal ISerializerPlugin GetPluginForFile(string inputFileName) + internal SerializerBase GetSerializerForFile(string inputFileName, ref ISerializerPlugin hint) { if (!File.Exists(inputFileName)) { XtraMessageBox.Show(this, string.Format(Resources.MainForm_LoadTll_SourceTllNotFound, inputFileName)); return null; } - var upperFileName = (Path.GetFileName(inputFileName) ?? "").ToUpper(); - foreach (var plugin in this.Plugins) + + List candidates = new List(); + if (hint != null) + candidates.Add(hint); + else { - foreach (var filter in plugin.FileFilter.ToUpper().Split(';')) + var upperFileName = (Path.GetFileName(inputFileName) ?? "").ToUpper(); + foreach (var plugin in this.Plugins) { - var regex = filter.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."); - if (Regex.IsMatch(upperFileName, regex)) - return plugin; + foreach (var filter in plugin.FileFilter.ToUpper().Split(';')) + { + var regex = filter.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."); + if (Regex.IsMatch(upperFileName, regex)) + { + candidates.Add(plugin); + break; + } + } + } + } + + foreach (var plugin in candidates) + { + try + { + var serializer = plugin.CreateSerializer(inputFileName); + if (serializer != null) + { + serializer.DefaultEncoding = this.defaultEncoding; + serializer.Load(); + hint = plugin; + return serializer; + } + } + catch (Exception ex) + { + if (ex is ArgumentException) + { + var msg = ex.ToString(); + if (msg.Contains("ZipFile..ctor()")) + { + XtraMessageBox.Show(this, string.Format(Resources.MainForm_LoadTll_InvalidZip, inputFileName)); + return null; + } + } } } @@ -446,18 +488,14 @@ namespace ChanSort.Ui return false; } - if (plugin == null) - plugin = this.GetPluginForFile(tvDataFile); // abort action if there is no currentTvSerializer for the input file - var serializer = plugin == null ? null : plugin.CreateSerializer(tvDataFile); + SerializerBase serializer = this.GetSerializerForFile(tvDataFile, ref plugin); if (serializer == null) return false; if (!this.PromptSaveAndContinue()) return false; - serializer.DefaultEncoding = this.defaultEncoding; - serializer.Load(); this.SetFileName(tvDataFile); this.currentPlugin = plugin; this.currentTvSerializer = serializer; @@ -530,6 +568,7 @@ namespace ChanSort.Ui this.BeginInvoke((Action) (() => this.ShowOpenReferenceFileDialog(false))); else if (res == DialogResult.No) { + //this.currentTvSerializer.ApplyCurrentProgramNumbers(); this.DataRoot.ApplyCurrentProgramNumbers(); this.RefreshGrid(this.gviewLeft, this.gviewRight); this.rbInsertSwap.Checked = true; @@ -590,9 +629,6 @@ namespace ChanSort.Ui this.pageProgNr.PageVisible = true; this.grpSubList.Visible = DataRoot.SortedFavorites; } - - //this.tabSubList.TabPages[0].PageVisible = !channelList.IsMixedSourceFavoritesList; - //this.pageProgNr.Enabled = this.pageProgNr.Visible; } else { @@ -610,7 +646,7 @@ namespace ChanSort.Ui UpdateGridReadOnly(); - this.UpdateInsertSlotTextBox(); + this.UpdateInsertSlotNumber(); this.UpdateMenu(); this.mnuFavList.Enabled = this.grpSubList.Visible; @@ -703,7 +739,7 @@ namespace ChanSort.Ui { foreach (var channel in list.Channels) { - if (channel.NewProgramNr < 0) + if (channel.NewProgramNr < 0 && channel.OldProgramNr >= 0) { hasUnsorted = true; break; @@ -871,6 +907,8 @@ namespace ChanSort.Ui // remove all the selected channels which are about to be added. // This may require an adjustment of the insert position when channels are removed in front of it and gaps are closed. var insertSlot = this.CurrentChannelList.InsertProgramNumber; + if (insertSlot == 1 && this.rbInsertAfter.Checked && this.gviewLeft.RowCount == 0) + insertSlot = 0; var contextRow = (ChannelInfo)this.gviewLeft.GetFocusedRow(); if (contextRow != null) { @@ -1684,7 +1722,7 @@ namespace ChanSort.Ui if (this.currentTvSerializer != null && this.currentTvSerializer.Features.CleanUpChannelData) { var msg = this.currentTvSerializer.CleanUpChannelData(); - this.FillChannelListCombo(); + this.FillChannelListTabs(); InfoBox.Show(this, msg, this.miCleanupChannels.Caption); this.RefreshGrid(gviewLeft, gviewRight); } @@ -1837,11 +1875,13 @@ namespace ChanSort.Ui this.gviewLeft.BeginSort(); this.gviewLeft.EndSort(); this.gviewRight.BeginSort(); - if (this.subListIndex > 0) + if (this.subListIndex > 0 && !this.CurrentChannelList.IsMixedSourceFavoritesList) this.colPrNr.FilterInfo = new ColumnFilterInfo("[NewProgramNr]<>-1"); else this.colPrNr.ClearFilter(); this.gviewRight.EndSort(); + + this.UpdateInsertSlotNumber(); } #endregion @@ -2050,7 +2090,7 @@ namespace ChanSort.Ui { var channel = (ChannelInfo) this.gviewLeft.GetRow(e.RowHandle); if (channel == null) return; - if (channel.OldProgramNr == -1) + if (channel.IsProxy) { e.Appearance.ForeColor = Color.Red; e.Appearance.Options.UseForeColor = true; @@ -2190,7 +2230,7 @@ namespace ChanSort.Ui { var channel = (ChannelInfo) this.gviewRight.GetRow(e.RowHandle); if (channel == null) return; - if (channel.OldProgramNr == -1) + if (channel.IsProxy) { e.Appearance.ForeColor = Color.Red; e.Appearance.Options.UseForeColor = true; @@ -2261,6 +2301,22 @@ namespace ChanSort.Ui #endregion + #region gviewRight_CustomColumnSort + private void gviewRight_CustomColumnSort(object sender, CustomColumnSortEventArgs e) + { + if (e.Column == this.colSlotOld) + { + // sort unassigned channels (PrNr = -1) to the bottom of the list + var ch1 = (int)this.gviewRight.GetListSourceRowCellValue(e.ListSourceRowIndex1, e.Column); + var ch2 = (int)this.gviewRight.GetListSourceRowCellValue(e.ListSourceRowIndex2, e.Column); + if (ch1 < 0) ch1 = int.MaxValue; + if (ch2 < 0) ch2 = int.MaxValue; + e.Result = System.Collections.Comparer.Default.Compare(ch1, ch2); + e.Handled = true; + } + } + #endregion + #region gviewRight_PopupMenuShowing private void gviewRight_PopupMenuShowing(object sender, PopupMenuShowingEventArgs e) @@ -2286,10 +2342,16 @@ namespace ChanSort.Ui if (this.CurrentChannelList == null) return; - var delta = this.curEditMode == EditMode.InsertAfter - ? -1 - : this.rbInsertAfter.Checked ? +1 : 0; - this.CurrentChannelList.InsertProgramNumber += delta; + if (this.gviewLeft.RowCount == 0) + this.CurrentChannelList.InsertProgramNumber = 1; + else + { + var delta = this.curEditMode == EditMode.InsertAfter + ? -1 + : this.rbInsertAfter.Checked ? +1 : 0; + this.CurrentChannelList.InsertProgramNumber += delta; + } + this.UpdateInsertSlotTextBox(); this.curEditMode = this.rbInsertBefore.Checked ? EditMode.InsertBefore @@ -2458,8 +2520,9 @@ namespace ChanSort.Ui { this.gviewRight.BeginSort(); this.gviewRight.ClearColumnsFilter(); - this.colSlotOld.FilterInfo = new ColumnFilterInfo("[OldProgramNr]<>-1"); - if (this.subListIndex > 0) + if (this.DataRoot != null && !this.DataRoot.ShowDeletedChannels) + this.colSlotOld.FilterInfo = new ColumnFilterInfo("[OldProgramNr]<>-1"); + if (this.subListIndex > 0 && !this.CurrentChannelList.IsMixedSourceFavoritesList) this.colPrNr.FilterInfo = new ColumnFilterInfo("[NewProgramNr]<>-1"); this.gviewRight.EndSort(); } diff --git a/source/ChanSort/MainForm.resx b/source/ChanSort/MainForm.resx index 52f2894..7f1b1a2 100644 --- a/source/ChanSort/MainForm.resx +++ b/source/ChanSort/MainForm.resx @@ -211,7 +211,7 @@ UID - @Invariant + Lock @@ -373,7 +373,7 @@ Sub List - @Invariant + False @@ -466,37 +466,37 @@ &A - @Invariant + &B - @Invariant + &C - @Invariant + &D - @Invariant + &E - @Invariant + Remove from Favorites &A - @Invariant + &B - @Invariant + &C - @Invariant + &D - @Invariant + &E - @Invariant + &Lock channel: on @@ -535,16 +535,16 @@ &English - @Invariant + &Deutsch - @Invariant + Português - @Invariant + ру́сский - @Invariant + Česky @@ -580,7 +580,7 @@ mnuInputSource - @Invariant + Opens a submenu for the program of favorite list selection. This menu can be directly activated with the Shift+F1 key @@ -589,7 +589,7 @@ mnuFavList - @Invariant + Program list @@ -1013,7 +1013,7 @@ \d{1,4} - @Invariant + RegEx @@ -1852,7 +1852,7 @@ DevExpress.XtraEditors.XtraForm, DevExpress.Utils.v15.2, Version=15.2.10.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a - 01/26/2017 12:52:08 + 06/08/2017 17:07:00 16, 16 @@ -1898,7 +1898,7 @@ ±E - @Invariant + btnToggleFavE @@ -1925,7 +1925,7 @@ ±D - @Invariant + btnToggleFavD @@ -1952,7 +1952,7 @@ ±C - @Invariant + btnToggleFavC @@ -1979,7 +1979,7 @@ ±B - @Invariant + btnToggleFavB @@ -2006,7 +2006,7 @@ ±A - @Invariant + btnToggleFavA @@ -2213,7 +2213,7 @@ Panel1 - @Invariant + Fill @@ -2438,7 +2438,7 @@ TS ID - @Invariant + Transport Stream ID @@ -2534,7 +2534,7 @@ Uid - @Invariant + 120 @@ -2801,7 +2801,7 @@ specific provider, satellite or country lists. Panel2 - @Invariant + 1444, 459 @@ -2810,7 +2810,7 @@ specific provider, satellite or country lists. splitContainerControl1 - @Invariant + splitContainerControl1 diff --git a/source/ChanSort/Properties/Resources.Designer.cs b/source/ChanSort/Properties/Resources.Designer.cs index bec7cf0..33d5fe4 100644 --- a/source/ChanSort/Properties/Resources.Designer.cs +++ b/source/ChanSort/Properties/Resources.Designer.cs @@ -238,6 +238,17 @@ namespace ChanSort.Ui.Properties { } } + /// + /// Looks up a localized string similar to The file is not a valid .zip archive. + ///TVs often export corrupted files to USB sticks formatted with the NTFS file system. + ///Please try exporting to a stick formatted with FAT32. + /// + internal static string MainForm_LoadTll_InvalidZip { + get { + return ResourceManager.GetString("MainForm_LoadTll_InvalidZip", resourceCulture); + } + } + /// /// Looks up a localized string similar to No plugin found to read/write {0} files.. /// diff --git a/source/ChanSort/Properties/Resources.de.resx b/source/ChanSort/Properties/Resources.de.resx index 673d46c..b0beea2 100644 --- a/source/ChanSort/Properties/Resources.de.resx +++ b/source/ChanSort/Properties/Resources.de.resx @@ -292,4 +292,9 @@ Mögliche Ursachen sind USB-Sticks, die mit NTFS formatiert sind (FAT32 sollte i Wählen Sie eine Vorlagedatei für die Senderreihenfolge + + Diese Datei ist kein gültiges .zip Archiv. +TVs exportieren oft defekte Dateien auf USB Sticks, die mit NTFS formatiert sind. +Bitte versuchen Sie, die Senderliste auf einen Stick mit FAT32 Formatierung zu exportieren. + \ No newline at end of file diff --git a/source/ChanSort/Properties/Resources.resx b/source/ChanSort/Properties/Resources.resx index 0bd390f..1366202 100644 --- a/source/ChanSort/Properties/Resources.resx +++ b/source/ChanSort/Properties/Resources.resx @@ -296,4 +296,9 @@ or firmware upgrades without running a new channel scan. Open Reference List + + The file is not a valid .zip archive. +TVs often export corrupted files to USB sticks formatted with the NTFS file system. +Please try exporting to a stick formatted with FAT32 + \ No newline at end of file diff --git a/source/ChanSort/ReferenceListForm.cs b/source/ChanSort/ReferenceListForm.cs index 3db13c7..0f31c86 100644 --- a/source/ChanSort/ReferenceListForm.cs +++ b/source/ChanSort/ReferenceListForm.cs @@ -76,10 +76,8 @@ namespace ChanSort.Ui if (main.DetectCommonFileCorruptions(dlg.FileName)) return null; - var plugin = dlg.FilterIndex <= main.Plugins.Count ? main.Plugins[dlg.FilterIndex - 1] : main.GetPluginForFile(dlg.FileName); - var ser = plugin.CreateSerializer(dlg.FileName); - ser.Load(); - return ser; + ISerializerPlugin hint = dlg.FilterIndex <= main.Plugins.Count ? main.Plugins[dlg.FilterIndex - 1] : null; + return main.GetSerializerForFile(dlg.FileName, ref hint); } } catch diff --git a/source/Test.Loader.Samsung/FileFormatDetectionTest.cs b/source/Test.Loader.Samsung/FileFormatDetectionTest.cs index e92ec4c..c633ef4 100644 --- a/source/Test.Loader.Samsung/FileFormatDetectionTest.cs +++ b/source/Test.Loader.Samsung/FileFormatDetectionTest.cs @@ -1,4 +1,5 @@ -using System.Text; +using System; +using System.IO; using ChanSort.Loader.Samsung; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -7,14 +8,41 @@ namespace Test.Loader.Samsung [TestClass] public class FileFormatDetectionTest { - private readonly StringBuilder errors = new StringBuilder(); - private const string RootPath = @"d:\sources\ChanSort\TestFiles_Samsung\"; + private static readonly string RootPath; + + static FileFormatDetectionTest() + { + RootPath = GetSolutionBaseDir() + @"\Test.Loader.Samsung\TestFiles\"; + } + + #region GetSolutionBaseDir() + protected static string GetSolutionBaseDir() + { + var dir = Path.GetDirectoryName(typeof(FileFormatDetectionTest).Assembly.Location); + do + { + if (File.Exists(dir + "\\ChanSort.sln")) + return dir; + dir = Path.GetDirectoryName(dir); + } while (!string.IsNullOrEmpty(dir)); + + dir = Environment.CurrentDirectory; + do + { + if (File.Exists(dir + "\\ChanSort.sln")) + return dir; + dir = Path.GetDirectoryName(dir); + } while (!string.IsNullOrEmpty(dir)); + + throw new InvalidOperationException("Cannot determine base directory of ChanSort solution"); + } + #endregion [TestMethod] public void LoadFileWithExcessiveHighFrequency_1() { // this seems to be a corrupt file caused by a buffer-overflow from analog channel names into the frequency data bytes - var s = new ScmSerializer(RootPath + @"ThomasSaur_DH\channel_list_UE55H6470_1201-Suchlauf-2015-04-26.scm"); + var s = new ScmSerializer(RootPath + @"channel_list_UE55H6470_1201-Suchlauf-2015-04-26.scm"); s.Load(); } @@ -22,7 +50,7 @@ namespace Test.Loader.Samsung public void LoadFileWithExcessiveHighFrequency_2() { // this seems to be a corrupt file caused by a buffer-overflow from analog channel names into the frequency data bytes - var s = new ScmSerializer(RootPath + @"ThomasSaur_DH\channel_list_UE55H6470_1201.scm"); + var s = new ScmSerializer(RootPath + @"channel_list_UE55H6470_1201.scm"); s.Load(); } @@ -30,7 +58,7 @@ namespace Test.Loader.Samsung public void LoadRenamedFile_HE40Cxxx_1201() { // This file uses the 1201 format (E,F,H,J), but has a "C" in its model name - var s = new ScmSerializer(RootPath + @"__C=F\Kinig\Reier Monika.scm"); + var s = new ScmSerializer(RootPath + @"E_format_with_C_model_name.scm"); s.Load(); Assert.AreEqual("E", s.Series); } @@ -39,7 +67,7 @@ namespace Test.Loader.Samsung public void LoadRenamedFile_LT24B_1201() { // This file uses the 1201 format (E,F,H,J), but has a "B" in its model name - var s = new ScmSerializer(RootPath + @"__B=F\DieterHerzberg_B\renamed.scm"); + var s = new ScmSerializer(RootPath + @"E_format_with_C_model_name.scm"); s.Load(); Assert.AreEqual("E", s.Series); } @@ -47,8 +75,8 @@ namespace Test.Loader.Samsung [TestMethod] public void LoadJSeriesWithScm1201Format() { - // J-series model with SCM format - var s = new ScmSerializer(RootPath + @"__J\HenryLoenwind_SCM\channel_list_UE32J5170_1201_orig.scm"); + // J-series model with E-J series SCM format + var s = new ScmSerializer(RootPath + @"channel_list_UE32J5170_1201_orig.scm"); s.Load(); Assert.AreEqual("E", s.Series); } diff --git a/source/Test.Loader.Samsung/TestFiles/E_format_with_B_model_name.scm b/source/Test.Loader.Samsung/TestFiles/E_format_with_B_model_name.scm new file mode 100644 index 0000000..7dc5d1a Binary files /dev/null and b/source/Test.Loader.Samsung/TestFiles/E_format_with_B_model_name.scm differ diff --git a/source/Test.Loader.Samsung/TestFiles/E_format_with_C_model_name.scm b/source/Test.Loader.Samsung/TestFiles/E_format_with_C_model_name.scm new file mode 100644 index 0000000..b081e51 Binary files /dev/null and b/source/Test.Loader.Samsung/TestFiles/E_format_with_C_model_name.scm differ diff --git a/source/Test.Loader.Samsung/TestFiles/channel_list_UE32J5170_1201_orig.scm b/source/Test.Loader.Samsung/TestFiles/channel_list_UE32J5170_1201_orig.scm new file mode 100644 index 0000000..026f66e Binary files /dev/null and b/source/Test.Loader.Samsung/TestFiles/channel_list_UE32J5170_1201_orig.scm differ diff --git a/source/Test.Loader.Samsung/TestFiles/channel_list_UE55H6470_1201-Suchlauf-2015-04-26.scm b/source/Test.Loader.Samsung/TestFiles/channel_list_UE55H6470_1201-Suchlauf-2015-04-26.scm new file mode 100644 index 0000000..e8db4dd Binary files /dev/null and b/source/Test.Loader.Samsung/TestFiles/channel_list_UE55H6470_1201-Suchlauf-2015-04-26.scm differ diff --git a/source/Test.Loader.Samsung/TestFiles/channel_list_UE55H6470_1201.scm b/source/Test.Loader.Samsung/TestFiles/channel_list_UE55H6470_1201.scm new file mode 100644 index 0000000..0237e23 Binary files /dev/null and b/source/Test.Loader.Samsung/TestFiles/channel_list_UE55H6470_1201.scm differ diff --git a/source/Translation.xlsx b/source/Translation.xlsx index 3fd4592..4b459bf 100644 Binary files a/source/Translation.xlsx and b/source/Translation.xlsx differ diff --git a/source/changelog.md b/source/changelog.md index 2021148..0aa0b8f 100644 --- a/source/changelog.md +++ b/source/changelog.md @@ -1,6 +1,18 @@ ChanSort Change Log =================== +2017-06-08 +- added experimental support for Loewe / Hisense 2017 servicelist.db + file format +- show error message when trying to open a .zip file that doen't contain + the expected files of a Samsung J series or Toshiba .zip channel list +- show error message when trying to open a broken .zip file, which is + most likely caused by exporting to a USB stick formatted with NTFS +- allow changing the "crypt" flag for Samsung .scm lists +- less reliable on file name / extension to detect file format + (trying all loaders which support the file extension until one can + successfully read the file) + 2017-01-26 - added Czech translation (thanks to Pavel Mizera) - fixed error when opening latest Hisense channel.db file format