mirror of
https://github.com/cyanfish/naps2.git
synced 2024-09-19 03:37:38 +03:00
Escl: Implement GetCaps
This commit is contained in:
parent
53a1beb057
commit
f569244b71
@ -39,6 +39,7 @@ internal class EsclApiController : WebApiController
|
||||
new XElement(PwgNs + "Version", caps.Version),
|
||||
new XElement(PwgNs + "MakeAndModel", caps.MakeAndModel),
|
||||
new XElement(PwgNs + "SerialNumber", caps.SerialNumber),
|
||||
new XElement(ScanNs + "Manufacturer", caps.Manufacturer),
|
||||
new XElement(ScanNs + "UUID", caps.Uuid),
|
||||
new XElement(ScanNs + "AdminURI", ""),
|
||||
new XElement(ScanNs + "IconURI", iconUri),
|
||||
|
@ -31,6 +31,7 @@ internal static class CapabilitiesParser
|
||||
Version = root.Element(PwgNs + "Version")?.Value ?? EsclCapabilities.DEFAULT_VERSION,
|
||||
MakeAndModel = root.Element(PwgNs + "MakeAndModel")?.Value,
|
||||
SerialNumber = root.Element(PwgNs + "SerialNumber")?.Value,
|
||||
Manufacturer = root.Element(ScanNs + "Manufacturer")?.Value,
|
||||
Uuid = root.Element(ScanNs + "UUID")?.Value,
|
||||
AdminUri = root.Element(ScanNs + "AdminURI")?.Value,
|
||||
IconUri = root.Element(ScanNs + "IconURI")?.Value,
|
||||
|
@ -7,6 +7,7 @@ public class EsclCapabilities
|
||||
public string Version { get; init; } = DEFAULT_VERSION;
|
||||
public string? MakeAndModel { get; init; }
|
||||
public string? SerialNumber { get; init; }
|
||||
public string? Manufacturer { get; init; }
|
||||
public string? Uuid { get; init; }
|
||||
public string? AdminUri { get; init; }
|
||||
public string? IconUri { get; init; }
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
@ -53,9 +54,6 @@ internal class EsclScanDriver : IScanDriver
|
||||
{
|
||||
return;
|
||||
}
|
||||
// TODO: When we implement scanner capabilities, store all the connection information in there so we can
|
||||
// try and connect directly before querying for a potentially-updated-IP (and then back-propagate the new
|
||||
// connection info).
|
||||
var id = service.Uuid;
|
||||
var name = string.IsNullOrEmpty(service.ScannerName)
|
||||
? $"{ip}"
|
||||
@ -73,37 +71,100 @@ internal class EsclScanDriver : IScanDriver
|
||||
}
|
||||
}
|
||||
|
||||
public Task<ScanCaps> GetCaps(ScanOptions options, CancellationToken cancelToken)
|
||||
public async Task<ScanCaps> GetCaps(ScanOptions options, CancellationToken cancelToken)
|
||||
{
|
||||
return Task.FromResult(new ScanCaps());
|
||||
if (cancelToken.IsCancellationRequested) return new ScanCaps();
|
||||
var client = await GetEsclClient(options, cancelToken);
|
||||
if (client == null) return new ScanCaps();
|
||||
|
||||
try
|
||||
{
|
||||
var caps = await client.GetCapabilities();
|
||||
return new ScanCaps
|
||||
{
|
||||
MetadataCaps = new()
|
||||
{
|
||||
Model = caps.MakeAndModel,
|
||||
Manufacturer = caps.Manufacturer,
|
||||
SerialNumber = caps.SerialNumber,
|
||||
IconUri = client.IconUri
|
||||
},
|
||||
PaperSourceCaps = new()
|
||||
{
|
||||
SupportsFlatbed = caps.PlatenCaps != null,
|
||||
SupportsFeeder = caps.AdfSimplexCaps != null,
|
||||
SupportsDuplex = caps.AdfDuplexCaps != null,
|
||||
CanCheckIfFeederHasPaper = true
|
||||
},
|
||||
FlatbedCaps = MapCaps(caps.PlatenCaps),
|
||||
FeederCaps = MapCaps(caps.AdfSimplexCaps),
|
||||
DuplexCaps = MapCaps(caps.AdfDuplexCaps)
|
||||
};
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.InnerException is TaskCanceledException or SocketException)
|
||||
{
|
||||
// A connection timeout manifests as TaskCanceledException
|
||||
_logger.LogError(ex, "Error connecting to ESCL device");
|
||||
throw new DeviceCommunicationException();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
return new ScanCaps();
|
||||
}
|
||||
|
||||
private PerSourceCaps? MapCaps(EsclInputCaps? caps)
|
||||
{
|
||||
if (caps == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return PerSourceCaps.UnionAll(caps.SettingProfiles.Select(profile => MapSettingProfile(caps, profile)));
|
||||
}
|
||||
|
||||
private PerSourceCaps MapSettingProfile(EsclInputCaps caps, EsclSettingProfile profile)
|
||||
{
|
||||
DpiCaps? dpiCaps = null;
|
||||
if (profile.DiscreteResolutions.Count > 0)
|
||||
{
|
||||
dpiCaps = new DpiCaps
|
||||
{
|
||||
Values = profile.DiscreteResolutions
|
||||
.Where(res => res.XResolution == res.YResolution)
|
||||
.Select(res => res.XResolution).ToImmutableList()
|
||||
};
|
||||
}
|
||||
else if (profile.XResolutionRange != null && profile.YResolutionRange != null)
|
||||
{
|
||||
int min = Math.Max(profile.XResolutionRange.Min, profile.YResolutionRange.Min);
|
||||
int max = Math.Min(profile.XResolutionRange.Max, profile.YResolutionRange.Max);
|
||||
int step = Math.Max(profile.XResolutionRange.Step, profile.YResolutionRange.Step);
|
||||
dpiCaps = DpiCaps.ForRange(min, max, step);
|
||||
}
|
||||
return new PerSourceCaps
|
||||
{
|
||||
DpiCaps = dpiCaps,
|
||||
BitDepthCaps = new BitDepthCaps
|
||||
{
|
||||
SupportsColor = profile.ColorModes.Contains(EsclColorMode.RGB24),
|
||||
SupportsGrayscale = profile.ColorModes.Contains(EsclColorMode.Grayscale8),
|
||||
SupportsBlackAndWhite = profile.ColorModes.Contains(EsclColorMode.BlackAndWhite1)
|
||||
},
|
||||
PageSizeCaps = caps.MaxWidth != null && caps.MaxHeight != null
|
||||
? new PageSizeCaps
|
||||
{
|
||||
ScanArea = new PageSize(caps.MaxWidth.Value / 300m, caps.MaxHeight.Value / 300m, PageSizeUnit.Inch)
|
||||
}
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
public async Task Scan(ScanOptions options, CancellationToken cancelToken, IScanEvents scanEvents,
|
||||
Action<IMemoryImage> callback)
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested) return;
|
||||
|
||||
EsclClient client;
|
||||
string deviceId = options.Device!.ID;
|
||||
|
||||
if (deviceId.StartsWith("http://") || deviceId.StartsWith("https://"))
|
||||
{
|
||||
client = new EsclClient(new Uri(deviceId));
|
||||
// TODO: Handle device offline?
|
||||
}
|
||||
else
|
||||
{
|
||||
var service = await FindDeviceEsclService(options, cancelToken);
|
||||
|
||||
if (cancelToken.IsCancellationRequested) return;
|
||||
if (service == null) throw new DeviceOfflineException();
|
||||
|
||||
client = new EsclClient(service);
|
||||
}
|
||||
|
||||
client.SecurityPolicy = options.EsclOptions.SecurityPolicy;
|
||||
client.Logger = _logger;
|
||||
client.CancelToken = cancelToken;
|
||||
var client = await GetEsclClient(options, cancelToken);
|
||||
if (client == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
@ -169,6 +230,31 @@ internal class EsclScanDriver : IScanDriver
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<EsclClient?> GetEsclClient(ScanOptions options, CancellationToken cancelToken)
|
||||
{
|
||||
EsclClient client;
|
||||
string deviceId = options.Device!.ID;
|
||||
|
||||
if (deviceId.StartsWith("http://") || deviceId.StartsWith("https://"))
|
||||
{
|
||||
client = new EsclClient(new Uri(deviceId));
|
||||
// TODO: Handle device offline?
|
||||
}
|
||||
else
|
||||
{
|
||||
var service = await FindDeviceEsclService(options, cancelToken);
|
||||
if (cancelToken.IsCancellationRequested) return null;
|
||||
if (service == null) throw new DeviceOfflineException();
|
||||
client = new EsclClient(service);
|
||||
}
|
||||
|
||||
client.SecurityPolicy = options.EsclOptions.SecurityPolicy;
|
||||
client.Logger = _logger;
|
||||
client.CancelToken = cancelToken;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async Task<EsclJob> CreateScanJobAndCorrectInvalidSettings(EsclClient client, EsclScanSettings scanSettings)
|
||||
{
|
||||
_logger.LogDebug("Creating ESCL job: format {Format}, source {Source}, mode {Mode}",
|
||||
|
@ -25,11 +25,6 @@ public class MetadataCaps
|
||||
/// </summary>
|
||||
public string? SerialNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The location note associated with the device.
|
||||
/// </summary>
|
||||
public string? Location { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URI for an icon associated with the device.
|
||||
/// </summary>
|
||||
|
@ -11,10 +11,11 @@ public class PerSourceCaps
|
||||
/// Gets an object representing the union of all possible option values allowed by the provided objects.
|
||||
/// This can be helpful when presenting the user with a single set of possible options for multiple sources.
|
||||
/// </summary>
|
||||
public static PerSourceCaps UnionAll(ICollection<PerSourceCaps> caps)
|
||||
public static PerSourceCaps UnionAll(IEnumerable<PerSourceCaps> caps)
|
||||
{
|
||||
var capsColl = caps as ICollection<PerSourceCaps> ?? caps.ToList();
|
||||
DpiCaps? dpiCaps = null;
|
||||
foreach (var dpiValues in caps.Select(x => x.DpiCaps?.Values).WhereNotNull())
|
||||
foreach (var dpiValues in capsColl.Select(x => x.DpiCaps?.Values).WhereNotNull())
|
||||
{
|
||||
dpiCaps = new DpiCaps
|
||||
{
|
||||
@ -22,7 +23,7 @@ public class PerSourceCaps
|
||||
};
|
||||
}
|
||||
BitDepthCaps? bitDepthCaps = null;
|
||||
foreach (var bd in caps.Select(x => x.BitDepthCaps).WhereNotNull())
|
||||
foreach (var bd in capsColl.Select(x => x.BitDepthCaps).WhereNotNull())
|
||||
{
|
||||
bitDepthCaps = new BitDepthCaps
|
||||
{
|
||||
@ -32,7 +33,7 @@ public class PerSourceCaps
|
||||
};
|
||||
}
|
||||
PageSizeCaps? pageSizeCaps = null;
|
||||
foreach (var area in caps.Select(x => x.PageSizeCaps?.ScanArea).WhereNotNull())
|
||||
foreach (var area in capsColl.Select(x => x.PageSizeCaps?.ScanArea).WhereNotNull())
|
||||
{
|
||||
pageSizeCaps = new PageSizeCaps
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user