Compare commits

...

4 Commits

25 changed files with 1950 additions and 2508 deletions

1
.gitignore vendored
View File

@ -134,3 +134,4 @@ imgui.ini
/Documentation/TSDuck-Samples/experiment2/*.ts /Documentation/TSDuck-Samples/experiment2/*.ts
/.vs/skyscraper8/CopilotIndices/17.14.1431.25910 /.vs/skyscraper8/CopilotIndices/17.14.1431.25910
/.vs /.vs
/skyscraper8/bin/Debug/satip

View File

@ -120,6 +120,11 @@ namespace skyscraper8.GS.GSE_BFBS
if (!startIndicator && endIndicator) if (!startIndicator && endIndicator)
gseLength -= 4; gseLength -= 4;
if (span.GetAvailableBytes() < gseLength)
{
//broken gse packet, invalid length
return;
}
gsePacket.GseDataBytes = span.ReadBytes(gseLength); gsePacket.GseDataBytes = span.ReadBytes(gseLength);
if (!startIndicator && endIndicator) if (!startIndicator && endIndicator)

View File

@ -1,293 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using log4net;
using skyscraper5.Dvb.Psi.Model;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Headless;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper5.Skyscraper.IO.TunerInterface;
using skyscraper5.Skyscraper.Plugins;
using skyscraper5.Skyscraper.Scraper;
using skyscraper5.Skyscraper.Scraper.Storage;
using skyscraper8.Skyscraper.Scraper.Storage;
namespace skyscraper5
{
public class Passing
{
public DataStorage DataStorage { get; set; }
public ObjectStorage ObjectStorage { get; set; }
private IStreamReader streamReader;
private List<TunerMetadata> tuners;
private List<SatellitePositionEntity> satellitePositions;
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
public Passing()
{
}
public bool Boot()
{
Ini ini = PluginManager.GetInstance().Ini;
StorageConnectionManager connectionManager = StorageConnectionManager.GetInstance();
IEnumerable<Tuple<int, bool, string>> allKnownFactoryNames = connectionManager.GetAllKnownFactoryNames();
foreach (Tuple<int, bool, string> knownFactoryName in allKnownFactoryNames)
{
logger.InfoFormat("Found {0} Storage Factory #{1}, {2}",knownFactoryName.Item2 ? "Data" : "Object",knownFactoryName.Item1,knownFactoryName.Item3);
}
logger.Debug("Acquiring Default Data Storage...");
DataStorageFactory dataStorageFactory = connectionManager.GetDefaultDataStorageFactory();
logger.Debug("Acquiring Default Object Storage...");
ObjectStorageFactory objectStorageFactory = connectionManager.GetDefaultObjectStorageFactory();
logger.Debug("Acquiring Data Storage...");
DataStorage = dataStorageFactory.CreateDataStorage();
if (DataStorage == null)
{
logger.FatalFormat("The data storage factory didn't create a data storage.");
return false;
}
bool isEquivalent = objectStorageFactory.IsEquivalent(dataStorageFactory);
if (isEquivalent)
{
logger.Debug("Using the Data Storage as Object Storage.");
ObjectStorage = (ObjectStorage)DataStorage;
}
else
{
logger.Debug("Acquiring Object Storage...");
ObjectStorage = objectStorageFactory.CreateObjectStorage();
if (ObjectStorage == null)
{
logger.FatalFormat("The object storage factory didn't create an object storage.");
return false;
}
}
TunerFactoryConnectionManager tunerFactoryConnectionManager = TunerFactoryConnectionManager.GetInstance();
ReadOnlyCollection<KeyValuePair<TunerFactoryIdAttribute, ITunerFactory>> tunerFactories = tunerFactoryConnectionManager.GetKnownFactories();
int tunerFactory = ini.ReadValue("startup", "tunerFactory", 0);
KeyValuePair<TunerFactoryIdAttribute, ITunerFactory> bootingTuner = tunerFactories.First(x => x.Key.Id == tunerFactory);
if (bootingTuner.Value == null)
{
Console.WriteLine("The tuner factory #{0} didn't load.", tunerFactory);
return false;
}
TunerFactoryConnectionManager.ConfigureFactoryFromIni(bootingTuner, ini);
streamReader = bootingTuner.Value.CreateStreamReader();
if (streamReader == null)
{
Console.WriteLine("The tuner factory #{0} didn't create a stream reader.");
return false;
}
try
{
streamReader.CheckForDVB();
}
catch (BadImageFormatException e)
{
Console.WriteLine("The configured tuner factory is not suitable for the current processor architecture. Tuning won't work, therefore functionality will be severely limited. Please configure a Tuner Factory Class suitable for this processor architecture in order to get the best experience. If you need to run crazycat69's StreamReader.dll on 64-bit machines, try RemoteStreamReader.");
streamReader = new NullTunerFactory().CreateStreamReader();
}
List<TunerMetadata> foundTuners = new List<TunerMetadata>();
bool checkForDvbExEx = streamReader.CheckForDVBExEx((index, name, type) =>
{
TunerMetadata tuner = new TunerMetadata(index, name, type);
foundTuners.Add(tuner);
});
foreach (TunerMetadata foundTuner in foundTuners)
{
streamReader.StopDVB();
bool startDvbEx = streamReader.StartDvbEx(foundTuner.Index);
if (!startDvbEx)
{
Console.WriteLine("Failed to start {0}", foundTuner.Name);
Thread.Sleep(1000);
continue;
}
foundTuner.Caps = streamReader.GetCaps();
byte[] macBuffer = new byte[6];
bool mac = streamReader.GetMAC(macBuffer);
if (!mac)
{
Console.WriteLine("Failed to read MAC Address of {0}", foundTuner.Name);
Thread.Sleep(1000);
}
else
{
foundTuner.MacAddress = new PhysicalAddress(macBuffer);
}
bool stopDvb = streamReader.StopDVB();
if (!stopDvb)
{
Console.WriteLine("Failed to stop {0}", foundTuner.Name);
Thread.Sleep(1000);
continue;
}
;
if (DataStorage.UiTunerTestFor(foundTuner))
{
DataStorage.UiTunerGetConfiguration(foundTuner);
}
if (tuners == null)
tuners = new List<TunerMetadata>();
tuners.Add(foundTuner);
}
satellitePositions = DataStorage.UiSatellitesListAll();
return true;
}
private HeadlessJob GetNextJob()
{
HeadlessJob headlessJob = DataStorage.GetQueuedJob();
if (headlessJob != null)
{
return headlessJob;
}
return new HeadlessJob(HeadlessJobType.BlindscanEverything);
}
public void Run()
{
while (true)
{
HeadlessJob headlessJob = GetNextJob();
Run(headlessJob);
if (!headlessJob.isSynthetic)
{
DataStorage.SetQueuedJobComplete(headlessJob);
}
}
}
private void Run(HeadlessJob job)
{
switch (job.jobType)
{
case HeadlessJobType.Sleep:
Thread.Sleep(job.iArg1);
break;
case HeadlessJobType.MassImport:
DirectoryInfo di = new DirectoryInfo(job.sArg1);
MassImportDirectory(di);
break;
case HeadlessJobType.BlindscanEverything:
PerformBlindscanEverything();
break;
case HeadlessJobType.ReimportByTag1:
ReimportTag(job.iArg1);
break;
default:
throw new NotImplementedException(job.jobType.ToString());
}
}
private void PerformBlindscanEverything()
{
if (tuners == null)
tuners = new List<TunerMetadata>();
if (tuners.Count == 0)
throw new ApplicationException("I'd need to blindscan everything, but I don't have any tuners I could use!");
throw new NotImplementedException(nameof(PerformBlindscanEverything));
}
private void ReimportTag(int tag1)
{
IReadOnlyList<string> queue = DataStorage.ListImportFileByTag1(tag1);
foreach(string filename in queue)
{
FileInfo fi = new FileInfo(filename);
if (!fi.Exists)
continue;
ScrapeStream(fi);
}
}
public void MassImportDirectory(DirectoryInfo sourceDir)
{
foreach (FileSystemInfo fileSystemInfo in sourceDir.GetFileSystemInfos())
{
DirectoryInfo di = fileSystemInfo as DirectoryInfo;
FileInfo fi = fileSystemInfo as FileInfo;
if (di != null)
{
MassImportDirectory(di);
}
else if (fi != null)
{
if (fi.Length == 0)
continue;
if (!fi.Extension.ToLowerInvariant().Equals(".ts"))
continue;
if (DataStorage.ImportFileKnown(fi))
continue;
FileStream fileStream = fi.OpenRead();
Stopwatch stopwatch = Stopwatch.StartNew();
int tstype;
ScrapeStream(fileStream,true, out tstype);
stopwatch.Stop();
Console.WriteLine("Importing {0} took {1}",fi.Name, stopwatch.Elapsed);
DataStorage.WaitForCompletion();
DataStorage.ImportMarkFileAsKnown(fi, stopwatch.Elapsed, tstype);
fileStream.Close();
}
else
{
throw new NotImplementedException(fileSystemInfo.GetType().ToString());
}
}
}
private void ScrapeStream(FileInfo fi)
{
FileStream fileStream = fi.OpenRead();
ScrapeStream(fileStream, true, out _);
fileStream.Close();
fileStream.Dispose();
}
private void ScrapeStream(Stream inStream, bool disk, out int tstype)
{
SkyscraperContext skyscraperContext = new SkyscraperContext(new TsContext(), DataStorage,ObjectStorage);
skyscraperContext.SourceIsDisk = disk;
skyscraperContext.InitalizeFilterChain();
skyscraperContext.IngestFromStream(inStream);
skyscraperContext.Dispose();
tstype = skyscraperContext.SpecialTsType;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
"profiles": { "profiles": {
"skyscraper8": { "skyscraper8": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "rotation-catalogue F:\\\\ F:\\\\rotate-us.csv",
"remoteDebugEnabled": false "remoteDebugEnabled": false
}, },
"Container (Dockerfile)": { "Container (Dockerfile)": {

View File

@ -1,278 +1,329 @@
using log4net; using log4net;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader; using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper8.SatIp; using skyscraper8.SatIp;
using skyscraper8.SatIp.RtspResponses; using skyscraper8.SatIp.RtspResponses;
using skyscraper8.SimpleServiceDiscoveryProtocol; using skyscraper8.SimpleServiceDiscoveryProtocol;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using skyscraper5.Mpeg2; using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.Scraper; using skyscraper5.Skyscraper.Scraper;
using skyscraper5.Skyscraper.Scraper.Storage.Filesystem; using skyscraper5.Skyscraper.Scraper.Storage.Filesystem;
using skyscraper5.Skyscraper.Scraper.Storage.InMemory; using skyscraper5.Skyscraper.Scraper.Storage.InMemory;
using skyscraper8.Skyscraper.Scraper.Storage; using skyscraper8.Skyscraper.Scraper.Storage;
namespace skyscraper8 namespace skyscraper8
{ {
internal class QuickAndDirtySatIpClient internal class QuickAndDirtySatIpClient
{ {
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name); private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
private const bool ENABLE_TS_WRITER = true; private const bool ENABLE_TS_WRITER = true;
public QuickAndDirtySatIpClient(string[] args) public QuickAndDirtySatIpClient(string[] args)
{ {
if (args.Length == 1) if (args.Length == 1)
{ {
logger.Fatal("Hey, what's your SAT>IP Server's IP Address? You can also say \"autodetect\" here."); logger.Fatal("Hey, what's your SAT>IP Server's IP Address? You can also say \"autodetect\" here.");
return; return;
} }
if (args[1].ToLowerInvariant().Contains("auto")) if (args[1].ToLowerInvariant().Contains("auto"))
{ {
ipAddress = AutodetectIPAddress(); ipAddress = AutodetectIPAddress();
} }
else else
{ {
ipAddress = IPAddress.Parse(args[1]); ipAddress = IPAddress.Parse(args[1]);
} }
if (args.Length == 2) if (args.Length == 2)
{ {
logger.Fatal("You didn't specify a DiSEqc setting. (Number between 1-4)"); logger.Fatal("You didn't specify a DiSEqc setting. (Number between 1-4)");
return; return;
} }
diseqcNumber = Int32.Parse(args[2]); diseqcNumber = Int32.Parse(args[2]);
if (args.Length == 3) if (args.Length == 3)
{ {
logger.Fatal("You didn't specify a polarity. Use H or V."); logger.Fatal("You didn't specify a polarity. Use H or V.");
return; return;
} }
polarity = args[3].ToUpperInvariant()[0]; polarity = args[3].ToUpperInvariant()[0];
if (args.Length == 4) if (args.Length == 4)
{ {
logger.Fatal("Please specify a frequency."); logger.Fatal("Please specify a frequency.");
return; return;
} }
frequency = Int32.Parse(args[4]); frequency = Int32.Parse(args[4]);
if (args.Length == 5) if (args.Length == 5)
{ {
logger.Fatal("What DVB Standard do we have here? S or S2?"); logger.Fatal("What DVB Standard do we have here? S or S2?");
return; return;
} }
isS2 = ParseDvbStandard(args[5]); isS2 = ParseDvbStandard(args[5]);
if (args.Length == 6) if (args.Length == 6)
{ {
logger.Fatal("What's the symbol rate?"); logger.Fatal("What's the symbol rate?");
return; return;
} }
symbolRate = Int32.Parse(args[6]); symbolRate = Int32.Parse(args[6]);
} }
private IPAddress AutodetectIPAddress() public void SetPlayoutMode(string[] args)
{ {
SsdpDevice firstSatIpServer = SsdpClient.GetFirstSatIpServer(1000, null); if (args.Length == 7)
if (firstSatIpServer == null) {
{ logger.Fatal("What's the target port?");
logger.WarnFormat("Didn't find any SAT>IP servers."); return;
return null; }
} destinationPort = int.Parse(args[7]);
IPAddress ipAddress = firstSatIpServer.GetIpAddress(); playoutMode = true;
logger.InfoFormat("Found SAT>IP Server at {0}", ipAddress); if (args.Length == 10)
return ipAddress; {
} if (args[8].ToLowerInvariant().Equals("multicast"))
{
private int symbolRate; destinationIp = IPAddress.Parse(args[9]);
private bool isS2; multicastMode = true;
private int frequency; }
private char polarity; else
private int diseqcNumber; {
private IPAddress ipAddress; logger.FatalFormat("Don't know what {0} means.", args[8]);
private RtspClient rtspClient; return;
public void Run() }
{ }
rtspClient = new RtspClient(ipAddress, 554); }
rtspClient.AutoReconnect = false;
Keepalive(); private IPAddress AutodetectIPAddress()
DiSEqC_Opcode opcode = BuildDiseqcOpcode(); {
string url = RtspClient.MakeUrl(opcode, frequency, isS2, symbolRate, null, false); SsdpDevice firstSatIpServer = SsdpClient.GetFirstSatIpServer(1000, null);
RtspDescribeResponse describe = rtspClient.GetDescribe(url); if (firstSatIpServer == null)
SessionDescriptionProtocol sessionDescriptionProtocol = describe.GetSessionDescriptionProtocol(); {
logger.WarnFormat("Didn't find any SAT>IP servers.");
packetQueue = new Queue<byte[]>(); return null;
RtspSetupResponse setup = rtspClient.GetSetup(url); }
if (setup.RtspStatusCode == 404) IPAddress ipAddress = firstSatIpServer.GetIpAddress();
{ logger.InfoFormat("Found SAT>IP Server at {0}", ipAddress);
logger.Fatal("Your SAT>IP server doesn't have a tuner available that can talk to the requested frequency."); return ipAddress;
return; }
}
setup.OnRtcpPacket += Setup_OnRtcpPacket; private int symbolRate;
setup.OnRtpPacket += Setup_OnRtpPacket; private bool isS2;
private int frequency;
if (dataStorage == null) private char polarity;
dataStorage = new InMemoryScraperStorage(); private int diseqcNumber;
if (objectStorage == null) private IPAddress ipAddress;
objectStorage = new FilesystemStorage(new DirectoryInfo(".")); private RtspClient rtspClient;
context = new SkyscraperContext(new TsContext(), dataStorage, objectStorage); public void Run()
context.EnableTimeout = true; {
context.TimeoutSeconds = 60; rtspClient = new RtspClient(ipAddress, 554);
context.InitalizeFilterChain(); rtspClient.AutoReconnect = false;
Keepalive();
RtspPlayResponse play = rtspClient.GetPlay(setup); DiSEqC_Opcode opcode = BuildDiseqcOpcode();
DateTime lastTimestamp = DateTime.Now; string url = RtspClient.MakeUrl(opcode, frequency, isS2, symbolRate, null, false);
RtspDescribeResponse describe = rtspClient.GetDescribe(url);
while (true) SessionDescriptionProtocol sessionDescriptionProtocol = describe.GetSessionDescriptionProtocol();
{
if (packetQueue.Count >= 1) packetQueue = new Queue<byte[]>();
{ RtspSetupResponse setup = rtspClient.GetSetup(url, destinationPort, multicastMode, destinationIp);
byte[] buffer; if (setup.RtspStatusCode == 404)
lock (packetQueue) {
{ logger.Fatal("Your SAT>IP server doesn't have a tuner available that can talk to the requested frequency.");
buffer = packetQueue.Dequeue(); return;
} }
context.IngestSinglePacket(buffer); setup.OnRtcpPacket += Setup_OnRtcpPacket;
} setup.OnRtpPacket += Setup_OnRtpPacket;
else
{ if (dataStorage == null)
Thread.Sleep(1); dataStorage = new InMemoryScraperStorage();
} if (objectStorage == null)
objectStorage = new FilesystemStorage(new DirectoryInfo("."));
if (context.IsAbortConditionMet()) if (!playoutMode)
break; {
context = new SkyscraperContext(new TsContext(), dataStorage, objectStorage);
if (DateTime.Now.Second != lastTimestamp.Second) context.EnableTimeout = true;
{ context.TimeoutSeconds = 60;
Keepalive(url); context.InitalizeFilterChain();
lastTimestamp = DateTime.Now; }
}
} RtspPlayResponse play = rtspClient.GetPlay(setup);
DateTime lastTimestamp = DateTime.Now;
rtspClient.GetTeardown(setup); bool initMessagePrinted = false;
rtspClient.Dispose();
} while (true)
{
private FileStream fs; if (playoutMode)
private uint stuffingBytes; {
private Queue<byte[]> packetQueue; Thread.Sleep(1000);
private ObjectStorage objectStorage; Keepalive(url);
private DataStorage dataStorage; if (!initMessagePrinted)
private SkyscraperContext context; {
private void Setup_OnRtpPacket(byte[] data, int length) logger.InfoFormat("Began SAT>IP playout to port {0}", destinationPort);
{ initMessagePrinted = true;
for (int i = 12; i < length; i += 1) }
{ }
if (data[i] == 'G') else
{ {
byte[] buffer = new byte[188]; if (packetQueue.Count >= 1)
Array.Copy(data, i, buffer, 0, 188); {
lock (packetQueue) byte[] buffer;
{ lock (packetQueue)
packetQueue.Enqueue(buffer); {
} buffer = packetQueue.Dequeue();
}
DumpPacket(buffer); context.IngestSinglePacket(buffer);
i += 187; if (!initMessagePrinted)
} {
else if (data[i] == 0xff) logger.InfoFormat("Began SAT>IP stream.");
{ initMessagePrinted = true;
stuffingBytes++; }
} }
} else
} {
Thread.Sleep(1);
private void DumpPacket(byte[] buffer) }
{
if (!ENABLE_TS_WRITER) if (context.IsAbortConditionMet())
return; break;
if (fs == null) if (DateTime.Now.Second != lastTimestamp.Second)
{ {
string fname = String.Format("{0}.ts", DateTime.Now.Ticks); Keepalive(url);
fs = File.OpenWrite(fname); lastTimestamp = DateTime.Now;
} }
}
fs.Write(buffer, 0, buffer.Length); }
}
rtspClient.GetTeardown(setup);
private int rtcps; rtspClient.Dispose();
private void Setup_OnRtcpPacket(byte[] data, int length) }
{
//rtcps++; private FileStream fs;
} private uint stuffingBytes;
private Queue<byte[]> packetQueue;
private DiSEqC_Opcode BuildDiseqcOpcode() private ObjectStorage objectStorage;
{ private DataStorage dataStorage;
DiSEqC_Opcode opcode = DiSEqC_Opcode.DISEQC_HIGH_NIBBLE; private SkyscraperContext context;
switch (diseqcNumber) private void Setup_OnRtpPacket(byte[] data, int length)
{ {
case 1: for (int i = 12; i < length; i += 1)
opcode |= DiSEqC_Opcode.DISEQC_OPTION_A; {
opcode |= DiSEqC_Opcode.DISEQC_POSITION_A; if (data[i] == 'G')
break; {
case 2: byte[] buffer = new byte[188];
opcode |= DiSEqC_Opcode.DISEQC_OPTION_A; Array.Copy(data, i, buffer, 0, 188);
opcode |= DiSEqC_Opcode.DISEQC_POSITION_B; lock (packetQueue)
break; {
case 3: packetQueue.Enqueue(buffer);
opcode |= DiSEqC_Opcode.DISEQC_OPTION_B; }
opcode |= DiSEqC_Opcode.DISEQC_POSITION_A;
break; DumpPacket(buffer);
case 4: i += 187;
opcode |= DiSEqC_Opcode.DISEQC_OPTION_B; }
opcode |= DiSEqC_Opcode.DISEQC_POSITION_B; else if (data[i] == 0xff)
break; {
default: stuffingBytes++;
throw new ArgumentOutOfRangeException("Your DiSEqC position should be a number between 1 and 4."); }
} }
}
switch (polarity)
{ private void DumpPacket(byte[] buffer)
case 'H': {
opcode |= DiSEqC_Opcode.DISEQC_HORIZONTAL; if (!ENABLE_TS_WRITER)
break; return;
case 'V':
opcode |= DiSEqC_Opcode.DISEQC_VERTICAL; if (fs == null)
break; {
default: string fname = String.Format("{0}.ts", DateTime.Now.Ticks);
throw new ArgumentException("The polarity should be H or V."); fs = File.OpenWrite(fname);
} }
return opcode; fs.Write(buffer, 0, buffer.Length);
} }
private bool ParseDvbStandard(string standard) private int rtcps;
{ private IPAddress destinationIp;
standard = standard.ToUpperInvariant(); private int destinationPort;
if (standard.StartsWith("DVB")) private bool playoutMode;
standard = standard.Substring(3); private bool multicastMode;
if (standard.StartsWith("-"))
standard = standard.Substring(1); private void Setup_OnRtcpPacket(byte[] data, int length)
{
switch (standard) //rtcps++;
{ }
case "S":
return false; private DiSEqC_Opcode BuildDiseqcOpcode()
case "S2": {
return true; DiSEqC_Opcode opcode = DiSEqC_Opcode.DISEQC_HIGH_NIBBLE;
case "S2X": switch (diseqcNumber)
return true; {
default: case 1:
throw new ArgumentException(String.Format("I have no idea what kind of Standard {0} is supposed to be.", standard)); opcode |= DiSEqC_Opcode.DISEQC_OPTION_A;
} opcode |= DiSEqC_Opcode.DISEQC_POSITION_A;
} break;
case 2:
private void Keepalive(string url = "/") opcode |= DiSEqC_Opcode.DISEQC_OPTION_A;
{ opcode |= DiSEqC_Opcode.DISEQC_POSITION_B;
RtspOptionsResponse options = rtspClient.GetOptions("/"); break;
if (options.RtspStatusCode != 200) case 3:
{ opcode |= DiSEqC_Opcode.DISEQC_OPTION_B;
throw new RtspException(String.Format("Unexpected RTSP Status code. Wanted {0}, got {1}", 200, options.RtspStatusCode)); opcode |= DiSEqC_Opcode.DISEQC_POSITION_A;
} break;
} case 4:
} opcode |= DiSEqC_Opcode.DISEQC_OPTION_B;
} opcode |= DiSEqC_Opcode.DISEQC_POSITION_B;
break;
default:
throw new ArgumentOutOfRangeException("Your DiSEqC position should be a number between 1 and 4.");
}
switch (polarity)
{
case 'H':
opcode |= DiSEqC_Opcode.DISEQC_HORIZONTAL;
break;
case 'V':
opcode |= DiSEqC_Opcode.DISEQC_VERTICAL;
break;
default:
throw new ArgumentException("The polarity should be H or V.");
}
return opcode;
}
private bool ParseDvbStandard(string standard)
{
standard = standard.ToUpperInvariant();
if (standard.StartsWith("DVB"))
standard = standard.Substring(3);
if (standard.StartsWith("-"))
standard = standard.Substring(1);
switch (standard)
{
case "S":
return false;
case "S2":
return true;
case "S2X":
return true;
default:
throw new ArgumentException(String.Format("I have no idea what kind of Standard {0} is supposed to be.", standard));
}
}
private void Keepalive(string url = "/")
{
RtspOptionsResponse options = rtspClient.GetOptions("/");
if (options.RtspStatusCode != 200)
{
throw new RtspException(String.Format("Unexpected RTSP Status code. Wanted {0}, got {1}", 200, options.RtspStatusCode));
}
}
}
}

View File

@ -1,347 +1,363 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using skyscraper5.Skyscraper; using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader; using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper8.SatIp.RtspRequests; using skyscraper8.SatIp.RtspRequests;
using skyscraper8.SatIp.RtspResponses; using skyscraper8.SatIp.RtspResponses;
namespace skyscraper8.SatIp namespace skyscraper8.SatIp
{ {
internal class RtspClient : Validatable, IDisposable internal class RtspClient : Validatable, IDisposable
{ {
private uint cseqCounter; private uint cseqCounter;
private const string USER_AGENT = "sophiaNetRtspClient/1.0"; private const string USER_AGENT = "sophiaNetRtspClient/1.0";
public RtspClient(string ip, int port) public RtspClient(string ip, int port)
{ {
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port); IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
ConstructStep2(ipEndPoint); ConstructStep2(ipEndPoint);
} }
public RtspClient(IPAddress ip, int port) public RtspClient(IPAddress ip, int port)
{ {
IPEndPoint ipEndPoint = new IPEndPoint(ip, port); IPEndPoint ipEndPoint = new IPEndPoint(ip, port);
ConstructStep2(ipEndPoint); ConstructStep2(ipEndPoint);
} }
private void ConstructStep2(IPEndPoint ipEndPoint) private void ConstructStep2(IPEndPoint ipEndPoint)
{ {
this.TcpClient = new TcpClient(); this.TcpClient = new TcpClient();
this.TcpClient.Connect(ipEndPoint); this.TcpClient.Connect(ipEndPoint);
this.RootPath = string.Format("rtsp://{0}:{1}", ipEndPoint.Address.ToString(), ipEndPoint.Port.ToString()); this.RootPath = string.Format("rtsp://{0}:{1}", ipEndPoint.Address.ToString(), ipEndPoint.Port.ToString());
this.NetworkStream = TcpClient.GetStream(); this.NetworkStream = TcpClient.GetStream();
this.BufferedStream = new BufferedStream(this.NetworkStream); this.BufferedStream = new BufferedStream(this.NetworkStream);
this.StreamReader = new StreamReader(this.BufferedStream); this.StreamReader = new StreamReader(this.BufferedStream);
this.StreamWriter = new StreamWriter(this.BufferedStream); this.StreamWriter = new StreamWriter(this.BufferedStream);
this.cseqCounter = 2; this.cseqCounter = 2;
this.ListenIp = GetListenIp(this.TcpClient.Client.LocalEndPoint); this.ListenIp = GetListenIp(this.TcpClient.Client.LocalEndPoint);
RtspOptionsResponse rtspOptionsResponse = GetOptions("/"); RtspOptionsResponse rtspOptionsResponse = GetOptions("/");
this.Valid = rtspOptionsResponse.Valid; this.Valid = rtspOptionsResponse.Valid;
} }
public bool AutoReconnect { get; set; } public bool AutoReconnect { get; set; }
public IPAddress ListenIp { get; set; } public IPAddress ListenIp { get; set; }
private IPAddress GetListenIp(EndPoint clientLocalEndPoint) private IPAddress GetListenIp(EndPoint clientLocalEndPoint)
{ {
IPEndPoint ipEndPoint = clientLocalEndPoint as IPEndPoint; IPEndPoint ipEndPoint = clientLocalEndPoint as IPEndPoint;
if (ipEndPoint == null) if (ipEndPoint == null)
{ {
throw new NotImplementedException(clientLocalEndPoint.GetType().Name); throw new NotImplementedException(clientLocalEndPoint.GetType().Name);
} }
if (ipEndPoint.Address.AddressFamily == AddressFamily.InterNetwork) if (ipEndPoint.Address.AddressFamily == AddressFamily.InterNetwork)
{ {
return ipEndPoint.Address; return ipEndPoint.Address;
} }
if (ipEndPoint.Address.IsIPv4MappedToIPv6) if (ipEndPoint.Address.IsIPv4MappedToIPv6)
{ {
return ipEndPoint.Address.MapToIPv4(); return ipEndPoint.Address.MapToIPv4();
} }
throw new NotImplementedException(String.Format("Don't know whether I can listen on IP {0}", ipEndPoint.ToString())); throw new NotImplementedException(String.Format("Don't know whether I can listen on IP {0}", ipEndPoint.ToString()));
} }
public string RootPath { get; set; } public string RootPath { get; set; }
private TcpClient TcpClient { get; set; } private TcpClient TcpClient { get; set; }
private NetworkStream NetworkStream { get; set; } private NetworkStream NetworkStream { get; set; }
private BufferedStream BufferedStream { get; set; } private BufferedStream BufferedStream { get; set; }
private StreamReader StreamReader { get; set; } private StreamReader StreamReader { get; set; }
private StreamWriter StreamWriter { get; set; } private StreamWriter StreamWriter { get; set; }
public RtspOptionsResponse GetOptions(string url) public RtspOptionsResponse GetOptions(string url)
{ {
RtspOptionsRequest request = new RtspOptionsRequest(); RtspOptionsRequest request = new RtspOptionsRequest();
request.RequestPath = url; request.RequestPath = url;
request.CSeq = cseqCounter++; request.CSeq = cseqCounter++;
request.UserAgent = USER_AGENT; request.UserAgent = USER_AGENT;
RtspResponseHeader header = GetResponse(request.ListHeaders(RootPath)); RtspResponseHeader header = GetResponse(request.ListHeaders(RootPath));
RtspOptionsResponse result = new RtspOptionsResponse(header); RtspOptionsResponse result = new RtspOptionsResponse(header);
return result; return result;
} }
public RtspDescribeResponse GetDescribe(string url) public RtspDescribeResponse GetDescribe(string url)
{ {
RtspDescribeRequest request = new RtspDescribeRequest(); RtspDescribeRequest request = new RtspDescribeRequest();
request.RequestPath = url; request.RequestPath = url;
request.CSeq = cseqCounter++; request.CSeq = cseqCounter++;
request.UserAgent = USER_AGENT; request.UserAgent = USER_AGENT;
request.Accept = "application/sdp"; request.Accept = "application/sdp";
RtspResponseHeader header = GetResponse(request.ListHeaders(RootPath)); RtspResponseHeader header = GetResponse(request.ListHeaders(RootPath));
RtspDescribeResponse result = new RtspDescribeResponse(header); RtspDescribeResponse result = new RtspDescribeResponse(header);
return result; return result;
} }
public RtspSetupResponse GetSetup(string url) public RtspSetupResponse GetSetup(string url, int destinationPort = 0, bool multicastMode = false, IPAddress multicastIp = null)
{ {
RtspSetupRequest request = new RtspSetupRequest(); RtspSetupRequest request = new RtspSetupRequest();
request.RequestPath = url; request.RequestPath = url;
request.CSeq = cseqCounter++; request.CSeq = cseqCounter++;
request.UserAgent = USER_AGENT; request.UserAgent = USER_AGENT;
Socket rtpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); int rtpPort = 0;
rtpSocket.Bind(new IPEndPoint(ListenIp, 0)); Socket rtpSocket = null;
int rtpPort = GetPortFromEndPoint(rtpSocket.LocalEndPoint); if (destinationPort == 0)
{
Socket rtcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); rtpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
rtcpSocket.Bind(new IPEndPoint(ListenIp, 0)); rtpSocket.Bind(new IPEndPoint(ListenIp, 0));
int rtcpPort = GetPortFromEndPoint(rtcpSocket.LocalEndPoint); rtpPort = GetPortFromEndPoint(rtpSocket.LocalEndPoint);
}
request.SetRtpAvpUnicast(rtpPort, rtcpPort); else
{
RtspResponseHeader response = GetResponse(request.ListHeaders(RootPath)); rtpPort = destinationPort;
RtspSetupResponse setupResponse = new RtspSetupResponse(response); }
switch (response.statusCode)
{ Socket rtcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
case 200: rtcpSocket.Bind(new IPEndPoint(ListenIp, 0));
setupResponse.RtpSocket = rtpSocket; int rtcpPort = GetPortFromEndPoint(rtcpSocket.LocalEndPoint);
setupResponse.RtcpSocket = rtcpSocket;
setupResponse.SetupListeners(); if (multicastMode && multicastIp != null)
setupResponse.Valid = true; {
break; request.SetRtpAvpMulticast(multicastIp, rtpPort, rtcpPort);
case 404: }
setupResponse.Valid = true; else
break; {
default: request.SetRtpAvpUnicast(rtpPort, rtcpPort);
throw new NotImplementedException(setupResponse.RtspStatusCode.ToString()); }
}
return setupResponse; RtspResponseHeader response = GetResponse(request.ListHeaders(RootPath));
} RtspSetupResponse setupResponse = new RtspSetupResponse(response);
switch (response.statusCode)
public RtspPlayResponse GetPlay(RtspSetupResponse setupData) {
{ case 200:
RtspPlayRequest request = new RtspPlayRequest(); setupResponse.RtpSocket = rtpSocket;
request.RequestPath = string.Format("/stream={0}", setupData.StreamId); setupResponse.RtcpSocket = rtcpSocket;
request.Session = setupData.Session; setupResponse.SetupListeners();
request.UserAgent = USER_AGENT; setupResponse.Valid = true;
break;
RtspPlayResponse response = new RtspPlayResponse(GetResponse(request.ListHeaders(RootPath))); case 404:
return response; setupResponse.Valid = true;
} break;
default:
public RtspTeardownResponse GetTeardown(RtspSetupResponse setupData) throw new NotImplementedException(setupResponse.RtspStatusCode.ToString());
{ }
RtspTeardownRequest request = new RtspTeardownRequest(); return setupResponse;
request.RequestPath = string.Format("/stream={0}", setupData.StreamId); }
request.Session = setupData.Session;
request.UserAgent = USER_AGENT; public RtspPlayResponse GetPlay(RtspSetupResponse setupData)
{
RtspTeardownResponse response = new RtspTeardownResponse(GetResponse(request.ListHeaders(RootPath))); RtspPlayRequest request = new RtspPlayRequest();
if (response.RtspStatusCode == 200) request.RequestPath = string.Format("/stream={0}", setupData.StreamId);
{ request.Session = setupData.Session;
setupData.InvokeCancellationTokens(); request.UserAgent = USER_AGENT;
if (AutoReconnect)
{ RtspPlayResponse response = new RtspPlayResponse(GetResponse(request.ListHeaders(RootPath)));
Reconnect(); return response;
} }
}
return response; public RtspTeardownResponse GetTeardown(RtspSetupResponse setupData)
} {
RtspTeardownRequest request = new RtspTeardownRequest();
private int GetPortFromEndPoint(EndPoint endpoint) request.RequestPath = string.Format("/stream={0}", setupData.StreamId);
{ request.Session = setupData.Session;
IPEndPoint ipEndPoint = endpoint as IPEndPoint; request.UserAgent = USER_AGENT;
if (ipEndPoint == null)
throw new NotImplementedException(endpoint.GetType().Name); RtspTeardownResponse response = new RtspTeardownResponse(GetResponse(request.ListHeaders(RootPath)));
return ipEndPoint.Port; if (response.RtspStatusCode == 200)
} {
setupData.InvokeCancellationTokens();
private RtspResponseHeader GetResponse(string request) if (AutoReconnect)
{ {
StreamWriter.Write(request); Reconnect();
StreamWriter.Flush(); }
}
RtspResponseHeader result = new RtspResponseHeader(); return response;
}
string response = StreamReader.ReadLine();
if (!response.StartsWith("RTSP/")) private int GetPortFromEndPoint(EndPoint endpoint)
throw new RtspException("Invalid RTSP response."); {
IPEndPoint ipEndPoint = endpoint as IPEndPoint;
response = response.Substring(5); if (ipEndPoint == null)
throw new NotImplementedException(endpoint.GetType().Name);
string versionString = response.Substring(0, 3); return ipEndPoint.Port;
result.rtspVersion = Version.Parse(versionString); }
response = response.Substring(4);
private RtspResponseHeader GetResponse(string request)
string statusCodeString = response.Substring(0,3); {
result.statusCode = ushort.Parse(statusCodeString); StreamWriter.Write(request);
StreamWriter.Flush();
response = response.Substring(4);
result.statusLine = response; RtspResponseHeader result = new RtspResponseHeader();
long contentLength = 0; string response = StreamReader.ReadLine();
if (!response.StartsWith("RTSP/"))
result.kv = new Dictionary<string, string>(); throw new RtspException("Invalid RTSP response.");
while (true)
{ response = response.Substring(5);
string lineIn = StreamReader.ReadLine();
if (string.IsNullOrEmpty(lineIn)) string versionString = response.Substring(0, 3);
break; result.rtspVersion = Version.Parse(versionString);
response = response.Substring(4);
int indexOf = lineIn.IndexOf(": ");
string key = lineIn.Substring(0, indexOf); string statusCodeString = response.Substring(0,3);
string value = lineIn.Substring(indexOf + 2); result.statusCode = ushort.Parse(statusCodeString);
result.kv.Add(key, value);
response = response.Substring(4);
if (key.Equals("Content-Length")) result.statusLine = response;
{
contentLength = long.Parse(value); long contentLength = 0;
}
} result.kv = new Dictionary<string, string>();
while (true)
if (contentLength > 0) {
{ string lineIn = StreamReader.ReadLine();
StreamReader.DiscardBufferedData(); if (string.IsNullOrEmpty(lineIn))
byte[] buffer = new byte[contentLength]; break;
int sucessfullyRead = BufferedStream.Read(buffer, 0, (int)contentLength);
if (sucessfullyRead != contentLength) int indexOf = lineIn.IndexOf(": ");
{ string key = lineIn.Substring(0, indexOf);
throw new IOException("incomplete read"); string value = lineIn.Substring(indexOf + 2);
} result.kv.Add(key, value);
result.payload = buffer; if (key.Equals("Content-Length"))
} {
contentLength = long.Parse(value);
return result; }
} }
public void Reconnect() if (contentLength > 0)
{ {
EndPoint clientRemoteEndPoint = this.TcpClient.Client.RemoteEndPoint; StreamReader.DiscardBufferedData();
IPEndPoint ipEndPoint = clientRemoteEndPoint as IPEndPoint; byte[] buffer = new byte[contentLength];
if (ipEndPoint == null) int sucessfullyRead = BufferedStream.Read(buffer, 0, (int)contentLength);
{ if (sucessfullyRead != contentLength)
throw new NotImplementedException(clientRemoteEndPoint.GetType().ToString()); {
} throw new IOException("incomplete read");
}
this.TcpClient.Close();
this.TcpClient = new TcpClient(); result.payload = buffer;
this.TcpClient.Connect(ipEndPoint); }
this.RootPath = string.Format("rtsp://{0}:{1}", ipEndPoint.Address, ipEndPoint.Port);
this.NetworkStream = TcpClient.GetStream(); return result;
this.BufferedStream = new BufferedStream(this.NetworkStream); }
this.StreamReader = new StreamReader(this.BufferedStream);
this.StreamWriter = new StreamWriter(this.BufferedStream); public void Reconnect()
this.cseqCounter = 2; {
this.ListenIp = GetListenIp(this.TcpClient.Client.LocalEndPoint); EndPoint clientRemoteEndPoint = this.TcpClient.Client.RemoteEndPoint;
} IPEndPoint ipEndPoint = clientRemoteEndPoint as IPEndPoint;
if (ipEndPoint == null)
/// <summary> {
/// Generates a SAT>IP Tuning string. throw new NotImplementedException(clientRemoteEndPoint.GetType().ToString());
/// </summary> }
/// <param name="diseqcChannel">The DiSEqC Command to send.</param>
/// <param name="freq">The frequency to tune to in MHz.</param> this.TcpClient.Close();
/// <param name="isS2">Set this to true if tuning to DVB-S2/S2X, or false for DVB-S</param> this.TcpClient = new TcpClient();
/// <param name="symbolrate">The transponder's symbol rate in Ksyms.</param> this.TcpClient.Connect(ipEndPoint);
/// <param name="forceBbframeMode">Set this to true to force a STiD135 to BBFrame mode. Set this to false if you do not want this, or if the tuner isn't a STiD135. false is always safe here.</param> this.RootPath = string.Format("rtsp://{0}:{1}", ipEndPoint.Address, ipEndPoint.Port);
/// <returns>A SAT>IP Tuning String</returns> this.NetworkStream = TcpClient.GetStream();
/// <exception cref="ArgumentOutOfRangeException">Thrown when an invalid DiSEqC Command is supplied.</exception> this.BufferedStream = new BufferedStream(this.NetworkStream);
public static string MakeUrl(DiSEqC_Opcode diseqcChannel, int freq, bool isS2, int symbolrate, byte? mis = null, bool forceBbframeMode = false) this.StreamReader = new StreamReader(this.BufferedStream);
{ this.StreamWriter = new StreamWriter(this.BufferedStream);
bool diseqcOk = false; this.cseqCounter = 2;
byte diseqc = 1; this.ListenIp = GetListenIp(this.TcpClient.Client.LocalEndPoint);
if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_A) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_A)) }
{
diseqc = 1; /// <summary>
diseqcOk = true; /// Generates a SAT>IP Tuning string.
} /// </summary>
if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_A) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_B)) /// <param name="diseqcChannel">The DiSEqC Command to send.</param>
{ /// <param name="freq">The frequency to tune to in MHz.</param>
diseqc = 2; /// <param name="isS2">Set this to true if tuning to DVB-S2/S2X, or false for DVB-S</param>
diseqcOk = true; /// <param name="symbolrate">The transponder's symbol rate in Ksyms.</param>
} /// <param name="forceBbframeMode">Set this to true to force a STiD135 to BBFrame mode. Set this to false if you do not want this, or if the tuner isn't a STiD135. false is always safe here.</param>
if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_B) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_A)) /// <returns>A SAT>IP Tuning String</returns>
{ /// <exception cref="ArgumentOutOfRangeException">Thrown when an invalid DiSEqC Command is supplied.</exception>
diseqc = 3; public static string MakeUrl(DiSEqC_Opcode diseqcChannel, int freq, bool isS2, int symbolrate, byte? mis = null, bool forceBbframeMode = false)
diseqcOk = true; {
} bool diseqcOk = false;
if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_B) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_B)) byte diseqc = 1;
{ if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_A) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_A))
diseqc = 4; {
diseqcOk = true; diseqc = 1;
} diseqcOk = true;
if (!diseqcOk) }
{ if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_A) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_B))
throw new ArgumentOutOfRangeException(nameof(diseqcChannel)); {
} diseqc = 2;
diseqcOk = true;
char pol; }
if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_HORIZONTAL)) if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_B) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_A))
{ {
pol = 'h'; diseqc = 3;
} diseqcOk = true;
else }
{ if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_B) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_B))
pol = 'v'; {
} diseqc = 4;
diseqcOk = true;
StringBuilder sb = new StringBuilder(); }
sb.AppendFormat("/?src={0}", diseqc); if (!diseqcOk)
sb.AppendFormat("&freq={0}", freq); {
sb.AppendFormat("&pol={0}", pol); throw new ArgumentOutOfRangeException(nameof(diseqcChannel));
sb.AppendFormat("&msys={0}", isS2 ? "dvbs2" : "dvbs"); }
sb.AppendFormat("&sr={0}", symbolrate);
sb.AppendFormat("&pids=all"); char pol;
if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_HORIZONTAL))
//Thanks to the Digital Devices Customer Support for giving me this advice. {
if (forceBbframeMode) pol = 'h';
{ }
sb.Append("&x_isi=0x80000000"); else
} {
else if (mis.HasValue) pol = 'v';
{ }
sb.AppendFormat("&x_isi={0}", mis.Value);
} StringBuilder sb = new StringBuilder();
sb.AppendFormat("/?src={0}", diseqc);
return sb.ToString(); sb.AppendFormat("&freq={0}", freq);
} sb.AppendFormat("&pol={0}", pol);
sb.AppendFormat("&msys={0}", isS2 ? "dvbs2" : "dvbs");
private bool disposed; sb.AppendFormat("&sr={0}", symbolrate);
public void Dispose() sb.AppendFormat("&pids=all");
{
if (disposed) //Thanks to the Digital Devices Customer Support for giving me this advice.
throw new ObjectDisposedException(nameof(RtspClient)); if (forceBbframeMode)
{
ListenIp = null; sb.Append("&x_isi=0x80000000");
RootPath = null; }
if (TcpClient.Connected) else if (mis.HasValue)
{ {
TcpClient.Close(); sb.AppendFormat("&x_isi={0}", mis.Value);
} }
TcpClient.Dispose(); return sb.ToString();
NetworkStream = null; }
BufferedStream = null;
StreamReader = null; private bool disposed;
StreamWriter = null; public void Dispose()
disposed = true; {
} if (disposed)
} throw new ObjectDisposedException(nameof(RtspClient));
}
ListenIp = null;
RootPath = null;
if (TcpClient.Connected)
{
TcpClient.Close();
}
TcpClient.Dispose();
NetworkStream = null;
BufferedStream = null;
StreamReader = null;
StreamWriter = null;
disposed = true;
}
}
}

View File

@ -1,57 +1,63 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Net;
using System.Threading.Tasks; using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.SatIp.RtspRequests
{ namespace skyscraper8.SatIp.RtspRequests
internal class RtspSetupRequest : RtspRequest {
{ internal class RtspSetupRequest : RtspRequest
public RtspSetupRequest() : base("SETUP") {
{ public RtspSetupRequest() : base("SETUP")
} {
}
public uint CSeq
{ public uint CSeq
set {
{ set
base.args["CSeq"] = Convert.ToString(value); {
} base.args["CSeq"] = Convert.ToString(value);
get }
{ get
return uint.Parse(base.args["CSeq"]); {
} return uint.Parse(base.args["CSeq"]);
} }
}
public string UserAgent
{ public string UserAgent
set {
{ set
base.args["User-Agent"] = value; {
} base.args["User-Agent"] = value;
get }
{ get
return base.args["User-Agent"]; {
} return base.args["User-Agent"];
} }
}
public string Transport
{ public string Transport
set {
{ set
base.args["Transport"] = value; {
} base.args["Transport"] = value;
get }
{ get
return base.args["Transport"]; {
} return base.args["Transport"];
} }
}
public void SetRtpAvpUnicast(int rtpPort, int rtcpPort)
{ public void SetRtpAvpUnicast(int rtpPort, int rtcpPort)
Transport = String.Format("RTP/AVP;unicast;client_port={0}-{1}", rtpPort, rtcpPort); {
} Transport = String.Format("RTP/AVP;unicast;client_port={0}-{1}", rtpPort, rtcpPort);
} }
} public void SetRtpAvpMulticast(IPAddress targetIp, int rtpPort, int rtcpPort)
{
Transport = String.Format("RTP/AVP;multicast;destination={2};port={0}-{1}", rtpPort, rtcpPort, targetIp.ToString());
}
}
}

View File

@ -53,23 +53,29 @@ namespace skyscraper8.SatIp.RtspResponses
if (listenersStarted) if (listenersStarted)
throw new RtspException("Listener already started."); throw new RtspException("Listener already started.");
rtpCancellation = new CancellationTokenSource(); if (RtpSocket != null)
{
rtpCancellation = new CancellationTokenSource();
}
rtcpCancellation = new CancellationTokenSource(); rtcpCancellation = new CancellationTokenSource();
Task.Run(async () => if (RtpSocket != null)
{ {
byte[] buffer = new byte[2048]; Task.Run(async () =>
{
while (!rtpCancellation.IsCancellationRequested) byte[] buffer = new byte[2048];
{
Array.Clear(buffer); while (!rtpCancellation.IsCancellationRequested)
int result = await RtpSocket.ReceiveAsync(buffer, rtpCancellation.Token); {
OnRtpPacket?.Invoke(buffer, result); Array.Clear(buffer);
} int result = await RtpSocket.ReceiveAsync(buffer, rtpCancellation.Token);
OnRtpPacket?.Invoke(buffer, result);
exitedThread++; }
}
); exitedThread++;
}
);
}
Task.Run(async () => Task.Run(async () =>
{ {
@ -92,8 +98,8 @@ namespace skyscraper8.SatIp.RtspResponses
internal void InvokeCancellationTokens() internal void InvokeCancellationTokens()
{ {
rtpCancellation.Cancel(); rtpCancellation?.Cancel();
rtcpCancellation.Cancel(); rtcpCancellation?.Cancel();
} }
public event OnRtpPacket OnRtpPacket; public event OnRtpPacket OnRtpPacket;

View File

@ -1,20 +0,0 @@
using System;
using System.Xml.Serialization;
namespace skyscraper5.Skyscraper.Equipment
{
public class BaseEquipment
{
[XmlIgnore]
public int Id { get; set; }
[XmlAttribute]
public string Manufacturer { get; set; }
[XmlAttribute]
public string Name { get; set; }
[XmlIgnore]
public DateTime DateAdded { get; set; }
}
}

View File

@ -1,57 +0,0 @@
using System;
using System.Xml.Serialization;
namespace skyscraper5.Skyscraper.Equipment
{
public class DishEntity : BaseEquipment
{
public DishEntity(string manufac, string name, int diameter, DishShape shape)
{
this.Manufacturer = manufac;
this.Name = name;
this.Diameter = diameter;
this.Shape = shape;
}
public DishEntity()
{
}
[XmlAttribute]
public int Diameter { get; set; }
[XmlAttribute]
public DishShape Shape { get; set; }
protected bool Equals(DishEntity other)
{
return Diameter == other.Diameter && Shape == other.Shape;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((DishEntity)obj);
}
public override int GetHashCode()
{
return HashCode.Combine(Diameter, (int)Shape);
}
public override string ToString()
{
return this.Name;
}
}
public enum DishShape : int
{
Offset,
PrimeFocus,
Mesh,
Other
}
}

View File

@ -1,20 +0,0 @@
namespace skyscraper5.Skyscraper.Equipment;
public class EquipmentCollection
{
public EquipmentCollection()
{
Dishes = new List<DishEntity>();
LNBs = new List<LnbEntity>();
SatellitePositions = new List<SatellitePositionEntity>();
}
public List<DishEntity> Dishes { get; set; }
public List<LnbEntity> LNBs { get; set; }
public List<SatellitePositionEntity> SatellitePositions { get; set; }
public override string ToString()
{
return String.Format("{0} dishes, {1} LNBs, {2} satellite positions", Dishes.Count, LNBs.Count, SatellitePositions.Count);
}
}

View File

@ -1,57 +0,0 @@
using System;
using System.Collections.Generic;
using skyscraper5.Skyscraper.Scraper.Storage;
using skyscraper8.Skyscraper.Scraper.Storage;
namespace skyscraper5.Skyscraper.Equipment
{
public static class EquipmentUtilities
{
public static void InsertDefaultLnbTypes(DataStorage storage)
{
List<LnbEntity> knownLnbTypes = storage.UiLnbTypesListAll();
foreach (LnbEntity defaultLnbType in GetDefaultLnbTypes())
{
if (!knownLnbTypes.Contains(defaultLnbType))
storage.UiLnbTypesAdd(defaultLnbType);
}
}
private static IEnumerable<LnbEntity> GetDefaultLnbTypes()
{
LnbEntity lnbType = new LnbEntity();
lnbType.Lof1 = 9750;
lnbType.Lof2 = 10600;
lnbType.LofSw = 11700;
lnbType.MinimumFrequency = 10700;
lnbType.MaximumFrequency = 12750;
lnbType.Manufacturer = "Generic";
lnbType.Name = "Universal LNB";
yield return lnbType;
}
public static void InsertDefaultDishTypes(DataStorage storage)
{
List<DishEntity> knownDishTypes = storage.UiDishTypesListAll();
foreach (DishEntity defaultDishType in GetDefaultDishTypes())
{
if (!knownDishTypes.Contains(defaultDishType))
storage.UiDishTypesAdd(defaultDishType);
}
}
private static IEnumerable<DishEntity> GetDefaultDishTypes()
{
int[] commonSizes = new int[] { 60, 80, 85, 100 };
foreach (int commonSize in commonSizes)
{
DishEntity dishType = new DishEntity();
dishType.Diameter = commonSize;
dishType.Shape = DishShape.Offset;
dishType.Manufacturer = "Generic";
dishType.Name = String.Format("{0} cm Offset dish antenna", commonSize);
yield return dishType;
}
}
}
}

View File

@ -1,59 +0,0 @@
using System;
using System.Xml.Serialization;
namespace skyscraper5.Skyscraper.Equipment
{
public class LnbEntity : BaseEquipment
{
public LnbEntity() { }
public LnbEntity(string manufac, string name, int lof1, int lof2, int lofSw, int minFreq, int maxFreq)
{
this.Manufacturer = manufac;
this.Name = name;
this.Lof1 = lof1;
this.Lof2 = lof2;
this.LofSw = lofSw;
this.MinimumFrequency = minFreq;
this.MaximumFrequency = maxFreq;
}
[XmlAttribute]
public int Lof1 { get; set; }
[XmlAttribute]
public int Lof2 { get; set; }
[XmlAttribute]
public int LofSw { get; set; }
[XmlAttribute]
public int MinimumFrequency { get; set; }
[XmlAttribute]
public int MaximumFrequency { get; set; }
protected bool Equals(LnbEntity other)
{
return Lof1 == other.Lof1 && Lof2 == other.Lof2 && LofSw == other.LofSw && MinimumFrequency == other.MinimumFrequency && MaximumFrequency == other.MaximumFrequency;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((LnbEntity)obj);
}
public override int GetHashCode()
{
return HashCode.Combine(Lof1, Lof2, LofSw, MinimumFrequency, MaximumFrequency);
}
public override string ToString()
{
return String.Format("{0} {1}", Manufacturer, Name);
}
}
}

View File

@ -1,108 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper5.Skyscraper
{
public class SatellitePositionEntity
{
public SatellitePositionEntity()
{
}
public SatellitePositionEntity(float angle, string name)
{
this.angle = angle;
this.name = name;
}
public float angle;
public string name;
public int Checksum => GetChecksum(angle);
public static int GetChecksum(float angle)
{
int result = (int)(angle * 10.0f);
return result;
}
public override string ToString()
{
if (angle > 0)
{
return String.Format("{0}°E {1}", angle, name);
}
else
{
float tmpAngle = angle;
tmpAngle *= -1;
return String.Format("{0}°W {1}", tmpAngle, name);
}
}
public static SatellitePositionEntity FromChecksum(int checksum)
{
float newAngle = checksum;
newAngle /= 10.0f;
return new SatellitePositionEntity(newAngle, "???");
}
protected bool Equals(SatellitePositionEntity other)
{
return angle.Equals(other.angle);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((SatellitePositionEntity)obj);
}
public override int GetHashCode()
{
return HashCode.Combine(angle);
}
/// <summary>
/// Returns an integer value for the cardinal direction.
/// </summary>
/// <returns>Returns 0 for east, 1 for west, 2 if satellite is at 0.0</returns>
public int GetCardinalDirectionAsInt()
{
if (angle > 0)
return 0;
else if (angle < 0)
return 1;
else
return 2;
}
/// <summary>
/// Get the letter matching the cardinal direction of the satellite.
/// </summary>
/// <returns>Returns either 'E' or 'W' depending on the angle.</returns>
public char GetCardinalDirectionAsChar()
{
int cardinalDirectionAsInt = GetCardinalDirectionAsInt();
return cardinalDirectionAsInt == 0 ? 'E' : 'W';
}
/// <summary>
/// Get a filename-safe number of the angle. Meaning the angle stripped of its sign and decimal separator.
/// </summary>
/// <returns>For example: For 19.2°E, this will return 192, For 34.5°W, this will return 345</returns>
public int GetCleanAngle()
{
int cleanAngle = (int)(angle * 10.0f);
if (cleanAngle < 0)
cleanAngle /= -1;
return cleanAngle;
}
}
}

View File

@ -13,7 +13,6 @@ using skyscraper5.src.Mpeg2.PacketFilter;
using skyscraper5.Skyscraper.IO; using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Scraper; using skyscraper5.Skyscraper.Scraper;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using skyscraper5.Skyscraper.Equipment;
namespace skyscraper8.Skyscraper.FrequencyListGenerator namespace skyscraper8.Skyscraper.FrequencyListGenerator
{ {
@ -94,12 +93,12 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
SearchResult sr1 = default; SearchResult sr1 = default;
int tpNum = default; int tpNum = default;
int lof1 = config.LnbType.Lof1 * 1000; int lof1 = config.Lof1 * 1000;
int lof2 = config.LnbType.Lof2 * 1000; int lof2 = config.Lof2 * 1000;
int lofSw = config.LnbType.LofSw * 1000; int lofSw = config.LofSw * 1000;
int diseqc = config.TunerMetadata.DiseqcType; int diseqc = config.TunerMetadata.DiseqcType;
int startFreq = config.LnbType.MinimumFrequency * 1000; int startFreq = config.MinimumFrequency * 1000;
int endFreq = config.LnbType.MaximumFrequency * 1000; int endFreq = config.MaximumFrequency * 1000;
bool anythingSuceeded = false; bool anythingSuceeded = false;
nint allocHGlobal = Marshal.AllocHGlobal(ushort.MaxValue); nint allocHGlobal = Marshal.AllocHGlobal(ushort.MaxValue);
@ -395,15 +394,15 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
foundFrequencies.Add(blindscanSearchResult); foundFrequencies.Add(blindscanSearchResult);
} }
config.DataStorage.InsertSearchResult(jobInDb, true, searchResult, polarityIndex, new SearchResult2()); config.DataStorage.InsertSearchResult(jobInDb, true, searchResult, polarityIndex, new SearchResult2());
config.Ui.OnBlindscanSearchResult1Callback(blindscanSearchResult, polarityIndex, config.LnbType.MinimumFrequency, config.LnbType.MaximumFrequency); config.Ui.OnBlindscanSearchResult1Callback(blindscanSearchResult, polarityIndex, config.MinimumFrequency, config.MaximumFrequency);
} }
public void RunScrape() public void RunScrape()
{ {
int lof1 = config.LnbType.Lof1 * 1000; int lof1 = config.Lof1 * 1000;
int lof2 = config.LnbType.Lof2 * 1000; int lof2 = config.Lof2 * 1000;
int lofSw = config.LnbType.LofSw * 1000; int lofSw = config.LofSw * 1000;
foreach (BlindscanSearchResult blindscanResult in foundFrequencies) foreach (BlindscanSearchResult blindscanResult in foundFrequencies)
@ -412,7 +411,7 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
blindscanResult.State = BlindscanResultState.Tuning; blindscanResult.State = BlindscanResultState.Tuning;
config.DataStorage.UpdateTransponderState(jobInDb, blindscanResult.IsSatellite(), blindscanResult.SearchResult, blindscanResult.State, blindscanResult.SearchResult2); config.DataStorage.UpdateTransponderState(jobInDb, blindscanResult.IsSatellite(), blindscanResult.SearchResult, blindscanResult.State, blindscanResult.SearchResult2);
config.Ui.OnBlindscanBeforeSetChannel(blindscanResult, config.LnbType); config.Ui.OnBlindscanBeforeSetChannel(blindscanResult);
bool channel = config.StreamReader.SetChannel(blindscanResult.SearchResult.Freq, blindscanResult.SearchResult.SR, blindscanResult.SearchResult.Pol, blindscanResult.SearchResult.FEC, lof1, lof2, lofSw); bool channel = config.StreamReader.SetChannel(blindscanResult.SearchResult.Freq, blindscanResult.SearchResult.SR, blindscanResult.SearchResult.Pol, blindscanResult.SearchResult.FEC, lof1, lof2, lofSw);
if (!channel) if (!channel)
{ {
@ -445,11 +444,11 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
config.Ui.OnBlindscanBeginRfSpectrum(); config.Ui.OnBlindscanBeginRfSpectrum();
int lof1 = config.LnbType.Lof1 * 1000; int lof1 = config.Lof1 * 1000;
int lof2 = config.LnbType.Lof2 * 1000; int lof2 = config.Lof2 * 1000;
int lofSw = config.LnbType.LofSw * 1000; int lofSw = config.LofSw * 1000;
int startFreq = config.LnbType.MinimumFrequency * 1000; int startFreq = config.MinimumFrequency * 1000;
int endFreq = config.LnbType.MaximumFrequency * 1000; int endFreq = config.MaximumFrequency * 1000;
RfSpectrumData spectrumData = RfSpectrumData.Create(); RfSpectrumData spectrumData = RfSpectrumData.Create();
@ -574,14 +573,14 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
{ {
int polarity = result.SearchResult.Pol; int polarity = result.SearchResult.Pol;
int freq = result.SearchResult.Freq; int freq = result.SearchResult.Freq;
bool isHighBand = freq > config.LnbType.LofSw * 1000; bool isHighBand = freq > config.LofSw * 1000;
bool isHorizontal = polarity == 0; bool isHorizontal = polarity == 0;
SendDiseqcCommand(isHighBand, isHorizontal); SendDiseqcCommand(isHighBand, isHorizontal);
logger.Log(PluginLogLevel.Info, string.Format("Trying to BLScanEx..."), 8); logger.Log(PluginLogLevel.Info, string.Format("Trying to BLScanEx..."), 8);
if (!config.StreamReader.BLScanEx(result.SearchResult.Freq, 5000, result.SearchResult.Pol, if (!config.StreamReader.BLScanEx(result.SearchResult.Freq, 5000, result.SearchResult.Pol,
config.LnbType.Lof1 * 1000, config.LnbType.Lof2 * 1000, config.Lof1 * 1000, config.Lof2 * 1000,
config.LnbType.LofSw * 1000, 1000000, (STD_TYPE)result.SearchResult.StdType, config.LofSw * 1000, 1000000, (STD_TYPE)result.SearchResult.StdType,
ref satelliteSr)) ref satelliteSr))
{ {
//No blindscan? No problem! Try anyway! //No blindscan? No problem! Try anyway!
@ -600,7 +599,7 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
Thread.Sleep(1000); Thread.Sleep(1000);
result.State = BlindscanResultState.Retrying; result.State = BlindscanResultState.Retrying;
logger.Log(PluginLogLevel.Info, string.Format("Retrying BLScanEx... ({0}/{1})", i + 1, attempts + 1), 8); logger.Log(PluginLogLevel.Info, string.Format("Retrying BLScanEx... ({0}/{1})", i + 1, attempts + 1), 8);
bool retryResult = config.StreamReader.BLScanEx(result.SearchResult.Freq, 5000, result.SearchResult.Pol, config.LnbType.Lof1 * 1000, config.LnbType.Lof2 * 1000, config.LnbType.LofSw * 1000, 1000000, (STD_TYPE)result.SearchResult.StdType,ref satelliteSr); bool retryResult = config.StreamReader.BLScanEx(result.SearchResult.Freq, 5000, result.SearchResult.Pol, config.Lof1 * 1000, config.Lof2 * 1000, config.LofSw * 1000, 1000000, (STD_TYPE)result.SearchResult.StdType,ref satelliteSr);
if (!retryResult) if (!retryResult)
{ {
result.State = BlindscanResultState.BlScanFailure; result.State = BlindscanResultState.BlScanFailure;
@ -676,7 +675,7 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
if (plot != null) if (plot != null)
{ {
result.State = BlindscanResultState.IqSaving; result.State = BlindscanResultState.IqSaving;
config.ObjectStorage.StoreIqGraph(jobInDb.JobGuid, result.GetFrequency(), result.GetPolarity(config.LnbType.LofSw), plot); config.ObjectStorage.StoreIqGraph(jobInDb.JobGuid, result.GetFrequency(), result.GetPolarity(config.LofSw), plot);
} }
} }
@ -707,22 +706,19 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
if (tsRecorder.PrepareRecording()) if (tsRecorder.PrepareRecording())
{ {
DateTime now = DateTime.Now; DateTime now = DateTime.Now;
int cardinalDirection = 0;
int angle = config.SatellitePosition.Checksum;
if (angle < 0)
{
angle *= -1;
cardinalDirection = 1;
}
string recordingFilename = string.Format( string recordingFilename = string.Format(
"skyscraper_{0:D4}{1:D2}{2:D2}_{3:D2}{4:D2}_{8:D4}{9}_{5}_{6}_{7}.ts", "skyscraper_{0:D4}{1:D2}{2:D2}_{3:D2}{4:D2}_{8}_{5}_{6}_{7}.ts",
now.Year, now.Month, now.Day, now.Hour, now.Minute, result.GetFrequency() / 1000, now.Year, //0
result.GetPolarity(config.LnbType.LofSw), result.GetSymbolRate() / 1000, now.Month, //1
config.SatellitePosition.GetCleanAngle(), now.Day, //2
config.SatellitePosition.GetCardinalDirectionAsChar()); now.Hour, //3
tsRecorder.SetNextFilename(recordingFilename); now.Minute, //4
result.GetFrequency() / 1000, //5
result.GetPolarity(config.LofSw), //6
result.GetSymbolRate() / 1000, //7
config.GetFiveLetterSattelitePosition()); //8
tsRecorder.SetNextFilename(recordingFilename);
tsRecorder.CreateBufferedStream(); tsRecorder.CreateBufferedStream();
} }
} }

View File

@ -4,7 +4,6 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using skyscraper5.Skyscraper; using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Equipment;
using skyscraper5.Skyscraper.Gps; using skyscraper5.Skyscraper.Gps;
using skyscraper5.Skyscraper.IO; using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Scraper; using skyscraper5.Skyscraper.Scraper;
@ -18,7 +17,7 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
public TunerMetadata TunerMetadata { get; set; } public TunerMetadata TunerMetadata { get; set; }
public int DiseqcIndex { get; set; } public int DiseqcIndex { get; set; }
public IGpsReceiver Gps { get; set; } public IGpsReceiver Gps { get; set; }
public SatellitePositionEntity SatellitePosition { get; set; } public double SatellitePosition { get; set; }
public bool DoHorizontalHigh { get; set; } public bool DoHorizontalHigh { get; set; }
public bool DoHorizontalLow { get; set; } public bool DoHorizontalLow { get; set; }
@ -29,9 +28,27 @@ namespace skyscraper8.Skyscraper.FrequencyListGenerator
public ObjectStorage ObjectStorage { get; set; } public ObjectStorage ObjectStorage { get; set; }
public ISkyscraperUiJunction Ui { get; set; } public ISkyscraperUiJunction Ui { get; set; }
public bool DoCollectRfSpectrum { get; set; } public bool DoCollectRfSpectrum { get; set; }
public LnbEntity LnbType { get; set; }
public bool DoCollectIqGraphs { get; set; } public bool DoCollectIqGraphs { get; set; }
public bool DoRecordTs { get; set; } public bool DoRecordTs { get; set; }
public Ini Ini { get; set; } public Ini Ini { get; set; }
} public int Lof1 { get; internal set; }
public int Lof2 { get; internal set; }
public int LofSw { get; internal set; }
public int MinimumFrequency { get; internal set; }
public int MaximumFrequency { get; internal set; }
internal string GetFiveLetterSattelitePosition()
{
if (SatellitePosition >= 0)
{
return String.Format("{0:D4}E", (int)(SatellitePosition * 10));
}
else
{
int westerner = (int)(SatellitePosition * 10);
westerner /= -1;
return String.Format("{0:D4}W", westerner);
}
}
}
} }

View File

@ -15,7 +15,7 @@ namespace skyscraper5.src.Skyscraper.FrequencyListGenerator
public class DbBlindscanJob public class DbBlindscanJob
{ {
public DbBlindscanJob(Guid jobGuid, PhysicalAddress tunerMac, STD_TYPE tunerStandard, int diseqCIndex, public DbBlindscanJob(Guid jobGuid, PhysicalAddress tunerMac, STD_TYPE tunerStandard, int diseqCIndex,
IGpsReceiver jobContextGps, SatellitePositionEntity satellitePosition) IGpsReceiver jobContextGps, double satellitePosition)
{ {
this.JobGuid = jobGuid; this.JobGuid = jobGuid;
this.TunerMAC = tunerMac; this.TunerMAC = tunerMac;
@ -37,7 +37,7 @@ namespace skyscraper5.src.Skyscraper.FrequencyListGenerator
} }
public SatellitePositionEntity SatPosition { get; private set; } public double SatPosition { get; private set; }
public GpsCoordinate? GpsCoordinate { get; private set; } public GpsCoordinate? GpsCoordinate { get; private set; }
@ -68,8 +68,15 @@ namespace skyscraper5.src.Skyscraper.FrequencyListGenerator
switch(mode) switch(mode)
{ {
case 1: case 1:
if (SatPosition != null) if (SatPosition >= 0.0)
return String.Format("{0:0.0}° {1}", SatPosition.angle, SatPosition.GetCardinalDirectionAsChar()); {
return String.Format("{0}°E", SatPosition);
}
else
{
double westerner = SatPosition /= -1;
return String.Format("{0}°W", westerner);
}
return TunerStandard.ToString(); return TunerStandard.ToString();
case 2: case 2:
return String.Format("({0}/{1}/{2}/{3})", (int)HorizontalLowState, (int)HorizontalHighState, (int)VerticalLowState, (int)VerticalHighState); return String.Format("({0}/{1}/{2}/{3})", (int)HorizontalLowState, (int)HorizontalHighState, (int)VerticalLowState, (int)VerticalHighState);
@ -78,6 +85,19 @@ namespace skyscraper5.src.Skyscraper.FrequencyListGenerator
} }
} }
public string GetFiveLetterOrbitalPosition()
{
if (SatPosition >= 0)
{
return String.Format("{0:4}E", (int)(SatPosition * 10));
}
else
{
double westerner = SatPosition /= -1;
return String.Format("{0:4}W", (int)(westerner * 10));
}
}
} }
public enum DbBlindscanJobPolarizationStatus : int public enum DbBlindscanJobPolarizationStatus : int

View File

@ -513,7 +513,10 @@ namespace skyscraper5.Skyscraper.IO.RemoteStreamReader
{ {
case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL: case RemoteStreamReaderConstants.COMMAND_SUCCESSFUL:
pRFLevel = NetworkStreamExtensions.ReadDouble(TcpStream); pRFLevel = NetworkStreamExtensions.ReadDouble(TcpStream);
return true; return true;
case RemoteStreamReaderConstants.COMMAND_FAILED:
pRFLevel = NetworkStreamExtensions.ReadDouble(TcpStream);
return false;
default: default:
throw new NotImplementedException(result.ToString()); throw new NotImplementedException(result.ToString());
} }

View File

@ -6,7 +6,7 @@ namespace skyscraper5.Skyscraper.IO.TunerInterface
{ {
[SkyscraperPlugin] [SkyscraperPlugin]
[TunerFactoryId(2,"skyscraper5 Remote Stream Reader")] [TunerFactoryId(2,"skyscraper5 Remote Stream Reader")]
internal class RemoteStreamReaderTunerFactory : ITunerFactory public class RemoteStreamReaderTunerFactory : ITunerFactory
{ {
public string Hostname { get; set; } public string Hostname { get; set; }

View File

@ -14,7 +14,6 @@ using skyscraper5.Mhp.Si.Model;
using skyscraper5.Mpeg2.Descriptors; using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Mpeg2.Psi.Model; using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Scte35; using skyscraper5.Scte35;
using skyscraper5.Skyscraper.Equipment;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader; using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper5.Skyscraper.Net; using skyscraper5.Skyscraper.Net;
using skyscraper5.src.InteractionChannel.Model; using skyscraper5.src.InteractionChannel.Model;
@ -98,7 +97,7 @@ namespace skyscraper5.Skyscraper.Scraper
/// Called before a SetChannel operation is performed. /// Called before a SetChannel operation is performed.
/// </summary> /// </summary>
/// <param name="blindscanResult"></param> /// <param name="blindscanResult"></param>
void OnBlindscanBeforeSetChannel(BlindscanSearchResult blindscanResult, LnbEntity lnbType); void OnBlindscanBeforeSetChannel(BlindscanSearchResult blindscanResult);
/// <summary> /// <summary>
/// Called after a band (meaning e.g. Horizontal High) has been scraped. /// Called after a band (meaning e.g. Horizontal High) has been scraped.

View File

@ -12,13 +12,11 @@ using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Rds.Messages; using skyscraper5.Rds.Messages;
using skyscraper5.Scte35; using skyscraper5.Scte35;
using skyscraper5.Skyscraper; using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Equipment;
using skyscraper5.Skyscraper.Gps; using skyscraper5.Skyscraper.Gps;
using skyscraper5.Skyscraper.Headless; using skyscraper5.Skyscraper.Headless;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader; using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper5.Skyscraper.Scraper.Storage.Utilities; using skyscraper5.Skyscraper.Scraper.Storage.Utilities;
using skyscraper5.src.InteractionChannel.Model; using skyscraper5.src.InteractionChannel.Model;
using skyscraper5.src.InteractionChannel.Model.Descriptors;
using skyscraper5.src.Skyscraper.FrequencyListGenerator; using skyscraper5.src.Skyscraper.FrequencyListGenerator;
using skyscraper5.src.Skyscraper.Scraper.Dns; using skyscraper5.src.Skyscraper.Scraper.Dns;
using skyscraper5.Teletext; using skyscraper5.Teletext;
@ -30,8 +28,6 @@ using System.Net.NetworkInformation;
using skyscraper5.src.InteractionChannel.Model2; using skyscraper5.src.InteractionChannel.Model2;
using skyscraper8.InteractionChannel.Model; using skyscraper8.InteractionChannel.Model;
using skyscraper8.InteractionChannel.Model2; using skyscraper8.InteractionChannel.Model2;
using skyscraper8.InteractionChannel.Model2.Descriptors;
using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform;
namespace skyscraper8.Skyscraper.Scraper.Storage namespace skyscraper8.Skyscraper.Scraper.Storage
{ {
@ -85,9 +81,6 @@ namespace skyscraper8.Skyscraper.Scraper.Storage
void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime); void T2MiSetTimestamp(int currentNetworkId, int currentTransportStreamId, int pid, DateTime resolveTime);
bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo); bool TestForRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo);
void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo); void SetRelatedContent(EitEvent lEvent, RctLinkInfo rctLinkInfo);
List<SatellitePositionEntity> UiSatellitesListAll();
void UiSatellitesAdd(SatellitePositionEntity newPosition);
void UiSatellitesDelete(SatellitePositionEntity satellitePosition);
bool UiTunerTestFor(TunerMetadata tuner); bool UiTunerTestFor(TunerMetadata tuner);
void UiTunerUpdate(TunerMetadata tuner); void UiTunerUpdate(TunerMetadata tuner);
void UiTunerInsert(TunerMetadata tuner); void UiTunerInsert(TunerMetadata tuner);
@ -107,11 +100,6 @@ namespace skyscraper8.Skyscraper.Scraper.Storage
bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier); bool T2MiTestForTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier);
void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier); void T2MiRememberTransmitter(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier);
void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset); void T2MiSetTransmitterTimeOffset(int? currentNetworkId, int? currentTransportStreamId, int relatedPid, ushort txIdentifier, ushort timeOffset);
List<LnbEntity> UiLnbTypesListAll();
void UiLnbTypesAdd(LnbEntity defaultLnbType);
List<DishEntity> UiDishTypesListAll();
void UiDishTypesAdd(DishEntity defaultDishType);
IEnumerable<Tuple<int, int, ProgramMapping>> SelectAllPmt(); IEnumerable<Tuple<int, int, ProgramMapping>> SelectAllPmt();
SdtService SelectSdtById(int networkId, int tsId, ushort programMappingProgramNumber); SdtService SelectSdtById(int networkId, int tsId, ushort programMappingProgramNumber);
IEnumerable<Tuple<int, int, SdtService>> SelectAllSdt(); IEnumerable<Tuple<int, int, SdtService>> SelectAllSdt();

View File

@ -13,7 +13,6 @@ using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Mpeg2.Psi.Model; using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Rds.Messages; using skyscraper5.Rds.Messages;
using skyscraper5.Scte35; using skyscraper5.Scte35;
using skyscraper5.Skyscraper.Equipment;
using skyscraper5.Skyscraper.Gps; using skyscraper5.Skyscraper.Gps;
using skyscraper5.Skyscraper.Headless; using skyscraper5.Skyscraper.Headless;
using skyscraper5.Skyscraper.IO; using skyscraper5.Skyscraper.IO;
@ -789,48 +788,6 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem
} }
} }
public List<SatellitePositionEntity> UiSatellitesListAll()
{
string path = Path.Combine(rootDirectory.FullName, "satellites.json");
FileInfo fi = new FileInfo(path);
if (!fi.Exists)
return new List<SatellitePositionEntity>();
else
{
return JsonConvert.DeserializeObject<List<SatellitePositionEntity>>(File.ReadAllText(fi.FullName));
}
}
public void UiSatellitesAdd(SatellitePositionEntity newPosition)
{
string path = Path.Combine(rootDirectory.FullName, "satellites.json");
FileInfo fi = new FileInfo(path);
List<SatellitePositionEntity> satellites;
if (!fi.Exists)
{
satellites = new List<SatellitePositionEntity>();
}
else
{
satellites = JsonConvert.DeserializeObject<List<SatellitePositionEntity>>(File.ReadAllText(fi.FullName));
}
satellites.Add(newPosition);
EnsureDirectoryExists(fi.Directory);
File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(satellites, Formatting.Indented));
}
public void UiSatellitesDelete(SatellitePositionEntity satellitePosition)
{
string path = Path.Combine(rootDirectory.FullName, "satellites.json");
FileInfo fi = new FileInfo(path);
List<SatellitePositionEntity> satellites = JsonConvert.DeserializeObject<List<SatellitePositionEntity>>(File.ReadAllText(fi.FullName));
satellites.RemoveAll(x => x.Checksum == satellitePosition.Checksum);
File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(satellites, Formatting.Indented));
}
public bool UiTunerTestFor(TunerMetadata tuner) public bool UiTunerTestFor(TunerMetadata tuner)
{ {
string fname = BitConverter.ToString(tuner.MacAddress.GetAddressBytes()); string fname = BitConverter.ToString(tuner.MacAddress.GetAddressBytes());
@ -935,35 +892,6 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem
File.WriteAllText(path, timeOffset.ToString()); File.WriteAllText(path, timeOffset.ToString());
} }
public List<LnbEntity> UiLnbTypesListAll()
{
string path = Path.Combine(rootDirectory.FullName, "0-UI", "LnbTypes");
DirectoryInfo di = new DirectoryInfo(path);
if (!di.Exists)
{
return new List<LnbEntity>();
}
List<LnbEntity> result = new List<LnbEntity>();
FileInfo[] fileInfos = di.GetFiles("*.json");
foreach (FileInfo fileInfo in fileInfos)
{
string fid = Path.GetFileNameWithoutExtension(fileInfo.Name);
if (!fid.IsNaturalNumeric())
continue;
int iid = int.Parse(fid);
string json = File.ReadAllText(fileInfo.FullName);
LnbEntity deserializeObject = JsonConvert.DeserializeObject<LnbEntity>(json);
if (deserializeObject.Id != iid)
continue;
if (result.Contains(deserializeObject))
continue;
result.Add(deserializeObject);
}
return result;
}
private int GetNextJsonFileNumber(DirectoryInfo di) private int GetNextJsonFileNumber(DirectoryInfo di)
{ {
@ -988,61 +916,6 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem
return result; return result;
} }
public void UiLnbTypesAdd(LnbEntity defaultLnbType)
{
string path = Path.Combine(rootDirectory.FullName, "0-UI", "LnbTypes");
DirectoryInfo di = new DirectoryInfo(path);
int fileId = GetNextJsonFileNumber(di);
defaultLnbType.Id = fileId;
defaultLnbType.DateAdded = DateTime.Now;
path = Path.Combine(rootDirectory.FullName, "0-UI", "LnbTypes", fileId.ToString() + ".json");
string json = JsonConvert.SerializeObject(defaultLnbType, jsonSerializerSettings);
File.WriteAllText(path, json);
}
public List<DishEntity> UiDishTypesListAll()
{
string path = Path.Combine(rootDirectory.FullName, "0-UI", "DishTypes");
DirectoryInfo di = new DirectoryInfo(path);
if (!di.Exists)
{
return new List<DishEntity>();
}
List<DishEntity> result = new List<DishEntity>();
FileInfo[] fileInfos = di.GetFiles("*.json");
foreach (FileInfo fileInfo in fileInfos)
{
string fid = Path.GetFileNameWithoutExtension(fileInfo.Name);
if (!fid.IsNaturalNumeric())
continue;
int iid = int.Parse(fid);
string json = File.ReadAllText(fileInfo.FullName);
DishEntity deserializeObject = JsonConvert.DeserializeObject<DishEntity>(json);
if (deserializeObject.Id != iid)
continue;
if (result.Contains(deserializeObject))
continue;
result.Add(deserializeObject);
}
return result;
}
public void UiDishTypesAdd(DishEntity defaultDishType)
{
string path = Path.Combine(rootDirectory.FullName, "0-UI", "DishTypes");
DirectoryInfo di = new DirectoryInfo(path);
int fileId = GetNextJsonFileNumber(di);
defaultDishType.Id = fileId;
defaultDishType.DateAdded = DateTime.Now;
path = Path.Combine(rootDirectory.FullName, "0-UI", "DishTypes", fileId.ToString() + ".json");
string json = JsonConvert.SerializeObject(defaultDishType, jsonSerializerSettings);
File.WriteAllText(path, json);
}
public object[] GetPluginConnector() public object[] GetPluginConnector()
{ {
@ -1243,7 +1116,7 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem
{ {
if (jobInDb.DateAdded == DateTime.MinValue) if (jobInDb.DateAdded == DateTime.MinValue)
jobInDb.DateAdded = DateTime.Now; jobInDb.DateAdded = DateTime.Now;
string path = Path.Combine(rootDirectory.FullName, "0-Blindscan",jobInDb.SatPosition.Checksum.ToString(), jobInDb.JobGuid.ToString(), "index.json"); string path = Path.Combine(rootDirectory.FullName, "0-Blindscan",jobInDb.GetFiveLetterOrbitalPosition(), jobInDb.JobGuid.ToString(), "index.json");
FileInfo fi = new FileInfo(path); FileInfo fi = new FileInfo(path);
EnsureDirectoryExists(fi.Directory); EnsureDirectoryExists(fi.Directory);
@ -1260,7 +1133,7 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem
SearchResult2 searchResult2) SearchResult2 searchResult2)
{ {
string freq = satellite ? String.Format("{0}_{1}", searchResult.Freq, searchResult.Pol) : String.Format("{0}", searchResult2); string freq = satellite ? String.Format("{0}_{1}", searchResult.Freq, searchResult.Pol) : String.Format("{0}", searchResult2);
string path = Path.Combine(rootDirectory.FullName, "0-Blindscan", jobInDb.SatPosition.Checksum.ToString(), jobInDb.JobGuid.ToString(), freq + ".json"); string path = Path.Combine(rootDirectory.FullName, "0-Blindscan", jobInDb.GetFiveLetterOrbitalPosition(), jobInDb.JobGuid.ToString(), freq + ".json");
string json = JsonConvert.SerializeObject(satellite ? searchResult : searchResult2, jsonSerializerSettings); string json = JsonConvert.SerializeObject(satellite ? searchResult : searchResult2, jsonSerializerSettings);
File.WriteAllText(path, json); File.WriteAllText(path, json);
} }
@ -1276,7 +1149,7 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem
{ {
string freq = resultSatellite ? String.Format("{0}_{1}", resultSr1.Freq, resultSr1.Pol) : String.Format("{0}", resultSr2); string freq = resultSatellite ? String.Format("{0}_{1}", resultSr1.Freq, resultSr1.Pol) : String.Format("{0}", resultSr2);
string jsonName = String.Format("{0}.json", humanReadableService.ServiceId); string jsonName = String.Format("{0}.json", humanReadableService.ServiceId);
string path = Path.Combine(rootDirectory.FullName, "0-Blindscan", jobInDb.SatPosition.Checksum.ToString(), jobInDb.JobGuid.ToString(), freq, jsonName); string path = Path.Combine(rootDirectory.FullName, "0-Blindscan", jobInDb.GetFiveLetterOrbitalPosition(), jobInDb.JobGuid.ToString(), freq, jsonName);
FileInfo fi = new FileInfo(path); FileInfo fi = new FileInfo(path);
EnsureDirectoryExists(fi.Directory); EnsureDirectoryExists(fi.Directory);
string json = JsonConvert.SerializeObject(humanReadableService, jsonSerializerSettings); string json = JsonConvert.SerializeObject(humanReadableService, jsonSerializerSettings);

View File

@ -12,7 +12,6 @@ using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Mpeg2.Psi.Model; using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Rds.Messages; using skyscraper5.Rds.Messages;
using skyscraper5.Scte35; using skyscraper5.Scte35;
using skyscraper5.Skyscraper.Equipment;
using skyscraper5.Skyscraper.Gps; using skyscraper5.Skyscraper.Gps;
using skyscraper5.Skyscraper.Headless; using skyscraper5.Skyscraper.Headless;
using skyscraper5.Skyscraper.IO; using skyscraper5.Skyscraper.IO;
@ -771,27 +770,6 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory
throw new NotImplementedException(); throw new NotImplementedException();
} }
private List<SatellitePositionEntity> satellitePositions;
public List<SatellitePositionEntity> UiSatellitesListAll()
{
if (satellitePositions == null)
satellitePositions = new List<SatellitePositionEntity>();
return satellitePositions.ToList();
}
public void UiSatellitesAdd(SatellitePositionEntity newPosition)
{
if (satellitePositions == null)
satellitePositions = new List<SatellitePositionEntity>();
satellitePositions.Add(newPosition);
}
public void UiSatellitesDelete(SatellitePositionEntity satellitePosition)
{
satellitePositions.RemoveAll(x => x.Checksum == satellitePosition.Checksum);
}
private List<TunerMetadata> uiTuners; private List<TunerMetadata> uiTuners;
public bool UiTunerTestFor(TunerMetadata tuner) public bool UiTunerTestFor(TunerMetadata tuner)
@ -905,40 +883,6 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory
t2miTransmitters[t2miTransmitterId].TimeOffset = timeOffset; t2miTransmitters[t2miTransmitterId].TimeOffset = timeOffset;
} }
private List<LnbEntity> _lnbTypes;
public List<LnbEntity> UiLnbTypesListAll()
{
if (_lnbTypes == null)
_lnbTypes = new List<LnbEntity>();
return _lnbTypes;
}
public void UiLnbTypesAdd(LnbEntity defaultLnbType)
{
if (_lnbTypes == null)
_lnbTypes = new List<LnbEntity>();
if (!_lnbTypes.Contains(defaultLnbType))
_lnbTypes.Add(defaultLnbType);
}
private List<DishEntity> _dishTypes;
public List<DishEntity> UiDishTypesListAll()
{
if (_dishTypes == null)
_dishTypes = new List<DishEntity>();
return _dishTypes;
}
public void UiDishTypesAdd(DishEntity defaultDishType)
{
if (_dishTypes == null)
_dishTypes = new List<DishEntity>();
if (!_dishTypes.Contains(defaultDishType))
_dishTypes.Add(defaultDishType);
}
public object[] GetPluginConnector() public object[] GetPluginConnector()
{ {

View File

@ -0,0 +1,112 @@
using log4net;
using log4net.Repository.Hierarchy;
using skyscraper5.Docsis.MacManagement;
using skyscraper5.Skyscraper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.Skyscraper
{
internal class TsRotationToCsv
{
public DirectoryInfo SourceDir { get; set; }
public FileInfo DestinationCsv { get; set; }
private List<FileInfo> files;
private ILog logger;
public void Run()
{
logger = LogManager.GetLogger(typeof(TsRotationToCsv));
files = new List<FileInfo>();
ScrapeDirectory(SourceDir);
files.Sort(new FileInfoComparerByDate(logger));
DumpCsv();
}
private void DumpCsv()
{
StreamWriter sw = new StreamWriter(DestinationCsv.FullName);
foreach (FileInfo file in files)
{
sw.WriteLine(String.Format("\"{0}\";{1};{2}", file.FullName, file.Length, file.LastWriteTime));
}
sw.Flush();
sw.Close();
}
private void ScrapeDirectory(DirectoryInfo di)
{
try
{
foreach (FileSystemInfo fsi in di.GetFileSystemInfos())
{
switch (fsi)
{
case FileInfo fi:
if (fi.Extension.ToLowerInvariant().Equals(".ts"))
{
files.Add(fi);
if (files.Count % 100 == 0)
logger.InfoFormat("Listed {0} files.", files.Count);
}
break;
case DirectoryInfo subdirectory:
ScrapeDirectory(subdirectory);
break;
default:
throw new NotImplementedException(di.GetType().Name);
}
}
}
catch (Exception e)
{
logger.WarnFormat(e.Message);
}
}
private class FileInfoComparerByDate : Comparer<FileInfo>
{
private ILog logger;
private int ups, downs, stands;
public FileInfoComparerByDate(ILog logger)
{
this.logger = logger;
}
public override int Compare(FileInfo? x, FileInfo? y)
{
long xTime = x.LastWriteTime.ToUnixTime();
long yTime = y.LastWriteTime.ToUnixTime();
int ops;
int v = xTime.CompareTo(yTime);
switch(v)
{
case -1:
ups++;
ops = ups;
break;
case 0:
stands++;
ops = stands;
break;
case 1:
downs++;
ops = downs;
break;
default:
throw new NotImplementedException(v.ToString());
}
if (ops % 1000 == 0)
{
logger.InfoFormat("Sort progress: {0} up, {1} down, {2} stand.", ups, downs, stands);
}
return v;
}
}
}
}