parent
6db135877a
commit
62a3355546
@ -1,5 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.yaml": "home-assistant"
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
nzbdrone {version} {branch}; urgency=low
|
||||
|
||||
* Automatic Release.
|
||||
|
||||
-- NzbDrone <contact@nzbdrone.com> Mon, 26 Aug 2013 00:00:00 -0700
|
@ -1 +0,0 @@
|
||||
8
|
@ -1,12 +0,0 @@
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: Sonarr <contact@nzbdrone.com>
|
||||
Source: nzbdrone
|
||||
Homepage: https://readarr.com
|
||||
Vcs-Git: git@github.com:readarr/Readarr.git
|
||||
Vcs-Browser: https://github.com/readarr/Readarr
|
||||
|
||||
Package: nzbdrone
|
||||
Architecture: all
|
||||
Depends: libmono-cil-dev (>= 3.2), sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
|
||||
Description: Readarr is a music collection manager
|
@ -1,24 +0,0 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: nzbdrone
|
||||
Source: https://github.com/readarr/Readarr
|
||||
|
||||
Files: *
|
||||
Copyright: 2010-2016 Readarr <hello@readarr.com>
|
||||
|
||||
License: GPL-3.0+
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
@ -1 +0,0 @@
|
||||
nzbdrone_bin/* opt/NzbDrone
|
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
# Sample debian/rules that uses debhelper.
|
||||
# This file was originally written by Joey Hess and Craig Small.
|
||||
# As a special exception, when this file is copied by dh-make into a
|
||||
# dh-make output file, you may use that output file without restriction.
|
||||
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
%:
|
||||
dh $@
|
@ -1,4 +1,3 @@
|
||||
<Project>
|
||||
<Import Project="Targets/PublishAllRids.targets" />
|
||||
<Import Project="Targets/CopyRuntimes.targets" />
|
||||
</Project>
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ConfigSavedEvent))]
|
||||
public class FpcalcCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IFingerprintingService _fingerprintingService;
|
||||
private readonly IConfigService _configService;
|
||||
|
||||
public FpcalcCheck(IFingerprintingService fingerprintingService,
|
||||
IConfigService configService,
|
||||
ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_fingerprintingService = fingerprintingService;
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
// always pass if fingerprinting is disabled
|
||||
if (_configService.AllowFingerprinting == AllowFingerprinting.Never)
|
||||
{
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
if (!_fingerprintingService.IsSetup())
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, $"fpcalc could not be found. Audio fingerprinting disabled.", "#fpcalc-missing");
|
||||
}
|
||||
|
||||
var fpcalcVersion = _fingerprintingService.FpcalcVersion();
|
||||
if (fpcalcVersion == null || fpcalcVersion < new Version("1.4.3"))
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, $"You have an old version of fpcalc. Please upgrade to 1.4.3.", "#fpcalc-upgrade");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,508 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Parser
|
||||
{
|
||||
public interface IFingerprintingService
|
||||
{
|
||||
bool IsSetup();
|
||||
Version FpcalcVersion();
|
||||
void Lookup(List<LocalBook> tracks, double threshold);
|
||||
}
|
||||
|
||||
public class AcoustId
|
||||
{
|
||||
public double Duration { get; set; }
|
||||
public string Fingerprint { get; set; }
|
||||
}
|
||||
|
||||
public class FingerprintingService : IFingerprintingService
|
||||
{
|
||||
private const string _acoustIdUrl = "https://api.acoustid.org/v2/lookup";
|
||||
private const string _acoustIdApiKey = "QANd68ji1L";
|
||||
private const int _fingerprintingTimeout = 10000;
|
||||
|
||||
private readonly Logger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpRequestBuilderFactory _customerRequestBuilder;
|
||||
private readonly ICached<AcoustId> _cache;
|
||||
|
||||
private readonly string _fpcalcPath;
|
||||
private readonly Version _fpcalcVersion;
|
||||
private readonly string _fpcalcArgs;
|
||||
|
||||
public FingerprintingService(Logger logger,
|
||||
IHttpClient httpClient,
|
||||
ICacheManager cacheManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_cache = cacheManager.GetCache<AcoustId>(GetType());
|
||||
|
||||
_customerRequestBuilder = new HttpRequestBuilder(_acoustIdUrl).CreateFactory();
|
||||
|
||||
// An exception here will cause Readarr to fail to start, so catch any errors
|
||||
try
|
||||
{
|
||||
_fpcalcPath = GetFpcalcPath();
|
||||
|
||||
if (_fpcalcPath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_fpcalcVersion = GetFpcalcVersion();
|
||||
_fpcalcArgs = GetFpcalcArgs();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Somthing went wrong detecting fpcalc");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSetup() => _fpcalcPath.IsNotNullOrWhiteSpace();
|
||||
public Version FpcalcVersion() => _fpcalcVersion;
|
||||
|
||||
private string GetFpcalcPath()
|
||||
{
|
||||
string path = null;
|
||||
|
||||
// Take the fpcalc from the install directory if it exists
|
||||
path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "fpcalc");
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
path += ".exe";
|
||||
}
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
// Otherwise search path for a candidate and check it works
|
||||
if (OsInfo.IsLinux)
|
||||
{
|
||||
// must be on users path on Linux
|
||||
path = "fpcalc";
|
||||
|
||||
// check that the command exists
|
||||
Process p = new Process();
|
||||
p.StartInfo.FileName = path;
|
||||
p.StartInfo.Arguments = "-version";
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
|
||||
try
|
||||
{
|
||||
p.Start();
|
||||
|
||||
// To avoid deadlocks, always read the output stream first and then wait.
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
p.WaitForExit(1000);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.Debug("fpcalc not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
else
|
||||
{
|
||||
// on OSX / Windows, we have put fpcalc in the application folder
|
||||
_logger.Warn("fpcalc missing from application directory");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Version GetFpcalcVersion()
|
||||
{
|
||||
if (_fpcalcPath == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Process p = new Process();
|
||||
p.StartInfo.FileName = _fpcalcPath;
|
||||
p.StartInfo.Arguments = $"-version";
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
|
||||
p.Start();
|
||||
|
||||
// To avoid deadlocks, always read the output stream first and then wait.
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
p.WaitForExit(1000);
|
||||
|
||||
if (p.ExitCode != 0)
|
||||
{
|
||||
_logger.Warn("Could not get fpcalc version (may be known issue with fpcalc v1.4)");
|
||||
return null;
|
||||
}
|
||||
|
||||
var versionstring = Regex.Match(output, @"\d\.\d\.\d").Value;
|
||||
if (versionstring.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var version = new Version(versionstring);
|
||||
_logger.Debug($"fpcalc version: {version}");
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private string GetFpcalcArgs()
|
||||
{
|
||||
var args = "";
|
||||
|
||||
if (_fpcalcVersion == null)
|
||||
{
|
||||
return args;
|
||||
}
|
||||
|
||||
if (_fpcalcVersion >= new Version("1.4.0"))
|
||||
{
|
||||
args = "-json";
|
||||
}
|
||||
|
||||
if (_fpcalcVersion >= new Version("1.4.3"))
|
||||
{
|
||||
args += " -ignore-errors";
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
public AcoustId ParseFpcalcJsonOutput(string output)
|
||||
{
|
||||
return Json.Deserialize<AcoustId>(output);
|
||||
}
|
||||
|
||||
public AcoustId ParseFpcalcTextOutput(string output)
|
||||
{
|
||||
var durationstring = Regex.Match(output, @"(?<=DURATION=)[\d\.]+(?=\s)").Value;
|
||||
double duration;
|
||||
if (durationstring.IsNullOrWhiteSpace() || !double.TryParse(durationstring, out duration))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fingerprint = Regex.Match(output, @"(?<=FINGERPRINT=)[^\s]+").Value;
|
||||
if (fingerprint.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AcoustId
|
||||
{
|
||||
Duration = duration,
|
||||
Fingerprint = fingerprint
|
||||
};
|
||||
}
|
||||
|
||||
public AcoustId ParseFpcalcOutput(string output)
|
||||
{
|
||||
if (output.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_fpcalcArgs.Contains("-json"))
|
||||
{
|
||||
return ParseFpcalcJsonOutput(output);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ParseFpcalcTextOutput(output);
|
||||
}
|
||||
}
|
||||
|
||||
public AcoustId GetFingerprint(string file)
|
||||
{
|
||||
return _cache.Get(file, () => GetFingerprintUncached(file), TimeSpan.FromMinutes(30));
|
||||
}
|
||||
|
||||
private AcoustId GetFingerprintUncached(string file)
|
||||
{
|
||||
if (IsSetup() && File.Exists(file))
|
||||
{
|
||||
Process p = new Process();
|
||||
p.StartInfo.FileName = _fpcalcPath;
|
||||
p.StartInfo.Arguments = $"{_fpcalcArgs} \"{file}\"";
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.RedirectStandardError = true;
|
||||
|
||||
_logger.Trace("Executing {0} {1}", p.StartInfo.FileName, p.StartInfo.Arguments);
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
StringBuilder error = new StringBuilder();
|
||||
|
||||
// see https://stackoverflow.com/questions/139593/processstartinfo-hanging-on-waitforexit-why?lq=1
|
||||
// this is most likely overkill...
|
||||
using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
|
||||
{
|
||||
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
|
||||
{
|
||||
DataReceivedEventHandler outputHandler = (sender, e) =>
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
outputWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
output.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
DataReceivedEventHandler errorHandler = (sender, e) =>
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
errorWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
error.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
p.OutputDataReceived += outputHandler;
|
||||
p.ErrorDataReceived += errorHandler;
|
||||
|
||||
p.Start();
|
||||
|
||||
p.BeginOutputReadLine();
|
||||
p.BeginErrorReadLine();
|
||||
|
||||
if (p.WaitForExit(_fingerprintingTimeout) &&
|
||||
outputWaitHandle.WaitOne(_fingerprintingTimeout) &&
|
||||
errorWaitHandle.WaitOne(_fingerprintingTimeout))
|
||||
{
|
||||
// Process completed.
|
||||
if (p.ExitCode != 0)
|
||||
{
|
||||
_logger.Warn($"fpcalc error: {error}");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ParseFpcalcOutput(output.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Timed out. Remove handlers to avoid object disposed error
|
||||
p.OutputDataReceived -= outputHandler;
|
||||
p.ErrorDataReceived -= errorHandler;
|
||||
|
||||
_logger.Warn($"fpcalc timed out. {error}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Lookup(List<LocalBook> tracks, double threshold)
|
||||
{
|
||||
if (!IsSetup())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Lookup(tracks.Select(x => Tuple.Create(x, GetFingerprint(x.Path))).ToList(), threshold);
|
||||
}
|
||||
|
||||
public void Lookup(List<Tuple<LocalBook, AcoustId>> files, double threshold)
|
||||
{
|
||||
var toLookup = files.Where(x => x.Item2 != null).ToList();
|
||||
if (!toLookup.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var request = GenerateRequest(toLookup);
|
||||
var response = GetResponse(request);
|
||||
ParseResponse(response, toLookup, threshold);
|
||||
}
|
||||
|
||||
public HttpRequest GenerateRequest(List<Tuple<LocalBook, AcoustId>> toLookup)
|
||||
{
|
||||
var httpRequest = _customerRequestBuilder.Create()
|
||||
.WithRateLimit(0.334)
|
||||
.Build();
|
||||
|
||||
var sb = new StringBuilder($"client={_acoustIdApiKey}&format=json&meta=recordingids&batch=1", 2000);
|
||||
for (int i = 0; i < toLookup.Count; i++)
|
||||
{
|
||||
sb.Append($"&duration.{i}={toLookup[i].Item2.Duration:F0}&fingerprint.{i}={toLookup[i].Item2.Fingerprint}");
|
||||
}
|
||||
|
||||
// they prefer a gzipped body
|
||||
httpRequest.SetContent(Encoding.UTF8.GetBytes(sb.ToString()).Compress());
|
||||
httpRequest.Headers.Add("Content-Encoding", "gzip");
|
||||
httpRequest.Headers.ContentType = "application/x-www-form-urlencoded";
|
||||
httpRequest.SuppressHttpError = true;
|
||||
httpRequest.RequestTimeout = TimeSpan.FromSeconds(5);
|
||||
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
public LookupResponse GetResponse(HttpRequest request, int retry = 3)
|
||||
{
|
||||
HttpResponse<LookupResponse> httpResponse;
|
||||
|
||||
try
|
||||
{
|
||||
httpResponse = _httpClient.Post<LookupResponse>(request);
|
||||
}
|
||||
catch (UnexpectedHtmlContentException e)
|
||||
{
|
||||
_logger.Warn(e, "AcoustId API gave invalid response");
|
||||
return retry > 0 ? GetResponse(request, retry - 1) : null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "AcoustId API lookup failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
var response = httpResponse.Resource;
|
||||
|
||||
if (httpResponse.HasHttpError || (response != null && response.Status != "ok"))
|
||||
{
|
||||
if (response?.Error != null)
|
||||
{
|
||||
if (response.Error.Code == AcoustIdErrorCode.TooManyRequests && retry > 0)
|
||||
{
|
||||
_logger.Trace($"Too many requests, retrying in 1s");
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
return GetResponse(request, retry - 1);
|
||||
}
|
||||
|
||||
_logger.Debug($"Webservice error {response.Error.Code}: {response.Error.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("HTTP Error - {0}", httpResponse);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void ParseResponse(LookupResponse response, List<Tuple<LocalBook, AcoustId>> toLookup, double threshold)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The API will give errors if fingerprint isn't found or is invalid.
|
||||
// We don't want to stop the entire import because the fingerprinting failed
|
||||
// so just log and return.
|
||||
foreach (var fileResponse in response.Fingerprints)
|
||||
{
|
||||
if (fileResponse.Results.Count == 0)
|
||||
{
|
||||
_logger.Debug("No results for given fingerprint.");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var result in fileResponse.Results.Where(x => x.Recordings != null))
|
||||
{
|
||||
_logger.Trace("Found: {0}, {1}, {2}", result.Id, result.Score, string.Join(", ", result.Recordings.Select(x => x.Id)));
|
||||
}
|
||||
|
||||
var ids = fileResponse.Results.Where(x => x.Score > threshold && x.Recordings != null).SelectMany(y => y.Recordings.Select(z => z.Id)).Distinct().ToList();
|
||||
_logger.Trace("All recordings: {0}", string.Join("\n", ids));
|
||||
|
||||
toLookup[fileResponse.index].Item1.AcoustIdResults = ids;
|
||||
}
|
||||
|
||||
_logger.Debug("Fingerprinting complete.");
|
||||
|
||||
var serializerSettings = Json.GetSerializerSettings();
|
||||
serializerSettings.Formatting = Formatting.None;
|
||||
var output = new { Fingerprints = toLookup.Select(x => new { Path = x.Item1.Path, AcoustIdResults = x.Item1.AcoustIdResults }) };
|
||||
_logger.Debug($"*** FingerprintingService TestCaseGenerator ***\n{JsonConvert.SerializeObject(output, serializerSettings)}");
|
||||
}
|
||||
|
||||
public class LookupResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public LookupError Error { get; set; }
|
||||
public List<LookupResultListItem> Fingerprints { get; set; }
|
||||
}
|
||||
|
||||
public enum AcoustIdErrorCode
|
||||
{
|
||||
// https://github.com/acoustid/acoustid-server/blob/f671339ad9ab049c4d4361d3eadb6660a8fe4dda/acoustid/api/errors.py#L10
|
||||
UnknownFormat = 1,
|
||||
MissingParameter = 2,
|
||||
InvalidFingerprint = 3,
|
||||
InvalidApikey = 4,
|
||||
Internal = 5,
|
||||
InvalidUserApikey = 6,
|
||||
InvalidUuid = 7,
|
||||
InvalidDuration = 8,
|
||||
InvalidBitrate = 9,
|
||||
InvalidForeignid = 10,
|
||||
InvalidMaxDurationDiff = 11,
|
||||
NotAllowed = 12,
|
||||
ServiceUnavailable = 13,
|
||||
TooManyRequests = 14,
|
||||
InvalidMusicbrainzAccessToken = 15,
|
||||
InsecureRequest = 16,
|
||||
UnknownApplication = 17,
|
||||
FingerprintNotFound = 18
|
||||
}
|
||||
|
||||
public class LookupError
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public AcoustIdErrorCode Code { get; set; }
|
||||
}
|
||||
|
||||
public class LookupResultListItem
|
||||
{
|
||||
public int index { get; set; }
|
||||
public List<LookupResult> Results { get; set; }
|
||||
}
|
||||
|
||||
public class LookupResult
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public double Score { get; set; }
|
||||
public List<RecordingResult> Recordings { get; set; }
|
||||
}
|
||||
|
||||
public class RecordingResult
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
<TemplatesExport />
|
@ -1,11 +0,0 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<RuntimeFiles Include="..\Runtimes\$(RuntimeIdentifier)\*" />
|
||||
</ItemGroup>
|
||||
<Target Name="CopyRuntimeFilesOnBuild" AfterTargets="AfterBuild" Condition="!$(RuntimeIdentifier.StartsWith('linux')) or '$(TargetFramework)' == 'net6.0'">
|
||||
<Copy SourceFiles="@(RuntimeFiles)" DestinationFolder="$(OutDir)" />
|
||||
</Target>
|
||||
<Target Name="CopyRuntimeFilesOnPublish" AfterTargets="Publish" Condition="!$(RuntimeIdentifier.StartsWith('linux')) or '$(TargetFramework)' == 'net6.0'">
|
||||
<Copy SourceFiles="@(RuntimeFiles)" DestinationFolder="$(PublishDir)" />
|
||||
</Target>
|
||||
</Project>
|
Loading…
Reference in new issue