diff --git a/source/ChanSort.Api/Controller/SerializerBase.cs b/source/ChanSort.Api/Controller/SerializerBase.cs
index 89504443..7ee7d029 100644
--- a/source/ChanSort.Api/Controller/SerializerBase.cs
+++ b/source/ChanSort.Api/Controller/SerializerBase.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
@@ -27,6 +28,7 @@ public class SupportedFeatures
public bool CanHaveGaps { get; set; } = true;
public bool EncryptedFlagEdit { get; set; }
public DeleteMode DeleteMode { get; set; } = DeleteMode.NotSupported;
+ public bool CanSaveAs { get; set; } = true;
public Favorites SupportedFavorites { get; set; } = Favorites.A | Favorites.B | Favorites.C | Favorites.D;
@@ -39,7 +41,7 @@ public class SupportedFeatures
private Encoding defaultEncoding;
- public string FileName { get; set; }
+ public string FileName { get; protected set; }
public DataRoot DataRoot { get; protected set; }
public SupportedFeatures Features { get; } = new SupportedFeatures();
@@ -59,6 +61,16 @@ public virtual Encoding DefaultEncoding
set { this.defaultEncoding = value; }
}
+ #region GetDataFilePaths
+ ///
+ /// returns the list of all data files that need to be copied for backup/restore
+ ///
+ public virtual IEnumerable GetDataFilePaths()
+ {
+ return new List { this.FileName };
+ }
+ #endregion
+
#region GetFileInformation()
public virtual string GetFileInformation()
{
@@ -192,6 +204,5 @@ protected long ParseLong(string input)
return 0;
}
#endregion
-
}
}
diff --git a/source/ChanSort.Loader.PhilipsXml/Serializer.cs b/source/ChanSort.Loader.PhilipsXml/Serializer.cs
index c5cce809..1dd40126 100644
--- a/source/ChanSort.Loader.PhilipsXml/Serializer.cs
+++ b/source/ChanSort.Loader.PhilipsXml/Serializer.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
+using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Schema;
@@ -31,17 +31,19 @@ class Serializer : SerializerBase
private readonly ChannelList cableChannels = new ChannelList(SignalSource.DvbC, "DVB-C");
private readonly ChannelList satChannels = new ChannelList(SignalSource.DvbS, "DVB-S");
- private XmlDocument doc;
- private byte[] content;
- private string textContent;
- private string newline;
- private int formatVersion;
+ private readonly List fileDataList = new List();
+ //private XmlDocument doc;
+ //private byte[] content;
+ //private string textContent;
+ //private string newline;
+ //private int formatVersion;
#region ctor()
public Serializer(string inputFile) : base(inputFile)
{
this.Features.ChannelNameEdit = ChannelNameEditMode.All;
this.Features.DeleteMode = DeleteMode.Physically;
+ this.Features.CanSaveAs = false;
this.DataRoot.AddChannelList(this.terrChannels);
this.DataRoot.AddChannelList(this.cableChannels);
@@ -59,18 +61,61 @@ public Serializer(string inputFile) : base(inputFile)
}
#endregion
-
#region Load()
-
public override void Load()
+ {
+ // read all files from a directory structure that looks like
+ // ./CM_TPM1013E_LA_CK.xml
+ // - or -
+ // ChannelMap_100/ChannelList/channellib/DVBC.xml
+ // ChannelMap_100/ChannelList/channellib/DVBT.xml
+ // ChannelMap_100/ChannelList/s2channellib/DVBS.xml
+ // ChannelMap_100/ChannelList/s2channellib/DVBSall.xml
+ // ChannelMap_100/ChannelList/chanLst.bin
+ // + optionally
+ // ChannelMap_100/ChannelList/channelFile.bin
+ // ChannelMap_100/ChannelList/Favorite.xml
+ // ChannelMap_100/ChannelList/satInfo.bin
+
+ var dataFiles = new[] { @"channellib\DVBC.xml", @"channellib\DVBT.xml", @"s2channellib\DVBS.xml", @"s2channellib\DVBSall.xml" };
+
+ // support for files in a ChannelMap_xxx directory structure
+ var dir = Path.GetDirectoryName(this.FileName);
+ var dirName = Path.GetFileName(dir).ToLower();
+ if (dirName == "channellib" || dirName == "s2channellib")
+ dir = Path.GetDirectoryName(dir);
+
+ var binFile = Path.Combine(dir, "chanLst.bin"); // the .bin file is used as a proxy for the whole directory structure
+ if (File.Exists(binFile))
+ {
+ this.FileName = binFile;
+ foreach (var file in dataFiles)
+ {
+ var fullPath = Path.GetFullPath(Path.Combine(dir, file));
+ this.LoadFile(fullPath);
+ }
+
+ return;
+ }
+
+ // otherwise load the single file that was originally selected by the user
+ LoadFile(this.FileName);
+ }
+ #endregion
+
+ #region LoadFile()
+
+ private void LoadFile(string fileName)
{
bool fail = false;
+ var fileData = new FileData();
try
{
- this.doc = new XmlDocument();
- this.content = File.ReadAllBytes(this.FileName);
- this.textContent = Encoding.UTF8.GetString(this.content);
- this.newline = this.textContent.Contains("\r\n") ? "\r\n" : "\n";
+ fileData.path = fileName;
+ fileData.doc = new XmlDocument();
+ fileData.content = File.ReadAllBytes(fileName);
+ fileData.textContent = Encoding.UTF8.GetString(fileData.content);
+ fileData.newline = fileData.textContent.Contains("\r\n") ? "\r\n" : "\n";
var settings = new XmlReaderSettings
{
@@ -79,9 +124,9 @@ public override void Load()
ValidationFlags = XmlSchemaValidationFlags.None,
DtdProcessing = DtdProcessing.Ignore
};
- using (var reader = XmlReader.Create(new StringReader(textContent), settings))
+ using (var reader = XmlReader.Create(new StringReader(fileData.textContent), settings))
{
- doc.Load(reader);
+ fileData.doc.Load(reader);
}
}
catch
@@ -89,11 +134,11 @@ public override void Load()
fail = true;
}
- var root = doc.FirstChild;
+ var root = fileData.doc.FirstChild;
if (root is XmlDeclaration)
root = root.NextSibling;
if (fail || root == null || root.LocalName != "ChannelMap")
- throw new FileLoadException("\"" + this.FileName + "\" is not a supported Philips XML file");
+ throw new FileLoadException("\"" + fileName + "\" is not a supported Philips XML file");
int rowId = 0;
@@ -104,18 +149,19 @@ public override void Load()
{
case "Channel":
if (rowId == 0)
- curList = this.DetectFormatAndFeatures(child);
+ curList = this.DetectFormatAndFeatures(fileData, child);
if (curList != null)
- this.ReadChannel(curList, child, rowId++);
+ this.ReadChannel(fileData, curList, child, rowId++);
break;
}
}
+ this.fileDataList.Add(fileData);
}
#endregion
#region DetectFormatAndFeatures()
- private ChannelList DetectFormatAndFeatures(XmlNode node)
+ private ChannelList DetectFormatAndFeatures(FileData file, XmlNode node)
{
var setupNode = node["Setup"] ?? throw new FileLoadException("Missing Setup XML element");
var bcastNode = node["Broadcast"] ?? throw new FileLoadException("Missing Broadcast XML element");
@@ -128,7 +174,7 @@ private ChannelList DetectFormatAndFeatures(XmlNode node)
if (setupNode.HasAttribute("ChannelName"))
{
- this.formatVersion = 1;
+ file.formatVersion = 1;
this.Features.SupportedFavorites = Favorites.A;
this.Features.SortedFavorites = true;
@@ -144,7 +190,7 @@ private ChannelList DetectFormatAndFeatures(XmlNode node)
}
else if (setupNode.HasAttribute("name"))
{
- this.formatVersion = 2;
+ file.formatVersion = 2;
this.Features.SupportedFavorites = 0;
this.Features.SortedFavorites = false;
foreach (var list in this.DataRoot.ChannelLists)
@@ -182,7 +228,7 @@ private ChannelList DetectFormatAndFeatures(XmlNode node)
#endregion
#region ReadChannel()
- private void ReadChannel(ChannelList curList, XmlNode node, int rowId)
+ private void ReadChannel(FileData file, ChannelList curList, XmlNode node, int rowId)
{
var setupNode = node["Setup"] ?? throw new FileLoadException("Missing Setup XML element");
var bcastNode = node["Broadcast"] ?? throw new FileLoadException("Missing Broadcast XML element");
@@ -196,9 +242,9 @@ private void ReadChannel(ChannelList curList, XmlNode node, int rowId)
var chan = new Channel(curList.SignalSource & SignalSource.MaskAdInput, rowId, rowId, setupNode);
chan.OldProgramNr = -1;
chan.IsDeleted = false;
- if (this.formatVersion == 1)
+ if (file.formatVersion == 1)
this.ParseChannelFormat1(data, chan);
- else if (this.formatVersion == 2)
+ else if (file.formatVersion == 2)
this.ParseChannelFormat2(data, chan);
if ((chan.SignalSource & SignalSource.MaskAdInput) == SignalSource.DvbT)
@@ -226,7 +272,9 @@ private void ParseChannelFormat1(Dictionary data, Channel chan)
chan.TransportStreamId = ParseInt(data.TryGet("Tsid"));
chan.ServiceId = ParseInt(data.TryGet("Sid"));
chan.FreqInMhz = ParseInt(data.TryGet("Frequency")); ;
- if (chan.FreqInMhz > 100000)
+ if (chan.FreqInMhz > 2000)
+ chan.FreqInMhz /= 1000;
+ if (chan.FreqInMhz > 2000)
chan.FreqInMhz /= 1000;
chan.ServiceType = ParseInt(data.TryGet("ServiceType"));
chan.SignalSource |= LookupData.Instance.IsRadioTvOrData(chan.ServiceType);
@@ -246,7 +294,7 @@ private void ParseChannelFormat2(Dictionary data, Channel chan)
chan.Name = data.TryGet("name");
chan.RawName = chan.Name;
chan.FreqInMhz = ParseInt(data.TryGet("frequency"));
- if (chan.FreqInMhz > 100000)
+ if (chan.FreqInMhz > 2000)
chan.FreqInMhz /= 1000;
chan.ServiceId = ParseInt(data.TryGet("serviceID"));
chan.OriginalNetworkId = ParseInt(data.TryGet("ONID"));
@@ -306,11 +354,31 @@ private void ChangeEncoding()
}
#endregion
+ #region GetDataFilePaths()
+ public override IEnumerable GetDataFilePaths()
+ {
+ return this.fileDataList.Select(f => f.path);
+ }
+ #endregion
+
+
+
#region Save()
+
public override void Save(string tvOutputFile)
+ {
+ // "Save As..." is not supported by this loader
+ foreach(var file in this.fileDataList)
+ this.SaveFile(file);
+ }
+
+ #endregion
+
+ #region SaveFile()
+ private void SaveFile(FileData file)
{
foreach (var list in this.DataRoot.ChannelLists)
- this.UpdateChannelList(list);
+ this.UpdateChannelList(file, list);
// by default .NET reformats the whole XML. These settings produce almost same format as the TV xml files use
var xmlSettings = new XmlWriterSettings();
@@ -319,25 +387,25 @@ public override void Save(string tvOutputFile)
xmlSettings.Indent = true;
xmlSettings.IndentChars = "";
xmlSettings.NewLineHandling = NewLineHandling.None;
- xmlSettings.NewLineChars = this.newline;
+ xmlSettings.NewLineChars = file.newline;
xmlSettings.OmitXmlDeclaration = false;
string xml;
using (var sw = new StringWriter())
using (var w = new CustomXmlWriter(sw, xmlSettings, false))
{
- this.doc.WriteTo(w);
+ file.doc.WriteTo(w);
w.Flush();
xml = sw.ToString();
}
var enc = new UTF8Encoding(false, false);
- File.WriteAllText(tvOutputFile, xml, enc);
+ File.WriteAllText(file.path, xml, enc);
}
#endregion
#region UpdateChannelList()
- private void UpdateChannelList(ChannelList list)
+ private void UpdateChannelList(FileData file, ChannelList list)
{
foreach (var channel in list.Channels)
{
@@ -351,9 +419,9 @@ private void UpdateChannelList(ChannelList list)
continue;
}
- if (this.formatVersion == 1)
+ if (file.formatVersion == 1)
this.UpdateChannelFormat1(ch);
- else if (this.formatVersion == 2)
+ else if (file.formatVersion == 2)
this.UpdateChannelFormat2(ch);
}
}
@@ -386,5 +454,18 @@ private string EncodeName(string name)
return sb.ToString();
}
#endregion
+
+
+ #region class FileData
+ private class FileData
+ {
+ public string path;
+ public XmlDocument doc;
+ public byte[] content;
+ public string textContent;
+ public string newline;
+ public int formatVersion;
+ }
+ #endregion
}
}
diff --git a/source/ChanSort.Loader.PhilipsXml/SerializerPlugin.cs b/source/ChanSort.Loader.PhilipsXml/SerializerPlugin.cs
index c47b78ac..e372e8db 100644
--- a/source/ChanSort.Loader.PhilipsXml/SerializerPlugin.cs
+++ b/source/ChanSort.Loader.PhilipsXml/SerializerPlugin.cs
@@ -6,7 +6,7 @@ public class SerializerPlugin : ISerializerPlugin
{
public string DllName { get; set; }
public string PluginName => "Philips .xml";
- public string FileFilter => "*.xml";
+ public string FileFilter => "*.xml;*.bin";
public SerializerBase CreateSerializer(string inputFile)
{
diff --git a/source/ChanSort/MainForm.cs b/source/ChanSort/MainForm.cs
index 6e921680..98a5c5f4 100644
--- a/source/ChanSort/MainForm.cs
+++ b/source/ChanSort/MainForm.cs
@@ -302,6 +302,7 @@ private void LoadFiles(ISerializerPlugin plugin, string tvDataFile)
//this.SetControlsEnabled(!this.dataRoot.IsEmpty);
this.UpdateFavoritesEditor(this.DataRoot.SupportedFavorites);
this.colEncrypted.OptionsColumn.AllowEdit = this.currentTvSerializer.Features.EncryptedFlagEdit;
+ this.UpdateMenu(true);
if (this.DataRoot.Warnings.Length > 0 && this.miShowWarningsAfterLoad.Checked)
this.BeginInvoke((Action) this.ShowFileInformation);
@@ -336,6 +337,9 @@ private void LoadFiles(ISerializerPlugin plugin, string tvDataFile)
internal bool DetectCommonFileCorruptions(string tvDataFile)
{
+ if (!File.Exists(tvDataFile)) // a loader (like Philips) may use internal file names that don't match the one in the UI, i.e. tvDataFile might be a directory path
+ return true;
+
var content = File.ReadAllBytes(tvDataFile);
var isAllZero = true;
for (int i = 0, c = content.Length; i < c; i++)
@@ -558,11 +562,11 @@ private bool LoadTvDataFile(ISerializerPlugin plugin, string tvDataFile)
this.currentTvSerializer?.Dispose();
serializer.DataRoot.ValidateAfterLoad();
- this.SetFileName(tvDataFile);
+ this.SetFileName(serializer.FileName);
this.currentPlugin = plugin;
this.currentTvSerializer = serializer;
this.DataRoot = serializer.DataRoot;
- this.AddFileToMruList(this.currentTvFile);
+ this.AddFileToMruList(tvDataFile);
this.UpdateMruMenu();
return true;
@@ -740,7 +744,7 @@ private void UpdateGridReadOnly()
private void ShowSaveFileDialog()
{
- var extension = Path.GetExtension(this.currentTvSerializer.FileName) ?? ".";
+ var extension = Path.GetExtension(this.currentTvFile) ?? ".";
using (var dlg = new SaveFileDialog())
{
dlg.InitialDirectory = Path.GetDirectoryName(this.currentTvFile);
@@ -778,7 +782,7 @@ private void SaveFiles()
this.SaveTvDataFile();
this.DataRoot.NeedsSaving = false;
this.RefreshGrid(this.gviewLeft, this.gviewRight);
- this.UpdateMenu();
+ this.UpdateMenu(true);
}
catch (IOException ex)
{
@@ -926,13 +930,16 @@ private void SaveTvDataFile()
this.splashScreenManager1.ShowWaitForm();
try
{
- // create backup file if none exists
- if (File.Exists(currentTvFile))
+ foreach (var filePath in this.currentTvSerializer.GetDataFilePaths())
{
- var bakFile = currentTvFile + ".bak";
- if (!File.Exists(bakFile))
- File.Copy(currentTvFile, bakFile);
+ if (File.Exists(filePath))
+ {
+ var bakFile = filePath + ".bak";
+ if (!File.Exists(bakFile))
+ File.Copy(filePath, bakFile);
+ }
}
+
this.currentTvSerializer.Save(this.currentTvFile);
this.DataRoot.ValidateAfterSave();
}
@@ -1587,7 +1594,7 @@ private void SetActiveGrid(GridView grid)
#region UpdateMenu
- private void UpdateMenu()
+ private void UpdateMenu(bool afterFileLoad = false)
{
var fileLoaded = this.DataRoot != null;
var isRight = this.lastFocusedGrid == this.gviewRight;
@@ -1611,15 +1618,19 @@ private void UpdateMenu()
this.btnToggleFavH.Enabled = mayEdit && (this.DataRoot.SupportedFavorites & Favorites.H) != 0 && this.subListIndex != 8;
this.btnToggleLock.Enabled = mayEdit;
- this.miReload.Enabled = fileLoaded;
- this.miFileInformation.Enabled = fileLoaded;
- this.miRestoreOriginal.Enabled = fileLoaded && File.Exists(this.currentTvFile + ".bak");
- this.miSave.Enabled = fileLoaded;
- this.miSaveAs.Enabled = fileLoaded;
- this.miOpenReferenceFile.Enabled = fileLoaded;
- this.miSaveReferenceFile.Enabled = fileLoaded;
- this.miExcelExport.Enabled = fileLoaded;
- this.miPrint.Enabled = fileLoaded;
+ if (afterFileLoad)
+ {
+ // this block may contain some time-expensive checks that only need to be done after loading a file
+ this.miReload.Enabled = fileLoaded;
+ this.miFileInformation.Enabled = fileLoaded;
+ this.miRestoreOriginal.Enabled = fileLoaded && this.GetPathOfMissingBackupFile() == null;
+ this.miSave.Enabled = fileLoaded;
+ this.miSaveAs.Enabled = fileLoaded && this.currentTvSerializer.Features.CanSaveAs;
+ this.miOpenReferenceFile.Enabled = fileLoaded;
+ this.miSaveReferenceFile.Enabled = fileLoaded;
+ this.miExcelExport.Enabled = fileLoaded;
+ this.miPrint.Enabled = fileLoaded;
+ }
this.miAddChannel.Enabled = fileLoaded && isRight;
@@ -1666,12 +1677,32 @@ private void UpdateMruMenu()
#endregion
+ #region GetPathOfMissingBackupFile()
+ ///
+ /// If any backup file exists, return NULL. Otherwise the name of any expected .bak file (in case the loader has multiple data files)
+ ///
+ ///
+ private string GetPathOfMissingBackupFile()
+ {
+ var files = this.currentTvSerializer.GetDataFilePaths().ToList();
+ string bakFile = null;
+ foreach (var dataFilePath in files)
+ {
+ bakFile = dataFilePath + ".bak";
+ if (File.Exists(bakFile))
+ return null;
+ }
+
+ return bakFile;
+ }
+ #endregion
+
#region RestoreBackupFile()
private void RestoreBackupFile()
{
- var bakFile = this.currentTvFile + ".bak";
- if (!File.Exists(bakFile))
+ var bakFile = this.GetPathOfMissingBackupFile();
+ if (bakFile != null)
{
XtraMessageBox.Show(this, string.Format(Resources.MainForm_miRestoreOriginal_ItemClick_NoBackup, bakFile),
this.miRestoreOriginal.Caption,
@@ -1690,9 +1721,14 @@ private void RestoreBackupFile()
try
{
- File.Copy(bakFile, this.currentTvFile, true);
- var attr = File.GetAttributes(this.currentTvFile);
- File.SetAttributes(this.currentTvFile, attr & ~FileAttributes.ReadOnly);
+ foreach (var dataFilePath in this.currentTvSerializer.GetDataFilePaths())
+ {
+ bakFile = dataFilePath + ".bak";
+ File.Copy(bakFile, dataFilePath, true);
+ var attr = File.GetAttributes(dataFilePath);
+ File.SetAttributes(dataFilePath, attr & ~FileAttributes.ReadOnly);
+ }
+
this.currentTvSerializer.DataRoot.NeedsSaving = false;
if (this.currentPlugin != null)
this.LoadFiles(this.currentPlugin, this.currentTvFile);
@@ -2310,7 +2346,7 @@ private void gviewLeft_RowClick(object sender, RowClickEventArgs e)
private void gviewLeft_EndSorting(object sender, EventArgs e)
{
- TryExecute(this.UpdateMenu);
+ TryExecute(() => this.UpdateMenu());
}
#endregion
diff --git a/source/changelog.md b/source/changelog.md
index 7b24d02b..22149051 100644
--- a/source/changelog.md
+++ b/source/changelog.md
@@ -1,6 +1,11 @@
ChanSort Change Log
===================
+2019-11-17
+- Philips: Improved support for ChannelMap_xxx channel lists directory structure.
+ Selecting any .xml or .bin file in the folder will now load all DVB\*.xml files from the
+ channellib and s2channellib sub folders.
+
2019-11-11
- LG hospitality TVs using files names like xx[modelname].TLL can now be loaded
(They use the naming pattern of binary TLL files, but contain GlobalClone/XML text data)