- merged projects so that there is one loader for each manufacturer

This commit is contained in:
Horst Beham
2021-01-23 14:22:18 +01:00
parent 4abe7a0b35
commit 086d4683fd
288 changed files with 466 additions and 10654 deletions

View File

@@ -26,6 +26,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTarget>x86</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -35,6 +36,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@@ -48,6 +50,7 @@
<CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
<CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>..\Release\</OutputPath>
@@ -60,34 +63,30 @@
<CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>
<CodeAnalysisFailOnMissingRules>true</CodeAnalysisFailOnMissingRules>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Data.SQLite">
<HintPath>..\DLL\System.Data.SQLite.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AnalogChannel.cs" />
<Compile Include="DigitalChannel.cs" />
<Compile Include="FavMode.cs" />
<Compile Include="ModelConstants.cs" />
<Compile Include="SamsungPlugin.cs" />
<Compile Include="Scm\AnalogChannel.cs" />
<Compile Include="Scm\DigitalChannel.cs" />
<Compile Include="Scm\ModelConstants.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resource.de.Designer.cs">
<DependentUpon>Resource.de.resx</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
<Compile Include="Resource.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Include="SatChannel.cs" />
<Compile Include="SatelliteMapping.cs" />
<Compile Include="ScmChannelBase.cs" />
<Compile Include="ScmSerializer.cs" />
<Compile Include="ScmSerializerPlugin.cs" />
<Compile Include="Scm\SatChannel.cs" />
<Compile Include="Scm\SatelliteMapping.cs" />
<Compile Include="Scm\ScmChannelBase.cs" />
<Compile Include="Scm\ScmSerializer.cs" />
<Compile Include="Zip\DbChannel.cs" />
<Compile Include="Zip\DbSerializer.cs" />
<Compile Include="Zip\Utf16InsideUtf8EnvelopeEncoding.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ChanSort.Api\ChanSort.Api.csproj">
@@ -100,18 +99,7 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resource.de.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.de.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ChanSort.Loader.Samsung
{
class FavMode
{
}
}

View File

