- 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
- iterating through loaders supporting a file extension till one can read the file
This commit is contained in:
hbeham
2017-06-08 20:01:42 +02:00
parent 16b3f3fbc0
commit 6cf02f4f90
38 changed files with 560 additions and 427 deletions

View File

@@ -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;

View File

@@ -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()

View File

@@ -30,6 +30,11 @@ namespace ChanSort.Api
public int MaxChannelNameLength { get; set; }
public int PresetProgramNrCount { get; private set; }
public IList<string> VisibleColumnFieldNames;
/// <summary>
/// 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.
/// </summary>
public bool IsMixedSourceFavoritesList { get; set; }
#region Caption

View File

@@ -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

View File

@@ -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

View File

@@ -6,11 +6,10 @@ namespace ChanSort.Api
{
public static class Tools
{
public static V TryGet<K, V>(this IDictionary<K, V> dict, K key)
public static V TryGet<K, V>(this IDictionary<K, V> 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()

View File

@@ -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);

View File

@@ -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
}
}

View File

@@ -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<long, ChannelInfo> channelsById = new Dictionary<long, ChannelInfo>();
private readonly Dictionary<int, ChannelList> channelLists = new Dictionary<int, ChannelList>();
private ChannelList favlist;
private readonly Dictionary<int,int> favListIdToFavIndex = new Dictionary<int, int>();
private List<string> 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
*
*/
/// <summary>
/// list of all table names in the database
/// </summary>
private readonly List<string> tableNames = new List<string>();
/// <summary>
/// mapping of Service.Pid => ChannelInfo
/// </summary>
private readonly Dictionary<long, ChannelInfo> channelsById = new Dictionary<long, ChannelInfo>();
/// <summary>
/// mapping of FavoriteList.Pid => ChannelList.
/// This dict does not include real user favorite lists (FAV1-FAV4).
/// </summary>
private readonly Dictionary<int, ChannelList> channelLists = new Dictionary<int, ChannelList>();
/// <summary>
/// This list is filled with all channels/services and serves as a holder for favorite lists 1-4
/// </summary>
private readonly ChannelList userFavList = new ChannelList(SignalSource.All, "Favorites");
/// <summary>
/// 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.
/// </summary>
private readonly Dictionary<int,int> favListIdToFavIndex = new Dictionary<int, int>();
/// <summary>
/// FavoriteList.Pid of the $all list
/// </summary>
private int pidAll;
/// <summary>
/// FavoriteList.Pid of the $av list
/// </summary>
private int pidAv;
/// <summary>
/// Fields of the ChannelInfo that will be shown in the UI
/// </summary>
private static readonly List<string> ColumnNames = new List<string>
{
"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
/// <summary>
/// This class holds information from the Tuner table
/// </summary>
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<string>();
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<HisTransponder, SQLiteDataReader, int> 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<ChannelInfo, SQLiteDataReader, int> 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

View File

@@ -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
}
}

View File

@@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace ChanSort.Loader.Hisense {
namespace ChanSort.Loader.Hisense2017 {
using System;

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -63,6 +63,7 @@ namespace ChanSort.Loader.Samsung
this.ReadConfigurationFromIniFile();
this.Features.ChannelNameEdit = ChannelNameEditMode.All;
this.Features.CleanUpChannelData = true;
this.Features.EncryptedFlagEdit = true;
}
#endregion

View File

@@ -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)

View File

@@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ChanSort.Loader.SamsungJ</RootNamespace>
<AssemblyName>ChanSort.Loader.SamsungJ</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>

View File

@@ -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_"))

View File

@@ -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)

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -332,6 +332,42 @@
<Project>{DCCFFA08-472B-4D17-BB90-8F513FC01392}</Project>
<Name>ChanSort.Api</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.GlobalClone\ChanSort.Loader.GlobalClone.csproj">
<Project>{5361c8cb-f737-4709-af8c-e1f0456f3c5b}</Project>
<Name>ChanSort.Loader.GlobalClone</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.Hisense2017\ChanSort.Loader.Hisense2017.csproj">
<Project>{9282e1db-cd1f-400a-aca1-17e0c4562acf}</Project>
<Name>ChanSort.Loader.Hisense2017</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.Hisense\ChanSort.Loader.Hisense.csproj">
<Project>{d093e7ee-d3ad-4e7b-af82-c6918ca017fb}</Project>
<Name>ChanSort.Loader.Hisense</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.LG\ChanSort.Loader.LG.csproj">
<Project>{e972d8a1-2f5f-421c-ac91-cff45e5191be}</Project>
<Name>ChanSort.Loader.LG</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.Panasonic\ChanSort.Loader.Panasonic.csproj">
<Project>{68da8072-3a29-4076-9f64-d66f38349585}</Project>
<Name>ChanSort.Loader.Panasonic</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.SamsungJ\ChanSort.Loader.SamsungJ.csproj">
<Project>{33897002-0537-49a4-b963-a18d17311b3d}</Project>
<Name>ChanSort.Loader.SamsungJ</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.Samsung\ChanSort.Loader.Samsung.csproj">
<Project>{a1c9a98d-368a-44e8-9b7f-7eaca46c9ec5}</Project>
<Name>ChanSort.Loader.Samsung</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.Toshiba\ChanSort.Loader.Toshiba.csproj">
<Project>{f6f02792-07f1-48d5-9af3-f945ca5e3931}</Project>
<Name>ChanSort.Loader.Toshiba</Name>
</ProjectReference>
<ProjectReference Include="..\ChanSort.Loader.VDR\ChanSort.Loader.VDR.csproj">
<Project>{74a18c6f-09ff-413e-90d9-827066fa5b36}</Project>
<Name>ChanSort.Loader.VDR</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="app.ico" />

