///|
pub(all) enum ChangeKind {
Added
Removed
Changed
} derive(Eq, Debug)
///|
pub extend ChangeKind with Eq::{equal, not_equal}
///|
pub extend ChangeKind with @debug.Debug::{to_repr}
///|
pub(all) struct CapabilityChange {
attribute : String
kind : ChangeKind
before : Array[IppValue]
after : Array[IppValue]
transient : Bool
} derive(Eq, Debug)
///|
pub extend CapabilityChange with Eq::{equal, not_equal}
///|
pub extend CapabilityChange with @debug.Debug::{to_repr}
///|
pub fn volatile_printer_attribute(name : String) -> Bool {
match name {
"printer-up-time"
| "printer-current-time"
| "printer-state"
| "printer-state-reasons"
| "printer-state-message"
| "queued-job-count"
| "printer-state-change-time"
| "printer-state-change-date-time"
| "printer-config-change-time"
| "printer-config-change-date-time"
| "printer-is-accepting-jobs" => true
_ => false
}
}
///| Multi-valued supported attributes are treated as multisets, not sequences.
///|
/// Duplicate values remain significant. Collection member order is retained.
fn same_values(left : Array[IppValue], right : Array[IppValue]) -> Bool {
if left.length() != right.length() {
return false
}
// Valid wire values have a stable typed JSON key. Keep actual values in
// buckets as well, so even manually constructed values with colliding JSON
// representations retain the exact enum/collection equality semantics.
let remaining : Map[String, Array[IppValue]] = Map([])
for value in right {
let key = value.to_json().stringify()
match remaining.get(key) {
Some(bucket) => bucket.push(value)
None => remaining[key] = [value]
}
}
for value in left {
let key = value.to_json().stringify()
let bucket = match remaining.get(key) {
Some(bucket) => bucket
None => return false
}
let mut index = bucket.length() - 1
while index >= 0 && bucket[index] != value {
index -= 1
}
if index < 0 {
return false
}
bucket[index] = bucket[bucket.length() - 1]
let _ = bucket.pop()
if bucket.is_empty() {
remaining.remove(key)
}
}
true
}
///|
pub fn compare_capabilities(
before : Capabilities,
after : Capabilities,
include_volatile~ : Bool = false,
) -> Array[CapabilityChange] {
let changes : Array[CapabilityChange] = []
let seen : Map[String, Bool] = Map([])
for attribute in before.printer.attributes {
seen[attribute.name] = true
let transient = volatile_printer_attribute(attribute.name)
if transient && !include_volatile {
continue
}
match after.printer.find(attribute.name) {
None =>
changes.push({
attribute: attribute.name,
kind: Removed,
before: attribute.values,
after: [],
transient,
})
Some(next) =>
if !same_values(attribute.values, next.values) {
changes.push({
attribute: attribute.name,
kind: Changed,
before: attribute.values,
after: next.values,
transient,
})
}
}
}
for attribute in after.printer.attributes {
if seen.contains(attribute.name) {
continue
}
let transient = volatile_printer_attribute(attribute.name)
if transient && !include_volatile {
continue
}
changes.push({
attribute: attribute.name,
kind: Added,
before: [],
after: attribute.values,
transient,
})
}
changes.sort_by(fn(left, right) { left.attribute.compare(right.attribute) })
changes
}
///|
pub fn CapabilityChange::to_json(self : CapabilityChange) -> Json {
{
"attribute": Json::string(self.attribute),
"kind": Json::string(
match self.kind {
Added => "added"
Removed => "removed"
Changed => "changed"
},
),
"before": Json::array(self.before.map(value => value.to_json())),
"after": Json::array(self.after.map(value => value.to_json())),
"transient": Json::boolean(self.transient),
}
}