@@ -1,63 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ChanSort.Loader.Samsung {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ChanSort.Loader.Samsung.Resource", typeof(Resource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,24 @@
using System.IO;
using ChanSort.Api;
using ChanSort.Loader.Samsung.Scm;
using ChanSort.Loader.Samsung.Zip;
namespace ChanSort.Loader.Samsung
{
public class SamsungPlugin : ISerializerPlugin
{
public string DllName { get; set; }
public string PluginName => "Samsung (*.scm, *.zip)";
public string FileFilter => "*.scm;*.zip";
public SerializerBase CreateSerializer(string inputFile)
{
var ext = Path.GetExtension(inputFile).ToLowerInvariant();
if (ext == ".scm")
return new ScmSerializer(inputFile);
if (ext == ".zip")
return new DbSerializer(inputFile);
return null;
}
}
}

View File

@@ -1,6 +1,6 @@
using ChanSort.Api;
namespace ChanSort.Loader.Samsung
namespace ChanSort.Loader.Samsung.Scm
{
internal class AnalogChannel : ScmChannelBase
{

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using ChanSort.Api;
namespace ChanSort.Loader.Samsung
namespace ChanSort.Loader.Samsung.Scm
{
public class DigitalChannel : ScmChannelBase
{

View File

@@ -1,6 +1,6 @@
using ChanSort.Api;
namespace ChanSort.Loader.Samsung
namespace ChanSort.Loader.Samsung.Scm
{
public enum FavoritesIndexMode
{

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using ChanSort.Api;
namespace ChanSort.Loader.Samsung
namespace ChanSort.Loader.Samsung.Scm
{
class SatChannel : ScmChannelBase
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Text;
namespace ChanSort.Loader.Samsung
namespace ChanSort.Loader.Samsung.Scm
{
internal class SatelliteMapping
{

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Text;
using ChanSort.Api;
namespace ChanSort.Loader.Samsung
namespace ChanSort.Loader.Samsung.Scm
{
public class ScmChannelBase : ChannelInfo
{

View File

@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using ChanSort.Api;
namespace ChanSort.Loader.Samsung
namespace ChanSort.Loader.Samsung.Scm
{
internal class ScmSerializer : SerializerBase
{

View File

@@ -1,16 +0,0 @@
using ChanSort.Api;
namespace ChanSort.Loader.Samsung
{
public class ScmSerializerPlugin : ISerializerPlugin
{
public string DllName { get; set; }
public string PluginName { get { return "Samsung B-H series"; } }
public string FileFilter { get { return "*.scm"; } }
public SerializerBase CreateSerializer(string inputFile)
{
return new ScmSerializer(inputFile);
}
}
}

View File

@@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Data.SQLite;
using ChanSort.Api;
namespace ChanSort.Loader.Samsung.Zip
{
internal class DbChannel : ChannelInfo
{
#region ctor()
internal DbChannel(SQLiteDataReader r, IDictionary<string, int> field, DbSerializer loader, Dictionary<long, string> providers, Satellite sat, Transponder tp)
{
var chType = r.GetInt32(field["chType"]);
this.SignalSource = DbSerializer.ChTypeToSignalSource(chType);
this.RecordIndex = r.GetInt64(field["SRV.srvId"]);
this.OldProgramNr = r.GetInt32(field["major"]);
this.FreqInMhz = (decimal)r.GetInt32(field["freq"]) / 1000;
this.ChannelOrTransponder =
(this.SignalSource & SignalSource.DvbT) == SignalSource.DvbT ? LookupData.Instance.GetDvbtTransponder(this.FreqInMhz).ToString() :
(this.SignalSource & SignalSource.DvbC) == SignalSource.DvbC ? LookupData.Instance.GetDvbcTransponder(this.FreqInMhz).ToString() :
(this.SignalSource & SignalSource.DvbS) == SignalSource.DvbS ? LookupData.Instance.GetAstraTransponder((int)this.FreqInMhz).ToString() :
"";
this.Name = loader.ReadUtf16(r, field["srvName"]);
this.Hidden = r.GetBoolean(field["hidden"]);
this.Encrypted = r.GetBoolean(field["scrambled"]);
this.Lock = r.GetBoolean(field["lockMode"]);
this.Skip = !r.GetBoolean(field["numSel"]);
if (sat != null)
{
this.Satellite = sat.Name;
this.SatPosition = sat.OrbitalPosition;
}
if (tp != null)
{
this.Transponder = tp;
this.SymbolRate = tp.SymbolRate;
this.Polarity = tp.Polarity;
}
if ((this.SignalSource & SignalSource.Digital) != 0)
this.ReadDvbData(r, field, loader, providers);
else
this.ReadAnalogData(r, field);
base.IsDeleted = this.OldProgramNr == -1;
}
#endregion
#region ReadAnalogData()
private void ReadAnalogData(SQLiteDataReader r, IDictionary<string, int> field)
{
}
#endregion
#region ReadDvbData()
protected void ReadDvbData(SQLiteDataReader r, IDictionary<string, int> field, DbSerializer loader, Dictionary<long, string> providers)
{
this.ShortName = loader.ReadUtf16(r, field["srvName"]);
this.RecordOrder = r.GetInt32(field["major"]);
int serviceType = r.GetInt32(field["srvType"]);
this.ServiceType = serviceType;
this.SignalSource |= LookupData.Instance.IsRadioTvOrData(serviceType);
this.OriginalNetworkId = r.GetInt32(field["onid"]);
this.TransportStreamId = r.GetInt32(field["tsid"]);
this.ServiceId = r.GetInt32(field["progNum"]);
this.VideoPid = r.GetInt32(field["vidPid"]);
if (!r.IsDBNull(field["provId"]))
this.Provider = providers.TryGet(r.GetInt64(field["provId"]));
this.AddDebug(r.GetInt32(field["lcn"]).ToString());
}
#endregion
}
}

View File

@@ -0,0 +1,643 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Text;
using ChanSort.Api;
namespace ChanSort.Loader.Samsung.Zip
{
/// <summary>
/// Loader for Samsung J/K/M/N/R/Q series .zip files (2015 - 2020)
/// </summary>
class DbSerializer : SerializerBase
{
private readonly Dictionary<long, DbChannel> channelById = new Dictionary<long, DbChannel>();
private readonly Dictionary<ChannelList, string> dbPathByChannelList = new Dictionary<ChannelList, string>();
private readonly List<string> tableNames = new List<string>();
private Encoding encoding;
private enum FileType { Unknown, SatDb, ChannelDbDvb, ChannelDbAnalog, ChannelDbIp }
#region ctor()
public DbSerializer(string inputFile) : base(inputFile)
{
DepencencyChecker.AssertVc2010RedistPackageX86Installed();
this.Features.ChannelNameEdit = ChannelNameEditMode.All;
this.Features.DeleteMode = DeleteMode.Physically;
this.Features.CanSkipChannels = true;
this.Features.CanLockChannels = true;
this.Features.CanHideChannels = true;
this.Features.SupportedFavorites = Favorites.A | Favorites.B | Favorites.C | Favorites.D | Favorites.E;
this.Features.SortedFavorites = true;
this.Features.AllowGapsInFavNumbers = true;
}
#endregion
#region Load()
public override void Load()
{
try
{
this.UnzipFileToTempFolder();
if (File.Exists(this.TempPath + "\\sat"))
{
try
{
using (var conn = new SQLiteConnection("Data Source=" + this.TempPath + "\\sat"))
{
conn.Open();
this.ReadSatDatabase(conn);
}
}
catch
{
}
}
var files = Directory.GetFiles(this.TempPath, "*.");
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_"))
continue;
try
{
using (var conn = new SQLiteConnection("Data Source=" + filePath))
{
FileType type;
conn.Open();
using (var cmd = conn.CreateCommand())
{
this.RepairCorruptedDatabaseImage(cmd);
type = this.DetectFileType(cmd);
}
switch (type)
{
case FileType.SatDb:
break;
case FileType.ChannelDbAnalog:
case FileType.ChannelDbDvb:
case FileType.ChannelDbIp:
ReadChannelDatabase(conn, filePath, type);
break;
}
}
}
catch
{
}
}
}
finally
{
// force closing the file and releasing the locks
SQLiteConnection.ClearAllPools();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
#endregion
#region RepairCorruptedDatabaseImage()
private void RepairCorruptedDatabaseImage(SQLiteCommand cmd)
{
cmd.CommandText = "REINDEX";
cmd.ExecuteNonQuery();
}
#endregion
#region DetectFileType()
private FileType DetectFileType(SQLiteCommand cmd)
{
this.tableNames.Clear();
cmd.CommandText = "select name from sqlite_master where type='table'";
using var r = cmd.ExecuteReader();
while (r.Read())
this.tableNames.Add(r.GetString(0).ToUpper());
if (tableNames.Contains("SAT") && tableNames.Contains("SAT_TP"))
return FileType.SatDb;
if (tableNames.Contains("CHNL") && tableNames.Contains("SRV") && tableNames.Contains("SRV_DVB"))
return FileType.ChannelDbDvb;
if (tableNames.Contains("CHNL") && tableNames.Contains("SRV") && tableNames.Contains("SRV_ANL"))
return FileType.ChannelDbAnalog;
if (tableNames.Contains("CHNL") && tableNames.Contains("SRV") && tableNames.Contains("SRV_IP"))
return FileType.ChannelDbIp;
return FileType.Unknown;
}
#endregion
#region ReadSatDatabase()
private void ReadSatDatabase(SQLiteConnection conn)
{
using (var cmd = conn.CreateCommand())
{
this.RepairCorruptedDatabaseImage(cmd);
this.ReadSatellites(cmd);
this.ReadTransponders(cmd);
}
}
#endregion
#region ReadSatellites()
private void ReadSatellites(SQLiteCommand cmd)
{
cmd.CommandText = "select distinct satId, cast(satName as blob), satPos, satDir from SAT";
using (var r = cmd.ExecuteReader())
{
while (r.Read())
{
Satellite sat = new Satellite(r.GetInt32(0));
int pos = Math.Abs(r.GetInt32(2));
// 171027 - ohuseyinoglu: For user-defined satellites, the direction may be -1
// (and not just 1 for "E", 0 for "W")
int dir = r.GetInt32(3);
sat.OrbitalPosition = $"{pos / 10}.{pos % 10}{(dir == 1 ? "E" : dir == 0 ? "W" : "")}";
sat.Name = ReadUtf16(r, 1);
this.DataRoot.AddSatellite(sat);
}
}
}
#endregion
#region ReadTransponders()
private void ReadTransponders(SQLiteCommand cmd)
{
cmd.CommandText = "select satId, tpFreq, tpPol, tpSr, tpId from SAT_TP";
using (var r = cmd.ExecuteReader())
{
while (r.Read())
{
// 171027 - ohuseyinoglu: tpId is the primary key of this table, we should be able to use it as "id/dict. index"
// It will also be our lookup value for the CHNL table
int id = r.GetInt32(4);
Transponder tp = new Transponder(id);
tp.FrequencyInMhz = (decimal)r.GetInt32(1) / 1000;
tp.Number = id;
tp.Polarity = r.GetInt32(2) == 0 ? 'H' : 'V';
tp.Satellite = this.DataRoot.Satellites.TryGet(r.GetInt32(0));
tp.SymbolRate = r.GetInt32(3);
this.DataRoot.AddTransponder(tp.Satellite, tp);
}
}
}
#endregion
#region ReadChannelDatabase()
private void ReadChannelDatabase(SQLiteConnection conn, string dbPath, FileType fileType)
{
this.channelById.Clear();
using (var cmd = conn.CreateCommand())
{
var providers = fileType == FileType.ChannelDbDvb ? this.ReadProviders(cmd) : null;
var channelList = this.ReadChannels(cmd, dbPath, providers, fileType);
this.ReadFavorites(cmd);
this.dbPathByChannelList.Add(channelList, dbPath);
}
}
#endregion
#region ReadProviders()
private Dictionary<long, string> ReadProviders(SQLiteCommand cmd)
{
var dict = new Dictionary<long, string>();
try
{
cmd.CommandText = "select provId, cast(provName as blob) from PROV";
var prevEncoding = this.encoding;
this.encoding = Encoding.BigEndianUnicode; // while Sat and Service names might be utf16 binary data inside an utf8 envelope, the providers are always plain utf16
using (var r = cmd.ExecuteReader())
{
while (r.Read())
dict.Add(r.GetInt64(0), ReadUtf16(r, 1));
}
this.encoding = prevEncoding;
}
catch
{
}
return dict;
}
#endregion
#region ReadChannels()
private ChannelList ReadChannels(SQLiteCommand cmd, string dbPath, Dictionary<long, string> providers, FileType fileType)
{
var signalSource = DetectSignalSource(cmd, fileType);
string name = Path.GetFileName(dbPath);
ChannelList channelList = this.CreateChannelList(signalSource, name);
string table = fileType == FileType.ChannelDbDvb ? "SRV_DVB" : fileType == FileType.ChannelDbAnalog ? "SRV_ANL" : "SRV_IP";
List<string> fieldNames = new List<string> {
"chType", "chNum", "freq", // CHNL
"SRV.srvId", "major", "progNum", "cast(srvName as blob) srvName", "srvType", "hidden", "scrambled", "lockMode", "numSel", "elim" // SRV
};
if (fileType == FileType.ChannelDbDvb)
fieldNames.AddRange(new[] {"onid", "tsid", "vidPid", "provId", "cast(shrtSrvName as blob) shrtSrvName", "lcn"}); // SRV_DVB
var sql = this.BuildQuery(table, fieldNames);
var fields = this.GetFieldMap(fieldNames);
cmd.CommandText = sql;
using (var r = cmd.ExecuteReader())
{
int prevNr = 0;
while (r.Read())
{
if (r.GetInt32(fields["elim"]) != 0)
continue;
// 171027 - ohuseyinoglu: With our change in transponder indexing, we can directly look it up by "chNum" now!
var tp = this.DataRoot.Transponder.TryGet(r.GetInt32(1));
// ... and get the satellite from that transponder - if set
// Note that we can have channels from multiple satellites in the same list, so this is a loop variable now
var sat = tp?.Satellite;
var channel = new DbChannel(r, fields, this, providers, sat, tp);
if (channel.OldProgramNr == prevNr) // when there is a SRV_EXT_APP table in the database, the service with the highest ext_app "recState" takes priority
continue;
this.DataRoot.AddChannel(channelList, channel);
this.channelById.Add(channel.RecordIndex, channel);
prevNr = channel.OldProgramNr;
}
}
this.DataRoot.AddChannelList(channelList);
return channelList;
}
#endregion
#region CreateChannelList()
private ChannelList CreateChannelList(SignalSource signalSource, string name)
{
var list = new ChannelList(signalSource, name);
if ((list.SignalSource & SignalSource.IP) != 0)
{
list.VisibleColumnFieldNames = new List<string>
{
"OldPosition", "Position", "PrNr", "Name", "Favorites", "SymbolRate"
};
}
return list;
}
#endregion
#region DetectSignalSource()
private static SignalSource DetectSignalSource(SQLiteCommand cmd, FileType fileType)
{
if (fileType == FileType.ChannelDbIp)
return SignalSource.IP|SignalSource.Digital;
var signalSource = fileType == FileType.ChannelDbAnalog ? SignalSource.Analog : SignalSource.Digital;
cmd.CommandText = "select distinct chType from CHNL";
using (var r = cmd.ExecuteReader())
{
if (r.Read())
{
var ss = ChTypeToSignalSource(r.GetInt32(0));
if (ss != 0)
signalSource = ss;
}
}
return signalSource;
}
#endregion
#region ChTypeToSignalSource()
internal static SignalSource ChTypeToSignalSource(int chType)
{
switch (chType)
{
case 1: return SignalSource.AnalogT;
case 2: return SignalSource.DvbT;
case 3: return SignalSource.AnalogC;
case 4: return SignalSource.DvbC;
case 7: return SignalSource.DvbS;
default: return 0;
}
}
#endregion
#region BuildQuery()
private string BuildQuery(string table, IList<string> fieldNames)
{
string sql = "select ";
for (int i = 0; i < fieldNames.Count; i++)
{
if (i > 0)
sql += ",";
sql += fieldNames[i];
}
sql += " from " + table + " inner join SRV on SRV.srvId="+table+".srvId inner join CHNL on CHNL.chId=SRV.chId";
if (this.tableNames.Contains("SRV_EXT_APP")) // in format 1352.0 there are duplicate "major" values in SRV and this recState seems to be the only difference
sql += " inner join SRV_EXT_APP on SRV_EXT_APP.srvId=SRV.srvId order by SRV.major, ifnull(SRV_EXT_APP.recState,0) desc";
return sql;
}
#endregion
#region GetFieldMap()
private IDictionary<string, int> GetFieldMap(IList<string> fieldNames)
{
Dictionary<string, int> field = new Dictionary<string, int>();
for (int i = 0; i < fieldNames.Count; i++)
{
var idx = fieldNames[i].LastIndexOf(' ') + 1;
field[fieldNames[i].Substring(idx)] = i;
}
return field;
}
#endregion
#region ReadFavorites()
private void ReadFavorites(SQLiteCommand cmd)
{
cmd.CommandText = "select srvId, fav, pos from SRV_FAV";
var r = cmd.ExecuteReader();
int favPosAdjust = tableNames.Contains("SRV_EXT_APP") ? 0 : 1;
while (r.Read())
{
var channel = this.channelById.TryGet(r.GetInt64(0));
if (channel == null)
continue;
int fav = r.GetInt32(1) - 1; // fav values start with 1 in the table
int pos = r.GetInt32(2) + favPosAdjust; // pos values start with 0 or 1
if (pos >= 0)
{
channel.Favorites |= (Favorites) (1 << fav);
channel.OldFavIndex[fav] = pos;
}
}
}
#endregion
#region ReadUtf16()
internal string ReadUtf16(SQLiteDataReader r, int fieldIndex)
{
if (r.IsDBNull(fieldIndex))
return null;
byte[] nameBytes = new byte[200];
int nameLen = (int)r.GetBytes(fieldIndex, 0, nameBytes, 0, nameBytes.Length);
this.encoding ??= AutoDetectUtf16Encoding(nameBytes, nameLen);
if (this.encoding == null)
return string.Empty;
return encoding.GetString(nameBytes, 0, nameLen).Replace("\0", ""); // remove trailing \0 characters found in Samsung "_T_..." channel list
}
#endregion
#region AutoDetectUtf16Endian()
private Encoding AutoDetectUtf16Encoding(byte[] nameBytes, int nameLen)
{
if (this.DefaultEncoding is UnicodeEncoding)
return this.DefaultEncoding;
int evenBytesZero = 0;
int oddBytesZero = 0;
int bytesAbove128 = 0;
for (int i = 0; i < nameLen; i += 2)
{
if (nameBytes[i] == 0)
++evenBytesZero;
if (nameBytes[i] >= 128)
++bytesAbove128;
if (nameBytes[i + 1] == 0)
++oddBytesZero;
if (nameBytes[i + 1] >= 128)
++bytesAbove128;
}
if (evenBytesZero + oddBytesZero == nameLen)
return null;
if (bytesAbove128 + 1 >= nameLen)
{
//this.Features.ChannelNameEdit = ChannelNameEditMode.None; // unclear if the encoder produces byte sequences that the TV can decode again
return new Utf16InsideUtf8EnvelopeEncoding();
}
return evenBytesZero >= oddBytesZero ? Encoding.BigEndianUnicode : Encoding.Unicode;
}
#endregion
#region DefaultEncoding
public override Encoding DefaultEncoding
{
get => base.DefaultEncoding;
set
{
if (!(value is UnicodeEncoding))
return;
var oldEncoding = base.DefaultEncoding;
if (oldEncoding != null)
{
// change encoding of channel names
foreach (var list in this.DataRoot.ChannelLists)
{
foreach (var chan in list.Channels)
{
byte[] bytes;
if (chan.Name != null)
{
bytes = oldEncoding.GetBytes(chan.Name);
chan.Name = value.GetString(bytes);
}
if (chan.ShortName != null)
{
bytes = oldEncoding.GetBytes(chan.ShortName);
chan.ShortName = value.GetString(bytes);
}
}
}
}
base.DefaultEncoding = value;
}
}
#endregion
#region Save()
public override void Save(string tvOutputFile)
{
foreach (var channelList in this.DataRoot.ChannelLists)
{
var dbPath = this.dbPathByChannelList[channelList];
SaveChannelList(channelList, dbPath);
}
// force closing the file and releasing the locks
SQLiteConnection.ClearAllPools();
GC.Collect();
GC.WaitForPendingFinalizers();
this.ZipToOutputFile(tvOutputFile);
}
#endregion
#region SaveChannelList()
private void SaveChannelList(ChannelList channelList, string dbPath)
{
using var conn = new SQLiteConnection("Data Source=" + dbPath);
conn.Open();
using var cmdUpdateSrv = PrepareUpdateCommand(conn);
using var cmdDeleteSrv = PrepareDeleteCommand(conn, (channelList.SignalSource & SignalSource.Digital) != 0);
using var cmdInsertFav = PrepareInsertFavCommand(conn);
using var cmdUpdateFav = PrepareUpdateFavCommand(conn);
using var cmdDeleteFav = PrepareDeleteFavCommand(conn);
using (var trans = conn.BeginTransaction())
{
Editor.SequentializeFavPos(channelList, 5);
this.WriteChannels(cmdUpdateSrv, cmdDeleteSrv, cmdInsertFav, cmdUpdateFav, cmdDeleteFav, channelList);
trans.Commit();
}
this.RepairCorruptedDatabaseImage(cmdUpdateSrv);
}
#endregion
#region Prepare*Command()
private SQLiteCommand PrepareUpdateCommand(SQLiteConnection conn)
{
var canUpdateNames = this.Features.ChannelNameEdit != ChannelNameEditMode.None;
var cmd = conn.CreateCommand();
var updateSrvName = canUpdateNames ? ", srvName=cast(@srvname as varchar)" : "";
cmd.CommandText = "update SRV set major=@nr, lockMode=@lock, hideGuide=@hidden, hidden=@hidden, numSel=@numsel" + updateSrvName + " where srvId=@id";
cmd.Parameters.Add(new SQLiteParameter("@id", DbType.Int64));
cmd.Parameters.Add(new SQLiteParameter("@nr", DbType.Int32));
cmd.Parameters.Add(new SQLiteParameter("@lock", DbType.Boolean));
cmd.Parameters.Add(new SQLiteParameter("@hidden", DbType.Boolean));
cmd.Parameters.Add(new SQLiteParameter("@numsel", DbType.Boolean));
if (canUpdateNames)
cmd.Parameters.Add(new SQLiteParameter("@srvname", DbType.Binary));
cmd.Prepare();
return cmd;
}
private SQLiteCommand PrepareDeleteCommand(SQLiteConnection conn, bool digital)
{
var cmd = conn.CreateCommand();
var sql = new StringBuilder();
cmd.CommandText = "select name from sqlite_master where sql like '%srvId integer%' order by name desc";
using (var r = cmd.ExecuteReader())
{
while (r.Read())
sql.AppendLine($"; delete from {r.GetString(0)} where srvId=@id");
}
cmd.CommandText = sql.ToString();
cmd.Parameters.Add(new SQLiteParameter("@id", DbType.Int64));
cmd.Prepare();
return cmd;
}
private SQLiteCommand PrepareInsertFavCommand(SQLiteConnection conn)
{
var cmd = conn.CreateCommand();
cmd.CommandText = "insert into SRV_FAV (srvId, fav, pos) values (@id, @fav, @pos)";
cmd.Parameters.Add(new SQLiteParameter("@id", DbType.Int64));
cmd.Parameters.Add(new SQLiteParameter("@fav", DbType.Int32));
cmd.Parameters.Add(new SQLiteParameter("@pos", DbType.Int32));
cmd.Prepare();
return cmd;
}
private SQLiteCommand PrepareUpdateFavCommand(SQLiteConnection conn)
{
var cmd = conn.CreateCommand();
cmd.CommandText = "update SRV_FAV set pos=@pos where srvId=@id and fav=@fav";
cmd.Parameters.Add(new SQLiteParameter("@id", DbType.Int64));
cmd.Parameters.Add(new SQLiteParameter("@fav", DbType.Int32));
cmd.Parameters.Add(new SQLiteParameter("@pos", DbType.Int32));
cmd.Prepare();
return cmd;
}
private SQLiteCommand PrepareDeleteFavCommand(SQLiteConnection conn)
{
var cmd = conn.CreateCommand();
cmd.CommandText = "delete from SRV_FAV where srvId=@id and fav=@fav";
cmd.Parameters.Add(new SQLiteParameter("@id", DbType.Int64));
cmd.Parameters.Add(new SQLiteParameter("@fav", DbType.Int32));
cmd.Prepare();
return cmd;
}
#endregion
#region WriteChannels()
private void WriteChannels(SQLiteCommand cmdUpdateSrv, SQLiteCommand cmdDeleteSrv, SQLiteCommand cmdInsertFav, SQLiteCommand cmdUpdateFav, SQLiteCommand cmdDeleteFav,
ChannelList channelList, bool analog = false)
{
bool canUpdateNames = this.Features.ChannelNameEdit != ChannelNameEditMode.None;
foreach (ChannelInfo channelInfo in channelList.Channels.ToList())
{
var channel = channelInfo as DbChannel;
if (channel == null) // ignore reference list proxy channels
continue;
if (channel.IsDeleted)
{
// delete channel from all tables that have a reference to srvId
cmdDeleteSrv.Parameters["@id"].Value = channel.RecordIndex;
cmdDeleteSrv.ExecuteNonQuery();
channelList.Channels.Remove(channelInfo);
continue;
}
// update channel record
cmdUpdateSrv.Parameters["@id"].Value = channel.RecordIndex;
cmdUpdateSrv.Parameters["@nr"].Value = channel.NewProgramNr;
cmdUpdateSrv.Parameters["@lock"].Value = channel.Lock;
cmdUpdateSrv.Parameters["@hidden"].Value = channel.Hidden;
cmdUpdateSrv.Parameters["@numsel"].Value = !channel.Skip;
if (canUpdateNames)
cmdUpdateSrv.Parameters["@srvname"].Value = channel.Name == null ? (object)DBNull.Value : encoding.GetBytes(channel.Name);
cmdUpdateSrv.ExecuteNonQuery();
// update favorites
for (int i=0, mask=1; i<5; i++, mask <<= 1)
{
int oldPos = channel.OldFavIndex[i];
int newPos = ((int)channel.Favorites & mask) != 0 ? channel.FavIndex[i] : -1;
if (newPos >= 0)
{
var c = oldPos < 0 ? cmdInsertFav : cmdUpdateFav;
c.Parameters["@id"].Value = channel.RecordIndex;
c.Parameters["@fav"].Value = i + 1;
c.Parameters["@pos"].Value = newPos - 1;
c.ExecuteNonQuery();
}
else
{
cmdDeleteFav.Parameters["@id"].Value = channel.RecordIndex;
cmdDeleteFav.Parameters["@fav"].Value = i + 1;
cmdDeleteFav.ExecuteNonQuery();
}
channel.FavIndex[i] = newPos;
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,100 @@
using System.IO;
using System.Text;
namespace ChanSort.Loader.Samsung.Zip
{
// Samsung 1242 format does not store UTF16 characters directly, but instead wraps 16 data bits inside a UTF-8 lead + continuation byte sequence.
// A 3 byte UTF-8 sequence is used to encode 16 bits of utf-16 big endian input: 1110aaaa 10bbbbcc 10ccdddd represents the 16bit big endian integer ccccddddaaaabbbb, i.e. 0xE4, 0x84, 0x80 => 0x00, 0x41 => "A" in UTF-16 BE
// The Samsung encoder seems to create some illegal UTF-8 sequences at the end of the string as a result of padding and operating on 32bit inputs (2 characters) with big-endianness, which
// this decoder has to take care of. 0xFFFD can appear both in the raw input bytes (0xFF, 0xFB) as well as already encoded into UTF-8 wrappings (0xEF,0xBF,0xBD)
// This implementation here decodes the UTF-8 byte sequence into UTF-16 Little Endian for the sake of simplicity: aaaa=4, bbbb=1, cccc=0, dddd=0 => 0xE4, 0x84, 0x80 => 0x41, 0x00 => "A" in UTF-16 LE.
// The encoder here operates on 16bit characters and not 32bit 2-characters, so there is no need for padding and no invalid UTF-8 sequences.
public class Utf16InsideUtf8EnvelopeEncoding : Encoding
{
public override int GetMaxByteCount(int charCount)
{
return charCount * 3;
}
public override int GetByteCount(char[] chars, int index, int count)
{
return count * 3;
}
public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
{
var utf16Le = Unicode.GetBytes(chars, charIndex, charCount);
int o = byteIndex;
int c = utf16Le.Length;
for (int i = 0; i < c; i += 2, o += 3)
{
var b0 = utf16Le[i + 0];
var b1 = utf16Le[i + 1];
bytes[o + 0] = (byte) (0xE0 + (b0 >> 4));
bytes[o + 1] = (byte) (0x80 + ((b0 & 0x0F) << 2) + (b1 >> 6));
bytes[o + 2] = (byte) (0x80 + (b1 & 0x3F));
}
return charCount * 3;
}
public override int GetMaxCharCount(int byteCount)
{
return (byteCount + 2) / 3;
}
public override int GetCharCount(byte[] bytes, int index, int count)
{
return (count + 2) / 3;
}
public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
{
using MemoryStream ms = new MemoryStream(40);
for (int i = byteIndex, c = byteIndex + byteCount; i < c; i++)
{
int b0 = bytes[i + 0];
if (b0 == 0 && i == c - 1) // satellite names end with a single trailing 0x00 byte
break;
if (b0 > 0xF7) // invalid UTF-8 lead byte. (0xFF, 0xFD) = 0xFFFD in BigEndian can appear unencoded at the end of the byte stream, likely as a padding
continue;
if (b0 >= 0xE0) // 3-byte UTF envelope for 2 input bytes
{
int b1 = bytes[i + 1];
int b2 = bytes[i + 2];
if ((b2 & 0xC0) != 0x80) // invalid 2nd UTF-8 continuation byte; only a single byte is encoded as 1110aaaa 10bbbbcc => aaaabbbb
{
b2 = 0;
--i;
}
int ch1 = ((b0 & 0x0F) << 4) | ((b1 & 0x3C) >> 2);
int ch2 = ((b1 & 0x03) << 6) | (b2 & 0x3F);
if (ch1 != 0xFF || ch2 != 0xFD) // ignore UTF-16 "replacement character" U-0xFFFD
{
ms.WriteByte((byte) ch1);
ms.WriteByte((byte) ch2);
}
i += 2;
}
else if (b0 >= 0xC0) // 2-byte UTF envelope for 1 input byte as 110xaaaa 10bbbbcc => aaaabbbb
{
int b1 = bytes[i + 1];
int ch = ((b0 & 0x0F) << 4) | ((b1 & 0x3C)>>2);
ms.WriteByte((byte)ch);
ms.WriteByte(0);
i++;
}
else if (b0 < 0x80) // 1-byte UTF envelope for 1 input byte < 0x80
{
ms.WriteByte(bytes[i]);
ms.WriteByte(0);
}
}
return Encoding.Unicode.GetChars(ms.GetBuffer(), 0, (int) ms.Length, chars, charIndex);
}
}
}