kopia lustrzana https://github.com/M0LTE/ft8spotter
Add project files.
rodzic
32816f40af
commit
3bd300635a
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29102.190
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ft8spotter", "ft8spotter\ft8spotter.csproj", "{C85CCBC8-5FA8-44A7-8AC0-C31CF99DDAAD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C85CCBC8-5FA8-44A7-8AC0-C31CF99DDAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C85CCBC8-5FA8-44A7-8AC0-C31CF99DDAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C85CCBC8-5FA8-44A7-8AC0-C31CF99DDAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C85CCBC8-5FA8-44A7-8AC0-C31CF99DDAAD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {422B7EC7-DA35-43ED-9699-670971F0ACDF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,260 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace ft8spotter
|
||||
{
|
||||
public class ClublogCtyXml
|
||||
{
|
||||
internal const string ClublogNamespace = "https://clublog.org/cty/v1.2";
|
||||
|
||||
public DateTime Updated { get; set; }
|
||||
public Entity[] Entities { get; set; }
|
||||
public ExceptionRecord[] Exceptions { get; set; }
|
||||
public Prefix[] Prefixes { get; set; }
|
||||
public InvalidOperation[] InvalidOperations { get; set; }
|
||||
public ZoneException[] ZoneExceptions { get; set; }
|
||||
|
||||
public static ClublogCtyXml Parse(string xml)
|
||||
{
|
||||
var result = new ClublogCtyXml();
|
||||
|
||||
XDocument xDocument = XDocument.Parse(xml);
|
||||
|
||||
result.Entities = Fetch(xDocument, "entities", "entity", (xe) => Entity.Parse(xe));
|
||||
result.Exceptions = Fetch(xDocument, "exceptions", "exception", (xe) => ExceptionRecord.Parse(xe));
|
||||
result.InvalidOperations = Fetch(xDocument, "invalid_operations", "invalid", (xe) => InvalidOperation.Parse(xe));
|
||||
result.Prefixes = Fetch(xDocument, "prefixes", "prefix", (xe) => Prefix.Parse(xe));
|
||||
result.ZoneExceptions = Fetch(xDocument, "zone_exceptions", "zone_exception", (xe) => ZoneException.Parse(xe));
|
||||
result.Updated = DateTime.Parse(xDocument.Root.Attribute("date").Value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static T[] Fetch<T>(XDocument xDocument, string parent, string elementName, Func<XElement, T> parse) => xDocument.Element(XName.Get("clublog", ClublogNamespace))
|
||||
.Descendants(XName.Get(parent, ClublogNamespace))
|
||||
.Descendants(XName.Get(elementName, ClublogNamespace))
|
||||
.Select(parse)
|
||||
.ToArray();
|
||||
|
||||
|
||||
internal static bool? GetNullableBool(XElement xe, string v)
|
||||
{
|
||||
var s = GetString(xe, v);
|
||||
|
||||
if (s == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (s == "FALSE")
|
||||
return false;
|
||||
|
||||
if (s == "TRUE")
|
||||
return true;
|
||||
|
||||
throw new NotImplementedException(v);
|
||||
}
|
||||
|
||||
internal static DateTime? GetNullableDateTime(XElement xe, string v)
|
||||
{
|
||||
var dt = GetString(xe, v);
|
||||
|
||||
if (dt == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return DateTime.Parse(dt);
|
||||
}
|
||||
|
||||
internal static bool GetBool(XElement xe, string v)
|
||||
{
|
||||
string s = GetString(xe, v);
|
||||
|
||||
if (s == "FALSE")
|
||||
return false;
|
||||
|
||||
if (s == "TRUE")
|
||||
return true;
|
||||
|
||||
throw new NotImplementedException(v);
|
||||
}
|
||||
|
||||
internal static string GetString(XElement xe, string elementName)
|
||||
{
|
||||
return xe.Element(XName.Get(elementName, ClublogCtyXml.ClublogNamespace))?.Value;
|
||||
}
|
||||
|
||||
internal static int? GetNullableInt(XElement xe, string v)
|
||||
{
|
||||
var s = GetString(xe, v);
|
||||
|
||||
if (s == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return int.Parse(s);
|
||||
}
|
||||
|
||||
internal static double? GetNullableDouble(XElement xe, string v)
|
||||
{
|
||||
var s = GetString(xe, v);
|
||||
|
||||
if (s == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return double.Parse(s);
|
||||
}
|
||||
}
|
||||
|
||||
public class ZoneException
|
||||
{
|
||||
public int Record { get; set; }
|
||||
public string Call { get; set; }
|
||||
public int Zone { get; set; }
|
||||
public DateTime? Start { get; set; }
|
||||
public DateTime? End { get; set; }
|
||||
|
||||
internal static ZoneException Parse(XElement xe)
|
||||
{
|
||||
var result = new ZoneException
|
||||
{
|
||||
Record = int.Parse(xe.Attribute("record").Value),
|
||||
Call = ClublogCtyXml.GetString(xe, "call"),
|
||||
Zone = int.Parse(ClublogCtyXml.GetString(xe, "zone")),
|
||||
End = ClublogCtyXml.GetNullableDateTime(xe, "end"),
|
||||
Start = ClublogCtyXml.GetNullableDateTime(xe, "start"),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidOperation
|
||||
{
|
||||
public int Record { get; set; }
|
||||
public string Call { get; set; }
|
||||
public DateTime? Start { get; set; }
|
||||
public DateTime? End { get; set; }
|
||||
|
||||
internal static InvalidOperation Parse(XElement xe)
|
||||
{
|
||||
var result = new InvalidOperation
|
||||
{
|
||||
Record = int.Parse(xe.Attribute("record").Value),
|
||||
Call = ClublogCtyXml.GetString(xe, "call"),
|
||||
End = ClublogCtyXml.GetNullableDateTime(xe, "end"),
|
||||
Start = ClublogCtyXml.GetNullableDateTime(xe, "start"),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class Entity
|
||||
{
|
||||
public int Adif { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Prefix { get; set; }
|
||||
public bool Deleted { get; set; }
|
||||
public int CqZone { get; set; }
|
||||
public string Continent { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public DateTime? Start { get; set; }
|
||||
public DateTime? End { get; set; }
|
||||
public bool? Whitelist { get; set; }
|
||||
public DateTime? WhitelistStart { get; set; }
|
||||
public DateTime? WhitelistEnd { get; set; }
|
||||
|
||||
internal static Entity Parse(XElement xe)
|
||||
{
|
||||
var result = new Entity
|
||||
{
|
||||
Adif = int.Parse(ClublogCtyXml.GetString(xe, "adif")),
|
||||
Continent = ClublogCtyXml.GetString(xe, "cont"),
|
||||
CqZone = int.Parse(ClublogCtyXml.GetString(xe, "cqz")),
|
||||
Deleted = ClublogCtyXml.GetBool(xe, "deleted"),
|
||||
End = ClublogCtyXml.GetNullableDateTime(xe, "end"),
|
||||
Start = ClublogCtyXml.GetNullableDateTime(xe, "start"),
|
||||
Latitude = double.Parse(ClublogCtyXml.GetString(xe, "lat")),
|
||||
Longitude = double.Parse(ClublogCtyXml.GetString(xe, "long")),
|
||||
Name = ClublogCtyXml.GetString(xe, "name"),
|
||||
Prefix = ClublogCtyXml.GetString(xe, "prefix"),
|
||||
Whitelist = ClublogCtyXml.GetNullableBool(xe, "whitelist"),
|
||||
WhitelistEnd = ClublogCtyXml.GetNullableDateTime(xe, "whitelist_end"),
|
||||
WhitelistStart = ClublogCtyXml.GetNullableDateTime(xe, "whitelist_start"),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class ExceptionRecord
|
||||
{
|
||||
public int Record { get; set; }
|
||||
public string Call { get; set; }
|
||||
public string Entity { get; set; }
|
||||
public int Adif { get; set; }
|
||||
public int CqZone { get; set; }
|
||||
public string Continent { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public DateTime? Start { get; set; }
|
||||
public DateTime? End { get; set; }
|
||||
|
||||
internal static ExceptionRecord Parse(XElement xe)
|
||||
{
|
||||
return new ExceptionRecord
|
||||
{
|
||||
Record = int.Parse(xe.Attribute("record").Value),
|
||||
Call = ClublogCtyXml.GetString(xe, "call"),
|
||||
End = ClublogCtyXml.GetNullableDateTime(xe, "end"),
|
||||
Start = ClublogCtyXml.GetNullableDateTime(xe, "start"),
|
||||
Adif = int.Parse(ClublogCtyXml.GetString(xe, "adif")),
|
||||
Continent = ClublogCtyXml.GetString(xe, "cont"),
|
||||
CqZone = int.Parse(ClublogCtyXml.GetString(xe, "cqz")),
|
||||
Entity = ClublogCtyXml.GetString(xe, "entity"),
|
||||
Latitude = double.Parse(ClublogCtyXml.GetString(xe, "lat")),
|
||||
Longitude = double.Parse(ClublogCtyXml.GetString(xe, "long")),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class Prefix
|
||||
{
|
||||
public int Record { get; set; }
|
||||
public string Call { get; set; }
|
||||
public string Entity { get; set; }
|
||||
public int? Adif { get; set; }
|
||||
public int? CqZone { get; set; }
|
||||
public string Continent { get; set; }
|
||||
public double? Longitude { get; set; }
|
||||
public double? Latitude { get; set; }
|
||||
public DateTime? Start { get; set; }
|
||||
public DateTime? End { get; set; }
|
||||
|
||||
internal static Prefix Parse(XElement xe)
|
||||
{
|
||||
return new Prefix
|
||||
{
|
||||
Record = int.Parse(xe.Attribute("record").Value),
|
||||
Call = ClublogCtyXml.GetString(xe, "call"),
|
||||
End = ClublogCtyXml.GetNullableDateTime(xe, "end"),
|
||||
Start = ClublogCtyXml.GetNullableDateTime(xe, "start"),
|
||||
Adif = ClublogCtyXml.GetNullableInt(xe, "adif"),
|
||||
Continent = ClublogCtyXml.GetString(xe, "cont"),
|
||||
CqZone = ClublogCtyXml.GetNullableInt(xe, "cqz"),
|
||||
Entity = ClublogCtyXml.GetString(xe, "entity"),
|
||||
Latitude = ClublogCtyXml.GetNullableDouble(xe, "lat"),
|
||||
Longitude = ClublogCtyXml.GetNullableDouble(xe, "long"),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
using Dapper;
|
||||
using MySql.Data.MySqlClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace ft8spotter
|
||||
{
|
||||
class Program
|
||||
{
|
||||
private const string configKey = "cloudlog_connection_string";
|
||||
|
||||
static ClublogCtyXml ctyXml;
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args.Any(a => a == "--help" || a == "-h" || a == "/?"))
|
||||
{
|
||||
Console.WriteLine(@"A work in progress, that listens to udp://localhost:2237 for WSJT-X, works out the DXCC entity of every call
|
||||
heard using Clublog's cty.xml, then queries a Cloudlog MySQL database directly (because there's no API yet)
|
||||
to see if it's a needed slot. If it is, it highlights the call in red in the console window.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(configFile))
|
||||
{
|
||||
Console.WriteLine("Cloudlog connection string?");
|
||||
string cs = Console.ReadLine();
|
||||
File.WriteAllText(configFile, $"{configKey}={cs}");
|
||||
}
|
||||
|
||||
ctyXml = ClublogCtyXml.Parse(File.ReadAllText("cty.xml"));
|
||||
|
||||
using (var client = new UdpClient(2237, AddressFamily.InterNetwork))
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
while (true)
|
||||
{
|
||||
var ipep = new IPEndPoint(IPAddress.Loopback, 0);
|
||||
|
||||
byte[] msg = client.Receive(ref ipep);
|
||||
if (msg[11] == 0x02)
|
||||
{
|
||||
string heardCall = GetHeardCall(msg);
|
||||
|
||||
if (heardCall == null)
|
||||
continue;
|
||||
|
||||
var entity = GetEntity(heardCall);
|
||||
|
||||
if (sw.Elapsed > TimeSpan.FromSeconds(5))
|
||||
{
|
||||
Console.WriteLine("------------------------------------------------------");
|
||||
sw.Restart();
|
||||
}
|
||||
|
||||
bool needed = entity == null ? false : GetNeeded(20, entity.Adif);
|
||||
|
||||
var colBefore = Console.ForegroundColor;
|
||||
if (needed)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
}
|
||||
Console.WriteLine($"{heardCall} - {entity?.Entity ?? "unknown"}");
|
||||
Console.ForegroundColor = colBefore;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static string configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".ft8spotter", "config");
|
||||
static string cs;
|
||||
static IDbConnection GetConnection()
|
||||
{
|
||||
if (cs == null)
|
||||
{
|
||||
var configSettings = File.ReadAllLines(configFile)
|
||||
.Where(line => line.Contains("="))
|
||||
.Select(line => new { key = line.Substring(0, line.IndexOf("=")), value = line.Substring(line.IndexOf("=") + 1) })
|
||||
.Where(kvp => !String.IsNullOrWhiteSpace(kvp.key) && !String.IsNullOrWhiteSpace(kvp.value))
|
||||
.ToDictionary(line => line.key, line => line.value);
|
||||
|
||||
cs = configSettings[configKey];
|
||||
}
|
||||
|
||||
return new MySqlConnection(cs);
|
||||
}
|
||||
|
||||
private static bool GetNeeded(int band, int? adif)
|
||||
{
|
||||
if (!adif.HasValue)
|
||||
return false;
|
||||
|
||||
int dxcc = adif.Value;
|
||||
|
||||
using (var conn = GetConnection())
|
||||
{
|
||||
conn.Open();
|
||||
// and col_mode = @mode
|
||||
return 0 == conn.ExecuteScalar<int>("select count(1) from TABLE_HRD_CONTACTS_V01 where col_dxcc=@dxcc and COL_BAND = @band", new { mode = "FT8", band = $"{band}M", dxcc });
|
||||
}
|
||||
}
|
||||
|
||||
private static MatchingEntity GetEntity(string heardCall)
|
||||
{
|
||||
var possibleExceptions = ctyXml.Exceptions.Where(c => String.Equals(heardCall, c.Call, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach (var possibleException in possibleExceptions)
|
||||
{
|
||||
if (possibleException.Start.HasValue)
|
||||
{
|
||||
if (possibleException.Start.Value.ToUniversalTime() > DateTime.UtcNow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// matches
|
||||
}
|
||||
}
|
||||
|
||||
if (possibleException.End.HasValue)
|
||||
{
|
||||
if (possibleException.End.Value.ToUniversalTime() < DateTime.UtcNow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// matches
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchingEntity
|
||||
{
|
||||
Call = possibleException.Call,
|
||||
Adif = possibleException.Adif,
|
||||
Continent = possibleException.Continent,
|
||||
CqZone = possibleException.CqZone,
|
||||
Entity = possibleException.Entity,
|
||||
Latitude = possibleException.Latitude,
|
||||
Longitude = possibleException.Longitude,
|
||||
};
|
||||
}
|
||||
|
||||
var fragments = GetFragments(heardCall);
|
||||
|
||||
foreach (var callFragement in fragments)
|
||||
{
|
||||
var possiblePrefixes = ctyXml.Prefixes.Where(p => String.Equals(callFragement, p.Call, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach (var possiblePrefix in possiblePrefixes)
|
||||
{
|
||||
if (possiblePrefix.Start.HasValue)
|
||||
{
|
||||
if (possiblePrefix.Start.Value.ToUniversalTime() > DateTime.UtcNow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// matches
|
||||
}
|
||||
}
|
||||
|
||||
if (possiblePrefix.End.HasValue)
|
||||
{
|
||||
if (possiblePrefix.End.Value.ToUniversalTime() < DateTime.UtcNow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// matches
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchingEntity
|
||||
{
|
||||
Call = possiblePrefix.Call,
|
||||
Adif = possiblePrefix.Adif,
|
||||
Continent = possiblePrefix.Continent,
|
||||
CqZone = possiblePrefix.CqZone,
|
||||
Entity = possiblePrefix.Entity,
|
||||
Latitude = possiblePrefix.Latitude,
|
||||
Longitude = possiblePrefix.Longitude,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetFragments(string call)
|
||||
{
|
||||
for (int i = call.Length; i > 0; i--)
|
||||
{
|
||||
yield return call.Substring(0, i);
|
||||
}
|
||||
}
|
||||
|
||||
static string GetHeardCall(byte[] msg)
|
||||
{
|
||||
/*int cur = 0;
|
||||
foreach (var batch in msg.Batch(8))
|
||||
{
|
||||
Console.Write(cur.ToString("00") + " ");
|
||||
foreach (var b in batch)
|
||||
{
|
||||
string bytestr = b.ToString("X").ToLower();
|
||||
|
||||
if (bytestr.Length == 1)
|
||||
{
|
||||
bytestr = "0" + bytestr;
|
||||
}
|
||||
|
||||
Console.Write(bytestr + " ");
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
||||
Console.Write(" ");
|
||||
foreach (var b in batch)
|
||||
{
|
||||
char ch = (char)b;
|
||||
if (Char.IsLetterOrDigit(ch) || Char.IsPunctuation(ch) || Char.IsSymbol(ch) || (ch == ' '))
|
||||
{
|
||||
Console.Write(ch);
|
||||
Console.Write(" ");
|
||||
}
|
||||
}
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
cur += 8;
|
||||
}*/
|
||||
|
||||
string text;
|
||||
try
|
||||
{
|
||||
text = Encoding.ASCII.GetString(msg.Skip(52).SkipLast(2).ToArray());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] split = text.Split(' ');
|
||||
|
||||
string heard;
|
||||
if (split.Length == 0)
|
||||
{
|
||||
heard = null;
|
||||
}
|
||||
else if (split.Length == 1)
|
||||
{
|
||||
heard = split[0];
|
||||
}
|
||||
else if (split.Length == 2)
|
||||
{
|
||||
heard = split[split.Length - 1];
|
||||
}
|
||||
else if (split.Length == 3)
|
||||
{
|
||||
heard = split[split.Length - 2];
|
||||
}
|
||||
else if (split.Length == 4)
|
||||
{
|
||||
heard = split[split.Length - 2];
|
||||
}
|
||||
else
|
||||
{
|
||||
heard = null;
|
||||
}
|
||||
|
||||
if (heard != null)
|
||||
{
|
||||
heard = heard.Replace("<", "").Replace(">", "");
|
||||
}
|
||||
|
||||
return heard;
|
||||
}
|
||||
}
|
||||
|
||||
internal class MatchingEntity
|
||||
{
|
||||
public string Call { get; set; }
|
||||
public int? Adif { get; set; }
|
||||
public string Continent { get; set; }
|
||||
public int? CqZone { get; set; }
|
||||
public string Entity { get; set; }
|
||||
public double? Latitude { get; set; }
|
||||
public double? Longitude { get; set; }
|
||||
}
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
|
||||
this IEnumerable<TSource> source, int size)
|
||||
{
|
||||
TSource[] bucket = null;
|
||||
var count = 0;
|
||||
|
||||
foreach (var item in source)
|
||||
{
|
||||
if (bucket == null)
|
||||
bucket = new TSource[size];
|
||||
|
||||
bucket[count++] = item;
|
||||
if (count != size)
|
||||
continue;
|
||||
|
||||
yield return bucket;
|
||||
|
||||
bucket = null;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
if (bucket != null && count > 0)
|
||||
yield return bucket.Take(count);
|
||||
}
|
||||
}
|
||||
}
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="cty.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="cty.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="1.60.6" />
|
||||
<PackageReference Include="MySql.Data" Version="8.0.16" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Ładowanie…
Reference in New Issue