View File

@@ -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

View File

@@ -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<string> isoEncodings = new List<string>();
@@ -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<ISerializerPlugin> candidates = new List<ISerializerPlugin>();
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();
}

View File

@@ -211,7 +211,7 @@
</data>
<data name="colUid1.Caption" xml:space="preserve">
<value>UID</value>
<comment>@Invariant</comment></data>
</data>
<data name="colOutLock.Caption" xml:space="preserve">
<value>Lock</value>
</data>
@@ -373,7 +373,7 @@
</data>
<data name="grpSubList.Text" xml:space="preserve">
<value>Sub List</value>
<comment>@Invariant</comment></data>
</data>
<data name="grpSubList.Visible" type="System.Boolean, mscorlib">
<value>False</value>
</data>
@@ -466,37 +466,37 @@
</data>
<data name="miFavASet.Caption" xml:space="preserve">
<value>&amp;A</value>
<comment>@Invariant</comment></data>
</data>
<data name="miFavBSet.Caption" xml:space="preserve">
<value>&amp;B</value>
<comment>@Invariant</comment></data>
</data>
<data name="miFavCSet.Caption" xml:space="preserve">
<value>&amp;C</value>
<comment>@Invariant</comment></data>
</data>
<data name="miFavDSet.Caption" xml:space="preserve">
<value>&amp;D</value>
<comment>@Invariant</comment></data>
</data>
<data name="miFavESet.Caption" xml:space="preserve">
<value>&amp;E</value>
<comment>@Invariant</comment></data>
</data>
<data name="mnuFavUnset.Caption" xml:space="preserve">
<value>Remove from Favorites</value>
</data>
<data name="miFavAUnset.Caption" xml:space="preserve">
<value>&amp;A</value>
<comment>@Invariant</comment></data>
</data>
<data name="miFavBUnset.Caption" xml:space="preserve">
<value>&amp;B</value>
<comment>@Invariant</comment></data>
</data>
<data name="miFavCUnset.Caption" xml:space="preserve">
<value>&amp;C</value>
<comment>@Invariant</comment></data>
</data>
<data name="miFavDUnset.Caption" xml:space="preserve">
<value>&amp;D</value>
<comment>@Invariant</comment></data>
</data>
<data name="miFavEUnset.Caption" xml:space="preserve">
<value>&amp;E</value>
<comment>@Invariant</comment></data>
</data>
<data name="miLockOn.Caption" xml:space="preserve">
<value>&amp;Lock channel: on</value>
</data>
@@ -535,16 +535,16 @@
</data>
<data name="miEnglish.Caption" xml:space="preserve">
<value>&amp;English</value>
<comment>@Invariant</comment></data>
</data>
<data name="miGerman.Caption" xml:space="preserve">
<value>&amp;Deutsch</value>
<comment>@Invariant</comment></data>
</data>
<data name="miPortuguese.Caption" xml:space="preserve">
<value>Português</value>
<comment>@Invariant</comment></data>
</data>
<data name="miRussian.Caption" xml:space="preserve">
<value>ру́сский</value>
<comment>@Invariant</comment></data>
</data>
<data name="miCzech.Caption" xml:space="preserve">
<value>Česky</value>
</data>
@@ -580,7 +580,7 @@
</data>
<data name="mnuInputSource.Caption" xml:space="preserve">
<value>mnuInputSource</value>
<comment>@Invariant</comment></data>
</data>
<data name="mnuGotoFavList.AccessibleDescription" xml:space="preserve">
<value>Opens a submenu for the program of favorite list selection. This menu can be directly activated with the Shift+F1 key</value>
</data>
@@ -589,7 +589,7 @@
</data>
<data name="mnuFavList.Caption" xml:space="preserve">
<value>mnuFavList</value>
<comment>@Invariant</comment></data>
</data>
<data name="miSelectFavList0.Caption" xml:space="preserve">
<value>Program list</value>
</data>
@@ -1013,7 +1013,7 @@
</data>
<data name="txtSetSlot.Properties.Mask.EditMask" xml:space="preserve">
<value>\d{1,4}</value>
<comment>@Invariant</comment></data>
</data>
<data name="txtSetSlot.Properties.Mask.MaskType" type="DevExpress.XtraEditors.Mask.MaskType, DevExpress.XtraEditors.v15.2">
<value>RegEx</value>
</data>
@@ -1852,7 +1852,7 @@
<value>DevExpress.XtraEditors.XtraForm, DevExpress.Utils.v15.2, Version=15.2.10.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a</value>
</data>
<data name="SharedImageCollection.Timestamp" type="System.DateTime, mscorlib">
<value>01/26/2017 12:52:08</value>
<value>06/08/2017 17:07:00</value>
</data>
<data name="SharedImageCollection.ImageSize" type="System.Drawing.Size, System.Drawing">
<value>16, 16</value>
@@ -1898,7 +1898,7 @@
</data>
<data name="btnToggleFavE.Text" xml:space="preserve">
<value>±E</value>
<comment>@Invariant</comment></data>
</data>
<data name="&gt;&gt;btnToggleFavE.Name" xml:space="preserve">
<value>btnToggleFavE</value>
</data>
@@ -1925,7 +1925,7 @@
</data>
<data name="btnToggleFavD.Text" xml:space="preserve">
<value>±D</value>
<comment>@Invariant</comment></data>
</data>
<data name="&gt;&gt;btnToggleFavD.Name" xml:space="preserve">
<value>btnToggleFavD</value>
</data>
@@ -1952,7 +1952,7 @@
</data>
<data name="btnToggleFavC.Text" xml:space="preserve">
<value>±C</value>
<comment>@Invariant</comment></data>
</data>
<data name="&gt;&gt;btnToggleFavC.Name" xml:space="preserve">
<value>btnToggleFavC</value>
</data>
@@ -1979,7 +1979,7 @@
</data>
<data name="btnToggleFavB.Text" xml:space="preserve">
<value>±B</value>
<comment>@Invariant</comment></data>
</data>
<data name="&gt;&gt;btnToggleFavB.Name" xml:space="preserve">
<value>btnToggleFavB</value>
</data>
@@ -2006,7 +2006,7 @@
</data>
<data name="btnToggleFavA.Text" xml:space="preserve">
<value>±A</value>
<comment>@Invariant</comment></data>
</data>
<data name="&gt;&gt;btnToggleFavA.Name" xml:space="preserve">
<value>btnToggleFavA</value>
</data>
@@ -2213,7 +2213,7 @@
</data>
<data name="splitContainerControl1.Panel1.Text" xml:space="preserve">
<value>Panel1</value>
<comment>@Invariant</comment></data>
</data>
<data name="gridRight.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
@@ -2438,7 +2438,7 @@
</data>
<data name="colTransportStreamId.Caption" xml:space="preserve">
<value>TS ID</value>
<comment>@Invariant</comment></data>
</data>
<data name="colTransportStreamId.ToolTip" xml:space="preserve">
<value>Transport Stream ID</value>
</data>
@@ -2534,7 +2534,7 @@
</data>
<data name="colUid.Caption" xml:space="preserve">
<value>Uid</value>
<comment>@Invariant</comment></data>
</data>
<data name="colUid.Width" type="System.Int32, mscorlib">
<value>120</value>
</data>
@@ -2801,7 +2801,7 @@ specific provider, satellite or country lists.</value>
</data>
<data name="splitContainerControl1.Panel2.Text" xml:space="preserve">
<value>Panel2</value>
<comment>@Invariant</comment></data>
</data>
<data name="splitContainerControl1.Size" type="System.Drawing.Size, System.Drawing">
<value>1444, 459</value>
</data>
@@ -2810,7 +2810,7 @@ specific provider, satellite or country lists.</value>
</data>
<data name="splitContainerControl1.Text" xml:space="preserve">
<value>splitContainerControl1</value>
<comment>@Invariant</comment></data>
</data>
<data name="&gt;&gt;splitContainerControl1.Name" xml:space="preserve">
<value>splitContainerControl1</value>
</data>

View File

@@ -238,6 +238,17 @@ namespace ChanSort.Ui.Properties {
}
}
/// <summary>
/// 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.
/// </summary>
internal static string MainForm_LoadTll_InvalidZip {
get {
return ResourceManager.GetString("MainForm_LoadTll_InvalidZip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No plugin found to read/write {0} files..
/// </summary>

View File

@@ -292,4 +292,9 @@ Mögliche Ursachen sind USB-Sticks, die mit NTFS formatiert sind (FAT32 sollte i
<data name="ReferenceListForm_ShowOpenFileDialog_Title" xml:space="preserve">
<value>Wählen Sie eine Vorlagedatei für die Senderreihenfolge</value>
</data>
<data name="MainForm_LoadTll_InvalidZip" xml:space="preserve">
<value>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.</value>
</data>
</root>

View File

@@ -296,4 +296,9 @@ or firmware upgrades without running a new channel scan.
<data name="MainForm_ShowOpenReferenceFileDialog_Title" xml:space="preserve">
<value>Open Reference List</value>
</data>
<data name="MainForm_LoadTll_InvalidZip" xml:space="preserve">
<value>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</value>
</data>
</root>

View File

@@ -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

View File

@@ -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);
}

Binary file not shown.

View File

@@ -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