Clear the clippy warnings that were failing CI before this branch
🐳 Android image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 1s
Build and test / Desktop (Linux) (push) Failing after 9m6s
Build and test / Layer separation (push) Successful in 26s
Traceability / Requirement traces (push) Failing after 23s
Build and test / Android (aarch64) (push) Failing after 22m38s
🐳 Android image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 1s
Build and test / Desktop (Linux) (push) Failing after 9m6s
Build and test / Layer separation (push) Successful in 26s
Traceability / Requirement traces (push) Failing after 23s
Build and test / Android (aarch64) (push) Failing after 22m38s
Nothing here is film simulation. These are lints that fail master today,
under the -D warnings CI runs with, mostly from a toolchain that learned
new ones rather than from anybody's code -- `is_multiple_of` and the
derivable `Default` did not exist as lints when this was written.
They are fixed rather than allowed, and by hand rather than by trusting
`cargo clippy --fix` wholesale: its automatic pass split a derive in two
and left a stray blank line, which is the sort of thing that is correct
and still wrong to commit.
The four that needed a decision rather than a rewrite:
- The distance transform's inner loop writes through its iterator now.
`q` stays, because it is the position the parabola is evaluated at as
well as the index it is written to -- the lint is about the write.
- `to_source` and `to_proto` take `self` by value. Their receiver is
`Copy`, so this is the same machine code and the honest signature.
- The export path's return type is five levels deep and now has a name,
plus a line saying why the `Option` wraps the `Result`: `None` is
cancellation, which is not a failure and has no error to report.
- A test fills a range instead of looping over one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1201,7 +1201,8 @@ mod tests {
|
||||
let heap = GPS_IFD + 2 + 12 + 4;
|
||||
let mut extra = Vec::new();
|
||||
extra.extend_from_slice(&1u16.to_le_bytes());
|
||||
for (tag, kind, count, value) in [(gps_tag::LATITUDE, 5u16, 3u32, heap)] {
|
||||
{
|
||||
let (tag, kind, count, value) = (gps_tag::LATITUDE, 5u16, 3u32, heap);
|
||||
extra.extend_from_slice(&tag.to_le_bytes());
|
||||
extra.extend_from_slice(&kind.to_le_bytes());
|
||||
extra.extend_from_slice(&count.to_le_bytes());
|
||||
|
||||
+28
-30
@@ -193,43 +193,41 @@ pub static SRGB_BASIS: [[f32; 3]; SPECTRUM] = [
|
||||
/// CIE standard illuminant D50, normalised to unit mean.
|
||||
pub static ILLUMINANT_D50: [f32; SPECTRUM] = [
|
||||
0.2850091, 0.3163289, 0.3476603, 0.4607654, 0.5738822, 0.6158049, 0.6577392, 0.6782233,
|
||||
0.6987191, 0.6858234, 0.6729277, 0.7718918, 0.8708675, 0.9431556, 1.015444, 1.035032,
|
||||
1.054608, 1.059008, 1.063407, 1.085171, 1.106947, 1.08864, 1.070332, 1.092213,
|
||||
1.114105, 1.119284, 1.124452, 1.127455, 1.130458, 1.15938, 1.188302, 1.180481,
|
||||
1.17266, 1.181749, 1.190839, 1.177362, 1.163872, 1.150697, 1.137511, 1.144401,
|
||||
1.151279, 1.119738, 1.088209, 1.11258, 1.136964, 1.146158, 1.155364, 1.154038,
|
||||
1.152723, 1.133402, 1.114082, 1.132331, 1.150569, 1.132005, 1.113442, 1.12813,
|
||||
1.142806, 1.170821, 1.198824, 1.176303, 1.153782, 1.085392, 1.017003, 1.041573,
|
||||
1.066154, 1.073626, 1.081109, 0.9878018, 0.8944825, 0.9506859, 1.006878, 1.042201,
|
||||
1.077513, 0.9940052, 0.9104973, 0.7909793, 0.6714613, 0.8182837, 0.9651179, 0.9380695,
|
||||
0.9110095,
|
||||
0.6987191, 0.6858234, 0.6729277, 0.7718918, 0.8708675, 0.9431556, 1.015444, 1.035032, 1.054608,
|
||||
1.059008, 1.063407, 1.085171, 1.106947, 1.08864, 1.070332, 1.092213, 1.114105, 1.119284,
|
||||
1.124452, 1.127455, 1.130458, 1.15938, 1.188302, 1.180481, 1.17266, 1.181749, 1.190839,
|
||||
1.177362, 1.163872, 1.150697, 1.137511, 1.144401, 1.151279, 1.119738, 1.088209, 1.11258,
|
||||
1.136964, 1.146158, 1.155364, 1.154038, 1.152723, 1.133402, 1.114082, 1.132331, 1.150569,
|
||||
1.132005, 1.113442, 1.12813, 1.142806, 1.170821, 1.198824, 1.176303, 1.153782, 1.085392,
|
||||
1.017003, 1.041573, 1.066154, 1.073626, 1.081109, 0.9878018, 0.8944825, 0.9506859, 1.006878,
|
||||
1.042201, 1.077513, 0.9940052, 0.9104973, 0.7909793, 0.6714613, 0.8182837, 0.9651179,
|
||||
0.9380695, 0.9110095,
|
||||
];
|
||||
|
||||
/// CIE standard illuminant D55, normalised to unit mean.
|
||||
pub static ILLUMINANT_D55: [f32; SPECTRUM] = [
|
||||
0.3792827, 0.4113047, 0.4433384, 0.576397, 0.7094555, 0.7537114, 0.7979789, 0.8155671,
|
||||
0.833167, 0.8118539, 0.7905292, 0.8934979, 0.9964551, 1.068554, 1.140653, 1.155029,
|
||||
1.169404, 1.166203, 1.163002, 1.17945, 1.195897, 1.168776, 1.141643, 1.156786,
|
||||
1.17193, 1.172024, 1.172105, 1.167984, 1.163864, 1.188436, 1.21302, 1.200751,
|
||||
1.188483, 1.193523, 1.198563, 1.181289, 1.164015, 1.147812, 1.131609, 1.134705, 1.137813,
|
||||
1.101042, 1.064282, 1.081673, 1.099051, 1.103254, 1.107444, 1.102089, 1.096735,
|
||||
1.074782, 1.052828, 1.063782, 1.074735, 1.054504, 1.034274, 1.042795, 1.051303,
|
||||
1.072442, 1.093592, 1.070347, 1.047101, 0.9872826, 0.9274523, 0.9458553, 0.9642701,
|
||||
0.9759335, 0.9875969, 0.9025656, 0.817546, 0.8703108, 0.9230756, 0.9562034, 0.9893313,
|
||||
0.9130185, 0.836694, 0.725612, 0.6145185, 0.7491601, 0.8838017, 0.8598812, 0.8359723,
|
||||
0.833167, 0.8118539, 0.7905292, 0.8934979, 0.9964551, 1.068554, 1.140653, 1.155029, 1.169404,
|
||||
1.166203, 1.163002, 1.17945, 1.195897, 1.168776, 1.141643, 1.156786, 1.17193, 1.172024,
|
||||
1.172105, 1.167984, 1.163864, 1.188436, 1.21302, 1.200751, 1.188483, 1.193523, 1.198563,
|
||||
1.181289, 1.164015, 1.147812, 1.131609, 1.134705, 1.137813, 1.101042, 1.064282, 1.081673,
|
||||
1.099051, 1.103254, 1.107444, 1.102089, 1.096735, 1.074782, 1.052828, 1.063782, 1.074735,
|
||||
1.054504, 1.034274, 1.042795, 1.051303, 1.072442, 1.093592, 1.070347, 1.047101, 0.9872826,
|
||||
0.9274523, 0.9458553, 0.9642701, 0.9759335, 0.9875969, 0.9025656, 0.817546, 0.8703108,
|
||||
0.9230756, 0.9562034, 0.9893313, 0.9130185, 0.836694, 0.725612, 0.6145185, 0.7491601,
|
||||
0.8838017, 0.8598812, 0.8359723,
|
||||
];
|
||||
|
||||
/// CIE standard illuminant D65, normalised to unit mean.
|
||||
pub static ILLUMINANT_D65: [f32; SPECTRUM] = [
|
||||
0.5707279, 0.5974089, 0.6240909, 0.7845818, 0.9450738, 0.9949285, 1.044784, 1.055895,
|
||||
1.067006, 1.028465, 0.9899253, 1.093749, 1.197574, 1.266906, 1.33625, 1.34084,
|
||||
1.345431, 1.328575, 1.31173, 1.317794, 1.323859, 1.283249, 1.242638, 1.245733,
|
||||
1.24884, 1.239978, 1.231116, 1.213917, 1.196718, 1.213266, 1.229825, 1.211073,
|
||||
1.192321, 1.190266, 1.188221, 1.165118, 1.142016, 1.121083, 1.100151, 1.097033,
|
||||
1.093914, 1.053359, 1.012803, 1.020344, 1.027885, 1.02556, 1.023236, 1.012384,
|
||||
1.001533, 0.9763502, 0.9511687, 0.9535133, 0.9558578, 0.9348881, 0.9139185, 0.9149908,
|
||||
0.9160632, 0.9278442, 0.9396252, 0.9168215, 0.8940177, 0.8451223, 0.796228, 0.8070075,
|
||||
0.817787, 0.8334315, 0.8490771, 0.7763022, 0.7035272, 0.7508158, 0.7981044, 0.8278048,
|
||||
0.8575052, 0.7918713, 0.7262385, 0.6281702, 0.530103, 0.6465155, 0.762928, 0.7433847,
|
||||
0.7238414,
|
||||
0.5707279, 0.5974089, 0.6240909, 0.7845818, 0.9450738, 0.9949285, 1.044784, 1.055895, 1.067006,
|
||||
1.028465, 0.9899253, 1.093749, 1.197574, 1.266906, 1.33625, 1.34084, 1.345431, 1.328575,
|
||||
1.31173, 1.317794, 1.323859, 1.283249, 1.242638, 1.245733, 1.24884, 1.239978, 1.231116,
|
||||
1.213917, 1.196718, 1.213266, 1.229825, 1.211073, 1.192321, 1.190266, 1.188221, 1.165118,
|
||||
1.142016, 1.121083, 1.100151, 1.097033, 1.093914, 1.053359, 1.012803, 1.020344, 1.027885,
|
||||
1.02556, 1.023236, 1.012384, 1.001533, 0.9763502, 0.9511687, 0.9535133, 0.9558578, 0.9348881,
|
||||
0.9139185, 0.9149908, 0.9160632, 0.9278442, 0.9396252, 0.9168215, 0.8940177, 0.8451223,
|
||||
0.796228, 0.8070075, 0.817787, 0.8334315, 0.8490771, 0.7763022, 0.7035272, 0.7508158,
|
||||
0.7981044, 0.8278048, 0.8575052, 0.7918713, 0.7262385, 0.6281702, 0.530103, 0.6465155,
|
||||
0.762928, 0.7433847, 0.7238414,
|
||||
];
|
||||
|
||||
@@ -165,7 +165,12 @@ fn film_key(t: &dr_pipeline::ops::FilmTables) -> u64 {
|
||||
mix(v.to_bits());
|
||||
}
|
||||
}
|
||||
for v in [t.curve_log_min, t.curve_log_max, t.density_max, t.lut_size as f32] {
|
||||
for v in [
|
||||
t.curve_log_min,
|
||||
t.curve_log_max,
|
||||
t.density_max,
|
||||
t.lut_size as f32,
|
||||
] {
|
||||
mix(v.to_bits());
|
||||
}
|
||||
for e in t.lut.iter().step_by(8).chain(t.curves.iter().step_by(8)) {
|
||||
@@ -228,7 +233,11 @@ impl AdjustPass {
|
||||
let lut = self.upload_film(
|
||||
"adjust-film-lut",
|
||||
wgpu::TextureDimension::D3,
|
||||
wgpu::Extent3d { width: n, height: n, depth_or_array_layers: n },
|
||||
wgpu::Extent3d {
|
||||
width: n,
|
||||
height: n,
|
||||
depth_or_array_layers: n,
|
||||
},
|
||||
&to_rgba(&t.lut),
|
||||
);
|
||||
self.film = Some(FilmTextures { curves, lut, key });
|
||||
@@ -282,7 +291,9 @@ impl AdjustPass {
|
||||
|
||||
/// The curve texture to bind: the loaded stock's, or the placeholder.
|
||||
fn film_curves_view(&self) -> &wgpu::TextureView {
|
||||
self.film.as_ref().map_or(&self.empty_film_curves, |f| &f.curves)
|
||||
self.film
|
||||
.as_ref()
|
||||
.map_or(&self.empty_film_curves, |f| &f.curves)
|
||||
}
|
||||
|
||||
/// The density lookup to bind: the loaded stock's, or the placeholder.
|
||||
@@ -352,7 +363,11 @@ impl AdjustPass {
|
||||
.device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("adjust-empty-film-curves"),
|
||||
size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
||||
size: wgpu::Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
@@ -365,7 +380,11 @@ impl AdjustPass {
|
||||
.device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("adjust-empty-film-lut"),
|
||||
size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
||||
size: wgpu::Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D3,
|
||||
|
||||
@@ -296,7 +296,11 @@ fn the_threshold_leaves_shallow_modulation_where_it_found_it() {
|
||||
// under the 12.5% at which the gate below starts letting detail through.
|
||||
let ripple: Vec<u8> = (0..SIZE * SIZE)
|
||||
.flat_map(|i| {
|
||||
let v = if (i % SIZE) % 2 == 0 { 128u8 } else { 132 };
|
||||
let v = if (i % SIZE).is_multiple_of(2) {
|
||||
128u8
|
||||
} else {
|
||||
132
|
||||
};
|
||||
[v, v, v, 255]
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -204,7 +204,7 @@ fn a_difference_below_the_threshold_is_averaged_and_one_above_it_is_not() {
|
||||
/// [`chroma_r`] is what keeps it out of the measurements.
|
||||
fn chroma_pattern(ctx: &GpuContext, size: u32, half_period: u32, swing: i32) -> DemosaicedImage {
|
||||
upload(ctx, size, move |x, _| {
|
||||
let on = (x / half_period) % 2 == 0;
|
||||
let on = (x / half_period).is_multiple_of(2);
|
||||
let d = if on { swing } else { -swing };
|
||||
[(128 + d) as u8, 128, (128 - d) as u8]
|
||||
})
|
||||
@@ -244,7 +244,7 @@ fn modulation(
|
||||
let mut count = 0.0;
|
||||
for x in margin..(width - margin) {
|
||||
let value = sample(linear(pixels, width, x, row));
|
||||
let sign = if (x / half_period) % 2 == 0 {
|
||||
let sign = if (x / half_period).is_multiple_of(2) {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
|
||||
@@ -183,12 +183,15 @@ fn transform(f: &[f32], out: &mut [f32], v: &mut [usize], z: &mut [f32]) {
|
||||
}
|
||||
|
||||
k = 0;
|
||||
for q in 0..n {
|
||||
// `q` is both the position and the value the parabola is evaluated at, so
|
||||
// it stays; what changes is that the write goes through the iterator
|
||||
// rather than re-indexing `out` on every step.
|
||||
for (q, slot) in out.iter_mut().enumerate().take(n) {
|
||||
while z[k + 1] < q as f32 {
|
||||
k += 1;
|
||||
}
|
||||
let p = v[k];
|
||||
out[q] = (q as f32 - p as f32).powi(2) + f[p];
|
||||
*slot = (q as f32 - p as f32).powi(2) + f[p];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,13 +417,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn an_empty_mask_is_everywhere_outside() {
|
||||
let d = signed_distance(&vec![0u8; 16], 4, 4, 128);
|
||||
let d = signed_distance(&[0u8; 16], 4, 4, 128);
|
||||
assert!(d.iter().all(|&v| v < 0.0), "no pixel can be inside");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_full_mask_is_everywhere_inside() {
|
||||
let d = signed_distance(&vec![255u8; 16], 4, 4, 128);
|
||||
let d = signed_distance(&[255u8; 16], 4, 4, 128);
|
||||
assert!(d.iter().all(|&v| v > 0.0), "no pixel can be outside");
|
||||
}
|
||||
|
||||
@@ -548,9 +551,7 @@ mod tests {
|
||||
// M5: masks reach the sidecar as indices, so the field they are shaped
|
||||
// by has to be reproducible.
|
||||
let mut m = vec![0u8; 64];
|
||||
for i in 20..30 {
|
||||
m[i] = 255;
|
||||
}
|
||||
m[20..30].fill(255);
|
||||
let a = signed_distance(&m, 8, 8, 128);
|
||||
let b = signed_distance(&m, 8, 8, 128);
|
||||
assert_eq!(a, b);
|
||||
|
||||
@@ -410,7 +410,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_wrong_sized_mask_is_an_error_not_a_panic() {
|
||||
let f = field();
|
||||
let err = Membership::compute(&f, &[&vec![0.0; 3]], &PriorOptions::default()).unwrap_err();
|
||||
let err = Membership::compute(&f, &[&[0.0; 3]], &PriorOptions::default()).unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
MembershipError::MaskSize {
|
||||
|
||||
@@ -56,13 +56,14 @@ const DETECTION_STRIDE: usize = 6 + PROTOTYPES;
|
||||
const PROTO_STRIDE: usize = 4;
|
||||
|
||||
/// How the image is presented to a fixed-shape graph.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub enum Tiling {
|
||||
/// One inference over the whole frame, letterboxed into the square input.
|
||||
///
|
||||
/// The default, and the right default: a photographic subject is usually
|
||||
/// *large* in frame, which is the case whole-image inference handles best
|
||||
/// and the case tiling helps least.
|
||||
#[default]
|
||||
Whole,
|
||||
/// Cover the frame with overlapping fixed-size windows.
|
||||
///
|
||||
@@ -77,12 +78,6 @@ pub enum Tiling {
|
||||
Grid { overlap: f32 },
|
||||
}
|
||||
|
||||
impl Default for Tiling {
|
||||
fn default() -> Self {
|
||||
Self::Whole
|
||||
}
|
||||
}
|
||||
|
||||
/// How the semantic pass is tuned.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct SemanticOptions {
|
||||
@@ -515,7 +510,7 @@ impl Letterbox {
|
||||
}
|
||||
|
||||
/// Input-space box back to source pixels.
|
||||
fn to_source(&self, x0: f32, y0: f32, x1: f32, y1: f32, w: &Window) -> (f32, f32, f32, f32) {
|
||||
fn to_source(self, x0: f32, y0: f32, x1: f32, y1: f32, w: &Window) -> (f32, f32, f32, f32) {
|
||||
(
|
||||
(x0 - self.pad_x) / self.scale + w.x,
|
||||
(y0 - self.pad_y) / self.scale + w.y,
|
||||
@@ -525,7 +520,7 @@ impl Letterbox {
|
||||
}
|
||||
|
||||
/// Source pixel to prototype-grid coordinates.
|
||||
fn to_proto(&self, sx: f32, sy: f32, w: &Window) -> (f32, f32) {
|
||||
fn to_proto(self, sx: f32, sy: f32, w: &Window) -> (f32, f32) {
|
||||
(
|
||||
((sx - w.x) * self.scale + self.pad_x) / PROTO_STRIDE as f32,
|
||||
((sy - w.y) * self.scale + self.pad_y) / PROTO_STRIDE as f32,
|
||||
|
||||
+18
-18
@@ -2936,6 +2936,24 @@ fn unit_suffix(unit: Unit) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// `dr_pipeline`'s morphology, as `dr_segment` names it.
|
||||
///
|
||||
/// Two enums for one idea, and deliberately: `dr-pipeline` describes the
|
||||
/// *edit* and `dr-segment` implements the *transform*, and neither depends on
|
||||
/// the other. The crossing is this function, which the compiler makes
|
||||
/// exhaustive on both sides.
|
||||
fn morphology_for(m: dr_pipeline::mask::Morphology) -> dr_segment::Morphology {
|
||||
use dr_pipeline::mask::Morphology as Edit;
|
||||
use dr_segment::Morphology as Transform;
|
||||
match m {
|
||||
Edit::None => Transform::None,
|
||||
Edit::Dilate => Transform::Dilate,
|
||||
Edit::Erode => Transform::Erode,
|
||||
Edit::Close => Transform::Close,
|
||||
Edit::Open => Transform::Open,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -4712,21 +4730,3 @@ mod tests {
|
||||
assert_eq!(session.rows().len(), all);
|
||||
}
|
||||
}
|
||||
|
||||
/// `dr_pipeline`'s morphology, as `dr_segment` names it.
|
||||
///
|
||||
/// Two enums for one idea, and deliberately: `dr-pipeline` describes the
|
||||
/// *edit* and `dr-segment` implements the *transform*, and neither depends on
|
||||
/// the other. The crossing is this function, which the compiler makes
|
||||
/// exhaustive on both sides.
|
||||
fn morphology_for(m: dr_pipeline::mask::Morphology) -> dr_segment::Morphology {
|
||||
use dr_pipeline::mask::Morphology as Edit;
|
||||
use dr_segment::Morphology as Transform;
|
||||
match m {
|
||||
Edit::None => Transform::None,
|
||||
Edit::Dilate => Transform::Dilate,
|
||||
Edit::Erode => Transform::Erode,
|
||||
Edit::Close => Transform::Close,
|
||||
Edit::Open => Transform::Open,
|
||||
}
|
||||
}
|
||||
|
||||
+14
-11
@@ -672,23 +672,26 @@ fn carried_metadata(meta: &dr_decode::Metadata) -> dr_export::SourceMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// One rendered photograph on its way to a file: the name it will be written
|
||||
/// under, the name it came from, the pixels, and whatever metadata travelled
|
||||
/// with them.
|
||||
type RenderedItem = (
|
||||
String,
|
||||
String,
|
||||
dr_export::Frame,
|
||||
Option<dr_export::SourceMetadata>,
|
||||
);
|
||||
|
||||
/// Fetch a photograph, apply its stored edit, and render it at full size.
|
||||
///
|
||||
/// `None` means the work was cancelled, which is not a failure and has no
|
||||
/// error to report — hence the `Option` outside the `Result`.
|
||||
fn render_from_library(
|
||||
request: &BatchRequest,
|
||||
path: &str,
|
||||
cache: Option<crate::library::CacheContext>,
|
||||
cancel: &Cancel,
|
||||
) -> Option<
|
||||
Result<
|
||||
(
|
||||
String,
|
||||
String,
|
||||
dr_export::Frame,
|
||||
Option<dr_export::SourceMetadata>,
|
||||
),
|
||||
ItemError,
|
||||
>,
|
||||
> {
|
||||
) -> Option<Result<RenderedItem, ItemError>> {
|
||||
let Some((creds, user_id)) = request.creds.clone() else {
|
||||
return Some(Err(ItemError::Fetch("no library is open".into())));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user