Compare commits

..

2 Commits

162 changed files with 12277 additions and 26847 deletions

1
.envrc
View File

@ -1,2 +1 @@
mkdir .direnv
use flake

2683
Cargo.lock generated

File diff suppressed because it is too large Load Diff

6534
Cargo.nix

File diff suppressed because it is too large Load Diff

View File

@ -2,15 +2,13 @@
resolver = "2"
members = [
"authdb",
# "bike-lights/bike",
"bike-lights/bike",
"bike-lights/core",
"bike-lights/simulator",
"changeset",
"config",
"config-derive",
"coordinates",
"cyberpunk",
"cyber-slides",
"cyberpunk-splash",
"dashboard",
"emseries",
@ -22,6 +20,7 @@ members = [
"gm-control-panel",
"hex-grid",
"icon-test",
"ifc",
"memorycache",
"nom-training",
"otg/core",
@ -31,6 +30,5 @@ members = [
"sgf",
"timezone-testing",
"tree",
"visions/server",
"gm-dash/server"
"visions/server", "gm-dash/server",
]

View File

@ -1,16 +0,0 @@
version: '3'
tasks:
build:
cmds:
- cargo build --release
update:
cmds:
- task build
- crate2nix generate
- nix build
lint:
cmds:
- cargo watch -x clippy

View File

@ -18,7 +18,7 @@ base64ct = { version = "1", features = [ "alloc" ] }
clap = { version = "4", features = [ "derive" ] }
serde = { version = "1.0", features = ["derive"] }
sha2 = { version = "0.10" }
sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite" ] }
sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite" ] }
thiserror = { version = "1" }
tokio = { version = "1", features = [ "full" ] }
uuid = { version = "0.4", features = [ "serde", "v4" ] }

View File

@ -119,7 +119,6 @@ impl<
> UI for BikeUI<D, P, LeftId, RightId, PreviousId, NextId, BrakeId>
{
fn check_event(&mut self, current_time: Instant) -> Option<Event> {
/*
if self.brake_sensor.is_high().unwrap_or(true) && !self.brake_enabled {
self.brake_enabled = true;
Some(Event::Brake)
@ -127,8 +126,6 @@ impl<
self.brake_enabled = false;
Some(Event::BrakeRelease)
} else if self.left_blinker_button.is_low(current_time) {
*/
if self.left_blinker_button.is_low(current_time) {
self.left_blinker_button.set_debounce(current_time);
Some(Event::LeftBlinker)
} else if self.right_blinker_button.is_low(current_time) {

View File

@ -1,174 +0,0 @@
width = 65;
length = 75;
height = 16;
wall_thickness = 2;
guide_thickness = 1;
power_width = 21;
output_width = 37.5;
half_wall_thickness = wall_thickness / 2;
standoff_thickness = 10;
hole_diameter = 3;
// The radius of a nut in mm. However, based on my measurements, I'm not actually sure I have this right. The short height of a nut is 7.86mm. Derive from there.
nut_radius = 8.5 * cos(30) / 2;
nut_height = 2.69; // mm
screw_radius = 2;
handlebar_radius = 15;
clasp_thickness = 4;
clasp_width = 35;
circular_face_count = 48;
module hexagon(r, h) {
pi = 3.1415926;
polyhedron(
points=[
[r, 0, 0],
[r * cos(60), r * sin(60), 0],
[r * cos(120), r * sin(120), 0],
[r * cos(180), r * sin(180), 0],
[r * cos(240), r * sin(240), 0],
[r * cos(300), r * sin(300), 0],
[r, 0, h],
[r * cos(60), r * sin(60), h],
[r * cos(120), r * sin(120), h],
[r * cos(180), r * sin(180), h],
[r * cos(240), r * sin(240), h],
[r * cos(300), r * sin(300), h],
],
faces=[
[0, 1, 2, 3, 4, 5],
[11, 10, 9, 8, 7, 6],
[6, 7, 1, 0],
[7, 8, 2, 1],
[8, 9, 3, 2],
[9, 10, 4, 3],
[10, 11, 5, 4],
[11, 6, 0, 5],
]
);
}
// Nut holders are blocks that have a hole drilled through them and a hexagonal-shaped cavity. The idea is to
module nut_holder() {
difference() {
translate([-4.5, -4.5, -2]) cube([9, 9, 4]);
union() {
translate([0, 0, -1]) hexagon(nut_radius, 2);
cylinder(h = 6, r = screw_radius, center = true, $fn = 24);
}
}
}
module screw_hole() {
union() {
translate([0, 0, 4]) cylinder(h = 2.1, r = screw_radius * 2, center = true, $fn = 24);
cylinder(h = 6, r = screw_radius, center = true, $fn = 24);
}
}
module base() {
cube([width, length, wall_thickness]);
}
module face() {
union() {
cube([width, length, wall_thickness / 2]);
translate([wall_thickness, wall_thickness, wall_thickness / 2]) cube([width-wall_thickness*2, length-wall_thickness*2, wall_thickness / 2]);
translate([4.5 + wall_thickness, 4.5 + wall_thickness, 4]) nut_holder();
translate([width - 4.5 - wall_thickness, 4.5 + wall_thickness, 4]) nut_holder();
translate([width - 4.5 - wall_thickness, length - 4.5 - wall_thickness, 4]) nut_holder();
translate([4.5 + wall_thickness, length - 4.5 - wall_thickness, 4]) nut_holder();
}
}
module wall(length) {
cube([length, height, wall_thickness]);
}
module power_wall() {
difference() {
wall(65);
translate([9, 2, -.5]) cube([power_width, height, wall_thickness + 1]);
}
}
module output_wall() {
difference() {
wall(65);
translate([9, 2, -.5]) cube([output_width, height, wall_thickness + 1]);
}
}
// Use hexagons as cutouts into which I can install a hex nut. This isn't quite right yet, but close.
// hexagon(nut_radius, 1);
// cube([standoff_thickness, standoff_thickness, 2]);
/*
difference() {
union() {
base();
rotate([90, 0, 90]) wall(75);
// translate([width - wall_thickness, 0, 0]) rotate([90, 0, 90]) wall(length);
// rotate([90, 0, 0]) power_wall();
// translate([0, length, 0]) rotate([90, 0, 0]) output_wall();
// translate([wall_thickness,
// wall_thickness,
// wall_thickness]) standoff();
// translate([width - wall_thickness - standoff_thickness,
// wall_thickness,
// wall_thickness]) standoff();
// translate([wall_thickness,
// length - wall_thickness - standoff_thickness,
// wall_thickness]) standoff();
// translate([width - wall_thickness - standoff_thickness,
// length - wall_thickness - standoff_thickness,
// wall_thickness]) standoff();
}
// translate([-half_wall_thickness, -wall_thickness - half_wall_thickness, height - half_wall_thickness]) cube([wall_thickness, length + wall_thickness * 2, wall_thickness]);
// translate([width - half_wall_thickness, -wall_thickness - half_wall_thickness, height - half_wall_thickness]) cube([wall_thickness, length + wall_thickness * 2, wall_thickness]);
// translate([-half_wall_thickness, -half_wall_thickness, height - half_wall_thickness]) rotate([0, 0, 270]) cube([wall_thickness, width + wall_thickness * 2, wall_thickness]);
// translate([-half_wall_thickness, length + half_wall_thickness, height - half_wall_thickness]) rotate([0, 0, 270]) cube([wall_thickness, width + wall_thickness * 2, wall_thickness]);
}
*/
module box() {
difference() {
union() {
cube([width, length, wall_thickness * 2]);
translate([0, 0, wall_thickness]) rotate([90, 0, 90]) wall(length);
translate([width - wall_thickness, 0, wall_thickness]) rotate([90, 0, 90]) wall(length);
translate([0, wall_thickness, wall_thickness]) rotate([90, 0, 0]) wall(width);
translate([0, length, wall_thickness]) rotate([90, 0, 0]) wall(width);
}
translate([4.5 + wall_thickness, 4.5 + wall_thickness, 4]) rotate([180, 0, 0]) screw_hole();
translate([width - 4.5 - wall_thickness, 4.5 + wall_thickness, 4]) rotate([180, 0, 0]) screw_hole();
translate([width - 4.5 - wall_thickness, length - 4.5 - wall_thickness, 4]) rotate([180, 0, 0]) screw_hole();
translate([4.5 + wall_thickness, length - 4.5 - wall_thickness, 4]) rotate([180, 0, 0]) screw_hole();
}
}
module top_clasp() {
difference() {
union() {
cylinder(h = clasp_width, r = handlebar_radius + clasp_thickness, center = true, $fn = circular_face_count);
translate([0, 0, -clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, -clasp_width / 2 + 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, clasp_width / 2 - 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([-handlebar_radius-5, -10, -clasp_width / 2 + 6]) cube([6, 20, clasp_width - 12]);
}
translate([-0.5, 0, 0]) cylinder(h = clasp_width+2, r = handlebar_radius + 1, center = true, $fn = circular_face_count);
translate([-0.5, -handlebar_radius - 10, -clasp_width / 2 - 1]) cube([handlebar_radius + 10, handlebar_radius * 2 + 20, clasp_width + 2]);
}
}
module body() {
union() {
box();
translate([width / 2, length / 2, -5 - handlebar_radius]) rotate([0, 90, 90]) top_clasp();
}
}
body();
translate([width + 10, 0, 0]) face();

View File

@ -1,21 +0,0 @@
handlebar_radius = 15;
clasp_thickness = 4;
circular_face_count = 48;
clasp_width = 35;
module top_clasp() {
difference() {
union() {
cylinder(h = clasp_width, r = handlebar_radius + clasp_thickness, center = true, $fn = circular_face_count);
translate([0, 0, -clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, -clasp_width / 2 + 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, clasp_width / 2 - 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([-handlebar_radius-5, -10, -clasp_width / 2 + 6]) cube([6, 20, clasp_width - 12]);
}
translate([-0.5, 0, 0]) cylinder(h = clasp_width+2, r = handlebar_radius + 1, center = true, $fn = circular_face_count);
translate([-0.5, -handlebar_radius - 10, -clasp_width / 2 - 1]) cube([handlebar_radius + 10, handlebar_radius * 2 + 20, clasp_width + 2]);
}
}
top_clasp();

View File

@ -1,8 +1,3 @@
module hexagon(r, h) {
cylinder(r = r, h = h, center = 2, $fn = 6);
}
module pill(length, bevel) {
hull() {
translate([0, 0, (-length / 2) + bevel]) sphere(r = bevel);
@ -10,27 +5,6 @@ module pill(length, bevel) {
}
}
module rounded_cube(dimensions, bevel = 0) {
x = dimensions[0];
y = dimensions[1];
z = dimensions[2];
if (bevel > 0) {
hull() {
translate([-x / 2 + bevel, -y / 2 + bevel, -z / 2 + bevel]) sphere(r = bevel);
translate([ x / 2 - bevel, -y / 2 + bevel, -z / 2 + bevel]) sphere(r = bevel);
translate([ x / 2 - bevel, y / 2 - bevel, -z / 2 + bevel]) sphere(r = bevel);
translate([-x / 2 + bevel, y / 2 - bevel, -z / 2 + bevel]) sphere(r = bevel);
translate([-x / 2 + bevel, -y / 2 + bevel, z / 2 - bevel]) sphere(r = bevel);
translate([ x / 2 - bevel, -y / 2 + bevel, z / 2 - bevel]) sphere(r = bevel);
translate([ x / 2 - bevel, y / 2 - bevel, z / 2 - bevel]) sphere(r = bevel);
translate([-x / 2 + bevel, y / 2 - bevel, z / 2 - bevel]) sphere(r = bevel);
}
} else {
cube(dimensions, center = true);
}
}
module box_face(dimensions, bevel = 0) {
x = dimensions[0];
y = dimensions[1];
@ -74,7 +48,6 @@ module box(length, width, height, bevel = 0) {
rotate([90, 0, 0])
rotate([0, 90, 0])
box_face([width, height, wall_thickness], bevel);
translate([length - wall_thickness + bevel, 0, bevel])
rotate([90, 0, 0])
rotate([0, 90, 0])
@ -82,11 +55,3 @@ module box(length, width, height, bevel = 0) {
}
}
module box_side_slider(length, width, height) {
difference() {
box_face([width - wall_thickness * 2 + 4, height, wall_thickness], bevel);
translate([-1, -1, 1]) cube([4-threshold, height+2, 4-threshold]);
color("red") translate([width - wall_thickness * 2 + 1, -1, 1]) cube([4-threshold, height+2, 4-threshold]);
}
}

View File

@ -4,7 +4,7 @@ threshold = 0.1;
board_length = 92;
board_width = 72;
board_height = 21.5;
wall_thickness = 4;
wall_thickness = 2;
bevel = 0.5;
hinge_radius = 2.5;
@ -13,63 +13,16 @@ case_width = board_width + wall_thickness * 2;
case_length = board_length + wall_thickness * 2;
case_height = board_height + wall_thickness;
handlebar_radius = 15;
clasp_thickness = 4;
circular_face_count = 48;
clasp_width = 35;
include <./common.scad>;
module top_clasp() {
difference() {
union() {
cylinder(h = clasp_width, r = handlebar_radius + clasp_thickness, center = true, $fn = circular_face_count);
translate([0, 0, -clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, -clasp_width / 2 + 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([0, 0, clasp_width / 2 - 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
translate([-handlebar_radius-5, -10, -clasp_width / 2 + 6]) cube([6, 20, clasp_width - 12]);
}
translate([-0.5, 0, 0]) cylinder(h = clasp_width+2, r = handlebar_radius + 1, center = true, $fn = circular_face_count);
translate([-0.5, -handlebar_radius - 10, -clasp_width / 2 - 1]) cube([handlebar_radius + 10, handlebar_radius * 2 + 20, clasp_width + 2]);
}
}
module hinge(length) {
difference() {
union() {
cube([hinge_radius * 2, length, hinge_radius], center = true);
translate([0, 0, -1.5]) rotate([90, 0, 0]) cylinder(h = length, r = hinge_radius, center = true);
cylinder(h = length, r = hinge_radius);
translate([0, -hinge_radius, 0])
cube([hinge_radius, hinge_radius * 2, length]);
}
translate([0, threshold / 2, -1.5]) rotate([90, 0, 0]) cylinder(h = length + threshold * 2, r = 1, center = true);
}
}
module base_case(length, width, height, bevel = 0) {
difference() {
union() {
channel(length + wall_thickness / 2, width, height, bevel);
translate([-bevel, 0, bevel])
rotate([90, 0, 0])
rotate([0, 90, 0])
box_face([width, height, wall_thickness], bevel);
// These are the sleds at the bottom of the case that should hold the lower of the two boards down
color("blue") translate([0, wall_thickness - 2, wall_thickness + 4]) cube([length - 8, 4, wall_thickness / 2]);
color("blue") translate([wall_thickness - 2, wall_thickness - 4, wall_thickness + 4]) cube([4, width, wall_thickness / 2]);
color("blue") translate([length - 25, width - wall_thickness * 3 / 2, wall_thickness + 6]) cube([16, wall_thickness, wall_thickness / 2]);
}
// This makes an indent at the bottom to accomodate solder joins
translate([wall_thickness + 2, wall_thickness + 2, wall_thickness / 2]) cube([length, width - wall_thickness * 2 - 4, wall_thickness / 2 + threshold]);
// This creates a cutout that lets the power plug slide in better.
translate([wall_thickness, width - wall_thickness, wall_thickness]) cube([length, 2, 6]);
// These two put in the slots that should allow the fourth wall to be slotted into place.
color("red") translate([length - 1, wall_thickness - 2, 4]) cube([2, 2, height]);
color("red") translate([length - 1, width - wall_thickness, 4]) cube([2, 2, height]);
translate([0, 0, -threshold / 2]) cylinder(h = length + threshold, r = 1);
}
}
@ -80,16 +33,11 @@ module main_case() {
difference() {
union() {
base_case(case_length,
box(case_length,
case_width,
case_height,
bevel);
translate([-bevel, 0, bevel])
rotate([90, 0, 0])
rotate([0, 90, 0])
box_face([case_width, case_height, wall_thickness], bevel);
translate([0, -hinge_radius - bevel + threshold, hinge_z_offset + bevel])
rotate([90, 0, 0])
rotate([0, 90, 0])
@ -117,94 +65,31 @@ module main_case() {
rotate([0, 180, 0])
linear_extrude(1)
text("right", size = 3);
// translate([case_length / 2, case_width / 2, -20]) rotate([0, 90, 0]) top_clasp();
}
translate([case_length / 2, case_width / 2, -threshold]) hexagon(4.5, 6);
# translate([8.5 + wall_thickness, case_width - wall_thickness - threshold, wall_thickness])
# cube([60, wall_thickness * 2, 7]);
}
}
module lamp() {
union() {
translate([0, 0, -0.5]) cube([12.9 + threshold, 8, 4], center = true);
translate([0, 0, .88]) cube([5 + threshold, 5 + threshold, 1.56], center = true);
/*
translate([0, 0, -1.56]) cube([12.9, 7.6, wall_thickness], center = true);
*/
}
}
module button() {
union() {
cube([3.5 + threshold, 6.1 + threshold, 4 + threshold], center = true);
translate([0, 0, -0.5]) cube([1.2, 7, 3 + threshold], center = true);
color("green", 1) translate([8.5 + wall_thickness, case_width - wall_thickness - threshold, wall_thickness])
cube([60, wall_thickness * 2, 7]);
}
}
module lid() {
lid_width = case_width + hinge_radius * 2 + wall_thickness;
hinge_length = case_length / 4;
union() {
difference() {
rounded_cube([case_length,
box_face([case_length,
lid_width,
wall_thickness],
bevel);
translate([0, lid_width / 5, 0.4]) lamp();
translate([-15, lid_width / 5, 0.4]) lamp();
translate([15, lid_width / 5, 0.4]) lamp();
translate([-30, lid_width / 5, 0]) button();
translate([30, lid_width / 5, 0]) button();
translate([0, lid_width / 5, -2]) cube([20, 7, 3], center = true);
color("black") translate([-2, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
color("black") translate([-17, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
color("black") translate([13, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
color("black") translate([-30, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
color("black") translate([30, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
color("black") translate([0, 10, -2]) rotate([0, 90, 0]) cylinder(h = 62, r = 1, center = true, $fn = circular_face_count);
color("red") translate([-33, 21, -2]) rotate([0, 90, 0]) cylinder(h = 5, r = 1, center = true, $fn = circular_face_count);
color("red") translate([-35, 13, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h = 18, r = 1, center = true, $fn = circular_face_count);
color("red") translate([33, 21, -2]) rotate([0, 90, 0]) cylinder(h = 5, r = 1, center = true, $fn = circular_face_count);
color("red") translate([35, 13, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h = 18, r = 1, center = true, $fn = circular_face_count);
color("red") translate([0, 5, -2]) rotate([0, 90, 0]) cylinder(h = 70, r = 1, center = true, $fn = circular_face_count);
translate([(case_length - 60) / 2, 14 + hinge_radius * 2, -threshold / 2])
cube([60, 16, wall_thickness + threshold]);
}
translate([case_length / 2 - hinge_length / 2, lid_width / 2 - wall_thickness / 2 - 0.5, -wall_thickness / 2]) rotate([0, 0, 90]) hinge(hinge_length);
translate([-case_length / 2 + hinge_length / 2, lid_width / 2 - wall_thickness / 2 - 0.5, -wall_thickness / 2]) rotate([0, 0, 90]) hinge(hinge_length);
translate([0, -lid_width / 2 + bevel, -3]) rounded_cube([20, wall_thickness / 2, 10], bevel);
color("blue") translate([-9, -lid_width / 2 + 1.5, -6]) rotate([90, 0, 0]) rotate([0, 90, 0]) linear_extrude(18) circle(1, $fn = 3);
color("blue") translate([-9, -lid_width / 2 + 1.5, -7]) rotate([90, 0, 0]) rotate([0, 90, 0]) linear_extrude(18) circle(1, $fn = 3);
}
}
module box_side() {
box_side_slider(case_length, case_width, case_height);
}
module case_base() {
difference() {
rounded_cube([case_length, case_width, wall_thickness + 2], bevel = 0.5);
translate([wall_thickness, 0, 2]) rounded_cube([case_length + threshold, board_width + threshold, 2 + threshold]);
// These give a screw-hole in the center which will allow the clamp to be attached
translate([0, 0, -1]) hexagon(4.5, 2);
translate([0, 0, -wall_thickness / 2]) cylinder(r = 2, h = wall_thickness + threshold, center = true);
// and now a bit of an indentation to help the clip remain in place
translate([0, 0, -4.5]) cube([clasp_width + threshold, clasp_width + threshold, wall_thickness], center = true);
// here are some grooves along the edges that can be used to piece parts together
translate([wall_thickness / 2, case_width / 2 - wall_thickness / 2, wall_thickness / 2])
cube([board_length + wall_thickness, wall_thickness / 2, wall_thickness / 2 + threshold], center = true);
translate([wall_thickness / 2, -case_width / 2 + wall_thickness / 2, wall_thickness / 2])
cube([board_length + wall_thickness, wall_thickness / 2, wall_thickness / 2 + threshold], center = true);
translate([case_length / 4 + 1, hinge_radius - 0.4, -hinge_radius])
rotate([180, 0, 0])
rotate([0, 90, 0]) hinge(case_length / 2 - 2);
}
}
// main_case();
// color("red", 1) translate([0, 0, case_height + wall_thickness / 2]) lid();
// color("red", 1) translate([0, 0, 40]) lid();

View File

@ -1,11 +1,4 @@
include <./control_panel.scad>
/*
difference() {
color("blue") rounded_cube([5, 5, 5], bevel = 0.5);
translate([0, 0, 1]) rounded_cube([4, 4, 4]);
};
*/
case_base();
main_case();

View File

@ -2,5 +2,4 @@
include <./control_panel.scad>
lid();
// lamp();

View File

@ -1,4 +0,0 @@
include <./control_panel.scad>
box_side();

View File

@ -38,7 +38,7 @@ pub struct Instant(pub U128F0);
impl Default for Instant {
fn default() -> Self {
Self(U128F0::from(0_u8))
Self(U128F0::from(0 as u8))
}
}
@ -198,7 +198,7 @@ impl Blinker {
direction: BlinkerDirection,
time: Instant,
) -> Self {
let mut ending_dashboard = OFF_DASHBOARD;
let mut ending_dashboard = OFF_DASHBOARD.clone();
match direction {
BlinkerDirection::Left => {
@ -213,7 +213,7 @@ impl Blinker {
}
}
let mut ending_body = OFF_BODY;
let mut ending_body = OFF_BODY.clone();
match direction {
BlinkerDirection::Left => {
for i in 0..30 {
@ -233,26 +233,26 @@ impl Blinker {
Blinker {
transition: Fade::new(
starting_dashboard,
starting_body,
ending_dashboard,
ending_body,
starting_dashboard.clone(),
starting_body.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_in: Fade::new(
OFF_DASHBOARD,
OFF_BODY,
ending_dashboard,
ending_body,
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_out: Fade::new(
ending_dashboard,
ending_body,
OFF_DASHBOARD,
OFF_BODY,
ending_dashboard.clone(),
ending_body.clone(),
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
BLINKER_FRAMES,
time,
),
@ -336,7 +336,7 @@ impl Pattern {
fn body(&self) -> BodyPattern {
match self {
Pattern::Water => WATER_BODY,
Pattern::Water => OFF_BODY,
Pattern::GayPride => PRIDE_BODY,
Pattern::TransPride => TRANS_PRIDE_BODY,
}
@ -375,7 +375,7 @@ impl App {
pattern.dashboard(),
pattern.body(),
DEFAULT_FRAMES,
Instant(0_u32.into()),
Instant((0 as u32).into()),
)),
dashboard_lights: OFF_DASHBOARD,
lights: OFF_BODY,
@ -386,8 +386,8 @@ impl App {
match self.state {
State::Pattern(ref pattern) => {
self.current_animation = Box::new(Fade::new(
self.dashboard_lights,
self.lights,
self.dashboard_lights.clone(),
self.lights.clone(),
pattern.dashboard(),
pattern.body(),
DEFAULT_FRAMES,
@ -396,8 +396,8 @@ impl App {
}
State::Brake => {
self.current_animation = Box::new(Fade::new(
self.dashboard_lights,
self.lights,
self.dashboard_lights.clone(),
self.lights.clone(),
BRAKES_DASHBOARD,
BRAKES_BODY,
BRAKES_FRAMES,
@ -406,16 +406,16 @@ impl App {
}
State::LeftBlinker => {
self.current_animation = Box::new(Blinker::new(
self.dashboard_lights,
self.lights,
self.dashboard_lights.clone(),
self.lights.clone(),
BlinkerDirection::Left,
time,
));
}
State::RightBlinker => {
self.current_animation = Box::new(Blinker::new(
self.dashboard_lights,
self.lights,
self.dashboard_lights.clone(),
self.lights.clone(),
BlinkerDirection::Right,
time,
));
@ -441,13 +441,19 @@ impl App {
State::LeftBlinker => self.state = State::Pattern(self.home_pattern),
_ => self.state = State::LeftBlinker,
},
Event::NextPattern => if let State::Pattern(ref pattern) = self.state {
self.home_pattern = pattern.next();
self.state = State::Pattern(self.home_pattern);
Event::NextPattern => match self.state {
State::Pattern(ref pattern) => {
self.home_pattern = pattern.next();
self.state = State::Pattern(self.home_pattern);
}
_ => (),
},
Event::PreviousPattern => if let State::Pattern(ref pattern) = self.state {
self.home_pattern = pattern.previous();
self.state = State::Pattern(self.home_pattern);
Event::PreviousPattern => match self.state {
State::Pattern(ref pattern) => {
self.home_pattern = pattern.previous();
self.state = State::Pattern(self.home_pattern);
}
_ => (),
},
Event::RightBlinker => match self.state {
State::Brake => self.state = State::BrakeRightBlinker,
@ -459,14 +465,17 @@ impl App {
}
pub fn tick(&mut self, time: Instant) {
if let Some(event) = self.ui.check_event(time) {
self.update_state(event);
self.update_animation(time);
match self.ui.check_event(time) {
Some(event) => {
self.update_state(event);
self.update_animation(time);
}
None => {}
};
let (dashboard, lights) = self.current_animation.tick(time);
self.dashboard_lights = dashboard;
self.lights = lights;
self.dashboard_lights = dashboard.clone();
self.lights = lights.clone();
self.ui.update_lights(dashboard, lights);
}
}

View File

@ -98,74 +98,7 @@ pub const DEFAULT_FRAMES: U16F0 = U16F0::lit("30");
pub const WATER_DASHBOARD: DashboardPattern = [WATER_1, WATER_2, WATER_3];
pub const WATER_BODY: BodyPattern = [
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_3,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_2,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
WATER_1,
];
pub const WATER_BODY: BodyPattern = [RGB_OFF; 60];
pub const PRIDE_DASHBOARD: DashboardPattern = [PRIDE_RED, PRIDE_GREEN, PRIDE_INDIGO];

View File

@ -7,7 +7,6 @@ edition = "2021"
[dependencies]
adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] }
async-std = "1.13.0"
cairo-rs = { version = "0.18" }
fixed = { version = "1" }
gio = { version = "0.18" }

View File

@ -1,7 +1,6 @@
use adw::prelude::*;
use async_std::channel::Sender;
use fixed::types::{I8F8, U128F0};
use glib::Object;
use glib::{Object, Sender};
use gtk::subclass::prelude::*;
use lights_core::{
App, BodyPattern, DashboardPattern, Event, Instant, FPS, OFF_BODY, OFF_DASHBOARD, RGB, UI,
@ -46,12 +45,6 @@ glib::wrapper! {
pub struct DashboardLights(ObjectSubclass<DashboardLightsPrivate>) @extends gtk::DrawingArea, gtk::Widget;
}
impl Default for DashboardLights {
fn default() -> Self {
Self::new()
}
}
impl DashboardLights {
pub fn new() -> Self {
let s: Self = Object::builder().build();
@ -110,12 +103,6 @@ glib::wrapper! {
pub struct BikeLights(ObjectSubclass<BikeLightsPrivate>) @extends gtk::DrawingArea, gtk::Widget;
}
impl Default for BikeLights {
fn default() -> Self {
Self::new()
}
}
impl BikeLights {
pub fn new() -> Self {
let s: Self = Object::builder().build();
@ -174,15 +161,12 @@ impl UI for GTKUI {
}
fn update_lights(&self, dashboard_lights: DashboardPattern, lights: BodyPattern) {
let tx = self.tx.clone();
glib::spawn_future(async move {
let _ = tx
.send(Update {
dashboard: dashboard_lights,
lights,
})
.await;
});
self.tx
.send(Update {
dashboard: dashboard_lights,
lights,
})
.unwrap();
}
}
@ -192,7 +176,8 @@ fn main() {
.build();
adw_app.connect_activate(move |adw_app| {
let (update_tx, update_rx) = async_std::channel::unbounded();
let (update_tx, update_rx) =
gtk::glib::MainContext::channel::<Update>(gtk::glib::Priority::DEFAULT);
let (event_tx, event_rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
@ -284,17 +269,6 @@ fn main() {
layout.append(&dashboard_lights);
layout.append(&bike_lights);
glib::spawn_future_local({
let dashboard_lights = dashboard_lights.clone();
let bike_lights = bike_lights.clone();
async move {
while let Ok(Update { dashboard, lights }) = update_rx.recv().await {
dashboard_lights.set_lights(dashboard);
bike_lights.set_lights(lights);
}
}
});
/*
update_rx.attach(None, {
let dashboard_lights = dashboard_lights.clone();
let bike_lights = bike_lights.clone();
@ -304,7 +278,6 @@ fn main() {
glib::ControlFlow::Continue
}
});
*/
window.set_content(Some(&layout));
window.present();

View File

@ -40,12 +40,6 @@ macro_rules! define_config {
values: std::collections::HashMap<ConfigName, ConfigOption>,
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
impl Config {
pub fn new() -> Self {
Self {

View File

@ -145,7 +145,7 @@ mod tests {
let coord2 = &lst1[idx];
assert!(coord2.is_adjacent(&coord1));
assert!(coord1.is_adjacent(coord2));
assert!(coord1.is_adjacent(&coord2));
}
#[test]
@ -166,10 +166,10 @@ mod tests {
let hexaddr = AxialAddr::new(q, r);
let en_distancaj_hexaddr: Vec<AxialAddr> = hexaddr.addresses(distance).collect();
let expected_cnt = (0..distance+1).map(|v| v * 6).fold(1, |acc, val| acc + val);
let expected_cnt = ((0..distance+1).map(|v| v * 6).fold(1, |acc, val| acc + val)) as usize;
assert_eq!(en_distancaj_hexaddr.len(), expected_cnt);
for c in en_distancaj_hexaddr {
assert!(c.distance(&hexaddr) <= distance);
assert!(c.distance(&hexaddr) <= distance as usize);
}
}
}

View File

@ -1,163 +1,169 @@
{
"registry+https://github.com/rust-lang/crates.io-index#addr2line@0.24.2": "1hd1i57zxgz08j6h5qrhsnm2fi0bcqvsh389fw400xm3arz2ggnz",
"registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.0": "09r6drylvgy8vv8k20lnbvwq8gp09h7smfn6h1rxsy15pgh629si",
"registry+https://github.com/rust-lang/crates.io-index#addr2line@0.21.0": "1jx0k3iwyqr8klqbzk6kjvr496yd94aspis10vwsj5wy7gib4c4a",
"registry+https://github.com/rust-lang/crates.io-index#adler32@1.2.0": "0d7jq7jsjyhsgbhnfq5fvrlh9j0i9g1fqrl2735ibv5f75yjgqda",
"registry+https://github.com/rust-lang/crates.io-index#adler@1.0.2": "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj",
"registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.11": "04chdfkls5xmhp1d48gnjsmglbqibizs3bpbj6rsj604m10si7g8",
"registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3": "05mrpkvdgp5d20y2p989f187ry9diliijgwrs254fs9s1m1x6q4f",
"registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21": "08zrzs022xwndihvzdn78yqarv2b9696y67i6h78nla3ww87jgb8",
"registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.6": "0yn9i8nc6mmv28ig9w3dga571q09vg9f1f650mi5z8phx42r6hli",
"registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.2": "1w510wnixvlgimkx1zjbvlxh6xps2vjgfqgwf5a6adlbjp5rv5mj",
"registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.16": "1iayppgq4wqbfbfcqmsbwgamj0s65012sskfvyx07pxavk3gyhh9",
"registry+https://github.com/rust-lang/crates.io-index#android-tzdata@0.1.1": "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9",
"registry+https://github.com/rust-lang/crates.io-index#android_system_properties@0.1.5": "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1",
"registry+https://github.com/rust-lang/crates.io-index#annotate-snippets@0.9.2": "07p8r6jzb7nqydq0kr5pllckqcdxlyld2g275v425axnzffpxbyc",
"registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.18": "16sjk4x3ns2c3ya1x28a44kh6p47c7vhk27251i015hik1lm7k4a",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.6": "1acqayy22fwzsrvr6n0lz6a4zvjjcvgr5sm941m7m0b2fr81cb9v",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.1.2": "036nm3lkyk43xbps1yql3583fp4hg3b1600is7mcyxs1gzrpm53r",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.7": "0kmf0fq4c8yribdpdpylzz1zccpy84hizmcsac3wrac1f7kk8dfa",
"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.10": "1yai2vppmd7zlvlrp9grwll60knrmscalf8l2qpfz8b7y5lkpk2m",
"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.95": "010vd1ki8w84dzgx6c81sc8qm9n02fxic1gkpv52zp4nwrn0kb1l",
"registry+https://github.com/rust-lang/crates.io-index#assert-json-diff@2.0.2": "04mg3w0rh3schpla51l18362hsirl23q93aisws2irrj32wg5r27",
"registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.5": "1dm1mdbs1x6y3m3pz0qlamgiskb50i4q859676kx0pz8r8pajr6n",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.3": "134jhzrz89labrdwxxnjxqjdg06qvaflj1wkfnmyapwyldfwcnn7",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.0.2": "0j3na4b1nma39g4x7cwvj009awxckjf3z2vkwhldgka44hqj72g2",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.2": "19v0fv400bmp4niqpzxnhg83vz12mmqv7l2l8vi80qcdxj0lpm8w",
"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.4": "11yxw02b6parn29s757z96rgiqbn8qy0fk9a3p3bhczm85dhfybh",
"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.75": "1rmcjkim91c5mw7h9wn8nv0k6x118yz0xg0z1q18svgn42mqqrm4",
"registry+https://github.com/rust-lang/crates.io-index#arrayvec@0.7.4": "04b7n722jij0v3fnm3qk072d5ysc2q30rl9fz33zpfhzah30mlwn",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@1.9.0": "0dbdlkzlncbibd3ij6y6jmvjd0cmdn48ydcfdpfhw09njd93r5c1",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@2.3.1": "0skvwxj6ysfc6d7bhczz9a2550260g62bm5gl0nmjxxyn007id49",
"registry+https://github.com/rust-lang/crates.io-index#async-executor@1.13.1": "1v6w1dbvsmw6cs4dk4lxj5dvrikc6xi479wikwaab2qy3h09mjih",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@2.1.1": "1337ywc1paw03rdlwh100kh8pa0zyp0nrlya8bpsn6zdqi5kz8qw",
"registry+https://github.com/rust-lang/crates.io-index#async-executor@1.8.0": "0z7rpayidhdqs4sdzjhh26z5155c1n94fycqni9793n4zjz5xbhp",
"registry+https://github.com/rust-lang/crates.io-index#async-global-executor@2.4.1": "1762s45cc134d38rrv0hyp41hv4iv6nmx59vswid2p0il8rvdc85",
"registry+https://github.com/rust-lang/crates.io-index#async-io@2.4.0": "0n8h0vy53n4vdkq529scqnkzm9vcl3r73za9nj81s2nfrhiv78j3",
"registry+https://github.com/rust-lang/crates.io-index#async-lock@3.4.0": "060vh45i809wcqyxzs5g69nqiqah7ydz0hpkcjys9258vqn4fvpz",
"registry+https://github.com/rust-lang/crates.io-index#async-io@2.3.1": "0rggn074kbqxxajci1aq14b17gp75rw9l6rpbazcv9q0bc6ap5wg",
"registry+https://github.com/rust-lang/crates.io-index#async-lock@3.3.0": "0yxflkfw46rad4lv86f59b5z555dlfmg1riz1n8830rgi0qb8d6h",
"registry+https://github.com/rust-lang/crates.io-index#async-std@1.13.0": "059nbiyijwbndyrz0050skvlvzhds0dmnl0biwmxwbw055glfd66",
"registry+https://github.com/rust-lang/crates.io-index#async-task@4.7.1": "1pp3avr4ri2nbh7s6y9ws0397nkx1zymmcr14sq761ljarh3axcb",
"registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.85": "0mm0gwad44zs7mna4a0m1z4dhzpmydfj73w4wm23c8xpnhrli4rz",
"registry+https://github.com/rust-lang/crates.io-index#async-task@4.7.0": "16975vx6aqy5yf16fs9xz5vx1zq8mwkzfmykvcilc1j7b6c6xczv",
"registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.77": "1adf1jh2yg39rkpmqjqyr9xyd6849p0d95425i6imgbhx0syx069",
"registry+https://github.com/rust-lang/crates.io-index#atoi@2.0.0": "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj",
"registry+https://github.com/rust-lang/crates.io-index#atomic-waker@1.1.2": "1h5av1lw56m0jf0fd3bchxq8a30xv0b4wv8s4zkp4s0i7mfvs18m",
"registry+https://github.com/rust-lang/crates.io-index#auto-future@1.0.0": "0wykbakzh227vz6frx9p48zsq0wpswgmb7v3917m53m7gr2pw7iw",
"registry+https://github.com/rust-lang/crates.io-index#atomic-write-file@0.1.2": "0dl4x0srdwjxm3zz3fj1c7m44i3b7mjiad550fqklj1n4bfbxkgd",
"registry+https://github.com/rust-lang/crates.io-index#autocfg@0.1.8": "0y4vw4l4izdxq1v0rrhvmlbqvalrqrmk60v1z0dqlgnlbzkl7phd",
"registry+https://github.com/rust-lang/crates.io-index#autocfg@1.4.0": "09lz3by90d2hphbq56znag9v87gfpd9gb8nr82hll8z6x2nhprdc",
"registry+https://github.com/rust-lang/crates.io-index#axum-core@0.4.5": "16b1496c4gm387q20hkv5ic3k5bd6xmnvk50kwsy6ymr8rhvvwh9",
"registry+https://github.com/rust-lang/crates.io-index#axum-macros@0.4.2": "1klv77c889jm05bzayaaiinalarhvh2crc2w4nvp3l581xaj7lap",
"registry+https://github.com/rust-lang/crates.io-index#axum-test@16.4.1": "1p5qxacvxsagnqq30nr2wznjyhgb8svsfb925ah3d2b0s91s9qv3",
"registry+https://github.com/rust-lang/crates.io-index#axum@0.7.9": "07z7wqczi9i8xb4460rvn39p4wjqwr32hx907crd1vwb2fy8ijpd",
"registry+https://github.com/rust-lang/crates.io-index#autocfg@1.1.0": "1ylp3cb47ylzabimazvbz9ms6ap784zhb6syaz6c1jqpmcmq0s6l",
"registry+https://github.com/rust-lang/crates.io-index#az@1.2.1": "0ww9k1w3al7x5qmb7f13v3s9c2pg1pdxbs8xshqy6zyrchj4qzkv",
"registry+https://github.com/rust-lang/crates.io-index#backtrace@0.3.74": "06pfif7nwx66qf2zaanc2fcq7m64i91ki9imw9xd3bnz5hrwp0ld",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.21.7": "0rw52yvsk75kar9wgqfwgb414kvil1gn7mqkrhn9zf1537mpsacx",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1": "1imqzgh7bxcikp5vx3shqvw9j09g9ly0xr0jma0q66i52r7jbcvj",
"registry+https://github.com/rust-lang/crates.io-index#backtrace@0.3.69": "0dsq23dhw4pfndkx2nsa1ml2g31idm7ss7ljxp8d57avygivg290",
"registry+https://github.com/rust-lang/crates.io-index#bare-metal@0.2.5": "1cy5pbb92fznnri72y6drfpjxj4qdmd62f0rrlgy70dxlppn9ssx",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.21.5": "1y8x2xs9nszj5ix7gg4ycn5a6wy7ca74zxwqri3bdqzdjha6lqrm",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.9.3": "0hs62r35bgxslawyrn1vp9rmvrkkm76fqv0vqcwd048vs876r7a8",
"registry+https://github.com/rust-lang/crates.io-index#base64ct@1.6.0": "0nvdba4jb8aikv60az40x2w1y96sjdq8z3yp09rwzmkhiwv1lg4c",
"registry+https://github.com/rust-lang/crates.io-index#bindgen@0.69.5": "1240snlcfj663k04bjsg629g4wx6f83flgbjh5rzpgyagk3864r7",
"registry+https://github.com/rust-lang/crates.io-index#bit-set@0.8.0": "18riaa10s6n59n39vix0cr7l2dgwdhcpbcm97x1xbyfp1q47x008",
"registry+https://github.com/rust-lang/crates.io-index#bit-vec@0.8.0": "1xxa1s2cj291r7k1whbxq840jxvmdsq9xgh7bvrxl46m80fllxjy",
"registry+https://github.com/rust-lang/crates.io-index#bindgen@0.69.4": "18194611hn3k1dkxlha7a52sr8vmfhl9blc54xhj08cahd8wh3d0",
"registry+https://github.com/rust-lang/crates.io-index#bit-set@0.5.3": "1wcm9vxi00ma4rcxkl3pzzjli6ihrpn9cfdi0c5b4cvga2mxs007",
"registry+https://github.com/rust-lang/crates.io-index#bit-vec@0.6.3": "1ywqjnv60cdh1slhz67psnp422md6jdliji6alq0gmly2xm9p7rl",
"registry+https://github.com/rust-lang/crates.io-index#bit_field@0.10.2": "0qav5rpm4hqc33vmf4vc4r0mh51yjx5vmd9zhih26n9yjs3730nw",
"registry+https://github.com/rust-lang/crates.io-index#bitfield@0.13.2": "06g7jb5r2b856vnhx76081fg90jvmy61kjqcfjysgmd5hclvvbs6",
"registry+https://github.com/rust-lang/crates.io-index#bitflags@1.3.2": "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy",
"registry+https://github.com/rust-lang/crates.io-index#bitflags@2.8.0": "0dixc6168i98652jxf0z9nbyn0zcis3g6hi6qdr7z5dbhcygas4g",
"registry+https://github.com/rust-lang/crates.io-index#bitflags@2.4.1": "01ryy3kd671b0ll4bhdvhsz67vwz1lz53fz504injrd7wpv64xrj",
"registry+https://github.com/rust-lang/crates.io-index#block-buffer@0.10.4": "0w9sa2ypmrsqqvc20nhwr75wbb5cjr4kkyhpjm1z1lv2kdicfy1h",
"registry+https://github.com/rust-lang/crates.io-index#blocking@1.6.1": "1si99l8zp7c4zq87y35ayjgc5c9b60jb8h0k14zfcs679z2l2gvh",
"registry+https://github.com/rust-lang/crates.io-index#build_html@2.5.0": "0p4k25yk3v0wf720wl5zcghvc9ik6l7lsh3fz86cq3g7x4nbhpi2",
"registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.16.0": "0b015qb4knwanbdlp1x48pkb4pm57b8gidbhhhxr900q2wb6fabr",
"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.21.0": "18wj81x9xhqcd6985r8qxmbik6szjfjfj62q3xklw8h2p3x7srgg",
"registry+https://github.com/rust-lang/crates.io-index#blocking@1.5.1": "064i3d6b8ln34fgdw49nmx9m36bwi3r3nv8c9xhcrpf4ilz92dva",
"registry+https://github.com/rust-lang/crates.io-index#build_html@2.4.0": "188nibbsv33vgjjiq9cn2irsgdb75gxfipavcavnyydcwxpzw21i",
"registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.14.0": "1v4arnv9kwk54v5d0qqpv4vyw2sgr660nk0w3apzixi1cm3yfc3z",
"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.14.0": "1ik1ma5n3bg700skkzhx50zjk7kj7mbsphi773if17l04pn2hk9p",
"registry+https://github.com/rust-lang/crates.io-index#byteorder@1.5.0": "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z",
"registry+https://github.com/rust-lang/crates.io-index#bytes@1.9.0": "16ykzx24v1x4f42v2lxyvlczqhdfji3v7r4ghwckpwijzvb1hn9j",
"registry+https://github.com/rust-lang/crates.io-index#bytesize@1.3.0": "1k3aak70iwz4s2gsjbxf0ws4xnixqbdz6p2ha96s06748fpniqx3",
"registry+https://github.com/rust-lang/crates.io-index#cairo-rs@0.18.5": "1qjfkcq3mrh3p01nnn71dy3kn99g21xx3j8xcdvzn8ll2pq6x8lc",
"registry+https://github.com/rust-lang/crates.io-index#bytes@1.5.0": "08w2i8ac912l8vlvkv3q51cd4gr09pwlg3sjsjffcizlrb0i5gd2",
"registry+https://github.com/rust-lang/crates.io-index#cairo-rs@0.18.3": "18d80lk853bjhx36rjaj78clzfjrmlgi01863drnmshdgxi16dpk",
"registry+https://github.com/rust-lang/crates.io-index#cairo-sys-rs@0.18.2": "0lfsxl7ylw3phbnwmz3k58j1gnqi6kc2hdc7g3bb7f4hwnl9yp38",
"registry+https://github.com/rust-lang/crates.io-index#cc@1.2.10": "0aaj2ivamhfzhgb9maasnfkh03s2mzhzpzwrkghgzbkfnv5qy80k",
"registry+https://github.com/rust-lang/crates.io-index#cc@1.0.83": "1l643zidlb5iy1dskc5ggqs4wqa29a02f44piczqc8zcnsq4y5zi",
"registry+https://github.com/rust-lang/crates.io-index#cexpr@0.6.0": "0rl77bwhs5p979ih4r0202cn5jrfsrbgrksp40lkfz5vk1x3ib3g",
"registry+https://github.com/rust-lang/crates.io-index#cfg-expr@0.15.8": "00lgf717pmf5qd2qsxxzs815v6baqg38d6m5i6wlh235p14asryh",
"registry+https://github.com/rust-lang/crates.io-index#cfg-expr@0.15.5": "1cqicd9qi8mzzgh63dw03zhbdihqfl3lbiklrkynyzkq67s5m483",
"registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0": "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds",
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz-build@0.2.1": "03rmzd69cn7fp0fgkjr5042b3g54s2l941afjm3001ls7kqkjgj3",
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz@0.8.6": "0vlksnmpb6rd4h55245agnfhphnpslwnq9al3aw3is43dd3f16nm",
"registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.39": "09g8nf409lb184kl9j4s85k0kn8wzgjkp5ls9zid50b886fwqdky",
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz@0.8.4": "0xhd3dsfs72im0sbc7w889lfy7bxgjlbvqhj5a1yvxhxwb08acg2",
"registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.31": "0f6vg67pipm8cziad2yms6a639pssnvysk1m05dd9crymmdnhb3z",
"registry+https://github.com/rust-lang/crates.io-index#clang-sys@1.8.1": "1x1r9yqss76z8xwpdanw313ss6fniwc1r7dzb5ycjn0ph53kj0hb",
"registry+https://github.com/rust-lang/crates.io-index#clap@4.5.26": "10v7qvn90calfbhap1c4r249i5c7fbxj09fn3szfz9pkis85xsx8",
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.26": "08f1mzcvi7zjhm7hvz6al4jnv70ccqhwiaq74hihlspwnl0iic4n",
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.24": "131ih3dm76srkbpfx7zfspp9b556zgzj31wqhl0ji2b39lcmbdsl",
"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.7.4": "19nwfls5db269js5n822vkc8dw0wjq2h1wf0hgr06ld2g52d2spl",
"registry+https://github.com/rust-lang/crates.io-index#clap@4.4.11": "1wj5gb2fnqls00zfahg3490bdfc36d9cwpl80qjacb5jyrqzdbxz",
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.4.11": "1fxdsmw1ilgswz3lg2hjlvsdyyz04k78scjirlbd7c9bc83ba5m2",
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.4.7": "0hk4hcxl56qwqsf4hmf7c0gr19r9fbxk0ah2bgkr36pmmaph966g",
"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.6.0": "1l8bragdvim7mva9flvd159dskn2bdkpl0jqrr41wnjfn8pcfbvh",
"registry+https://github.com/rust-lang/crates.io-index#cloudabi@0.0.3": "0kxcg83jlihy0phnd2g8c2c303px3l2p3pkjz357ll6llnd5pz6x",
"registry+https://github.com/rust-lang/crates.io-index#color_quant@1.1.0": "12q1n427h2bbmmm1mnglr57jaz2dj9apk0plcxw7nwqiai7qjyrx",
"registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.3": "1439m3r3jy3xqck8aa13q658visn71ki76qa93cy55wkmalwlqsv",
"registry+https://github.com/rust-lang/crates.io-index#concurrent-queue@2.5.0": "0wrr3mzq2ijdkxwndhf79k952cp4zkz35ray8hvsxl96xrx1k82c",
"registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.0": "1ix7w85kwvyybwi2jdkl3yva2r2bvdcc3ka2grjfzfgrapqimgxc",
"registry+https://github.com/rust-lang/crates.io-index#concurrent-queue@2.4.0": "0qvk23ynj311adb4z7v89wk3bs65blps4n24q8rgl23vjk6lhq6i",
"registry+https://github.com/rust-lang/crates.io-index#const-oid@0.9.6": "1y0jnqaq7p2wvspnx7qj76m7hjcqpz73qzvr9l2p9n2s51vr6if2",
"registry+https://github.com/rust-lang/crates.io-index#convert_case@0.6.0": "1jn1pq6fp3rri88zyw6jlhwwgf6qiyc08d6gjv0qypgkl862n67c",
"registry+https://github.com/rust-lang/crates.io-index#cookie-factory@0.3.3": "18mka6fk3843qq3jw1fdfvzyv05kx7kcmirfbs2vg2kbw9qzm1cq",
"registry+https://github.com/rust-lang/crates.io-index#cookie@0.17.0": "096c52jg9iq4lfcps2psncswv33fc30mmnaa2sbzzcfcw71kgyvy",
"registry+https://github.com/rust-lang/crates.io-index#cookie@0.18.1": "0iy749flficrlvgr3hjmf3igr738lk81n5akzf4ym4cs6cxg7pjd",
"registry+https://github.com/rust-lang/crates.io-index#cool_asserts@2.0.3": "1v18dg7ifx41k2f82j3gsnpm1fg9wk5s4zv7sf42c7pnad72b7zf",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation-sys@0.8.7": "12w8j73lazxmr1z0h98hf3z623kl8ms7g07jch7n4p8f9nwlhdkp",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation-sys@0.8.6": "13w6sdf06r0hn7bx2b45zxsg1mm2phz34jikm6xc5qrbr6djpsh6",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation@0.9.4": "13zvbbj07yk3b61b8fhwfzhy35535a583irf23vlcg59j7h9bqci",
"registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.16": "1hy466fkhxjbb16i7na95wz8yr14d0kd578pwzj5lbkz14jh5f0n",
"registry+https://github.com/rust-lang/crates.io-index#cortex-m-rt-macros@0.7.0": "1iyki0wq8pj0qbjhw1mbq5njraihhyr7ydcbqzdzwg10dziz7xph",
"registry+https://github.com/rust-lang/crates.io-index#cortex-m-rt@0.7.3": "1cfxg502gvcmaczmaij5maxbvaxnda5w6gp14cbin44ksl9yi17f",
"registry+https://github.com/rust-lang/crates.io-index#cortex-m@0.7.7": "1fbca698v4gv57mv5fc48jrz8wcy6sv675n6fsrsah4qykc11ilf",
"registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.11": "1l0gzsyy576n017g9bf0vkv5hhg9cpz1h1libxyfdlzcgbh0yhnf",
"registry+https://github.com/rust-lang/crates.io-index#crc-any@2.5.0": "0wzs26q5cf29fhfnrkrjsr8dpai0rlm4im8b53by8rbrbzzwjbm6",
"registry+https://github.com/rust-lang/crates.io-index#crc-catalog@2.4.0": "1xg7sz82w3nxp1jfn425fvn1clvbzb3zgblmxsyqpys0dckp9lqr",
"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.4.2": "1czp7vif73b8xslr3c9yxysmh9ws2r8824qda7j47ffs9pcnjxx9",
"registry+https://github.com/rust-lang/crates.io-index#crc@3.2.1": "0dnn23x68qakzc429s1y9k9y3g8fn5v9jwi63jcz151sngby9rk9",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-deque@0.8.6": "0l9f1saqp1gn5qy0rxvkmz4m6n7fc0b3dbm6q1r5pmgpnyvi3lcx",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-epoch@0.9.18": "03j2np8llwf376m3fxqx859mgp9f83hj1w34153c7a9c7i5ar0jv",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-queue@0.3.12": "059igaxckccj6ndmg45d5yf7cm4ps46c18m21afq3pwiiz1bnn0g",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.21": "0a3aa2bmc8q35fb67432w16wvi54sfmb69rk9h5bhd18vw0c99fh",
"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.3.2": "03c8f29yx293yf43xar946xbls1g60c207m9drf8ilqhr25vsh5m",
"registry+https://github.com/rust-lang/crates.io-index#crc@3.0.1": "1zkx87a5x06xfd6xm5956w4vmdfs0wcxpsn7iwj5jbp2rcapmv46",
"registry+https://github.com/rust-lang/crates.io-index#critical-section@1.1.2": "05pj0pvkdyc9r30xxabam4n8zxdbzxcddr0gdypajcbqjgwgynbh",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-deque@0.8.4": "0la7fx9n1vbx3h23va0xmcy36hziql1pkik08s3j3asv4479ma7w",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-epoch@0.9.16": "1anr32r8px0vb65cgwbwp3zhqz69scz5dgq9bmx54w5qa59yjbrd",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-queue@0.3.9": "0lz17pgydh29w8brld8dysi1m4n5bxfpnj8w9bxk0q6xpyyzbg5r",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.17": "13y7wh993i7q71kg6wcfj65w3rlmizzrz7cqgz1l9whlgw9rcvf0",
"registry+https://github.com/rust-lang/crates.io-index#crunchy@0.2.2": "1dx9mypwd5mpfbbajm78xcrg5lirqk7934ik980mmaffg3hdm0bs",
"registry+https://github.com/rust-lang/crates.io-index#crypto-common@0.1.6": "1cvby95a6xg7kxdz5ln3rl9xh66nz66w46mm3g56ri1z5x815yqv",
"registry+https://github.com/rust-lang/crates.io-index#data-encoding@2.7.0": "0vxdv88fnvnxw29gk84gj5662xlv0p5hmlxpwp7d60cckp8fwq0f",
"registry+https://github.com/rust-lang/crates.io-index#data-encoding@2.5.0": "1rcbnwfmfxhlshzbn3r7srm3azqha3mn33yxyqxkzz2wpqcjm5ky",
"registry+https://github.com/rust-lang/crates.io-index#debug-helper@0.3.13": "0bhnpzpgmg8dkdr27g2b49slf6ca79m4idcb01z2krs0qkifhy7m",
"registry+https://github.com/rust-lang/crates.io-index#deflate@0.8.6": "0x6iqlayg129w63999kz97m279m0jj4x4sm6gkqlvmp73y70yxvk",
"registry+https://github.com/rust-lang/crates.io-index#der@0.7.9": "1h4vzjfa1lczxdf8avfj9qlwh1qianqlxdy1g5rn762qnvkzhnzm",
"registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.11": "1d1ibqqnr5qdrpw8rclwrf1myn3wf0dygl04idf4j2s49ah6yaxl",
"registry+https://github.com/rust-lang/crates.io-index#diff@0.1.13": "1j0nzjxci2zqx63hdcihkp0a4dkdmzxd7my4m7zk6cjyfy34j9an",
"registry+https://github.com/rust-lang/crates.io-index#der@0.7.8": "070bwiyr80800h31c5zd96ckkgagfjgnrrdmz3dzg2lccsd3dypz",
"registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.10": "1p4i64nkadamksa943d6gk39sl1kximz0xr69n408fvsl1q0vcwf",
"registry+https://github.com/rust-lang/crates.io-index#digest@0.10.7": "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy",
"registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.7.0": "09ky8s3higkf677lmyqg30hmj66gpg7hx907s6hfvbk2a9av05r5",
"registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.8.0": "15s3j4ry943xqlac63bp81sgdk9s3yilysabzww35j9ibmnaic50",
"registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5": "1q0alair462j21iiqwrr21iabkfnb13d6x5w95lkdg21q2xrqdlp",
"registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.4": "0p8pyg10csc782qlwx3znr6qx46ni96m1qh597kmyrf6s3s8axa8",
"registry+https://github.com/rust-lang/crates.io-index#dotenvy@0.15.7": "16s3n973n5aqym02692i1npb079n5mb0fwql42ikmwn8wnrrbbqs",
"registry+https://github.com/rust-lang/crates.io-index#either@1.13.0": "1w2c1mybrd7vljyxk77y9f4w9dyjrmp3yp82mk7bcm8848fazcb0",
"registry+https://github.com/rust-lang/crates.io-index#encoding_rs@0.8.35": "1wv64xdrr9v37rqqdjsyb8l8wzlcbab80ryxhrszvnj59wy0y0vm",
"registry+https://github.com/rust-lang/crates.io-index#env_logger@0.10.2": "1005v71kay9kbz1d5907l0y7vh9qn2fqsp2yfgb8bjvin6m0bm2c",
"registry+https://github.com/rust-lang/crates.io-index#either@1.9.0": "01qy3anr7jal5lpc20791vxrw0nl6vksb5j7x56q2fycgcyy8sm2",
"registry+https://github.com/rust-lang/crates.io-index#embedded-alloc@0.5.1": "05gqqv9nyr33vbd0i8ab2bmfcc5kwgk0msk4pk7w5fncba8igbnx",
"registry+https://github.com/rust-lang/crates.io-index#embedded-dma@0.2.0": "0ijld5jblcka4b95s1lwxd9k109nyaap34h44g122ddjbidpwkwr",
"registry+https://github.com/rust-lang/crates.io-index#embedded-hal@0.2.7": "1zv6pkgg2yl0mzvh3jp326rhryqfnv4l27h78v7p7maag629i51m",
"registry+https://github.com/rust-lang/crates.io-index#encoding_rs@0.8.33": "1qa5k4a0ipdrxq4xg9amms9r9pnnfn7nfh2i9m3mw0ka563b6s3j",
"registry+https://github.com/rust-lang/crates.io-index#env_logger@0.10.1": "1kmy9xmfjaqfvd4wkxr1f7d16ld3h9b487vqs2q9r0s8f3kg7cwm",
"registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.1": "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl",
"registry+https://github.com/rust-lang/crates.io-index#errno@0.3.10": "0pgblicz1kjz9wa9m0sghkhh2zw1fhq1mxzj7ndjm746kg5m5n1k",
"registry+https://github.com/rust-lang/crates.io-index#errno@0.3.8": "0ia28ylfsp36i27g1qih875cyyy4by2grf80ki8vhgh6vinf8n52",
"registry+https://github.com/rust-lang/crates.io-index#etcetera@0.8.0": "0hxrsn75dirbjhwgkdkh0pnpqrnq17ypyhjpjaypgax1hd91nv8k",
"registry+https://github.com/rust-lang/crates.io-index#event-listener-strategy@0.5.3": "1ch5gf6knllyq12jkb5zdfag573dh44307q4pwwi2g37sc6lwgiw",
"registry+https://github.com/rust-lang/crates.io-index#event-listener-strategy@0.4.0": "1lwprdjqp2ibbxhgm9khw7s7y7k4xiqj5i5yprqiks6mnrq4v3lm",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@2.5.3": "1q4w3pndc518crld6zsqvvpy9lkzwahp2zgza9kbzmmqh9gif1h2",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@5.4.0": "1bii2gn3vaa33s0gr2zph7cagiq0ppcfxcxabs24ri9z9kgar4il",
"registry+https://github.com/rust-lang/crates.io-index#exr@1.73.0": "1q47yq78q9k210r6jy1wwrilxwwxqavik9l3l426rd17k7srfcgq",
"registry+https://github.com/rust-lang/crates.io-index#fallible-iterator@0.3.0": "0ja6l56yka5vn4y4pk6hn88z0bpny7a8k1919aqjzp0j1yhy9k1a",
"registry+https://github.com/rust-lang/crates.io-index#fallible-streaming-iterator@0.1.9": "0nj6j26p71bjy8h42x6jahx1hn0ng6mc2miwpgwnp8vnwqf4jq3k",
"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0": "1ghiahsw1jd68df895cy5h3gzwk30hndidn3b682zmshpgmrx41p",
"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.7": "130ga18vyxbb5idbgi07njymdaavvk6j08yh1dfarm294ssm6s0y",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@4.0.1": "04k7qbi5kgs36s905gxijj41kcr78xs2s6cp6vbg50254z7wvwl4",
"registry+https://github.com/rust-lang/crates.io-index#exr@1.71.0": "1a58k179b0h8zpf1cfgc2vl60j2syg7cdgdzp9j6cgmb6lgpcal3",
"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.0.1": "19flpv5zbzpf0rk4x77z4zf25in0brg8l7m304d3yrf47qvwxjr5",
"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.1": "0s5885wdsih2hqx3hsl7l8cl3666fgsgiwvglifzy229hpydmmk4",
"registry+https://github.com/rust-lang/crates.io-index#field-offset@0.3.6": "0zq5sssaa2ckmcmxxbly8qgz3sxpb8g1lwv90sdh1z74qif2gqiq",
"registry+https://github.com/rust-lang/crates.io-index#fixed@1.28.0": "0nn85j5x8yzx10q49jdzia4yp6pnasnxpnwh0p9aqr7qkfwf1il5",
"registry+https://github.com/rust-lang/crates.io-index#flate2@1.0.35": "0z6h0wa095wncpfngx75wyhyjnqwld7wax401gsvnzjhzgdbydn9",
"registry+https://github.com/rust-lang/crates.io-index#fluent-bundle@0.15.3": "14zl0cjn361is69pb1zry4k2zzh5nzsfv0iz05wccl00x0ga5q3z",
"registry+https://github.com/rust-lang/crates.io-index#finl_unicode@1.2.0": "1ipdx778849czik798sjbgk5yhwxqybydac18d2g9jb20dxdrkwg",
"registry+https://github.com/rust-lang/crates.io-index#fixed@1.24.0": "0zbfwzk4mrfbawpx2ahz533bkb97jzihv7fxiyhpmwf0wzkrrih2",
"registry+https://github.com/rust-lang/crates.io-index#flate2@1.0.28": "03llhsh4gqdirnfxxb9g2w9n0721dyn4yjir3pz7z4vjaxb3yc26",
"registry+https://github.com/rust-lang/crates.io-index#fluent-bundle@0.15.2": "1zbzm13rfz7fay7bps7jd4j1pdnlxmdzzfymyq2iawf9vq0wchp2",
"registry+https://github.com/rust-lang/crates.io-index#fluent-langneg@0.13.0": "152yxplc11vmxkslvmaqak9x86xnavnhdqyhrh38ym37jscd0jic",
"registry+https://github.com/rust-lang/crates.io-index#fluent-syntax@0.11.1": "0gd3cdvsx9ymbb8hijcsc9wyf8h1pbcbpsafg4ldba56ji30qlra",
"registry+https://github.com/rust-lang/crates.io-index#fluent@0.16.1": "0njmdpwz52yjzyp55iik9k6vrixqiy7190d98pk0rgdy0x3n6x5v",
"registry+https://github.com/rust-lang/crates.io-index#flume@0.11.1": "15ch0slxa8sqsi6c73a0ky6vdnh48q8cxjf7rksa3243m394s3ns",
"registry+https://github.com/rust-lang/crates.io-index#fluent-syntax@0.11.0": "0y6ac7z7sbv51nsa6km5z8rkjj4nvqk91vlghq1ck5c3cjbyvay0",
"registry+https://github.com/rust-lang/crates.io-index#fluent@0.16.0": "19s7z0gw95qdsp9hhc00xcy11nwhnx93kknjmdvdnna435w97xk1",
"registry+https://github.com/rust-lang/crates.io-index#flume@0.11.0": "10girdbqn77wi802pdh55lwbmymy437k7kklnvj12aaiwaflbb2m",
"registry+https://github.com/rust-lang/crates.io-index#fnv@1.0.7": "1hc2mcqha06aibcaza94vbi81j6pr9a1bbxrxjfhc91zin8yr7iz",
"registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.4": "0vsxw2iwpgs7yy6l7pndm7b8nllaq5vdxwnmjn1qpm5kyzhzvlm0",
"registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1": "0jxgzd04ra4imjv8jgkmdq59kj8fsz6w4zxsbmlai34h26225c00",
"registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2": "1cgk0vyd7r45cj769jym4a6s7vwshvd0z4bqrb92q1fwibmkkwzn",
"registry+https://github.com/rust-lang/crates.io-index#form_urlencoded@1.2.1": "0milh8x7nl4f450s3ddhg57a3flcv6yq8hlkyk6fyr3mcb128dp1",
"registry+https://github.com/rust-lang/crates.io-index#frunk@0.4.2": "11v242h7zjka0lckxcffn5pjgr3jzxyljy7ffr0ppy8jkssm38qi",
"registry+https://github.com/rust-lang/crates.io-index#frunk_core@0.4.2": "1mjqnn7dclwn8d5g0mrfkg360cgn70a7mm8arx6fc1xxn3x6j95g",
"registry+https://github.com/rust-lang/crates.io-index#frunk_derives@0.4.2": "0blsy6aq6rbvxcc0337g15083w24s8539fmv8rwp1qan2qprkymh",
"registry+https://github.com/rust-lang/crates.io-index#frunk_proc_macro_helpers@0.1.2": "0b1xl4cfrfai7qi5cb4h9x0967miv3dvwvnsmr1vg4ljhgflmd9m",
"registry+https://github.com/rust-lang/crates.io-index#fuchsia-cprng@0.1.1": "1fnkqrbz7ixxzsb04bsz9p0zzazanma8znfdqjvh39n14vapfvx0",
"registry+https://github.com/rust-lang/crates.io-index#futures-channel@0.3.31": "040vpqpqlbk099razq8lyn74m0f161zd0rp36hciqrwcg2zibzrd",
"registry+https://github.com/rust-lang/crates.io-index#futures-core@0.3.31": "0gk6yrxgi5ihfanm2y431jadrll00n5ifhnpx090c2f2q1cr1wh5",
"registry+https://github.com/rust-lang/crates.io-index#futures-executor@0.3.31": "17vcci6mdfzx4gbk0wx64chr2f13wwwpvyf3xd5fb1gmjzcx2a0y",
"registry+https://github.com/rust-lang/crates.io-index#fugit@0.3.7": "1rzp49521akq49vs9m8llgmdkk08zb77rry10a7srm9797b6l60p",
"registry+https://github.com/rust-lang/crates.io-index#futures-channel@0.3.29": "1jxsifvrbqzdadk0svbax71cba5d3qg3wgjq8i160mxmd1kdckgz",
"registry+https://github.com/rust-lang/crates.io-index#futures-core@0.3.29": "1308bpj0g36nhx2y6bl4mm6f1gnh9xyvvw2q2wpdgnb6dv3247gb",
"registry+https://github.com/rust-lang/crates.io-index#futures-executor@0.3.29": "1g4pjni0sw28djx6mlcfz584abm2lpifz86cmng0kkxh7mlvhkqg",
"registry+https://github.com/rust-lang/crates.io-index#futures-intrusive@0.5.0": "0vwm08d1pli6bdaj0i7xhk3476qlx4pll6i0w03gzdnh7lh0r4qx",
"registry+https://github.com/rust-lang/crates.io-index#futures-io@0.3.31": "1ikmw1yfbgvsychmsihdkwa8a1knank2d9a8dk01mbjar9w1np4y",
"registry+https://github.com/rust-lang/crates.io-index#futures-lite@2.6.0": "0cmmgszlmkwsac9pyw5rfjakmshgx4wmzmlyn6mmjs0jav4axvgm",
"registry+https://github.com/rust-lang/crates.io-index#futures-macro@0.3.31": "0l1n7kqzwwmgiznn0ywdc5i24z72zvh9q1dwps54mimppi7f6bhn",
"registry+https://github.com/rust-lang/crates.io-index#futures-sink@0.3.31": "1xyly6naq6aqm52d5rh236snm08kw8zadydwqz8bip70s6vzlxg5",
"registry+https://github.com/rust-lang/crates.io-index#futures-task@0.3.31": "124rv4n90f5xwfsm9qw6y99755y021cmi5dhzh253s920z77s3zr",
"registry+https://github.com/rust-lang/crates.io-index#futures-util@0.3.31": "10aa1ar8bgkgbr4wzxlidkqkcxf77gffyj8j7768h831pcaq784z",
"registry+https://github.com/rust-lang/crates.io-index#futures@0.3.31": "0xh8ddbkm9jy8kc5gbvjp9a4b6rqqxvc8471yb2qaz5wm2qhgg35",
"registry+https://github.com/rust-lang/crates.io-index#futures-io@0.3.29": "1ajsljgny3zfxwahba9byjzclrgvm1ypakca8z854k2w7cb4mwwb",
"registry+https://github.com/rust-lang/crates.io-index#futures-lite@2.2.0": "1flj85i6xm0rjicxixmajrp6rhq8i4bnbzffmrd6h23ln8jshns4",
"registry+https://github.com/rust-lang/crates.io-index#futures-macro@0.3.29": "1nwd18i8kvpkdfwm045hddjli0n96zi7pn6f99zi9c74j7ym7cak",
"registry+https://github.com/rust-lang/crates.io-index#futures-sink@0.3.29": "05q8jykqddxzp8nwf00wjk5m5mqi546d7i8hsxma7hiqxrw36vg3",
"registry+https://github.com/rust-lang/crates.io-index#futures-task@0.3.29": "1qmsss8rb5ppql4qvd4r70h9gpfcpd0bg2b3qilxrnhdkc397lgg",
"registry+https://github.com/rust-lang/crates.io-index#futures-util@0.3.29": "0141rkqh0psj4h8x8lgsl1p29dhqr7z2wcixkcbs60z74kb2d5d1",
"registry+https://github.com/rust-lang/crates.io-index#futures@0.3.29": "0dak2ilpcmyjrb1j54fzy9hlw6vd10vqljq9gd59pbrq9dqr00ns",
"registry+https://github.com/rust-lang/crates.io-index#gcd@2.3.0": "06l4fib4dh4m6gazdrzzzinhvcpcfh05r4i4gzscl03vnjhqnx8x",
"registry+https://github.com/rust-lang/crates.io-index#gdk-pixbuf-sys@0.18.0": "1xya543c4ffd2n7aiwwrdxsyc9casdbasafi6ixcknafckm3k61z",
"registry+https://github.com/rust-lang/crates.io-index#gdk-pixbuf@0.18.5": "1v7svvl0g7zybndmis5inaqqgi1mvcc6s1n8rkb31f5zn3qzbqah",
"registry+https://github.com/rust-lang/crates.io-index#gdk-pixbuf@0.18.3": "0b68ssdyapvq3bgsna9frabbzhjkvvzz8jld4mxkphr29nvk4vs4",
"registry+https://github.com/rust-lang/crates.io-index#gdk4-sys@0.7.2": "1w7yvir565sjrrw828lss07749hfpfsr19jdjzwivkx36brl7ayv",
"registry+https://github.com/rust-lang/crates.io-index#gdk4@0.7.3": "1xiacc63p73apr033gjrb9dsk0y4yxnsljwfxbwfry41snd03nvy",
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.11.2": "0a7w8w0rg47nmcinnfzv443lcyb8mplwc251p1jyr5xj2yh6wzv6",
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.14.7": "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45",
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.15": "1mzlnrb3dgyd1fb84gvw10pyr8wdqdl4ry4sr64i1s8an66pqmn4",
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.11": "03q7120cc2kn7ry013i67zmjl2g9q73h1ks5z08hq5v9syz0d47y",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.11.4": "01hbw3isapzpzff8l6aw55jnaqx2bcscrbwyf3rglkbbfp397p9y",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.13.1": "1whrkvdg26gp1r7f95c6800y6ijqw5y0z8rgj6xihpi136dxdciz",
"registry+https://github.com/rust-lang/crates.io-index#gimli@0.31.1": "0gvqc0ramx8szv76jhfd4dms0zyamvlg4whhiz11j34hh3dqxqh7",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.12.0": "0ibhjyrslfv9qm400gp4hd50v9ibva01j4ab9bwiq1aycy9jayc0",
"registry+https://github.com/rust-lang/crates.io-index#gimli@0.28.1": "0lv23wc8rxvmjia3mcxc6hj9vkqnv1bqq0h8nzjcgf71mrxx6wa2",
"registry+https://github.com/rust-lang/crates.io-index#gio-sys@0.18.1": "1lip8z35iy9d184x2qwjxlbxi64q9cpayy7v1p5y9xdsa3w6smip",
"registry+https://github.com/rust-lang/crates.io-index#gio@0.18.4": "0wsc6mnx057s4ailacg99dwgna38dbqli5x7a6y9rdw75x9qzz6l",
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.16.3": "1z73bl10zmxwrv16v4f5wcky1f3z5a2v0hknca54al4k2p5ka695",
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.17.10": "05p7ab2vn8962cbchi7a6hndhvw64nqk4w5kpg5z53iizsgdfrbs",
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.18.0": "0p5c2ayiam5bkp9wvq9f9ihwp06nqs5j801npjlwnhrl8rpwac9l",
"registry+https://github.com/rust-lang/crates.io-index#glib-macros@0.18.5": "1p5cla53fcp195zp0hkqpmnn7iwmkdswhy7xh34002bw8y7j5c0b",
"registry+https://github.com/rust-lang/crates.io-index#glib-macros@0.18.3": "19crnw5a57w02njpbsmdqwbkncl6hw6g3mv554y8dqzcrri3jybj",
"registry+https://github.com/rust-lang/crates.io-index#glib-sys@0.18.1": "164qhsfmlzd5mhyxs8123jzbdfldwxbikfpq5cysj3lddbmy4g06",
"registry+https://github.com/rust-lang/crates.io-index#glib@0.18.5": "1r8fw0627nmn19bgk3xpmcfngx3wkn7mcpq5a8ma3risx3valg93",
"registry+https://github.com/rust-lang/crates.io-index#glob@0.3.2": "1cm2w34b5w45fxr522h5b0fv1bxchfswcj560m3pnjbia7asvld8",
"registry+https://github.com/rust-lang/crates.io-index#glib@0.18.4": "0kjws6ns6dym48nzxz9skhipk55flc2hy5q5kzg4w12wvizvs6wm",
"registry+https://github.com/rust-lang/crates.io-index#glob@0.3.1": "16zca52nglanv23q5qrwd5jinw3d3as5ylya6y1pbx47vkxvrynj",
"registry+https://github.com/rust-lang/crates.io-index#gloo-timers@0.3.0": "1519157n7xppkk6pdw5w52vy1llzn5iljkqd7q1h5609jv7l7cdv",
"registry+https://github.com/rust-lang/crates.io-index#gobject-sys@0.18.0": "0i6fhp3m6vs3wkzyc22rk2cqj68qvgddxmpaai34l72da5xi4l08",
"registry+https://github.com/rust-lang/crates.io-index#graphene-rs@0.18.1": "00f4q1ra4haap5i7lazwhkdgnb49fs8adk2nm6ki6mjhl76jh8iv",
@ -168,166 +174,149 @@
"registry+https://github.com/rust-lang/crates.io-index#gtk4-macros@0.7.2": "0bw3cchiycf7dw1bw4p8946gv38azxy05a5w0ndgcmxnz6fc8znm",
"registry+https://github.com/rust-lang/crates.io-index#gtk4-sys@0.7.3": "1f2ylskyqkjdik9fij2m46pra4jagnif5xyalbxfk3334fmc9n2l",
"registry+https://github.com/rust-lang/crates.io-index#gtk4@0.7.3": "0hh8nzglmz94v1m1h6vy8z12m6fr7ia467ry0md5fa4p7sm53sss",
"registry+https://github.com/rust-lang/crates.io-index#h2@0.3.26": "1s7msnfv7xprzs6xzfj5sg6p8bjcdpcqcmjjbkd345cyi1x55zl1",
"registry+https://github.com/rust-lang/crates.io-index#half@2.4.1": "123q4zzw1x4309961i69igzd1wb7pj04aaii3kwasrz3599qrl3d",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.14.5": "1wa1vy1xs3mp11bn3z9dv0jricgr6a2j0zkf1g19yz3vw4il89z5",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.2": "12dj0yfn59p3kh3679ac0w1fagvzf4z2zp87a13gbbqbzw0185dz",
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.10.0": "1h8lzvnl9qxi3zyagivzz2p1hp6shgddfmccyf6jv7s1cdicz0kk",
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.9.1": "1byq4nyrflm5s6wdx5qwp96l1qbp2d0nljvrr5yqrsfy51qzz93b",
"registry+https://github.com/rust-lang/crates.io-index#h2@0.3.22": "0y41jlflvw8niifdirgng67zdmic62cjf5m2z69hzrpn5qr50qjd",
"registry+https://github.com/rust-lang/crates.io-index#half@2.2.1": "1l1gdlzxgm7wc8xl5fxas20kfi1j35iyb7vfjkghbdzijcvazd02",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.14.3": "012nywlg0lj9kwanh69my5x67vjlfmzfi9a0rq4qvis2j8fil3r9",
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.8.4": "1xy8agkyp0llbqk9fcffc1xblayrrywlyrm2a7v93x8zygm4y2g8",
"registry+https://github.com/rust-lang/crates.io-index#headers-core@0.2.0": "0ab469xfpd411mc3dhmjhmzrhqikzyj8a17jn5bkj9zfpy0n9xp7",
"registry+https://github.com/rust-lang/crates.io-index#headers@0.3.9": "0w62gnwh2p1lml0zqdkrx9dp438881nhz32zrzdy61qa0a9kns06",
"registry+https://github.com/rust-lang/crates.io-index#heck@0.4.1": "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m",
"registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0": "1sjmpsdl8czyh9ywl3qcsfsq9a307dg4ni2vnlwgnzzqhc4y0113",
"registry+https://github.com/rust-lang/crates.io-index#hermit-abi@0.3.9": "092hxjbjnq5fmz66grd9plxd0sh6ssg5fhgwwwqbrzgzkjwdycfj",
"registry+https://github.com/rust-lang/crates.io-index#hermit-abi@0.4.0": "1k1zwllx6nfq417hy38x4akw1ivlv68ymvnzyxs76ffgsqcskxpv",
"registry+https://github.com/rust-lang/crates.io-index#hex-string@0.1.0": "02sgrgrbp693jv0v5iga7z47y6aj93cq0ia39finby9x17fw53l4",
"registry+https://github.com/rust-lang/crates.io-index#hex@0.4.3": "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z",
"registry+https://github.com/rust-lang/crates.io-index#hkdf@0.12.4": "1xxxzcarz151p1b858yn5skmhyrvn8fs4ivx5km3i1kjmnr8wpvv",
"registry+https://github.com/rust-lang/crates.io-index#hmac@0.12.1": "0pmbr069sfg76z7wsssfk5ddcqd9ncp79fyz6zcm6yn115yc6jbc",
"registry+https://github.com/rust-lang/crates.io-index#home@0.5.11": "1kxb4k87a9sayr8jipr7nq9wpgmjk4hk4047hmf9kc24692k75aq",
"registry+https://github.com/rust-lang/crates.io-index#http-body-util@0.1.2": "0kslwazg4400qnc2azkrgqqci0fppv12waicnsy5d8hncvbjjd3r",
"registry+https://github.com/rust-lang/crates.io-index#home@0.5.9": "19grxyg35rqfd802pcc9ys1q3lafzlcjcv2pl2s5q8xpyr5kblg3",
"registry+https://github.com/rust-lang/crates.io-index#http-body@0.4.6": "1lmyjfk6bqk6k9gkn1dxq770sb78pqbqshga241hr5p995bb5skw",
"registry+https://github.com/rust-lang/crates.io-index#http-body@1.0.1": "111ir5k2b9ihz5nr9cz7cwm7fnydca7dx4hc7vr16scfzghxrzhy",
"registry+https://github.com/rust-lang/crates.io-index#http@0.2.12": "1w81s4bcbmcj9bjp7mllm8jlz6b31wzvirz8bgpzbqkpwmbvn730",
"registry+https://github.com/rust-lang/crates.io-index#http@1.2.0": "1skglzdf98j5nzxlii540n11is0w4l80mi5sm3xrj716asps4v7i",
"registry+https://github.com/rust-lang/crates.io-index#httparse@1.9.5": "0ip9v8m9lvgvq1lznl31wvn0ch1v254na7lhid9p29yx9rbx6wbx",
"registry+https://github.com/rust-lang/crates.io-index#http@0.2.11": "1fwz3mhh86h5kfnr5767jlx9agpdggclq7xsqx930fflzakb2iw9",
"registry+https://github.com/rust-lang/crates.io-index#http@1.0.0": "1sllw565jn8r5w7h928nsfqq33x586pyasdfr7vid01scwwgsamk",
"registry+https://github.com/rust-lang/crates.io-index#httparse@1.8.0": "010rrfahm1jss3p022fqf3j3jmm72vhn4iqhykahb9ynpaag75yq",
"registry+https://github.com/rust-lang/crates.io-index#httpdate@1.0.3": "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz",
"registry+https://github.com/rust-lang/crates.io-index#humantime@2.1.0": "1r55pfkkf5v0ji1x6izrjwdq9v6sc7bv99xj6srywcar37xmnfls",
"registry+https://github.com/rust-lang/crates.io-index#hyper-tls@0.5.0": "01crgy13102iagakf6q4mb75dprzr7ps1gj0l5hxm1cvm7gks66n",
"registry+https://github.com/rust-lang/crates.io-index#hyper-util@0.1.10": "1d1iwrkysjhq63pg54zk3vfby1j7zmxzm9zzyfr4lwvp0szcybfz",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.10.16": "0wwjh9p3mzvg3fss2lqz5r7ddcgl1fh9w6my2j69d6k0lbcm41ha",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.32": "1rvcb0smz8q1i0y6p7rwxr02x5sclfg2hhxf3g0774zczn0cgps1",
"registry+https://github.com/rust-lang/crates.io-index#hyper@1.5.2": "1q7akfb443yrjzkmnnbp2vs8zi15hgbk466rr4y144v4ppabhvr5",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.28": "107gkvqx4h9bl17d602zkm2dgpfq86l2dr36yzfsi8l3xcsy35mz",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone-haiku@0.1.2": "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.61": "085jjsls330yj1fnwykfzmb2f10zp6l7w4fhq81ng81574ghhpi3",
"registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0": "09j5kskirl59mvqc8kabhy7005yyy7dp88jw9f6f3gkf419a8byv",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid@1.5.0": "0dznvd1c5b02iilqm044q4hvar0sqibq1z46prqwjzwif61vpb0k",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform@1.5.0": "0kmmi1kmj9yph6mdgkc7v3wz6995v7ly3n80vbg0zr78bp1iml81",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform_data@1.5.0": "0vkgjixm0wzp2n3v5mw4j89ly05bg3lx96jpdggbwlpqi0rzzj7x",
"registry+https://github.com/rust-lang/crates.io-index#icu_normalizer@1.5.0": "0kx8qryp8ma8fw1vijbgbnf7zz9f2j4d14rw36fmjs7cl86kxkhr",
"registry+https://github.com/rust-lang/crates.io-index#icu_normalizer_data@1.5.0": "05lmk0zf0q7nzjnj5kbmsigj3qgr0rwicnn5pqi9n7krmbvzpjpq",
"registry+https://github.com/rust-lang/crates.io-index#icu_properties@1.5.1": "1xgf584rx10xc1p7zjr78k0n4zn3g23rrg6v2ln31ingcq3h5mlk",
"registry+https://github.com/rust-lang/crates.io-index#icu_properties_data@1.5.0": "0scms7pd5a7yxx9hfl167f5qdf44as6r3bd8myhlngnxqgxyza37",
"registry+https://github.com/rust-lang/crates.io-index#icu_provider@1.5.0": "1nb8vvgw8dv2inqklvk05fs0qxzkw8xrg2n9vgid6y7gm3423m3f",
"registry+https://github.com/rust-lang/crates.io-index#icu_provider_macros@1.5.0": "1mjs0w7fcm2lcqmbakhninzrjwqs485lkps4hz0cv3k36y9rxj0y",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.58": "081vcr8z8ddhl5r1ywif6grnswk01b2ac4nks2bhn8zzdimvh9l3",
"registry+https://github.com/rust-lang/crates.io-index#idna@0.1.5": "0kl4gs5kaydn4v07c6ka33spm9qdh2np0x7iw7g5zd8z1c7rxw1q",
"registry+https://github.com/rust-lang/crates.io-index#idna@1.0.3": "0zlajvm2k3wy0ay8plr07w22hxkkmrxkffa6ah57ac6nci984vv8",
"registry+https://github.com/rust-lang/crates.io-index#idna_adapter@1.2.0": "0wggnkiivaj5lw0g0384ql2d7zk4ppkn3b1ry4n0ncjpr7qivjns",
"registry+https://github.com/rust-lang/crates.io-index#idna@0.5.0": "1xhjrcjqq0l5bpzvdgylvpkgk94panxgsirzhjnnqfdgc4a9nkb3",
"registry+https://github.com/rust-lang/crates.io-index#image@0.23.14": "18gn2f7xp30pf9aqka877knlq308khxqiwjvsccvzaa4f9zcpzr4",
"registry+https://github.com/rust-lang/crates.io-index#image@0.24.9": "17gnr6ifnpzvhjf6dwbl9hki8x6bji5mwcqp0048x1jm5yfi742n",
"registry+https://github.com/rust-lang/crates.io-index#include_dir@0.7.4": "1pfh3g45z88kwq93skng0n6g3r7zkhq9ldqs9y8rvr7i11s12gcj",
"registry+https://github.com/rust-lang/crates.io-index#include_dir_macros@0.7.4": "0x8smnf6knd86g69p19z5lpfsaqp8w0nx14kdpkz1m8bxnkqbavw",
"registry+https://github.com/rust-lang/crates.io-index#image@0.24.7": "04d7f25b8nlszfv9a474n4a0al4m2sv9gqj3yiphhqr0syyzsgbg",
"registry+https://github.com/rust-lang/crates.io-index#indent_write@2.2.0": "1hqjp80argdskrhd66g9sh542yxy8qi77j6rc69qd0l7l52rdzhc",
"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.7.0": "07s7jmdymvd0rm4yswp0j3napx57hkjm9gs9n55lvs2g78vj5y32",
"registry+https://github.com/rust-lang/crates.io-index#intl-memoizer@0.5.2": "1nkvql7c7b76axv4g68di1p2m9bnxq1cbn6mlqcawf72zhhf08py",
"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.1.0": "07rxrqmryr1xfnmhrjlz8ic6jw28v6h5cig3ws2c9d0wifhy2c6m",
"registry+https://github.com/rust-lang/crates.io-index#intl-memoizer@0.5.1": "0vx6cji8ifw77zrgipwmvy1i3v43dcm58hwjxpb1h29i98z46463",
"registry+https://github.com/rust-lang/crates.io-index#intl_pluralrules@7.0.2": "0wprd3h6h8nfj62d8xk71h178q7zfn3srxm787w4sawsqavsg3h7",
"registry+https://github.com/rust-lang/crates.io-index#ipnet@2.10.1": "025p9wm94q1w2l13hbbr4cbmfygly3a2ag8g5s618l2jhq4l3hnx",
"registry+https://github.com/rust-lang/crates.io-index#ipnet@2.9.0": "1hzrcysgwf0knf83ahb3535hrkw63mil88iqc6kjaryfblrqylcg",
"registry+https://github.com/rust-lang/crates.io-index#iron@0.6.1": "1s4mf8395f693nhwsr0znw3j5frzn56gzllypyl50il85p50ily6",
"registry+https://github.com/rust-lang/crates.io-index#is-terminal@0.4.13": "0jwgjjz33kkmnwai3nsdk1pz9vb6gkqvw1d1vq7bs3q48kinh7r6",
"registry+https://github.com/rust-lang/crates.io-index#is_terminal_polyfill@1.70.1": "1kwfgglh91z33kl0w5i338mfpa3zs0hidq5j4ny4rmjwrikchhvr",
"registry+https://github.com/rust-lang/crates.io-index#itertools@0.12.1": "0s95jbb3ndj1lvfxyq5wanc0fm0r6hg6q4ngb92qlfdxvci10ads",
"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.14": "0x26kr9m062mafaxgcf2p6h2x7cmixm0zw95aipzn2hr3d5jlnnp",
"registry+https://github.com/rust-lang/crates.io-index#is-terminal@0.4.9": "12xgvc7nsrp3pn8hcxajfhbli2l5wnh3679y2fmky88nhj4qj26b",
"registry+https://github.com/rust-lang/crates.io-index#itertools@0.10.5": "0ww45h7nxx5kj6z2y6chlskxd1igvs4j507anr6dzg99x1h25zdh",
"registry+https://github.com/rust-lang/crates.io-index#itertools@0.12.0": "1c07gzdlc6a1c8p8jrvvw3gs52bss3y58cs2s21d9i978l36pnr5",
"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.10": "0k7xjfki7mnv6yzjrbnbnjllg86acmbnk4izz2jmm1hx2wd6v95i",
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.1.22": "1wnh0bmmswpgwhgmlizz545x8334nlbmkq8imy9k224ri3am7792",
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.3.1": "1c1k53svpdyfhibkmm0ir5w0v3qmcmca8xr8vnnmizwf6pdagm7m",
"registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.77": "13x2qcky5l22z4xgivi59xhjjx4kxir1zg7gcj0f1ijzd4yg7yhw",
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.3.0": "0gkv0zx95i4fr40fj1a10d70lqi6lfyia8r5q8qjxj8j4pj0005w",
"registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.66": "1ji9la5ydg0vy17q54i7dnwc0wwb9zkx662w1583pblylm6wdsff",
"registry+https://github.com/rust-lang/crates.io-index#kv-log-macro@1.0.7": "0zwp4bxkkp87rl7xy2dain77z977rvcry1gmr5bssdbn541v7s0d",
"registry+https://github.com/rust-lang/crates.io-index#language-tags@0.2.2": "16hrjdpa827carq5x4b8zhas24d8kg4s16m6nmmn1kb7cr5qh7d9",
"registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.5.0": "1zk6dqqni0193xg6iijh7i3i44sryglwgvx20spdvwk3r6sbrlmv",
"registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.4.0": "0in6ikhw8mgl33wjv6q6xfrb5b9jr16q8ygjy803fay4zcisvaz2",
"registry+https://github.com/rust-lang/crates.io-index#lazycell@1.3.0": "0m8gw7dn30i0zjjpjdyf6pc16c34nl71lpv461mix50x3p70h3c3",
"registry+https://github.com/rust-lang/crates.io-index#lebe@0.5.2": "1j2l6chx19qpa5gqcw434j83gyskq3g2cnffrbl3842ymlmpq203",
"registry+https://github.com/rust-lang/crates.io-index#libadwaita-sys@0.5.3": "16n6xsy6jhbj0jbpz8yvql6c9b89a99v9vhdz5s37mg1inisl42y",
"registry+https://github.com/rust-lang/crates.io-index#libadwaita@0.5.3": "174pzn9dwsk8ikvrhx13vkh0zrpvb3rhg9yd2q5d2zjh0q6fgrrg",
"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.169": "02m253hs8gw0m1n8iyrsc4n15yzbqwhddi7w1l0ds7i92kdsiaxm",
"registry+https://github.com/rust-lang/crates.io-index#libloading@0.8.6": "0d2ccr88f8kv3x7va2ccjxalcjnhrci4j2kwxp7lfmbkpjs4wbzw",
"registry+https://github.com/rust-lang/crates.io-index#libm@0.2.11": "1yjgk18rk71rjbqcw9l1zaqna89p9s603k7n327nqs8dn88vwmc3",
"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.151": "1x28f0zgp4zcwr891p8n9ag9w371sbib30vp4y6hi2052frplb9h",
"registry+https://github.com/rust-lang/crates.io-index#libloading@0.8.5": "194dvczq4sifwkzllfmw0qkgvilpha7m5xy90gd6i446vcpz4ya9",
"registry+https://github.com/rust-lang/crates.io-index#libm@0.2.8": "0n4hk1rs8pzw8hdfmwn96c4568s93kfxqgcqswr7sajd2diaihjf",
"registry+https://github.com/rust-lang/crates.io-index#libspa-sys@0.8.0": "07yh4i5grzbxkchg6dnxlwbdw2wm5jnd7ffbhl77jr0388b9f3dz",
"registry+https://github.com/rust-lang/crates.io-index#libspa@0.8.0": "044qs48yl0llp2dmrgwxj9y1pgfy09i6fhq661zqqb9a3fwa9wv5",
"registry+https://github.com/rust-lang/crates.io-index#libsqlite3-sys@0.30.1": "0jcikvgbj84xc7ikdmpc8m4y5lyqgrb9aqblphwk67kv95xgp69f",
"registry+https://github.com/rust-lang/crates.io-index#libyml@0.0.5": "106963pwg1gc3165bdlk8bbspmk919gk10vshhqglks3z8m700ik",
"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15": "1aq7r2g7786hyxhv40spzf2nhag5xbw2axxc1k8z5k1dsgdm4v6j",
"registry+https://github.com/rust-lang/crates.io-index#litemap@0.7.4": "012ili3vppd4952sh6y3qwcd0jkd0bq2qpr9h7cppc8sj11k7saf",
"registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.12": "05qvxa6g27yyva25a5ghsg85apdxkvr77yhkyhapj6r8vnf8pbq7",
"registry+https://github.com/rust-lang/crates.io-index#libsqlite3-sys@0.27.0": "05pp60ncrmyjlxxjj187808jkvpxm06w5lvvdwwvxd2qrmnj4kng",
"registry+https://github.com/rust-lang/crates.io-index#linked_list_allocator@0.10.5": "11k2dv6v5kq45kbvahll434f9iwfw0vsyaycp76q3vh5ahzldyls",
"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.12": "0mhlla3gk1jgn6mrq9s255rvvq8a1w3yk2vpjiwsd6hmmy1imkf4",
"registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.11": "0iggx0h4jx63xm35861106af3jkxq06fpqhpkhgw0axi2n38y5iw",
"registry+https://github.com/rust-lang/crates.io-index#log@0.3.9": "0jq23hhn5h35k7pa8r7wqnsywji6x3wn1q5q7lif5q536if8v7p1",
"registry+https://github.com/rust-lang/crates.io-index#log@0.4.25": "17ydv5zhfv1zzygy458bmg3f3jx1vfziv9d74817w76yhfqgbjq4",
"registry+https://github.com/rust-lang/crates.io-index#log@0.4.20": "13rf7wphnwd61vazpxr7fiycin6cb1g8fmvgqg18i464p0y1drmm",
"registry+https://github.com/rust-lang/crates.io-index#logger@0.4.0": "14xlxvkspcfnspjil0xi63qj5cybxn1hjmr5gq8m4v1g9k5p54bc",
"registry+https://github.com/rust-lang/crates.io-index#matches@0.1.10": "1994402fq4viys7pjhzisj4wcw894l53g798kkm2y74laxk0jci5",
"registry+https://github.com/rust-lang/crates.io-index#matchit@0.7.3": "156bgdmmlv4crib31qhgg49nsjk88dxkdqp80ha2pk2rk6n6ax0f",
"registry+https://github.com/rust-lang/crates.io-index#md-5@0.10.6": "1kvq5rnpm4fzwmyv5nmnxygdhhb2369888a06gdc9pxyrzh7x7nq",
"registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4": "18z32bhxrax0fnjikv475z7ii718hq457qwmaryixfxsl2qrmjkq",
"registry+https://github.com/rust-lang/crates.io-index#memoffset@0.9.1": "12i17wh9a9plx869g7j4whf62xw68k5zd4k0k5nh6ys5mszid028",
"registry+https://github.com/rust-lang/crates.io-index#memchr@2.6.4": "0rq1ka8790ns41j147npvxcqcl2anxyngsdimy85ag2api0fwrgn",
"registry+https://github.com/rust-lang/crates.io-index#memoffset@0.9.0": "0v20ihhdzkfw1jx00a7zjpk2dcp5qjq6lz302nyqamd9c4f4nqss",
"registry+https://github.com/rust-lang/crates.io-index#mime@0.2.6": "1q1s1ax1gaz8ld3513nvhidfwnik5asbs1ma3hp6inp5dn56nqms",
"registry+https://github.com/rust-lang/crates.io-index#mime@0.3.17": "16hkibgvb9klh0w0jk5crr5xv90l3wlf77ggymzjmvl1818vnxv8",
"registry+https://github.com/rust-lang/crates.io-index#mime_guess@1.8.8": "18qcd5aa3363mb742y7lf39j7ha88pkzbv9ff2qidlsdxsjjjs91",
"registry+https://github.com/rust-lang/crates.io-index#mime_guess@2.0.5": "03jmg3yx6j39mg0kayf7w4a886dl3j15y8zs119zw01ccy74zi7p",
"registry+https://github.com/rust-lang/crates.io-index#mime_guess@2.0.4": "1vs28rxnbfwil6f48hh58lfcx90klcvg68gxdc60spwa4cy2d4j1",
"registry+https://github.com/rust-lang/crates.io-index#minimal-lexical@0.2.1": "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.3.7": "0dblrhgbm0wa8jjl8cjp81akaj36yna92df4z1h9b26n3spal7br",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.4.4": "0jsfv00hl5rmx1nijn59sr9jmjd4rjnjhh4kdjy8d187iklih9d9",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.3": "093r1kd1r9dyf05cbvsibgmh96pxp3qhzfvpd6f15bpggamjqh5q",
"registry+https://github.com/rust-lang/crates.io-index#mio@1.0.3": "1gah0h4ia3avxbwym0b6bi6lr6rpysmj9zvw6zis5yq0z0xq91i8",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.7.1": "1ivl3rbbdm53bzscrd01g60l46lz5krl270487d8lhjvwl5hx0g7",
"registry+https://github.com/rust-lang/crates.io-index#mio@1.0.2": "1v1cnnn44awxbcfm4zlavwgkvbyg7gp5zzjm8mqf1apkrwflvq40",
"registry+https://github.com/rust-lang/crates.io-index#modifier@0.1.0": "0n3fmgli1nsskl0whrfzm1gk0rmwwl6pw1q4nb9sqqmn5h8wkxa1",
"registry+https://github.com/rust-lang/crates.io-index#multer@2.1.0": "1hjiphaypj3phqaj5igrzcia9xfmf4rr4ddigbh8zzb96k1bvb01",
"registry+https://github.com/rust-lang/crates.io-index#nary_tree@0.4.3": "1iqray1a716995l9mmvz5sfqrwg9a235bvrkpcn8bcqwjnwfv1pv",
"registry+https://github.com/rust-lang/crates.io-index#native-tls@0.2.12": "0rkl65z70n7sy4d5w0qa99klg1hr43wx6kcprk4d2n9xr2r4wqd8",
"registry+https://github.com/rust-lang/crates.io-index#native-tls@0.2.11": "0bmrlg0fmzxaycjpkgkchi93av07v2yf9k33gc12ca9gqdrn28h7",
"registry+https://github.com/rust-lang/crates.io-index#nb@0.1.3": "0vyh31pbwrg21f8hz1ipb9i20qwnfwx47gz92i9frdhk0pd327c0",
"registry+https://github.com/rust-lang/crates.io-index#nb@1.1.0": "179kbn9l6vhshncycagis7f8mfjppz4fhvgnmcikqz30mp23jm4d",
"registry+https://github.com/rust-lang/crates.io-index#nix@0.27.1": "0ly0kkmij5f0sqz35lx9czlbk6zpihb7yh1bsy4irzwfd2f4xc1f",
"registry+https://github.com/rust-lang/crates.io-index#no-std-compat@0.4.1": "132vrf710zsdp40yp1z3kgc2ss8pi0z4gmihsz3y7hl4dpd56f5r",
"registry+https://github.com/rust-lang/crates.io-index#nom@7.1.3": "0jha9901wxam390jcf5pfa0qqfrgh8li787jx2ip0yk5b8y9hwyj",
"registry+https://github.com/rust-lang/crates.io-index#num-bigint-dig@0.8.4": "0lb12df24wgxxbspz4gw1sf1kdqwvpdcpwq4fdlwg4gj41c1k16w",
"registry+https://github.com/rust-lang/crates.io-index#num-conv@0.1.0": "1ndiyg82q73783jq18isi71a7mjh56wxrk52rlvyx0mi5z9ibmai",
"registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.46": "13w5g54a9184cqlbsq80rnxw4jj4s0d8wv75jsq5r2lms8gncsbr",
"registry+https://github.com/rust-lang/crates.io-index#num-iter@0.1.45": "1gzm7vc5g9qsjjl3bqk9rz1h6raxhygbrcpbfl04swlh0i506a8l",
"registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.45": "1ncwavvwdmsqzxnn65phv6c6nn72pnv9xhpmjd6a429mzf4k6p92",
"registry+https://github.com/rust-lang/crates.io-index#num-iter@0.1.43": "0lp22isvzmmnidbq9n5kbdh8gj0zm3yhxv1ddsn5rp65530fc0vx",
"registry+https://github.com/rust-lang/crates.io-index#num-rational@0.3.2": "01sgiwny9iflyxh2xz02sak71v2isc3x608hfdpwwzxi3j5l5b0j",
"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19": "0h984rhdkkqd4ny9cif7y2azl3xdfb7768hb9irhpsch4q3gq787",
"registry+https://github.com/rust-lang/crates.io-index#num-rational@0.4.1": "1c0rb8x4avxy3jvvzv764yk7afipzxncfnqlb10r3h53s34s2f06",
"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.17": "0z16bi5zwgfysz6765v3rd6whfbjpihx3mhsn4dg8dzj2c221qrr",
"registry+https://github.com/rust-lang/crates.io-index#num_cpus@1.16.0": "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1",
"registry+https://github.com/rust-lang/crates.io-index#object@0.36.7": "11vv97djn9nc5n6w1gc6bd96d2qk2c8cg1kw5km9bsi3v4a8x532",
"registry+https://github.com/rust-lang/crates.io-index#once_cell@1.20.2": "0xb7rw1aqr7pa4z3b00y7786gyf8awx2gca3md73afy76dzgwq8j",
"registry+https://github.com/rust-lang/crates.io-index#num_enum@0.5.11": "1japmqhcxwn1d3k7q8jw58y7xfby51s16nzd6dkj483cj2pnqr0z",
"registry+https://github.com/rust-lang/crates.io-index#num_enum_derive@0.5.11": "16f7r4jila0ckcgdnfgqyhhb90w9m2pdbwayyqmwcci0j6ygkgyw",
"registry+https://github.com/rust-lang/crates.io-index#object@0.32.1": "1c02x4kvqpnl3wn7gz9idm4jrbirbycyqjgiw6lm1g9k77fzkxcw",
"registry+https://github.com/rust-lang/crates.io-index#once_cell@1.19.0": "14kvw7px5z96dk4dwdm1r9cqhhy2cyj1l5n5b29mynbb8yr15nrz",
"registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1": "173xxvfc63rr5ybwqwylsir0vq6xsj4kxiv4hmg4c3vscdmncj59",
"registry+https://github.com/rust-lang/crates.io-index#openssl-probe@0.1.5": "1kq18qm48rvkwgcggfkqq6pm948190czqc94d6bm2sir5hq1l0gz",
"registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.104": "0hf712xcxmycnlc09r8d446b3mwqchsbfrjv374fp7grrc3g7as5",
"registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.68": "1xbiz2bmba2fibg70s462yk2fndp3f9vz11c7iw0ilh2y54bqx31",
"registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.97": "02s670ir38fsavphdna07144y41dkvrcfkwnjzg82zfrrlsavsn3",
"registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.61": "0idv3n9n9f2sxq8cqzxvq44633vg5sx4n9q1p3g6dn66ikf1k13b",
"registry+https://github.com/rust-lang/crates.io-index#pango-sys@0.18.0": "1iaxalcaaj59cl9n10svh4g50v8jrc1a36kd7n9yahx8j7ikfrs3",
"registry+https://github.com/rust-lang/crates.io-index#pango@0.18.3": "1r5ygq7036sv7w32kp8yxr6vgggd54iaavh3yckanmq4xg0px8kw",
"registry+https://github.com/rust-lang/crates.io-index#parking@2.2.1": "1fnfgmzkfpjd69v4j9x737b1k8pnn054bvzcn5dm3pkgq595d3gk",
"registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.3": "09ws9g6245iiq8z975h8ycf818a66q3c6zv4b5h8skpm7hc1igzi",
"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.10": "1y3cf9ld9ijf7i4igwzffcn0xl16dxyn4c5bwgjck1dkgabiyh0y",
"registry+https://github.com/rust-lang/crates.io-index#parse-zoneinfo@0.3.1": "093cs8slbd6kyfi6h12isz0mnaayf5ha8szri1xrbqj4inqhaahz",
"registry+https://github.com/rust-lang/crates.io-index#panic-halt@0.2.0": "04nqaa97ph20ppyy58grwr23hrbw83pn0gf7apf73rdx1q7595ny",
"registry+https://github.com/rust-lang/crates.io-index#parking@2.2.0": "1blwbkq6im1hfxp5wlbr475mw98rsyc0bbr2d5n16m38z253p0dv",
"registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.1": "13r2xk7mnxfc5g0g6dkdxqdqad99j7s7z8zhzz4npw5r0g0v4hip",
"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.9": "13h0imw1aq86wj28gxkblhkzx6z1gk8q18n0v76qmmj6cliajhjc",
"registry+https://github.com/rust-lang/crates.io-index#parse-zoneinfo@0.3.0": "0h8g6jy4kckn2gk8sd5adaws180n1ip65xhzw5jxlq4w8ibg41f7",
"registry+https://github.com/rust-lang/crates.io-index#paste@1.0.14": "0k7d54zz8zrz0623l3xhvws61z5q2wd3hkwim6gylk8212placfy",
"registry+https://github.com/rust-lang/crates.io-index#pem-rfc7468@0.7.0": "04l4852scl4zdva31c1z6jafbak0ni5pi0j38ml108zwzjdrrcw8",
"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@1.0.1": "0cgq08v1fvr6bs5fvy390cz830lq4fak8havdasdacxcw790s09i",
"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.1": "0gi8wgx0dcy8rnv1kywdv98lwcx67hz0a0zwpib5v2i08r88y573",
"registry+https://github.com/rust-lang/crates.io-index#phf@0.11.3": "0y6hxp1d48rx2434wgi5g8j1pr8s5jja29ha2b65435fh057imhz",
"registry+https://github.com/rust-lang/crates.io-index#phf@0.11.2": "1p03rsw66l7naqhpgr1a34r9yzi1gv9jh16g3fsk6wrwyfwdiqmd",
"registry+https://github.com/rust-lang/crates.io-index#phf@0.7.24": "066xwv4dr6056a9adlkarwp4n94kbpwngbmd47ngm3cfbyw49nmk",
"registry+https://github.com/rust-lang/crates.io-index#phf_codegen@0.11.3": "0si1n6zr93kzjs3wah04ikw8z6npsr39jw4dam8yi9czg2609y5f",
"registry+https://github.com/rust-lang/crates.io-index#phf_codegen@0.11.2": "0nia6h4qfwaypvfch3pnq1nd2qj64dif4a6kai3b7rjrsf49dlz8",
"registry+https://github.com/rust-lang/crates.io-index#phf_codegen@0.7.24": "0zjiblicfm0nrmr2xxrs6pnf6zz2394wgch6dcbd8jijkq98agmh",
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.11.3": "0gc4np7s91ynrgw73s2i7iakhb4lzdv1gcyx7yhlc0n214a2701w",
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.11.2": "1c14pjyxbcpwkdgw109f7581cc5fa3fnkzdq1ikvx7mdq9jcrr28",
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.7.24": "0qi62gxk3x3whrmw5c4i71406icqk11qmpgln438p6qm7k4lqdh9",
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.11.3": "1rallyvh28jqd9i916gk5gk2igdmzlgvv5q0l3xbf3m6y8pbrsk7",
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.11.2": "0azphb0a330ypqx3qvyffal5saqnks0xvl8rj73jlk3qxxgbkz4h",
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.7.24": "18371fla0vsj7d6d5rlfb747xbr2in11ar9vgv5qna72bnhp2kr3",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-internal@1.1.8": "1yzfhf6l27nhzv7r5hfrwj2g0x7xmfhgil19fj9am4srqp06csnm",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16": "16wzc7z7dfkf9bmjin22f5282783f6mdksnr0nv0j5ym5f9gyg1v",
"registry+https://github.com/rust-lang/crates.io-index#pin-project@1.1.8": "05jr3xfy1spgmz3q19l4mmvv46vgvkvsgphamifx7x45swxcabhy",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-internal@1.1.3": "01a4l3vb84brv9v7wl71chzxra2kynm6yvcjca66xv3ij6fgsna3",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.13": "0n0bwr5qxlf0mhn2xkl36sy55118s9qmvx2yl5f3ixkb007lbywa",
"registry+https://github.com/rust-lang/crates.io-index#pin-project@1.1.3": "08k4cpy8q3j93qqgnrbzkcgpn7g0a88l4a9nm33kyghpdhffv97x",
"registry+https://github.com/rust-lang/crates.io-index#pin-utils@0.1.0": "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb",
"registry+https://github.com/rust-lang/crates.io-index#piper@0.2.4": "0rn0mjjm0cwagdkay77wgmz3sqf8fqmv9d9czm79mvr2yj8c9j4n",
"registry+https://github.com/rust-lang/crates.io-index#pio@0.2.1": "1qvq03nbx6vjix7spr5fcxcbxw39flm1y72kxl1g728gnna9dq3n",
"registry+https://github.com/rust-lang/crates.io-index#piper@0.2.1": "1m45fkdq7q5l9mv3b0ra10qwm0kb67rjp2q8y91958gbqjqk33b6",
"registry+https://github.com/rust-lang/crates.io-index#pipewire-sys@0.8.0": "04hiy3rl8v3j2dfzp04gr7r8l5azzqqsvqdzwa7sipdij27ii7l4",
"registry+https://github.com/rust-lang/crates.io-index#pipewire@0.8.0": "1nldg1hz4v0qr26lzdxqpvrac4zbc3pb6436sl392425bjx4brh8",
"registry+https://github.com/rust-lang/crates.io-index#pkcs1@0.7.5": "0zz4mil3nchnxljdfs2k5ab1cjqn7kq5lqp62n9qfix01zqvkzy8",
"registry+https://github.com/rust-lang/crates.io-index#pkcs8@0.10.2": "1dx7w21gvn07azszgqd3ryjhyphsrjrmq5mmz1fbxkj5g0vv4l7r",
"registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.31": "1wk6yp2phl91795ia0lwkr3wl4a9xkrympvhqq8cxk4d75hwhglm",
"registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.27": "0r39ryh1magcq4cz5g9x88jllsnxnhcqr753islvyk4jp9h2h1r6",
"registry+https://github.com/rust-lang/crates.io-index#plugin@0.2.6": "1q7nghkpvxxr168y2jnzh3w7qc9vfrby9n7ygy3xpj0bj71hsshs",
"registry+https://github.com/rust-lang/crates.io-index#png@0.16.8": "1ipl44q3vy4kvx6j296vk7d4v8gvcg203lrkvvixwixq1j98fciw",
"registry+https://github.com/rust-lang/crates.io-index#png@0.17.16": "09kmkms9fmkbkarw0lnf0scqvjwwg3r7riddag0i3q39r0pil5c2",
"registry+https://github.com/rust-lang/crates.io-index#polling@3.7.4": "0bs4nhwfwsvlzlhah2gbhj3aa9ynvchv2g350wapswh26a65c156",
"registry+https://github.com/rust-lang/crates.io-index#png@0.17.10": "0r5a8a25ad0jq2pkp2zbab3wwhpgp6jmdg6d0ybjnw6kilnvyxfx",
"registry+https://github.com/rust-lang/crates.io-index#polling@3.4.0": "052am20b5r03nwhpnjw86rv3dwsdabvb07anv3fqxfbs65r4w19h",
"registry+https://github.com/rust-lang/crates.io-index#powerfmt@0.2.0": "14ckj2xdpkhv3h6l5sdmb9f1d57z8hbfpdldjc2vl5givq2y77j3",
"registry+https://github.com/rust-lang/crates.io-index#ppv-lite86@0.2.20": "017ax9ssdnpww7nrl1hvqh2lzncpv04nnsibmnw9nxjnaqlpp5bp",
"registry+https://github.com/rust-lang/crates.io-index#pretty_assertions@1.4.1": "0v8iq35ca4rw3rza5is3wjxwsf88303ivys07anc5yviybi31q9s",
"registry+https://github.com/rust-lang/crates.io-index#ppv-lite86@0.2.17": "1pp6g52aw970adv3x2310n7glqnji96z0a9wiamzw89ibf0ayh2v",
"registry+https://github.com/rust-lang/crates.io-index#pretty_env_logger@0.5.0": "076w9dnvcpx6d3mdbkqad8nwnsynb7c8haxmscyrz7g3vga28mw6",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@1.3.1": "069r1k56bvgk0f58dm5swlssfcp79im230affwk6d9ck20g04k3z",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.2": "092x5acqnic14cw6vacqap5kgknq3jn4c6jij9zi6j85839jc3xh",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.1": "06jbv5w6s04dbjbwq0iv7zil12ildf3w8dvvb4pqvhig4gm5zp4p",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error-attr@1.0.4": "0sgq6m5jfmasmwwy8x4mjygx5l7kp8s4j60bv25ckv2j1qc41gm1",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error@1.0.4": "1373bhxaf0pagd8zkyd03kkx6bchzf6g0dkwrwzsnal9z47lj9fs",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.93": "169dw9wch753if1mgyi2nfl1il77gslvh6y2q46qplprwml6m530",
"registry+https://github.com/rust-lang/crates.io-index#proptest@1.6.0": "0l4y4bb8hffv7cys7d59qwqdmvmqjfzz0x9vblc08209clqfkjhl",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.78": "1bjak27pqdn4f4ih1c9nr3manzyavsgqmf76ygw9k76q8pb2lhp2",
"registry+https://github.com/rust-lang/crates.io-index#proptest@1.4.0": "1gzmw40pgmwzb7x6jsyr88z5w151snv5rp1g0dlcp1iw3h9pdd1i",
"registry+https://github.com/rust-lang/crates.io-index#qoi@0.4.1": "00c0wkb112annn2wl72ixyd78mf56p4lxkhlmsggx65l3v3n8vbz",
"registry+https://github.com/rust-lang/crates.io-index#quick-error@1.2.3": "1q6za3v78hsspisc197bg3g7rpc989qycy8ypr8ap8igv10ikl51",
"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.38": "1k0s75w61k6ch0rs263r4j69b7vj1wadqgb9dia4ylc9mymcqk8f",
"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.35": "1vv8r2ncaz4pqdr78x7f138ka595sp2ncr1sa2plm4zxbsmwj7i9",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.3.23": "0v679h38pjjqj5h4md7v2slsvj6686qgcn7p9fbw3h43iwnk1b34",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.4.6": "14qjfv3gggzhnma20k0sc1jf8y6pplsaq7n1j9ls5c8kf2wl0a2m",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.6.5": "1jl4449jcl4wgmzld6ffwqj5gwxrp8zvx8w573g1z368qg6xlwbd",
@ -344,203 +333,187 @@
"registry+https://github.com/rust-lang/crates.io-index#rand_pcg@0.1.2": "0i0bdla18a8x4jn1w0fxsbs3jg7ajllz6azmch1zw33r06dv1ydb",
"registry+https://github.com/rust-lang/crates.io-index#rand_xorshift@0.1.1": "0p2x8nr00hricpi2m6ca5vysiha7ybnghz79yqhhx6sl4gkfkxyb",
"registry+https://github.com/rust-lang/crates.io-index#rand_xorshift@0.3.0": "13vcag7gmqspzyabfl1gr9ykvxd2142q2agrj8dkyjmfqmgg4nyj",
"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.12.1": "1qpwim68ai5h0j7axa8ai8z0payaawv3id0lrgkqmapx7lx8fr8l",
"registry+https://github.com/rust-lang/crates.io-index#rayon@1.10.0": "1ylgnzwgllajalr4v00y4kj22klq2jbwllm70aha232iah0sc65l",
"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.12.0": "1vaq0q71yfvcwlmia0iqf6ixj2fibjcf2xjy92n1m1izv1mgpqsw",
"registry+https://github.com/rust-lang/crates.io-index#rayon@1.8.0": "1cfdnvchf7j4cpha5jkcrrsr61li9i9lp5ak7xdq6d3pvc1xn9ww",
"registry+https://github.com/rust-lang/crates.io-index#rdrand@0.4.0": "1cjq0kwx1bk7jx3kzyciiish5gqsj7620dm43dc52sr8fzmm9037",
"registry+https://github.com/rust-lang/crates.io-index#redox_syscall@0.5.8": "0d48ylyd6gsamynyp257p6n2zl4dw2fhnn5z9y3nhgpri6rn5a03",
"registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.9": "02092l8zfh3vkmk47yjc8d631zhhcd49ck2zr133prvd3z38v7l0",
"registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.5": "0p41p3hj9ww7blnbwbj9h7rwxzxg0c1hvrdycgys8rxyhqqw859b",
"registry+https://github.com/rust-lang/crates.io-index#regex@1.11.1": "148i41mzbx8bmq32hsj1q4karkzzx5m60qza6gdw4pdc9qdyyi5m",
"registry+https://github.com/rust-lang/crates.io-index#redox_syscall@0.4.1": "1aiifyz5dnybfvkk4cdab9p2kmphag1yad6iknc7aszlxxldf8j7",
"registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.3": "0gs8q9yhd3kcg4pr00ag4viqxnh5l7jpyb9fsfr8hzh451w4r02z",
"registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.2": "17rd2s8xbiyf6lb4aj2nfi44zqlj98g2ays8zzj2vfs743k79360",
"registry+https://github.com/rust-lang/crates.io-index#regex@1.10.2": "0hxkd814n4irind8im5c9am221ri6bprx49nc7yxv02ykhd9a2rq",
"registry+https://github.com/rust-lang/crates.io-index#remove_dir_all@0.5.3": "1rzqbsgkmr053bxxl04vmvsd1njyz0nxvly97aip6aa2cmb15k9s",
"registry+https://github.com/rust-lang/crates.io-index#reqwest@0.11.27": "0qjary4hpplpgdi62d2m0xvbn6lnzckwffm0rgkm2x51023m6ryx",
"registry+https://github.com/rust-lang/crates.io-index#reserve-port@2.0.1": "10x21rdb1hjzp6n5flbbw3hfd7brmirckz1q0zsf3a7s5d516f4q",
"registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.7": "06amqm85raq26v6zg00fbf93lbj3kx559n2lpxc3wrvbbiy5vis7",
"registry+https://github.com/rust-lang/crates.io-index#rusqlite@0.32.1": "0vlx040bppl414pbjgbp7qr4jdxwszi9krx0m63zzf2f2whvflvp",
"registry+https://github.com/rust-lang/crates.io-index#rusqlite_migration@1.3.1": "076dm65g0sngzrb93r07va4l5zl3gjx9gq5mlsh21p7p0bl44fwj",
"registry+https://github.com/rust-lang/crates.io-index#rust-multipart-rfc7578_2@0.6.1": "0mwd3i2mk91n6diaxnkw28vyjbifhrm5ls73pcpfzz8a1i0lidq3",
"registry+https://github.com/rust-lang/crates.io-index#rustc-demangle@0.1.24": "07zysaafgrkzy2rjgwqdj2a8qdpsm6zv6f5pgpk9x0lm40z9b6vi",
"registry+https://github.com/rust-lang/crates.io-index#reqwest@0.11.23": "0hgvzb7r46656r9vqhl5qk1kbr2xzjb91yr2cb321160ka6sxc9p",
"registry+https://github.com/rust-lang/crates.io-index#rp-pico@0.8.0": "0mmx8dyl0q1a9fgz12hrvwd7civqbd1j7g1w5c5i6pcfdwg7fhb3",
"registry+https://github.com/rust-lang/crates.io-index#rp2040-boot2@0.3.0": "08dv9ndvdzyjz4wdlxcikf1m1s6wwi80027ldkihx59zyr2g74kw",
"registry+https://github.com/rust-lang/crates.io-index#rp2040-hal-macros@0.1.0": "0piaczzlbrfdhidnqkg04xs1rzal3w3zjplrh6pf3vwpwiir0iw6",
"registry+https://github.com/rust-lang/crates.io-index#rp2040-hal@0.9.2": "1jk725cf6nx6rhn06swbx47yaq3j134m0hpnv47p5mkdgspbkwhz",
"registry+https://github.com/rust-lang/crates.io-index#rp2040-pac@0.5.0": "0k3fm4fww6gcy7w7zwbmmqn9wzz4sih13l1m93sl7x8mb0vxin8j",
"registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.6": "1z0d1aavfm0v4pv8jqmqhhvvhvblla1ydzlvwykpc3mkzhj523jx",
"registry+https://github.com/rust-lang/crates.io-index#rustc-demangle@0.1.23": "0xnbk2bmyzshacjm2g1kd4zzv2y2az14bw3sjccq5qkpmsfvn9nn",
"registry+https://github.com/rust-lang/crates.io-index#rustc-hash@1.1.0": "1qkc5khrmv5pqi5l5ca9p5nl5hs742cagrndhbrlk3dhlrx3zm08",
"registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.4.1": "14lvdsmr5si5qbqzrajgb6vfn69k0sfygrvfvr2mps26xwi3mjyg",
"registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.43": "1xjfhdnmqsbwnfmm77vyh7ldhqx0g9waqm4982404d7jdgp93257",
"registry+https://github.com/rust-lang/crates.io-index#rustls-pemfile@1.0.4": "1324n5bcns0rnw6vywr5agff3rwfvzphi7rmbyzwnv6glkhclx0w",
"registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.19": "1m39qd65jcd1xgqzdm3017ppimiggh2446xngwp1ngr8hjbmpi7p",
"registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.2.3": "02h3x57lcr8l2pm0a645s9whdh33pn5cnrwvn5cb57vcrc53x3hk",
"registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.4.0": "0rpk9rcdk405xhbmgclsh4pai0svn49x35aggl4nhbkd4a2zb85z",
"registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.28": "05m3vacvbqbg6r6ksmx9k5afpi0lppjdv712crrpsrfax2jp5rbj",
"registry+https://github.com/rust-lang/crates.io-index#rusty-fork@0.3.0": "0kxwq5c480gg6q0j3bg4zzyfh2kwmc3v2ba94jw8ncjc8mpcqgfb",
"registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.18": "17xx2s8j1lln7iackzd9p0sv546vjq71i779gphjq923vjh5pjzk",
"registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.16": "0k7b90xr48ag5bzmfjp82rljasw2fx28xr3bg1lrpx7b5sljm3gr",
"registry+https://github.com/rust-lang/crates.io-index#safemem@0.3.3": "0wp0d2b2284lw11xhybhaszsczpbq1jbdklkxgifldcknmy3nw7g",
"registry+https://github.com/rust-lang/crates.io-index#schannel@0.1.27": "0gbbhy28v72kd5iina0z2vcdl3vz63mk5idvkzn5r52z6jmfna8z",
"registry+https://github.com/rust-lang/crates.io-index#schannel@0.1.22": "126zy5jb95fc5hvzyjwiq6lc81r08rdcn6affn00ispp9jzk6dqc",
"registry+https://github.com/rust-lang/crates.io-index#scoped-tls@1.0.1": "15524h04mafihcvfpgxd8f4bgc3k95aclz8grjkg9a0rxcvn9kz1",
"registry+https://github.com/rust-lang/crates.io-index#scoped_threadpool@0.1.9": "1a26d3lk40s9mrf4imhbik7caahmw2jryhhb6vqv6fplbbgzal8x",
"registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0": "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l",
"registry+https://github.com/rust-lang/crates.io-index#security-framework-sys@2.14.0": "0chwn01qrnvs59i5220bymd38iddy4krbnmfnhf4k451aqfj7ns9",
"registry+https://github.com/rust-lang/crates.io-index#security-framework@2.11.1": "00ldclwx78dm61v7wkach9lcx76awlrv0fdgjdwch4dmy12j4yw9",
"registry+https://github.com/rust-lang/crates.io-index#security-framework-sys@2.9.1": "0yhciwlsy9dh0ps1gw3197kvyqx1bvc4knrhiznhid6kax196cp9",
"registry+https://github.com/rust-lang/crates.io-index#security-framework@2.9.2": "1pplxk15s5yxvi2m1sz5xfmjibp96cscdcl432w9jzbk0frlzdh5",
"registry+https://github.com/rust-lang/crates.io-index#self_cell@0.10.3": "0pci3zh23b7dg6jmlxbn8k4plb7hcg5jprd1qiz0rp04p1ilskp1",
"registry+https://github.com/rust-lang/crates.io-index#self_cell@1.1.0": "1gmxk5bvnnimcif7v1jk8ai2azfvh9djki545nd86vsnphjgrzf2",
"registry+https://github.com/rust-lang/crates.io-index#semver@1.0.24": "1fmvjjkd3f64y5fqr1nakkq371mnwzv09fbz5mbmdxril63ypdiw",
"registry+https://github.com/rust-lang/crates.io-index#self_cell@1.0.2": "1rmdglwnd77wcw2gv76finpgzjhkynx422d0jpahrf2fsqn37273",
"registry+https://github.com/rust-lang/crates.io-index#semver-parser@0.7.0": "18vhypw6zgccnrlm5ps1pwa0khz7ry927iznpr88b87cagr1v2iq",
"registry+https://github.com/rust-lang/crates.io-index#semver@0.9.0": "00q4lkcj0rrgbhviv9sd4p6qmdsipkwkbra7rh11jrhq5kpvjzhx",
"registry+https://github.com/rust-lang/crates.io-index#semver@1.0.20": "140hmbfa743hbmah1zjf07s8apavhvn04204qjigjiz5w6iscvw3",
"registry+https://github.com/rust-lang/crates.io-index#serde@0.9.15": "1bsla8l5xr9pp5sirkal6mngxcq6q961km88jvf339j5ff8j7dil",
"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.217": "0w2ck1p1ajmrv1cf51qf7igjn2nc51r0izzc00fzmmhkvxjl5z02",
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.217": "180r3rj5gi5s1m23q66cr5wlfgc5jrs6n1mdmql2njnhk37zg6ss",
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.136": "1lipcjhh1zazh283i4wsl4l14knh81q2rlkwmag8v8s2rwihqsik",
"registry+https://github.com/rust-lang/crates.io-index#serde_path_to_error@0.1.16": "19hlz2359l37ifirskpcds7sxg0gzpqvfilibs7whdys0128i6dg",
"registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.8": "1q89g70azwi4ybilz5jb8prfpa575165lmrffd49vmcf76qpqq47",
"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.209": "029yqqbb3c8v3gc720fhxn49dhgvb88zbyprdg5621riwzzy1z4r",
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.209": "0w114ksg1ymnmqdisd0g1j3g8jgz6pam45xg6yb47dfpkybip0x5",
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.127": "1b99lgg1d986gwz5fbmmzmvjmqg5bx0lzmhy6rqp5gc2kxnw0hw0",
"registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.5": "1hgh6s3jjwyzhfk3xwb6pnnr1misq9nflwq0f026jafi37s24dpb",
"registry+https://github.com/rust-lang/crates.io-index#serde_urlencoded@0.7.1": "1zgklbdaysj3230xivihs30qi5vkhigg323a9m62k8jwf4a1qjfk",
"registry+https://github.com/rust-lang/crates.io-index#serde_yml@0.0.12": "1p8xwz4znd6fj962y22fdvvv16gb8c0hx4iv5hjplngiidcdvqjr",
"registry+https://github.com/rust-lang/crates.io-index#sha1@0.10.6": "1fnnxlfg08xhkmwf2ahv634as30l1i3xhlhkvxflmasi5nd85gz3",
"registry+https://github.com/rust-lang/crates.io-index#sha2@0.10.8": "1j1x78zk9il95w9iv46dh9wm73r6xrgj32y6lzzw7bxws9dbfgbr",
"registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0": "0r1y6bv26c1scpxvhg2cabimrmwgbp4p3wy6syj9n0c4s3q2znhg",
"registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.2": "1cb5akgq8ajnd5spyn587srvs4n26ryq0p78nswffwhv46sf1sd9",
"registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.1": "18crkkw5k82bvcx088xlf5g4n3772m24qhzgfan80nda7d3rn8nq",
"registry+https://github.com/rust-lang/crates.io-index#signature@2.2.0": "1pi9hd5vqfr3q3k49k37z06p7gs5si0in32qia4mmr1dancr6m3p",
"registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.7": "1zkq40c3iajcnr5936gjp9jjh1lpzhy44p3dq3fiw75iwr1w2vfn",
"registry+https://github.com/rust-lang/crates.io-index#siphasher@0.2.3": "1b53m53l24lyhr505lwqzrpjyq5qfnic71mynrcfvm43rybf938b",
"registry+https://github.com/rust-lang/crates.io-index#siphasher@1.0.1": "17f35782ma3fn6sh21c027kjmd227xyrx06ffi8gw4xzv9yry6an",
"registry+https://github.com/rust-lang/crates.io-index#siphasher@0.3.11": "03axamhmwsrmh0psdw3gf7c0zc4fyl5yjxfifz9qfka6yhkqid9q",
"registry+https://github.com/rust-lang/crates.io-index#slab@0.4.9": "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg",
"registry+https://github.com/rust-lang/crates.io-index#smallvec@1.13.2": "0rsw5samawl3wsw6glrsb127rx6sh89a8wyikicw6dkdcjd1lpiw",
"registry+https://github.com/rust-lang/crates.io-index#smallvec@1.11.2": "0w79x38f7c0np7hqfmzrif9zmn0avjvvm31b166zdk9d1aad1k2d",
"registry+https://github.com/rust-lang/crates.io-index#snowflake@1.3.0": "1wadr7bxdxbmkbqkqsvzan6q1h3mxqpxningi3ss3v9jaav7n817",
"registry+https://github.com/rust-lang/crates.io-index#socket2@0.5.8": "1s7vjmb5gzp3iaqi94rh9r63k9cj00kjgbfn7gn60kmnk6fjcw69",
"registry+https://github.com/rust-lang/crates.io-index#socket2@0.5.5": "1sgq315f1njky114ip7wcy83qlphv9qclprfjwvxcpfblmcsqpvv",
"registry+https://github.com/rust-lang/crates.io-index#spin@0.5.2": "0b84m6dbzrwf2kxylnw82d3dr8w06av7rfkr8s85fb5f43rwyqvf",
"registry+https://github.com/rust-lang/crates.io-index#spin@0.9.8": "0rvam5r0p3a6qhc18scqpvpgb3ckzyqxpgdfyjnghh8ja7byi039",
"registry+https://github.com/rust-lang/crates.io-index#spki@0.7.3": "17fj8k5fmx4w9mp27l970clrh5qa7r5sjdvbsln987xhb34dc7nr",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-core@0.8.3": "1q31dawr61wc6q2f12my4fw082mbv8sxwz1082msjsk76rlpn03a",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros-core@0.8.3": "1bg7sn6l8dc4pzrqx2dwc3sp7dbn97msfqahpycnl55bqnn917sf",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros@0.8.3": "047k67sylscv0gdhwwqrn0s33jy1mvq8rmqq6s8fygv4g2ny44ii",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-mysql@0.8.3": "0czjzzjm2y6lkhxvvzrzwgp0pmlhymcnym20hn9n9kh01s7jfq25",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-postgres@0.8.3": "04wnjl51kfx0qbfsfmhqdshpmw32vzz2p8dksmj6gvb3ydbqmff5",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-sqlite@0.8.3": "0h05ca26g428h4337k4nm0ww75bcdkiqzp883m7fc92v78fsfp7q",
"registry+https://github.com/rust-lang/crates.io-index#sqlx@0.8.3": "0pvlpq0plgyxf5kikcv786pf0pjv8dx5shlvz72l510d7hxyf424",
"registry+https://github.com/rust-lang/crates.io-index#sqlformat@0.2.3": "0v0p70wjdshj18zgjjac9xlx8hmpx33xhq7g8x9rg4s4gjyvg0ff",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-core@0.7.3": "1gdz44yb9qwxv4xl4hv6w4vbqx0zzdlzsf9j9gcj1qir6wy0ljyq",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros-core@0.7.3": "0h88wahkxa6nam536lhwr1y0yxlr6la8b1x0hs0n88v790clbgfh",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros@0.7.3": "19gjwisiym07q7ibkp9nkvvbywjh0r5rc572msvzyzadvh01r5l9",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-mysql@0.7.3": "190ygz5a3pqcd9vvqjv2i4r1xh8vi53j4272yrld07zpblwrawg3",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-postgres@0.7.3": "090wm9s6mm53ggn1xwr183cnn8yxly8rgcksdk4hrlfcnz1hmb6n",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-sqlite@0.7.3": "143laha7wf8dmi0xwycwqmvxdcnb25dq7jnqrsgvmis8v6vpc291",
"registry+https://github.com/rust-lang/crates.io-index#sqlx@0.7.3": "1kv3hyx7izmmsjqh3l47zrfhjlcblpg20cvnk7pr8dm7klkkr86v",
"registry+https://github.com/rust-lang/crates.io-index#stable_deref_trait@1.2.0": "1lxjr8q2n534b2lhkxd6l6wcddzjvnksi58zv11f9y0jjmr15wd8",
"registry+https://github.com/rust-lang/crates.io-index#stringprep@0.1.5": "1cb3jis4h2b767csk272zw92lc6jzfzvh8d6m1cd86yqjb9z6kbv",
"registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1": "0kzvqlw8hxqb7y598w1s0hxlnmi84sg5vsipp3yg5na5d1rvba3x",
"registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1": "14ijxaymghbl1p0wql9cib5zlwiina7kall6w7g89csprkgbvhhk",
"registry+https://github.com/rust-lang/crates.io-index#stringprep@0.1.4": "1rkfsf7riynsmqj3hbldfrvmna0i9chx2sz39qdpl40s4d7dfhdv",
"registry+https://github.com/rust-lang/crates.io-index#strsim@0.10.0": "08s69r4rcrahwnickvi0kq49z524ci50capybln83mg6b473qivk",
"registry+https://github.com/rust-lang/crates.io-index#subtle@2.5.0": "1g2yjs7gffgmdvkkq0wrrh0pxds3q0dv6dhkw9cdpbib656xdkc1",
"registry+https://github.com/rust-lang/crates.io-index#syn@1.0.109": "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj",
"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.96": "102wk3cgawimi3i0q3r3xw3i858zkyingg6y7gsxfy733amsvl6m",
"registry+https://github.com/rust-lang/crates.io-index#sync_wrapper@0.1.2": "0q01lyj0gr9a93n10nxsn8lwbzq97jqd6b768x17c8f7v7gccir0",
"registry+https://github.com/rust-lang/crates.io-index#sync_wrapper@1.0.2": "0qvjyasd6w18mjg5xlaq5jgy84jsjfsvmnn12c13gypxbv75dwhb",
"registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1": "0wc9f002ia2zqcbj0q2id5x6n7g1zjqba7qkg2mr0qvvmdk7dby8",
"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.48": "0gqgfygmrxmp8q32lia9p294kdd501ybn6kn2h4gqza0irik2d8g",
"registry+https://github.com/rust-lang/crates.io-index#system-configuration-sys@0.5.0": "1jckxvdr37bay3i9v52izgy52dg690x5xfg3hd394sv2xf4b2px7",
"registry+https://github.com/rust-lang/crates.io-index#system-configuration@0.5.1": "1rz0r30xn7fiyqay2dvzfy56cvaa3km74hnbz2d72p97bkf3lfms",
"registry+https://github.com/rust-lang/crates.io-index#system-deps@6.2.2": "0j93ryw031n3h8b0nfpj5xwh3ify636xmv8kxianvlyyipmkbrd3",
"registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.12.16": "1cg3bnx1gdkdr5hac1hzxy64fhw4g7dqkd0n3dxy5lfngpr1mi31",
"registry+https://github.com/rust-lang/crates.io-index#system-deps@6.2.0": "0c836abhh3k8yn5ymg8wx383ay7n731gkrbbp3gma352yq7mhb9a",
"registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.12.12": "02lk65ik5ffb8vl9qzq02v0df8kxrp16zih78a33mji49789zhql",
"registry+https://github.com/rust-lang/crates.io-index#tempdir@0.3.7": "1n5n86zxpgd85y0mswrp5cfdisizq2rv3la906g6ipyc03xvbwhm",
"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.15.0": "016pmkbwn3shas44gcwq1kc9lajalb90qafhiip5fvv8h6f5b2ls",
"registry+https://github.com/rust-lang/crates.io-index#termcolor@1.4.1": "0mappjh3fj3p2nmrg4y7qv94rchwi9mzmgmfflr8p2awdj7lyy86",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.69": "1h84fmn2nai41cxbhk6pqf46bxqq1b344v8yz089w1chzi76rvjg",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.11": "1hkkn7p2y4cxbffcrprybkj0qy1rl1r6waxmxqvr764axaxc3br6",
"registry+https://github.com/rust-lang/crates.io-index#thiserror@1.0.69": "0lizjay08agcr5hs9yfzzj6axs53a2rgx070a1dsi3jpkcrzbamn",
"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.11": "1z0649rpa8c2smzx129bz4qvxmdihj30r2km6vfpcv9yny2g4lnl",
"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.8.1": "1r88v07zdafzf46y63vs39rmzwl4vqd4g2c5qarz9mqa8nnavwby",
"registry+https://github.com/rust-lang/crates.io-index#termcolor@1.4.0": "0jfllflbxxffghlq6gx4csv0bv0qv77943dcx01h9zssy39w66zz",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.51": "1ps9ylhlk2vn19fv3cxp40j3wcg1xmb117g2z2fbf4vmg2bj4x01",
"registry+https://github.com/rust-lang/crates.io-index#thiserror@1.0.51": "1drvyim21w5sga3izvnvivrdp06l2c24xwbhp0vg1mhn2iz2277i",
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.6.1": "0ds48vs919ccxa3fv1www7788pzkvpg434ilqkq7sjb5dmqg8lws",
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.9.1": "0ghyxlz566dzc3scvgmzys11dhq2ri77kb8sznjakijlxby104xs",
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.9.0": "04b2fd3clxm0pmdlfip8xj594zyrsfwmh641i6x1gfiz9l7jn5vd",
"registry+https://github.com/rust-lang/crates.io-index#time-core@0.1.2": "1wx3qizcihw6z151hywfzzyd1y5dl804ydyxci6qm07vbakpr4pg",
"registry+https://github.com/rust-lang/crates.io-index#time-macros@0.2.19": "1pl558z26pp342l5y91n6dxb60xwhar975wk6jc4npiygq0ycd18",
"registry+https://github.com/rust-lang/crates.io-index#time-macros@0.2.16": "0gx4ngf5g7ydqa8lf7kh9sy72rd4dhvpi31y1jvswi0288rpw696",
"registry+https://github.com/rust-lang/crates.io-index#time@0.1.45": "0nl0pzv9yf56djy8y5dx25nka5pr2q1ivlandb3d24pksgx7ly8v",
"registry+https://github.com/rust-lang/crates.io-index#time@0.3.37": "08bvydyc14plkwhchzia5bcdbmm0mk5fzilsdpjx06w6hf48drrm",
"registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6": "0bxqaw7z8r2kzngxlzlgvld1r6jbnwyylyvyjbv1q71rvgaga5wi",
"registry+https://github.com/rust-lang/crates.io-index#tinyvec@1.8.1": "1s41rv7n39sjsxz3kd3d4adw45ndkxz1d18rfbz2wd7s9n8bhb82",
"registry+https://github.com/rust-lang/crates.io-index#time@0.3.31": "0gjqcdsdbh0r5vi4c2vrj5a6prdviapx731wwn07cvpqqd1blmzn",
"registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.5": "1khf3j95bwwksj2hw76nlvwlwpwi4d1j421lj6x35arqqprjph43",
"registry+https://github.com/rust-lang/crates.io-index#tinyvec@1.6.0": "0l6bl2h62a5m44jdnpn7lmj14rd44via8180i7121fvm73mmrk47",
"registry+https://github.com/rust-lang/crates.io-index#tinyvec_macros@0.1.1": "081gag86208sc3y6sdkshgw3vysm5d34p431dzw0bshz66ncng0z",
"registry+https://github.com/rust-lang/crates.io-index#tokio-macros@2.5.0": "1f6az2xbvqp7am417b78d1za8axbvjvxnmkakz9vr8s52czx81kf",
"registry+https://github.com/rust-lang/crates.io-index#tokio-macros@2.4.0": "0lnpg14h1v3fh2jvnc8cz7cjf0m7z1xgkwfpcyy632g829imjgb9",
"registry+https://github.com/rust-lang/crates.io-index#tokio-native-tls@0.3.1": "1wkfg6zn85zckmv4im7mv20ca6b1vmlib5xwz9p7g19wjfmpdbmv",
"registry+https://github.com/rust-lang/crates.io-index#tokio-stream@0.1.17": "0ix0770hfp4x5rh5bl7vsnr3d4iz4ms43i522xw70xaap9xqv9gc",
"registry+https://github.com/rust-lang/crates.io-index#tokio-stream@0.1.14": "0hi8hcwavh5sdi1ivc9qc4yvyr32f153c212dpd7sb366y6rhz1r",
"registry+https://github.com/rust-lang/crates.io-index#tokio-tungstenite@0.21.0": "0f5wj0crsx74rlll97lhw0wk6y12nhdnqvmnjx002hjn08fmcfy8",
"registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.13": "0y0h10a52c7hrldmr3410bp7j3fadq0jn9nf7awddgd2an6smz6p",
"registry+https://github.com/rust-lang/crates.io-index#tokio@1.43.0": "17pdm49ihlhfw3rpxix3kdh2ppl1yv7nwp1kxazi5r1xz97zlq9x",
"registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.10": "058y6x4mf0fsqji9rfyb77qbfyc50y4pk2spqgj6xsyr693z66al",
"registry+https://github.com/rust-lang/crates.io-index#tokio@1.39.3": "1xgzhj7bxqqpjaabjkgsx8hi0f600bzj4iyp9f0a9gr3k6dwkawv",
"registry+https://github.com/rust-lang/crates.io-index#toml@0.8.2": "0g9ysjaqvm2mv8q85xpqfn7hi710hj24sd56k49wyddvvyq8lp8q",
"registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.3": "0jsy7v8bdvmzsci6imj8fzgd255fmy5fzp6zsri14yrry7i77nkw",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.19.15": "08bl7rp5g6jwmfpad9s8jpw8wjrciadpnbaswgywpr9hv9qbfnqv",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.20.2": "0f7k5svmxw98fhi28jpcyv7ldr2s3c867pjbji65bdxjpd44svir",
"registry+https://github.com/rust-lang/crates.io-index#tower-http@0.6.2": "15wnvhl6cpir9125s73bqjzjsvfb0fmndmsimnl2ddnlhfvs6gs0",
"registry+https://github.com/rust-lang/crates.io-index#tower-layer@0.3.3": "03kq92fdzxin51w8iqix06dcfgydyvx7yr6izjq0p626v9n2l70j",
"registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.3": "1hzfkvkci33ra94xjx64vv3pp0sq346w06fpkcdwjcid7zhvdycd",
"registry+https://github.com/rust-lang/crates.io-index#tower@0.5.2": "1ybmd59nm4abl9bsvy6rx31m4zvzp5rja2slzpn712y9b68ssffh",
"registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.28": "0v92l9cxs42rdm4m5hsa8z7ln1xsiw1zc2iil8c6k7lzq0jf2nir",
"registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.33": "170gc7cxyjx824r9kr17zc9gvzx89ypqfdzq259pr56gg5bwjwp6",
"registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.41": "1l5xrzyjfyayrwhvhldfnwdyligi1mpqm8mzbi2m1d6y6p2hlkkq",
"registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.2": "0lmfzmmvid2yp2l36mbavhmqgsvzqf7r2wiwz73ml4xmwaf1rg5n",
"registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.27": "1rvb5dn9z6d0xdj14r403z0af0bbaqhg02hq4jc97g5wds6lqw1l",
"registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.32": "0m5aglin3cdwxpvbg6kz0r9r0k31j48n0kcfwsp6l49z26k3svf0",
"registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.40": "1vv48dac9zgj9650pg2b4d0j3w6f3x9gbggf43scq5hrlysklln3",
"registry+https://github.com/rust-lang/crates.io-index#traitobject@0.1.0": "0yb0n8822mr59j200fyr2fxgzzgqljyxflx9y8bdy3rlaqngilgg",
"registry+https://github.com/rust-lang/crates.io-index#try-lock@0.2.5": "0jqijrrvm1pyq34zn1jmy2vihd4jcrjlvsh4alkjahhssjnsn8g4",
"registry+https://github.com/rust-lang/crates.io-index#tungstenite@0.21.0": "1qaphb5kgwgid19p64grhv2b9kxy7f1059yy92l9kwrlx90sdwcy",
"registry+https://github.com/rust-lang/crates.io-index#type-map@0.5.0": "17qaga12nkankr7hi2mv43f4lnc78hg480kz6j9zmy4g0h28ddny",
"registry+https://github.com/rust-lang/crates.io-index#type-map@0.4.0": "0ilsqq7pcl3k9ggxv2x5fbxxfd6x7ljsndrhc38jmjwnbr63dlxn",
"registry+https://github.com/rust-lang/crates.io-index#typeable@0.1.2": "11w8dywgnm32hb291izjvh4zjd037ccnkk77ahk63l913zwzc40l",
"registry+https://github.com/rust-lang/crates.io-index#typemap@0.3.3": "1xm1gbvz9qisj1l6d36hrl9pw8imr8ngs6qyanjnsad3h0yfcfv5",
"registry+https://github.com/rust-lang/crates.io-index#typenum@1.17.0": "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2",
"registry+https://github.com/rust-lang/crates.io-index#typeshare-annotation@1.0.4": "0kx38ah6638pkqq5cac7nmvbg6x43v7fj5jgibla4lj8fv1dc5d6",
"registry+https://github.com/rust-lang/crates.io-index#typeshare@1.0.4": "1svc92lg35r12mqdpbs4wbkw7g72v2302niyw5v1w290250hzghr",
"registry+https://github.com/rust-lang/crates.io-index#typeshare-annotation@1.0.2": "1adpfhyz3lqjjbq2ym69mv62ymqyd5651gxlqdy8aa446l70srzw",
"registry+https://github.com/rust-lang/crates.io-index#typeshare@1.0.1": "1mi7snkx2b4g84x8vx38v1myg5r6g48c865j0nz5zcsc8lpilkgl",
"registry+https://github.com/rust-lang/crates.io-index#unarray@0.1.4": "154smf048k84prsdgh09nkm2n0w0336v84jd4zikyn6v6jrqbspa",
"registry+https://github.com/rust-lang/crates.io-index#unic-langid-impl@0.9.5": "1rckyn5wqd5h8jxhbzlbbagr459zkzg822r4k5n30jaryv0j4m0a",
"registry+https://github.com/rust-lang/crates.io-index#unic-langid@0.9.5": "0i2s024frmpfa68lzy8y8vnb1rz3m9v0ga13f7h2afx7f8g9vp93",
"registry+https://github.com/rust-lang/crates.io-index#unic-langid-impl@0.9.4": "1ijvqmsrg6qw3b1h9bh537pvwk2jn2kl6ck3z3qlxspxcch5mmab",
"registry+https://github.com/rust-lang/crates.io-index#unic-langid@0.9.4": "05pm5p3j29c9jw9a4dr3v64g3x6g3zh37splj47i7vclszk251r3",
"registry+https://github.com/rust-lang/crates.io-index#unicase@1.4.2": "0cwazh4qsmm9msckjk86zc1z35xg7hjxjykrgjalzdv367w6aivz",
"registry+https://github.com/rust-lang/crates.io-index#unicase@2.8.1": "0fd5ddbhpva7wrln2iah054ar2pc1drqjcll0f493vj3fv8l9f3m",
"registry+https://github.com/rust-lang/crates.io-index#unicode-bidi@0.3.18": "1xcxwbsqa24b8vfchhzyyzgj0l6bn51ib5v8j6krha0m77dva72w",
"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.14": "10ywa1pg0glgkr4l3dppjxizr9r2b7im0ycbfa0137l69z5fdfdd",
"registry+https://github.com/rust-lang/crates.io-index#unicode-normalization@0.1.24": "0mnrk809z3ix1wspcqy97ld5wxdb31f3xz6nsvg5qcv289ycjcsh",
"registry+https://github.com/rust-lang/crates.io-index#unicode-properties@0.1.3": "1l3mbgzwz8g14xcs09p4ww3hjkjcf0i1ih13nsg72bhj8n5jl3z7",
"registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0": "14qla2jfx74yyb9ds3d2mpwpa4l4lzb9z57c6d2ba511458z5k7n",
"registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14": "1bzn2zv0gp8xxbxbhifw778a7fc93pa6a1kj24jgg9msj07f7mkx",
"registry+https://github.com/rust-lang/crates.io-index#unicase@2.7.0": "12gd74j79f94k4clxpf06l99wiv4p30wjr0qm04ihqk9zgdd9lpp",
"registry+https://github.com/rust-lang/crates.io-index#unicode-bidi@0.3.14": "05i4ps31vskq1wdp8yf315fxivyh1frijly9d4gb5clygbr2h9bg",
"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.12": "0jzf1znfpb2gx8nr8mvmyqs1crnv79l57nxnbiszc7xf7ynbjm1k",
"registry+https://github.com/rust-lang/crates.io-index#unicode-normalization@0.1.22": "08d95g7b1irc578b2iyhzv4xhsa4pfvwsqxcl9lbcpabzkq16msw",
"registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.10.1": "0dky2hm5k51xy11hc3nk85p533rvghd462b6i0c532b7hl4j9mhx",
"registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.13": "0p92vl8n7qc8mxz45xn6qbgi0259z96n32a158l6vj5bywwdadh3",
"registry+https://github.com/rust-lang/crates.io-index#unicode_categories@0.1.1": "0kp1d7fryxxm7hqywbk88yb9d1avsam9sg76xh36k5qx2arj9v1r",
"registry+https://github.com/rust-lang/crates.io-index#unsafe-any@0.4.2": "0zwwphsqkw5qaiqmjwngnfpv9ym85qcsyj7adip9qplzjzbn00zk",
"registry+https://github.com/rust-lang/crates.io-index#url@1.7.2": "0nim1c90mxpi9wgdw2xh8dqd72vlklwlzam436akcrhjac6pqknx",
"registry+https://github.com/rust-lang/crates.io-index#url@2.5.4": "0q6sgznyy2n4l5lm16zahkisvc9nip9aa5q1pps7656xra3bdy1j",
"registry+https://github.com/rust-lang/crates.io-index#url@2.5.0": "0cs65961miawncdg2z20171w0vqrmraswv2ihdpd8lxp7cp31rii",
"registry+https://github.com/rust-lang/crates.io-index#urlencoding@2.1.3": "1nj99jp37k47n0hvaz5fvz7z6jd0sb4ppvfy3nphr1zbnyixpy6s",
"registry+https://github.com/rust-lang/crates.io-index#usb-device@0.2.9": "0205a850jhw9gb96scwfx1k4iwpjvighvz3m80mjkda9r2nw6v0z",
"registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6": "1a9ns3fvgird0snjkd3wbdhwd3zdpc2h5gpyybrfr6ra5pkqxk09",
"registry+https://github.com/rust-lang/crates.io-index#utf16_iter@1.0.5": "0ik2krdr73hfgsdzw0218fn35fa09dg2hvbi1xp3bmdfrp9js8y8",
"registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4": "1gmna9flnj8dbyd8ba17zigrp9c4c3zclngf5lnb5yvz1ri41hdn",
"registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.2": "088807qwjq46azicqwbhlmzwrbkz7l4hpw43sdkdyyk524vdxaq6",
"registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.1": "02ip1a0az0qmc2786vxk2nqwsgcwf17d3a38fkf0q7hrmwh9c6vi",
"registry+https://github.com/rust-lang/crates.io-index#uuid@0.4.0": "0cdj2v6v2yy3zyisij69waksd17cyir1n58kwyk1n622105wbzkw",
"registry+https://github.com/rust-lang/crates.io-index#uuid@0.8.2": "1dy4ldcp7rnzjy56dxh7d2sgrcvn4q77y0a8r0a48946h66zjp5w",
"registry+https://github.com/rust-lang/crates.io-index#uuid@1.12.0": "1i2i7ar5651d58ip1l8cghg3y60pn0rqmssvw6lm8d4s3xc1hh3l",
"registry+https://github.com/rust-lang/crates.io-index#value-bag@1.10.0": "1lnsixdpi1ldms1adxyafyx7lyrqxhhskgwrjckmml6majmc9x1y",
"registry+https://github.com/rust-lang/crates.io-index#uuid@1.6.1": "0q45jxahvysldn3iy04m8xmr8hgig80855y9gq9di8x72v7myfay",
"registry+https://github.com/rust-lang/crates.io-index#value-bag@1.7.0": "02r8wccrzi3bzlkrslkcfw9pwp8kwif9szif2i9arn9dzqx44vhj",
"registry+https://github.com/rust-lang/crates.io-index#vcell@0.1.3": "00n0ss2z3rh0ihig6d4w7xp72g58f7g1m6s5v4h3nc6jacdrqhvp",
"registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15": "09i4nf5y8lig6xgj3f7fyrvzd3nlaw4znrihw8psidvv5yk4xkdc",
"registry+https://github.com/rust-lang/crates.io-index#version-compare@0.2.0": "12y9262fhjm1wp0aj3mwhads7kv0jz8h168nn5fb8b43nwf9abl5",
"registry+https://github.com/rust-lang/crates.io-index#version-compare@0.1.1": "0acg4pmjdbmclg0m7yhijn979mdy66z3k8qrcnvn634f1gy456jp",
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.1.5": "1pf91pvj8n6akh7w6j5ypka6aqz08b3qpzgs0ak2kjf4frkiljwi",
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.5": "0nhhi4i5x89gm911azqbn7avs9mdacw2i3vcz3cnmz3mv4rqz4hb",
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.4": "0gs8grwdlgh0xq660d7wr80x14vxbizmd8dbp29p2pdncx8lp1s9",
"registry+https://github.com/rust-lang/crates.io-index#void@1.0.2": "0zc8f0ksxvmhvgx4fdg0zyn6vdnbxd2xv9hfx4nhzg6kbs4f80ka",
"registry+https://github.com/rust-lang/crates.io-index#volatile-register@0.2.2": "1k0rkm81qyhn4r8f03z0sch2kyikkgjjfalpaami9c08c8m7whyy",
"registry+https://github.com/rust-lang/crates.io-index#wait-timeout@0.2.0": "1xpkk0j5l9pfmjfh1pi0i89invlavfrd9av5xp0zhxgb29dhy84z",
"registry+https://github.com/rust-lang/crates.io-index#want@0.3.1": "03hbfrnvqqdchb5kgxyavb9jabwza0dmh2vw5kg0dq8rxl57d9xz",
"registry+https://github.com/rust-lang/crates.io-index#warp@0.3.7": "07137zd13lchy5hxpspd0hs6sl19b0fv2zc1chf02nwnzw1d4y23",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.10.0+wasi-snapshot-preview1": "07y3l8mzfzzz4cj09c8y90yak4hpsi9g7pllyzpr6xvwrabka50s",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.11.0+wasi-snapshot-preview1": "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw",
"registry+https://github.com/rust-lang/crates.io-index#wasite@0.1.0": "0nw5h9nmcl4fyf4j5d4mfdjfgvwi1cakpi349wc4zrr59wxxinmq",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.100": "1ihbf1hq3y81c4md9lyh6lcwbx6a5j0fw4fygd423g62lm8hc2ig",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.50": "0q8ymi6i9r3vxly551dhxcyai7nc491mspj0j1wbafxwq074fpam",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.100": "1plm8dh20jg2id0320pbmrlsv6cazfv6b6907z19ys4z1jj7xs4a",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.100": "01xls2dvzh38yj17jgrbiib1d3nyad7k2yw9s0mpklwys333zrkz",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.100": "0gffxvqgbh9r9xl36gprkfnh3w9gl8wgia6xrin7v11sjcxxf18s",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.100": "1x8ymcm6yi3i1rwj78myl1agqv2m86i648myy3lc97s9swlqkp0y",
"registry+https://github.com/rust-lang/crates.io-index#web-sys@0.3.77": "1lnmc1ffbq34qw91nndklqqm75rasaffj2g4f8h1yvqqz4pdvdik",
"registry+https://github.com/rust-lang/crates.io-index#weezl@0.1.8": "10lhndjgs6y5djpg3b420xngcr6jkmv70q8rb1qcicbily35pa2k",
"registry+https://github.com/rust-lang/crates.io-index#whoami@1.5.2": "0vdvm6sga4v9515l6glqqfnmzp246nq66dd09cw5ri4fyn3mnb9p",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.89": "09l8lyylsdssz993h4fzja69zpvpykaw84fivs210fjgwqjzcmhv",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.39": "04lsxpw4jqfwh7c0crzx0smj52nvwp1w3bh4098sq90149da2dmc",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.89": "10sj1gr2naxv5q116yjb929hhpvz45dxbkvyk8hyc2lknzy85szh",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.89": "1cl2w7k5jn2jbd5kx613c8k8vjvda22hfgcgx7y2mk93fbrxnqh1",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.89": "17s5rppad113c6ggkaq8c3cg7a3zz15i78wxcg6mcl1n15iv7fbs",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.89": "0kh6akdldy13z9xqj0skz6b4npq1d98bjkgzb8ccq59hibvd9l0f",
"registry+https://github.com/rust-lang/crates.io-index#web-sys@0.3.66": "03q1z22djv5ncqkyydcvnchmdsl5gvnyzcyixkxnifw6xi24mhjh",
"registry+https://github.com/rust-lang/crates.io-index#weezl@0.1.7": "1frdbq6y5jn2j93i20hc80swpkj30p1wffwxj1nr4fp09m6id4wi",
"registry+https://github.com/rust-lang/crates.io-index#whoami@1.4.1": "0l6ca9pl92wmngsn1dh9ih716v216nmn2zvcn94k04x9p1b3gz12",
"registry+https://github.com/rust-lang/crates.io-index#winapi-i686-pc-windows-gnu@0.4.0": "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc",
"registry+https://github.com/rust-lang/crates.io-index#winapi-util@0.1.9": "1fqhkcl9scd230cnfj8apfficpf5c9vhwnk4yy9xfc1sw69iq8ng",
"registry+https://github.com/rust-lang/crates.io-index#winapi-util@0.1.6": "15i5lm39wd44004i9d5qspry2cynkrpvwzghr6s2c3dsk28nz7pj",
"registry+https://github.com/rust-lang/crates.io-index#winapi-x86_64-pc-windows-gnu@0.4.0": "0gqq64czqb64kskjryj8isp62m2sgvx25yyj3kpc2myh85w24bki",
"registry+https://github.com/rust-lang/crates.io-index#winapi@0.3.9": "06gl025x418lchw1wxj64ycr7gha83m44cjr5sarhynd9xkrm0sw",
"registry+https://github.com/rust-lang/crates.io-index#windows-core@0.52.0": "1nc3qv7sy24x0nlnb32f7alzpd6f72l4p24vl65vydbyil669ark",
"registry+https://github.com/rust-lang/crates.io-index#windows-core@0.51.1": "0r1f57hsshsghjyc7ypp2s0i78f7b1vr93w68sdb8baxyf2czy7i",
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.48.0": "1aan23v5gs7gya1lc46hqn9mdh8yph3fhxmhxlw36pn6pqc28zb7",
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.52.0": "0gd3v4ji88490zgb6b5mq5zgbvwv7zx1ibn8v3x83rwcdbryaar8",
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.59.0": "0fw5672ziw8b3zpmnbp9pdv1famk74f1l9fcbc3zsrzdg56vqf0y",
"registry+https://github.com/rust-lang/crates.io-index#windows-targets@0.48.5": "034ljxqshifs1lan89xwpcy1hp0lhdh4b5n0d2z4fwjx2piacbws",
"registry+https://github.com/rust-lang/crates.io-index#windows-targets@0.52.6": "0wwrx625nwlfp7k93r2rra568gad1mwd888h1jwnl0vfg5r4ywlv",
"registry+https://github.com/rust-lang/crates.io-index#windows-targets@0.52.0": "1kg7a27ynzw8zz3krdgy6w5gbqcji27j1sz4p7xk2j5j8082064a",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_gnullvm@0.48.5": "1n05v7qblg1ci3i567inc7xrkmywczxrs1z3lj3rkkxw18py6f1b",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_gnullvm@0.52.6": "1lrcq38cr2arvmz19v32qaggvj8bh1640mdm9c2fr877h0hn591j",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_gnullvm@0.52.0": "1shmn1kbdc0bpphcxz0vlph96bxz0h1jlmh93s9agf2dbpin8xyb",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_msvc@0.48.5": "1g5l4ry968p73g6bg6jgyvy9lb8fyhcs54067yzxpcpkf44k2dfw",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_msvc@0.52.6": "0sfl0nysnz32yyfh773hpi49b1q700ah6y7sacmjbqjjn5xjmv09",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_msvc@0.52.0": "1vvmy1ypvzdvxn9yf0b8ygfl85gl2gpcyvsvqppsmlpisil07amv",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnu@0.48.5": "0gklnglwd9ilqx7ac3cn8hbhkraqisd0n83jxzf9837nvvkiand7",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnu@0.52.6": "02zspglbykh1jh9pi7gn8g1f97jh1rrccni9ivmrfbl0mgamm6wf",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnullvm@0.52.6": "0rpdx1537mw6slcpqa0rm3qixmsb79nbhqy5fsm3q2q9ik9m5vhf",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnu@0.52.0": "04zkglz4p3pjsns5gbz85v4s5aw102raz4spj4b0lmm33z5kg1m2",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_msvc@0.48.5": "01m4rik437dl9rdf0ndnm2syh10hizvq0dajdkv2fjqcywrw4mcg",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_msvc@0.52.6": "0rkcqmp4zzmfvrrrx01260q3xkpzi6fzi2x2pgdcdry50ny4h294",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_msvc@0.52.0": "16kvmbvx0vr0zbgnaz6nsks9ycvfh5xp05bjrhq65kj623iyirgz",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnu@0.48.5": "13kiqqcvz2vnyxzydjh73hwgigsdr2z1xpzx313kxll34nyhmm2k",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnu@0.52.6": "0y0sifqcb56a56mvn7xjgs8g43p33mfqkd8wj1yhrgxzma05qyhl",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnu@0.52.0": "1zdy4qn178sil5sdm63lm7f0kkcjg6gvdwmcprd2yjmwn8ns6vrx",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnullvm@0.48.5": "1k24810wfbgz8k48c2yknqjmiigmql6kk3knmddkv8k8g1v54yqb",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnullvm@0.52.6": "03gda7zjx1qh8k9nnlgb7m3w3s1xkysg55hkd1wjch8pqhyv5m94",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnullvm@0.52.0": "17lllq4l2k1lqgcnw1cccphxp9vs7inq99kjlm2lfl9zklg7wr8s",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.48.5": "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.52.6": "1v7rb5cibyzx8vak29pdrk8nx9hycsjs4w0jgms08qk49jl6v7sq",
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.5.40": "0xk8maai7gyxda673mmw3pj1hdizy5fpi7287vaywykkk19sk4zm",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.52.0": "012wfq37f18c09ij5m6rniw7xxn5fcvrxbqd0wd8vgnl3hfn9yfz",
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.5.30": "1ifj9vnqna5qp0d7nb9mrinzf8j7zi1m0gv75870vm91jyw3sp4v",
"registry+https://github.com/rust-lang/crates.io-index#winreg@0.50.0": "1cddmp929k882mdh6i9f2as848f13qqna6czwsqzkh1pqnr5fkjj",
"registry+https://github.com/rust-lang/crates.io-index#write16@1.0.0": "0dnryvrrbrnl7vvf5vb1zkmwldhjkf2n5znliviam7bm4900z2fi",
"registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5": "0lawr6y0bwqfyayf3z8zmqlhpnzhdx0ahs54isacbhyjwa7g778y",
"registry+https://github.com/rust-lang/crates.io-index#yansi-term@0.1.2": "1w8vjlvxba6yvidqdvxddx3crl6z66h39qxj8xi6aqayw2nk0p7y",
"registry+https://github.com/rust-lang/crates.io-index#yansi@1.0.1": "0jdh55jyv0dpd38ij4qh60zglbw9aa8wafqai6m0wa7xaxk3mrfg",
"registry+https://github.com/rust-lang/crates.io-index#yoke-derive@0.7.5": "0m4i4a7gy826bfvnqa9wy6sp90qf0as3wps3wb0smjaamn68g013",
"registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.5": "0h3znzrdmll0a7sglzf9ji0p5iqml11wrj1dypaf6ad6kbpnl3hj",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.7.35": "0gnf2ap2y92nwdalzz3x7142f2b83sni66l39vxp2ijd6j080kzs",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.7.35": "1w36q7b9il2flg0qskapgi9ymgg7p985vniqd09vi0mwib8lz6qv",
"registry+https://github.com/rust-lang/crates.io-index#zerofrom-derive@0.1.5": "022q55phhb44qbrcfbc48k0b741fl8gnazw3hpmmndbx5ycfspjr",
"registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.5": "0bnd8vjcllzrvr3wvn8x14k2hkrpyy1fm3crkn2y3plmr44fxwyg",
"registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.1": "1pjdrmjwmszpxfd7r860jx54cyk94qk59x13sc307cvr5256glyf",
"registry+https://github.com/rust-lang/crates.io-index#zerovec-derive@0.10.3": "1ik322dys6wnap5d3gcsn09azmssq466xryn5czfm13mn7gsdbvf",
"registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4": "0yghix7n3fjfdppwghknzvx9v8cf826h2qal5nqvy8yzg4yqjaxa",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.7.31": "06k0zk4x4n9s1blgxmxqb1g81y8q334aayx61gyy6v9y1dajkhdk",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.7.31": "0gcfyrmlrhmsz16qxjp2qzr6vixyaw1p04zl28f08lxkvfz62h0w",
"registry+https://github.com/rust-lang/crates.io-index#zeroize@1.7.0": "0bfvby7k9pdp6623p98yz2irqnamcyzpn7zh20nqmdn68b0lwnsj",
"registry+https://github.com/rust-lang/crates.io-index#zune-inflate@0.2.54": "00kg24jh3zqa3i6rg6yksnb71bch9yi1casqydl00s7nw8pk7avk"
}

View File

@ -1,16 +0,0 @@
[package]
name = "cyber-slides"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std = "1.13.0"
cairo-rs = "0.18"
cyberpunk = { path = "../cyberpunk" }
gio = "0.18"
glib = "0.18"
gtk = { version = "0.7", package = "gtk4" }
serde = { version = "1.0.210", features = ["derive"] }
serde_yml = "0.0.12"

View File

@ -1,410 +0,0 @@
use std::{
cell::RefCell,
collections::HashMap,
fs::File,
io::Read,
ops::Index,
path::Path,
rc::Rc,
sync::{Arc, RwLock},
time::{Duration, Instant},
};
use cairo::{Context, Rectangle};
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text};
use glib::Object;
use gtk::{
glib::{self},
prelude::*,
subclass::prelude::*,
EventControllerKey,
};
use serde::{Deserialize, Serialize};
const FPS: u64 = 60;
const PURPLE: (f64, f64, f64) = (0.7, 0., 1.);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
enum Position {
Top,
Middle,
Bottom,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Step {
text: String,
position: Position,
transition: Duration,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Default)]
struct Script(Vec<Step>);
impl Script {
fn from_file(path: &Path) -> Result<Script, serde_yml::Error> {
let mut buf: Vec<u8> = Vec::new();
let mut f = File::open(path).unwrap();
f.read_to_end(&mut buf).unwrap();
let script = serde_yml::from_slice(&buf)?;
Ok(Self(script))
}
fn iter(&self) -> impl Iterator<Item = &'_ Step> {
self.0.iter()
}
fn len(&self) -> usize {
self.0.len()
}
}
impl Index<usize> for Script {
type Output = Step;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
struct Fade {
text: String,
position: Position,
duration: Duration,
start_time: Instant,
}
trait Animation {
fn position(&self) -> Position;
fn tick(&self, now: Instant, context: &Context, width: f64);
}
impl Animation for Fade {
fn position(&self) -> Position {
self.position.clone()
}
fn tick(&self, now: Instant, context: &Context, width: f64) {
let total_frames = self.duration.as_secs() * FPS;
let alpha_rate: f64 = 1. / total_frames as f64;
let frames = (now - self.start_time).as_secs_f64() * FPS as f64;
let alpha = alpha_rate * frames;
let text_display = Text::new(self.text.clone(), context, 64., width);
context.move_to(0., text_display.extents().height());
context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha);
text_display.draw();
}
}
struct CrossFade {
old_text: String,
new_text: String,
position: Position,
duration: Duration,
start_time: Instant,
}
impl Animation for CrossFade {
fn position(&self) -> Position {
self.position.clone()
}
fn tick(&self, now: Instant, context: &Context, width: f64) {
let total_frames = self.duration.as_secs() * FPS;
let alpha_rate: f64 = 1. / total_frames as f64;
let frames = (now - self.start_time).as_secs_f64() * FPS as f64;
let alpha = alpha_rate * frames;
let text_display = Text::new(self.old_text.clone(), context, 64., width);
context.move_to(0., text_display.extents().height());
context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, 1. - alpha);
text_display.draw();
let text_display = Text::new(self.new_text.clone(), context, 64., width);
context.move_to(0., text_display.extents().height());
context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha);
text_display.draw();
}
}
#[derive(Debug)]
pub struct CyberScreenState {
script: Script,
idx: Option<usize>,
top: Option<Step>,
middle: Option<Step>,
bottom: Option<Step>,
}
impl Default for CyberScreenState {
fn default() -> Self {
Self {
script: Script(vec![]),
idx: None,
top: None,
middle: None,
bottom: None,
}
}
}
impl CyberScreenState {
fn new(script: Script) -> CyberScreenState {
CyberScreenState { script, ..Default::default() }
}
fn next_page(&mut self) -> Box<dyn Animation> {
let idx = match self.idx {
None => 0,
Some(idx) => {
if idx < self.script.len() {
idx + 1
} else {
idx
}
}
};
self.idx = Some(idx);
let step = self.script[idx].clone();
let (old, new) = match step.position {
Position::Top => {
let old = self.top.replace(step.clone());
(old, step)
}
Position::Middle => {
let old = self.middle.replace(step.clone());
(old, step)
}
Position::Bottom => {
let old = self.bottom.replace(step.clone());
(old, step)
}
};
match old {
Some(old) => Box::new(CrossFade {
old_text: old.text.clone(),
new_text: new.text.clone(),
position: new.position,
duration: new.transition,
start_time: Instant::now(),
}),
None => Box::new(Fade {
text: new.text.clone(),
position: new.position,
duration: new.transition,
start_time: Instant::now(),
}),
}
}
}
#[derive(Default)]
pub struct CyberScreenPrivate {
state: Rc<RefCell<CyberScreenState>>,
// For crossfading to work, I have to detect that there is an old animation in a position, and
// replace it with the new one.
animations: Rc<RefCell<HashMap<Position, Box<dyn Animation>>>>,
}
#[glib::object_subclass]
impl ObjectSubclass for CyberScreenPrivate {
const NAME: &'static str = "CyberScreen";
type Type = CyberScreen;
type ParentType = gtk::DrawingArea;
}
impl ObjectImpl for CyberScreenPrivate {}
impl WidgetImpl for CyberScreenPrivate {}
impl DrawingAreaImpl for CyberScreenPrivate {}
impl CyberScreenPrivate {
fn set_script(&self, script: Script) {
*self.state.borrow_mut() = CyberScreenState::new(script);
}
fn next_page(&self) {
let transition = self.state.borrow_mut().next_page();
self.animations
.borrow_mut()
.insert(transition.position(), transition);
}
}
glib::wrapper! {
pub struct CyberScreen(ObjectSubclass<CyberScreenPrivate>) @extends gtk::DrawingArea, gtk::Widget;
}
impl CyberScreen {
fn new(script: Script) -> Self {
let s: Self = Object::builder().build();
s.imp().set_script(script);
s.set_draw_func({
let s = s.clone();
move |_, context, width, height| {
let now = Instant::now();
context.set_source_rgb(0., 0., 0.);
let _ = context.paint();
let pen = GlowPen::new(width, height, 2., 8., (0.7, 0., 1.));
AsymLineCutout {
orientation: gtk::Orientation::Horizontal,
start_x: 25.,
start_y: height as f64 / 7.,
start_length: width as f64 / 3.,
cutout_length: width as f64 / 3. - 100.,
height: 50.,
end_length: width as f64 / 3. - 50.,
invert: false,
}
.draw(&pen);
pen.stroke();
AsymLine {
orientation: gtk::Orientation::Horizontal,
start_x: width as f64 / 4.,
start_y: height as f64 * 6. / 7.,
start_length: width as f64 * 2. / 3. - 25.,
height: 50.,
end_length: 0.,
invert: false,
}
.draw(&pen);
pen.stroke();
let tracery = pen.finish();
let _ = context.set_source(tracery);
let _ = context.paint();
let animations = s.imp().animations.borrow_mut();
let lr_margin = 50.;
let max_width = width as f64 - lr_margin * 2.;
let region_height = height as f64 / 5.;
if let Some(animation) = animations.get(&Position::Top) {
let y = height as f64 * 1. / 5.;
let surface = context
.target()
.create_for_rectangle(Rectangle::new(20., y, max_width, region_height))
.unwrap();
let ctx = Context::new(&surface).unwrap();
animation.tick(now, &ctx, max_width);
}
if let Some(animation) = animations.get(&Position::Middle) {
let y = height as f64 * 2. / 5.;
let surface = context
.target()
.create_for_rectangle(Rectangle::new(20., y, max_width, region_height))
.unwrap();
let ctx = Context::new(&surface).unwrap();
animation.tick(now, &ctx, max_width);
}
if let Some(animation) = animations.get(&Position::Bottom) {
let y = height as f64 * 3. / 5.;
let surface = context
.target()
.create_for_rectangle(Rectangle::new(20., y, max_width, region_height))
.unwrap();
let ctx = Context::new(&surface).unwrap();
animation.tick(now, &ctx, max_width);
}
}
});
s
}
fn next_page(&self) {
self.imp().next_page();
self.queue_draw();
}
}
fn main() {
let script = Arc::new(RwLock::new(Script::default()));
let app = gtk::Application::builder()
.application_id("com.luminescent-dreams.cyberpunk-slideshow")
.build();
app.add_main_option(
"script",
glib::char::Char::from(b's'),
glib::OptionFlags::IN_MAIN,
glib::OptionArg::String,
"",
None,
);
app.connect_handle_local_options({
let script = script.clone();
move |_, options| {
if let Some(script_path) = options.lookup::<String>("script").unwrap() {
let mut script = script.write().unwrap();
*script = Script::from_file(Path::new(&script_path)).unwrap();
-1
} else {
1
}
}
});
app.connect_activate(move |app| {
let window = gtk::ApplicationWindow::new(app);
let screen = CyberScreen::new(script.read().unwrap().clone());
let events = EventControllerKey::new();
events.connect_key_released({
let app = app.clone();
let window = window.clone();
let screen = screen.clone();
move |_, key, _, _| {
let name = key
.name()
.map(|s| s.as_str().to_owned())
.unwrap_or("".to_owned());
match name.as_ref() {
"Right" => screen.next_page(),
"q" => app.quit(),
"Escape" => window.unfullscreen(),
_ => {}
}
}
});
window.add_controller(events);
window.set_child(Some(&screen));
window.set_width_request(800);
window.set_height_request(600);
window.present();
window.connect_maximized_notify(|window| {
window.fullscreen();
});
let _ = glib::spawn_future_local({
let screen = screen.clone();
async move {
loop {
screen.queue_draw();
async_std::task::sleep(Duration::from_millis(1000 / FPS)).await;
}
}
});
});
app.run();
}

View File

@ -7,9 +7,7 @@ license = "GPL-3.0-only"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std = "1.13.0"
cairo-rs = { version = "0.18" }
cyberpunk = { path = "../cyberpunk" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4" }

View File

@ -1,8 +1,7 @@
use cairo::{
Context, FontSlant, FontWeight, Format, ImageSurface, LinearGradient, Pattern,
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, LinearGradient, Pattern,
TextExtents,
};
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, SlashMeter};
use glib::Object;
use gtk::{prelude::*, subclass::prelude::*, EventControllerKey};
use std::{
@ -172,7 +171,7 @@ impl SplashPrivate {
start_y: extents.height() + 10.,
start_length: 0.,
height: extents.height() / 2.,
end_length: 0.,
total_length: extents.width() + extents.height() / 2.,
invert: false,
}
.draw(&pen);
@ -184,7 +183,7 @@ impl SplashPrivate {
start_y: extents.height() + 60.,
start_length: extents.width(),
height: extents.height() / 2.,
end_length: 0.,
total_length: extents.width() + extents.height() / 2.,
invert: false,
}
.draw(&pen);
@ -209,7 +208,7 @@ impl SplashPrivate {
start_x: 20.,
start_y: center_y - 20. - title_height / 2.,
start_length,
end_length: *self.width.borrow() as f64 - 120. - start_length,
total_length: *self.width.borrow() as f64 - 120.,
cutout_length: title_width,
height: title_height,
invert: false,
@ -244,7 +243,7 @@ impl SplashPrivate {
start_y: *self.height.borrow() as f64 / 2. + 100.,
start_length: 400.,
height: 50.,
end_length: 0.,
total_length: 650.,
invert: true,
}
.draw(&pen);
@ -259,7 +258,7 @@ impl SplashPrivate {
start_y: *self.height.borrow() as f64 / 2. + 200.,
start_length: 600.,
height: 50.,
end_length: 0.,
total_length: 650.,
invert: false,
}
.draw(&pen);
@ -420,6 +419,212 @@ impl Splash {
}
}
struct AsymLineCutout {
orientation: gtk::Orientation,
start_x: f64,
start_y: f64,
start_length: f64,
total_length: f64,
cutout_length: f64,
height: f64,
invert: bool,
}
impl AsymLineCutout {
fn draw(&self, pen: &impl Pen) {
let dodge = if self.invert {
self.height
} else {
-self.height
};
match self.orientation {
gtk::Orientation::Horizontal => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x + self.start_length, self.start_y);
pen.line_to(
self.start_x + self.start_length + self.height,
self.start_y + dodge,
);
pen.line_to(
self.start_x + self.start_length + self.height + self.cutout_length,
self.start_y + dodge,
);
pen.line_to(
self.start_x
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.),
self.start_y + dodge / 2.,
);
pen.line_to(self.total_length, self.start_y + dodge / 2.);
}
gtk::Orientation::Vertical => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x, self.start_y + self.start_length);
pen.line_to(
self.start_x + dodge,
self.start_y + self.start_length + self.height,
);
pen.line_to(
self.start_x + dodge,
self.start_y + self.start_length + self.height + self.cutout_length,
);
pen.line_to(
self.start_x + dodge / 2.,
self.start_y
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.),
);
pen.line_to(self.start_x + dodge / 2., self.total_length);
}
_ => panic!("unknown orientation"),
}
}
}
struct AsymLine {
orientation: gtk::Orientation,
start_x: f64,
start_y: f64,
start_length: f64,
height: f64,
total_length: f64,
invert: bool,
}
impl AsymLine {
fn draw(&self, pen: &impl Pen) {
let dodge = if self.invert {
self.height
} else {
-self.height
};
match self.orientation {
gtk::Orientation::Horizontal => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x + self.start_length, self.start_y);
pen.line_to(
self.start_x + self.start_length + self.height,
self.start_y + dodge,
);
pen.line_to(self.start_x + self.total_length, self.start_y + dodge);
}
gtk::Orientation::Vertical => {}
_ => panic!("unknown orientation"),
}
}
}
struct SlashMeter {
orientation: gtk::Orientation,
start_x: f64,
start_y: f64,
count: u8,
fill_count: u8,
height: f64,
length: f64,
}
impl SlashMeter {
fn draw(&self, context: &Context) {
match self.orientation {
gtk::Orientation::Horizontal => {
let angle: f64 = 0.8;
let run = self.height / angle.tan();
let width = self.length / (self.count as f64 * 2.);
for c in 0..self.count {
context.set_line_width(1.);
let start_x = self.start_x + c as f64 * width * 2.;
context.move_to(start_x, self.start_y);
context.line_to(start_x + run, self.start_y - self.height);
context.line_to(start_x + run + width, self.start_y - self.height);
context.line_to(start_x + width, self.start_y);
context.line_to(start_x, self.start_y);
if c < self.fill_count {
let _ = context.fill();
} else {
let _ = context.stroke();
}
}
}
gtk::Orientation::Vertical => {}
_ => panic!("unknown orientation"),
}
}
}
trait Pen {
fn move_to(&self, x: f64, y: f64);
fn line_to(&self, x: f64, y: f64);
fn stroke(&self);
fn finish(self) -> Pattern;
}
struct GlowPen {
blur_context: Context,
draw_context: Context,
}
impl GlowPen {
fn new(
width: i32,
height: i32,
line_width: f64,
blur_line_width: f64,
color: (f64, f64, f64),
) -> Self {
let blur_context =
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
blur_context.set_line_width(blur_line_width);
blur_context.set_source_rgba(color.0, color.1, color.2, 0.5);
blur_context.push_group();
blur_context.set_line_cap(LineCap::Round);
let draw_context =
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
draw_context.set_line_width(line_width);
draw_context.set_source_rgb(color.0, color.1, color.2);
draw_context.push_group();
draw_context.set_line_cap(LineCap::Round);
Self {
blur_context,
draw_context,
}
}
}
impl Pen for GlowPen {
fn move_to(&self, x: f64, y: f64) {
self.blur_context.move_to(x, y);
self.draw_context.move_to(x, y);
}
fn line_to(&self, x: f64, y: f64) {
self.blur_context.line_to(x, y);
self.draw_context.line_to(x, y);
}
fn stroke(&self) {
self.blur_context.stroke().expect("to draw the blur line");
self.draw_context
.stroke()
.expect("to draw the regular line");
}
fn finish(self) -> Pattern {
let foreground = self.draw_context.pop_group().unwrap();
self.blur_context.set_source(foreground).unwrap();
self.blur_context.paint().unwrap();
self.blur_context.pop_group().unwrap()
}
}
fn main() {
let app = gtk::Application::builder()
@ -497,7 +702,8 @@ fn main() {
});
app.connect_activate(move |app| {
let (gtk_tx, gtk_rx) = async_std::channel::unbounded();
let (gtk_tx, gtk_rx) =
gtk::glib::MainContext::channel::<State>(gtk::glib::Priority::DEFAULT);
let window = gtk::ApplicationWindow::new(app);
window.present();
@ -528,25 +734,19 @@ fn main() {
});
window.add_controller(keyboard_events);
glib::spawn_future_local({
let splash = splash.clone();
async move {
while let Ok(state) = gtk_rx.recv().await {
println!("received state");
splash.set_state(state);
}
}
gtk_rx.attach(None, move |state| {
splash.set_state(state);
glib::ControlFlow::Continue
});
glib::spawn_future_local({
std::thread::spawn({
let state = state.clone();
async move {
move || {
state.write().unwrap().start();
loop {
async_std::task::sleep(Duration::from_millis(1000 / 60)).await;
std::thread::sleep(Duration::from_millis(1000 / 60));
state.write().unwrap().run(Instant::now());
println!("state: {:?}", state.read().unwrap());
let _ = gtk_tx.send(*state.read().unwrap()).await;
let _ = gtk_tx.send(*state.read().unwrap());
}
}
});

View File

@ -1,12 +0,0 @@
[package]
name = "cyberpunk"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cairo-rs = { version = "0.18" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4" }

View File

@ -1,301 +0,0 @@
use cairo::{
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, Pattern,
TextExtents,
};
pub struct AsymLineCutout {
pub orientation: gtk::Orientation,
pub start_x: f64,
pub start_y: f64,
pub start_length: f64,
pub cutout_length: f64,
pub end_length: f64,
pub height: f64,
pub invert: bool,
}
impl AsymLineCutout {
pub fn draw(&self, pen: &impl Pen) {
let dodge = if self.invert {
self.height
} else {
-self.height
};
match self.orientation {
gtk::Orientation::Horizontal => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x + self.start_length, self.start_y);
pen.line_to(
self.start_x + self.start_length + self.height,
self.start_y + dodge,
);
pen.line_to(
self.start_x + self.start_length + self.height + self.cutout_length,
self.start_y + dodge,
);
pen.line_to(
self.start_x
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.),
self.start_y + dodge / 2.,
);
pen.line_to(
self.start_x
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.)
+ self.end_length,
self.start_y + dodge / 2.,
);
}
gtk::Orientation::Vertical => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x, self.start_y + self.start_length);
pen.line_to(
self.start_x + dodge,
self.start_y + self.start_length + self.height,
);
pen.line_to(
self.start_x + dodge,
self.start_y + self.start_length + self.height + self.cutout_length,
);
pen.line_to(
self.start_x + dodge / 2.,
self.start_y
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.),
);
pen.line_to(
self.start_x + dodge / 2.,
self.start_y
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.)
+ self.end_length,
);
}
_ => panic!("unknown orientation"),
}
}
}
// Represents an asymetrical line that starts at one location, then a 45-degree angle and then
// another line afterwards.
pub struct AsymLine {
// Will this be drawn left-to-right or up-to-down?
pub orientation: gtk::Orientation,
// Starting address
pub start_x: f64,
pub start_y: f64,
// Length of the first segment
pub start_length: f64,
// Height to dodge over to the next section
pub height: f64,
// Total length of the entire line.
pub end_length: f64,
// When normal, the angle dodge is upwards. When inverted, the angle dodge is downwards.
pub invert: bool,
}
impl AsymLine {
pub fn draw(&self, pen: &impl Pen) {
let dodge = if self.invert {
self.height
} else {
-self.height
};
match self.orientation {
gtk::Orientation::Horizontal => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x + self.start_length, self.start_y);
pen.line_to(
self.start_x + self.start_length + self.height,
self.start_y + dodge,
);
pen.line_to(
self.start_x + self.start_length + self.height + self.end_length,
self.start_y + dodge,
);
}
gtk::Orientation::Vertical => {}
_ => panic!("unknown orientation"),
}
}
}
pub struct SlashMeter {
pub orientation: gtk::Orientation,
pub start_x: f64,
pub start_y: f64,
pub count: u8,
pub fill_count: u8,
pub height: f64,
pub length: f64,
}
impl SlashMeter {
pub fn draw(&self, context: &Context) {
match self.orientation {
gtk::Orientation::Horizontal => {
let angle: f64 = 0.8;
let run = self.height / angle.tan();
let width = self.length / (self.count as f64 * 2.);
for c in 0..self.count {
context.set_line_width(1.);
let start_x = self.start_x + c as f64 * width * 2.;
context.move_to(start_x, self.start_y);
context.line_to(start_x + run, self.start_y - self.height);
context.line_to(start_x + run + width, self.start_y - self.height);
context.line_to(start_x + width, self.start_y);
context.line_to(start_x, self.start_y);
if c < self.fill_count {
let _ = context.fill();
} else {
let _ = context.stroke();
}
}
}
gtk::Orientation::Vertical => {}
_ => panic!("unknown orientation"),
}
}
}
/// Represents a pen for drawing a pattern. This is good for complex patterns that may require
/// multiple identical steps.
pub trait Pen {
/// Move the pen to a location.
fn move_to(&self, x: f64, y: f64);
/// Draw a line from the current location to the specified destination.
fn line_to(&self, x: f64, y: f64);
/// Instantiate the line.
fn stroke(&self);
/// Convert all of the drawing into a pattern that can be painted to a drawing context.
fn finish(self) -> Pattern;
}
pub struct GlowPen {
blur_context: Context,
draw_context: Context,
}
impl GlowPen {
pub fn new(
width: i32,
height: i32,
line_width: f64,
blur_line_width: f64,
color: (f64, f64, f64),
) -> Self {
let blur_context =
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
blur_context.set_line_width(blur_line_width);
blur_context.set_source_rgba(color.0, color.1, color.2, 0.5);
blur_context.push_group();
blur_context.set_line_cap(LineCap::Round);
let draw_context =
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
draw_context.set_line_width(line_width);
draw_context.set_source_rgb(color.0, color.1, color.2);
draw_context.push_group();
draw_context.set_line_cap(LineCap::Round);
Self {
blur_context,
draw_context,
}
}
}
impl Pen for GlowPen {
fn move_to(&self, x: f64, y: f64) {
self.blur_context.move_to(x, y);
self.draw_context.move_to(x, y);
}
fn line_to(&self, x: f64, y: f64) {
self.blur_context.line_to(x, y);
self.draw_context.line_to(x, y);
}
fn stroke(&self) {
self.blur_context.stroke().expect("to draw the blur line");
self.draw_context
.stroke()
.expect("to draw the regular line");
}
fn finish(self) -> Pattern {
let foreground = self.draw_context.pop_group().unwrap();
self.blur_context.set_source(foreground).unwrap();
self.blur_context.paint().unwrap();
self.blur_context.pop_group().unwrap()
}
}
pub struct Text<'a> {
content: Vec<String>,
context: &'a Context,
}
impl<'a> Text<'a> {
pub fn new(content: String, context: &'a Context, size: f64, width: f64) -> Self {
context.select_font_face("Alegreya Sans SC", FontSlant::Normal, FontWeight::Bold);
context.set_font_size(size);
let lines = word_wrap(content, context, width);
Self { content: lines, context }
}
pub fn extents(&self) -> TextExtents {
self.context.text_extents(&self.content[0]).unwrap()
}
pub fn draw(&self) {
let mut baseline = 0.;
for line in self.content.iter() {
baseline += self.context.text_extents(line).unwrap().height() + 10.;
self.context.move_to(0., baseline);
let _ = self.context.show_text(line);
}
}
}
fn word_wrap(content: String, context: &Context, max_width: f64) -> Vec<String> {
let mut lines = vec![];
let words: Vec<&str> = content.split_whitespace().collect();
let mut start: usize = 0;
let mut line = String::new();
for idx in 0..words.len() + 1 {
line = words[start..idx].join(" ");
let extents = context.text_extents(&line).unwrap();
if extents.width() > max_width {
let line = words[start..idx-1].join(" ");
start = idx-1;
lines.push(line.clone());
}
}
if !line.is_empty() {
lines.push(line);
}
lines
}

View File

@ -31,9 +31,7 @@ impl ApplicationWindow {
let provider = gtk::CssProvider::new();
provider.load_from_data(&stylesheet);
#[allow(deprecated)]
let context = window.style_context();
#[allow(deprecated)]
context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER);
let layout = gtk::Box::builder()

View File

@ -36,7 +36,6 @@ impl Default for TransitClock {
s.set_draw_func({
let s = s.clone();
move |_, context, width, height| {
#[allow(deprecated)]
let style_context = WidgetExt::style_context(&s);
let center_x = width as f64 / 2.;
let center_y = height as f64 / 2.;
@ -46,9 +45,7 @@ impl Default for TransitClock {
let sunrise = info.sunrise - NaiveTime::from_hms_opt(0, 0, 0).unwrap();
let sunset = info.sunset - NaiveTime::from_hms_opt(0, 0, 0).unwrap();
#[allow(deprecated)]
let night_color = style_context.lookup_color("dark_5").unwrap();
#[allow(deprecated)]
let day_color = style_context.lookup_color("blue_1").unwrap();
PieChart::new(&style_context)

View File

@ -1,7 +1,5 @@
use cairo::Context;
use gtk::{gdk::RGBA, prelude::*};
#[allow(deprecated)]
use gtk::StyleContext;
use gtk::{gdk::RGBA, prelude::*, StyleContext};
use std::f64::consts::PI;
#[derive(Clone, Debug)]
@ -29,9 +27,7 @@ pub struct PieChart {
}
impl PieChart {
#[allow(deprecated)]
pub fn new(style_context: &StyleContext) -> Self {
#[allow(deprecated)]
Self {
rotation: 0.,
wedges: vec![],

View File

@ -132,7 +132,7 @@ impl SolunaClient {
#[cfg(test)]
mod test {
use super::*;
use serde_json;
const EXAMPLE: &str = "{\"sunRise\":\"7:15\",\"sunTransit\":\"12:30\",\"sunSet\":\"17:45\",\"moonRise\":null,\"moonTransit\":\"7:30\",\"moonUnder\":\"19:54\",\"moonSet\":\"15:02\",\"moonPhase\":\"Waning Crescent\",\"moonIllumination\":0.35889454647387764,\"sunRiseDec\":7.25,\"sunTransitDec\":12.5,\"sunSetDec\":17.75,\"moonRiseDec\":null,\"moonSetDec\":15.033333333333333,\"moonTransitDec\":7.5,\"moonUnderDec\":19.9,\"minor1Start\":null,\"minor1Stop\":null,\"minor2StartDec\":14.533333333333333,\"minor2Start\":\"14:32\",\"minor2StopDec\":15.533333333333333,\"minor2Stop\":\"15:32\",\"major1StartDec\":6.5,\"major1Start\":\"06:30\",\"major1StopDec\":8.5,\"major1Stop\":\"08:30\",\"major2StartDec\":18.9,\"major2Start\":\"18:54\",\"major2StopDec\":20.9,\"major2Stop\":\"20:54\",\"dayRating\":1,\"hourlyRating\":{\"0\":20,\"1\":20,\"2\":0,\"3\":0,\"4\":0,\"5\":0,\"6\":20,\"7\":40,\"8\":40,\"9\":20,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":20,\"16\":20,\"17\":20,\"18\":40,\"19\":20,\"20\":20,\"21\":20,\"22\":0,\"23\":0}}";

View File

@ -9,7 +9,6 @@ documentation = "https://docs.rs/emseries"
homepage = "https://github.com/luminescent-dreams/emseries"
repository = "https://github.com/luminescent-dreams/emseries"
categories = ["database-implementations"]
edition = "2021"
include = [
"**/*.rs",

View File

@ -10,7 +10,7 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::types::{Recordable, Timestamp};
use types::{Recordable, Timestamp};
/// This trait is used for constructing queries for searching the database.
pub trait Criteria {

View File

@ -10,6 +10,10 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/
extern crate serde;
extern crate serde_json;
extern crate uuid;
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use std::cmp::Ordering;
@ -20,8 +24,8 @@ use std::fs::OpenOptions;
use std::io::{BufRead, BufReader, LineWriter, Write};
use std::iter::Iterator;
use crate::criteria::Criteria;
use crate::types::{EmseriesReadError, EmseriesWriteError, Record, RecordId, Recordable};
use criteria::Criteria;
use types::{EmseriesReadError, EmseriesWriteError, Record, RecordId, Recordable};
// A RecordOnDisk, a private data structure, is useful for handling all of the on-disk
// representations of a record. Unlike [Record], this one can accept an empty data value to

View File

@ -1,11 +0,0 @@
version: '3'
tasks:
build:
cmds:
- cargo build
lint:
cmds:
- cargo watch -x clippy

View File

@ -1,5 +1,3 @@
use std::fmt::Display;
use build_html::{self, Html, HtmlContainer};
#[derive(Clone, Debug, Default)]
@ -25,14 +23,13 @@ impl FromIterator<(&str, &str)> for Attributes {
}
*/
impl Display for Attributes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let result = self.0
impl ToString for Attributes {
fn to_string(&self) -> String {
self.0
.iter()
.map(|(key, value)| format!("{}=\"{}\"", key, value))
.collect::<Vec<String>>()
.join(" ");
write!(f, "{}", result)
.join(" ")
}
}
@ -205,7 +202,7 @@ impl Html for Button {
"<button {ty} {name} {attrs}>{label}</button>",
name = name,
label = self.label,
attrs = self.attributes
attrs = self.attributes.to_string()
)
}
}

View File

@ -118,21 +118,17 @@ impl Deref for FileId {
}
}
/*
pub trait FileRoot {
fn root(&self) -> PathBuf;
}
*/
// pub struct Context(PathBuf);
pub struct Context(PathBuf);
/*
impl FileRoot for Context {
fn root(&self) -> PathBuf {
self.0.clone()
}
}
*/
pub struct Store {
files_root: PathBuf,

View File

@ -153,7 +153,7 @@ mod test {
#[test]
#[ignore]
fn it_allows_valid_dates() {
let reference = chrono::NaiveDate::from_ymd_opt(2006, 1, 2).unwrap();
let reference = chrono::NaiveDate::from_ymd_opt(2006, 01, 02).unwrap();
let field = DateField::new(reference);
field.imp().year.set_value(Some(2023));
field.imp().month.set_value(Some(10));

View File

@ -80,10 +80,10 @@ impl TimeFormatter {
0 => Err(ParseError),
1 => Err(ParseError),
2 => chrono::NaiveTime::from_hms_opt(parts[0], parts[1], 0)
.map(TimeFormatter)
.map(|v| TimeFormatter(v))
.ok_or(ParseError),
3 => chrono::NaiveTime::from_hms_opt(parts[0], parts[1], parts[2])
.map(TimeFormatter)
.map(|v| TimeFormatter(v))
.ok_or(ParseError),
_ => Err(ParseError),
}

View File

@ -443,7 +443,7 @@ mod test {
async fn put_record(&self, record: TraxRecord) -> Result<RecordId, WriteError> {
let id = RecordId::default();
let record = Record {
id,
id: id,
data: record,
};
self.put_records.write().unwrap().push(record.clone());
@ -509,7 +509,7 @@ mod test {
Record {
id: RecordId::default(),
data: TraxRecord::TimeDistance(ft_core::TimeDistance {
datetime: oct_13_am,
datetime: oct_13_am.clone(),
activity: TimeDistanceActivity::Biking,
distance: Some(15000. * si::M),
duration: Some(3600. * si::S),

View File

@ -144,7 +144,7 @@ impl HistoricalView {
let mut model = gio::ListStore::new::<Date>();
let mut days = interval.days().map(Date::new).collect::<Vec<Date>>();
days.reverse();
model.extend(days);
model.extend(days.into_iter());
self.imp()
.list_view
.set_model(Some(&gtk::NoSelection::new(Some(model))));

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit
use chrono::SecondsFormat;
use chrono_tz::Etc::UTC;
use dimensioned::si;
use emseries::{Record, RecordId};
use emseries::{Record, RecordId, Series, Timestamp};
use ft_core::{self, DurationWorkout, DurationWorkoutActivity, SetRepActivity, TraxRecord};
use serde::{
de::{self, Visitor},
@ -26,7 +26,7 @@ use serde::{
use std::{
fmt,
fs::File,
io::{BufRead, BufReader},
io::{BufRead, BufReader, Read},
str::FromStr,
};

18
flake.lock generated
View File

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
@ -35,11 +35,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1714906307,
"narHash": "sha256-UlRZtrCnhPFSJlDQE7M0eyhgvuuHBTe1eJ9N9AQlJQ0=",
"lastModified": 1681303793,
"narHash": "sha256-JEdQHsYuCfRL2PICHlOiH/2ue3DwoxUX7DJ6zZxZXFk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "25865a40d14b3f9cf19f19b924e2ab4069b09588",
"rev": "fe2ecaf706a5907b5e54d979fbde4924d84b65fc",
"type": "github"
},
"original": {
@ -76,11 +76,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1731966246,
"narHash": "sha256-e/V7Ffm5wPd9DVzCThnPZ7lFxd43bb64tSk8/oGP4Ag=",
"lastModified": 1698205128,
"narHash": "sha256-jP+81TkldLtda8bzmsBWahETGsyFkoDOCT244YkA+S4=",
"owner": "1Password",
"repo": "typeshare",
"rev": "e0e5f27ee34d7d4da76a9dc96a11552e98be56da",
"rev": "c3ee2ad8f27774c45db7af4f2ba746c4ae11de21",
"type": "github"
},
"original": {

View File

@ -22,7 +22,6 @@
name = "ld-tools-devshell";
buildInputs = [
pkgs.cargo-nextest
pkgs.cargo-watch
pkgs.clang
pkgs.crate2nix
pkgs.glib
@ -35,17 +34,17 @@
pkgs.libadwaita
pkgs.librsvg
pkgs.nodejs
pkgs.nodePackages.eslint
pkgs.nodePackages.typescript
pkgs.nodePackages.typescript-language-server
pkgs.openssl
pkgs.pipewire
pkgs.pkg-config
pkgs.rustup
pkgs.sqlite
pkgs.cargo-nextest
pkgs.wasm-pack
pkgs.sqlx-cli
pkgs.udev
pkgs.wasm-pack
pkgs.go-task
typeshare.packages."x86_64-linux".default
pkgs.nodePackages_latest.typescript-language-server
];
@ -83,20 +82,18 @@
};
in rec {
cyber-slides = cargo_nix.workspaceMembers.cyber-slides.build;
cyberpunk-splash = cargo_nix.workspaceMembers.cyberpunk-splash.build;
dashboard = cargo_nix.workspaceMembers.dashboard.build;
# file-service = cargo_nix.workspaceMembers.file-service.build;
file-service = cargo_nix.workspaceMembers.file-service.build;
fitnesstrax = cargo_nix.workspaceMembers.fitnesstrax.build;
otg-gtk = cargo_nix.workspaceMembers.otg-gtk.build;
all = pkgs.symlinkJoin {
name = "all";
paths = [
cyber-slides
cyberpunk-splash
dashboard
# file-service
file-service
fitnesstrax
otg-gtk
];

View File

@ -294,21 +294,21 @@ mod tests {
use fluent_bundle::{FluentArgs, FluentValue};
use unic_langid::LanguageIdentifier;
const EN_TRANSLATIONS: &str = "
const EN_TRANSLATIONS: &'static str = "
preferences = Preferences
history = History
time_display = {$time} during the day
nested_display = nesting a time display: {time_display}
";
const EO_TRANSLATIONS: &str = "
const EO_TRANSLATIONS: &'static str = "
history = Historio
";
#[test]
fn translations() {
let en_id = "en-US".parse::<LanguageIdentifier>().unwrap();
let mut fluent = FluentErgo::new(&[en_id.clone()]);
let mut fluent = FluentErgo::new(&vec![en_id.clone()]);
fluent
.add_from_text(en_id, String::from(EN_TRANSLATIONS))
.expect("text should load");
@ -322,7 +322,7 @@ history = Historio
fn translation_fallback() {
let eo_id = "eo".parse::<LanguageIdentifier>().unwrap();
let en_id = "en".parse::<LanguageIdentifier>().unwrap();
let mut fluent = FluentErgo::new(&[eo_id.clone(), en_id.clone()]);
let mut fluent = FluentErgo::new(&vec![eo_id.clone(), en_id.clone()]);
fluent
.add_from_text(en_id, String::from(EN_TRANSLATIONS))
.expect("text should load");
@ -342,7 +342,7 @@ history = Historio
#[test]
fn placeholder_insertion_should_strip_placeholder_markers() {
let en_id = "en".parse::<LanguageIdentifier>().unwrap();
let mut fluent = FluentErgo::new(&[en_id.clone()]);
let mut fluent = FluentErgo::new(&vec![en_id.clone()]);
fluent
.add_from_text(en_id, String::from(EN_TRANSLATIONS))
.expect("text should load");
@ -357,7 +357,7 @@ history = Historio
#[test]
fn placeholder_insertion_should_strip_nested_placeholder_markers() {
let en_id = "en".parse::<LanguageIdentifier>().unwrap();
let mut fluent = FluentErgo::new(&[en_id.clone()]);
let mut fluent = FluentErgo::new(&vec![en_id.clone()]);
fluent
.add_from_text(en_id, String::from(EN_TRANSLATIONS))
.expect("text should load");

View File

@ -7,8 +7,8 @@ use std::iter::Iterator;
#[derive(Clone)]
pub struct ApplicationWindow {
pub window: adw::ApplicationWindow,
// pub layout: gtk::FlowBox,
// pub playlists: Vec<PlaylistCard>,
pub layout: gtk::FlowBox,
pub playlists: Vec<PlaylistCard>,
}
impl ApplicationWindow {
@ -31,9 +31,7 @@ impl ApplicationWindow {
let provider = gtk::CssProvider::new();
provider.load_from_data(&stylesheet);
#[allow(deprecated)]
let context = window.style_context();
#[allow(deprecated)]
context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER);
let layout = gtk::FlowBox::new();
@ -59,10 +57,8 @@ impl ApplicationWindow {
Self {
window,
/*
layout,
playlists,
*/
}
}
}

View File

@ -27,12 +27,12 @@ impl State {
fn add_audio(&self, device: String) {
let mut st = self.internal.write().unwrap();
st.device_list.push(device);
(*st).device_list.push(device);
}
fn audio_devices(&self) -> Vec<String> {
let st = self.internal.read().unwrap();
st.device_list.clone()
(*st).device_list.clone()
}
}

View File

@ -107,6 +107,8 @@ impl ObjectSubclass for HexGridWindowPrivate {
layout.append(&drawing_area);
layout.append(&sidebar);
layout.show();
Self {
drawing_area,
hex_address,

400
ifc/Cargo.lock generated Normal file
View File

@ -0,0 +1,400 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bumpalo"
version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
name = "cc"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cxx"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
[[package]]
name = "cxxbridge-macro"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "iana-time-zone"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "international-fixed-calendar"
version = "0.1.0"
dependencies = [
"chrono",
"serde",
"thiserror",
]
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "proc-macro2"
version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "scratch"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

14
ifc/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "ifc"
description = "chrono-compatible-ish date objects for the International Fixed Calendar"
version = "0.1.0"
authors = ["Savanni D'Gerinel <savanni@luminescent-dreams.com>"]
edition = "2018"
keywords = ["date", "time", "calendar"]
categories = ["date-and-time"]
license = "GPL-3.0-only"
[dependencies]
chrono = { version = "0.4" }
serde = { version = "1.0", features = ["derive"] }
thiserror = { version = "1" }

8
ifc/readme.md Normal file
View File

@ -0,0 +1,8 @@
# International Fixed Calendar
This is a fun project implementing a library for the [International Fixed Calendar](https://en.wikipedia.org/wiki/International_Fixed_Calendar).
This is at least somewhat compatible with [Chrono](https://github.com/chronotope/chrono), in that I have implemented these traits:
* `From<NaiveDate>`
* `Datelike`

1042
ifc/src/lib.rs Normal file

File diff suppressed because it is too large Load Diff

34
ifc/src/static/index.html Normal file
View File

@ -0,0 +1,34 @@
<html>
<head>
<title> {{month}} {{year}} </title>
<link href="/css" rel="stylesheet" type="text/css" media="screen" />
</head>
<body>
<h1> IFC Fixed Calendar: {{month}}, {{year}} years after the invention of agriculture </h1>
<table>
<thead>
<tr>
<th> Sunday </th>
<th> Monday </th>
<th> Tuesday </th>
<th> Wednesday </th>
<th> Thursday </th>
<th> Friday </th>
<th> Saturday </th>
</tr>
</thead>
<tbody>
{{#weeks}}
<tr>
{{#days}}
<td class="{{highlight}}"> {{day}} </td>
{{/days}}
</tr>
{{/weeks}}
</tbody>
</table>
</body>
</html>

35
ifc/src/static/month.html Normal file
View File

@ -0,0 +1,35 @@
<html>
<head>
<title>{{month}} {{year}}</title>
<link href="/css" rel="stylesheet" type="text/css" media="screen" />
</head>
<body>
<h1>
IFC Fixed Calendar: {{month}}, {{year}} years after the invention of
agriculture
</h1>
<table>
<thead>
<tr>
<th>Sunday</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
</tr>
</thead>
<tbody>
{{#weeks}}
<tr>
{{#days}}
<td class="{{highlight}}">{{day}}</td>
{{/days}}
</tr>
{{/weeks}}
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>{{day_out_of_time}} {{year}}</title>
<link href="/css" rel="stylesheet" type="text/css" media="screen" />
</head>
<body>
<h1>
IFC Fixed Calendar: {{day_out_of_time}}, {{year}} years after the
invention of agriculture
</h1>
</body>
</html>

18
ifc/src/static/styles.css Normal file
View File

@ -0,0 +1,18 @@
table {
width: 98%;
border: 1px solid black;
border-collapse: collapse;
}
th, td {
width: 14%;
font-family: sans-serif;
font-size: larger;
border: 1px solid black;
padding: 1em 0em 5em 1em;
}
.today {
background-color: rgb(200, 200, 255);
}

22
ifc/src/today.rs Normal file
View File

@ -0,0 +1,22 @@
/*
Copyright 2020-2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of the Luminescent Dreams Tools.
Luminescent Dreams Tools 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.
Luminescent Dreams Tools 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 Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/
extern crate chrono;
extern crate chrono_tz;
extern crate ifc as IFC;
use chrono::{Datelike, Utc};
fn main() {
let d = IFC::IFC::from(Utc::today());
println!("{} {}, {}", d.month(), d.day(), d.year());
}

119
ifc/src/web.rs Normal file
View File

@ -0,0 +1,119 @@
/*
Copyright 2020-2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of the Luminescent Dreams Tools.
Luminescent Dreams Tools 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.
Luminescent Dreams Tools 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 Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/
use chrono::{Datelike, Utc};
use ifc as IFC;
use iron::headers;
use iron::middleware::Handler;
use iron::modifiers::Header;
use iron::prelude::*;
use iron::status;
use mustache::{compile_str, Template};
use router::Router;
use serde::Serialize;
pub const STYLES: &'static str = include_str!("static/styles.css");
pub const INDEX: &'static str = include_str!("static/index.html");
pub struct IndexHandler {
pub template: Template,
}
#[derive(Serialize)]
pub struct DayEntry {
day: u8,
highlight: String,
}
#[derive(Serialize)]
pub struct Week {
days: Vec<DayEntry>,
}
impl Week {
fn new(start_day: u8, today: u8) -> Week {
Week {
days: (1..8)
.map(|d| DayEntry {
day: d + start_day,
highlight: if today == (d + start_day) {
String::from("today")
} else {
String::from("")
},
})
.collect(),
}
}
}
#[derive(Serialize)]
pub struct IndexTemplateParams {
month: String,
year: i32,
weeks: Vec<Week>,
}
impl IndexTemplateParams {
fn new(date: IFC::IFC) -> IndexTemplateParams {
let day = date.day() as u8;
IndexTemplateParams {
month: String::from(IFC::Month::from(date.month())),
year: date.year(),
weeks: (0..4).map(|wn| Week::new(wn * 7, day)).collect(),
}
}
}
impl Handler for IndexHandler {
fn handle(&self, _: &mut Request) -> IronResult<Response> {
let d = IFC::IFC::from(Utc::today());
Ok(Response::with((
status::Ok,
Header(headers::ContentType(iron::mime::Mime(
iron::mime::TopLevel::Text,
iron::mime::SubLevel::Html,
vec![],
))),
self.template
.render_to_string(&IndexTemplateParams::new(d))
.expect("the template to render"),
)))
}
}
fn css(_: &mut Request) -> IronResult<Response> {
Ok(Response::with((
status::Ok,
Header(headers::ContentType(iron::mime::Mime(
iron::mime::TopLevel::Text,
iron::mime::SubLevel::Css,
vec![],
))),
STYLES,
)))
}
fn main() {
let mut router = Router::new();
router.get(
"/",
IndexHandler {
template: compile_str(INDEX).expect("the template to compile"),
},
"index",
);
router.get("/css", css, "styles");
Iron::new(router).http("127.0.0.1:3000").unwrap();
}

View File

@ -78,7 +78,7 @@ mod tests {
})
.await;
assert_eq!(value, Value(16));
assert!(*run.read().unwrap());
assert_eq!(*run.read().unwrap(), true);
}
#[tokio::test]
@ -97,6 +97,6 @@ mod tests {
})
.await;
assert_eq!(value, Value(15));
assert!(!(*run.read().unwrap()));
assert_eq!(*run.read().unwrap(), false);
}
}

View File

@ -80,12 +80,12 @@ mod tests {
use super::*;
use cool_asserts::assert_matches;
const DATA: &str = "15";
const DATA: &'static str = "15";
#[test]
fn function() {
let resp = parse_number_a::<nom::error::VerboseError<&str>>()
.map(Container)
.map(|val| Container(val))
.parse(DATA);
assert_matches!(resp, Ok((_, content)) =>
assert_eq!(content, Container(15))
@ -95,7 +95,7 @@ mod tests {
#[test]
fn parser() {
let resp = parse_number_b::<nom::error::VerboseError<&str>>()
.map(Container)
.map(|val| Container(val))
.parse(DATA);
assert_matches!(resp, Ok((_, content)) =>
assert_eq!(content, Container(15))

View File

@ -396,7 +396,8 @@ mod test {
(Coordinate { column: 17, row: 0 }, Color::White),
(Coordinate { column: 17, row: 1 }, Color::White),
(Coordinate { column: 18, row: 1 }, Color::White),
],
]
.into_iter(),
)
.unwrap();
test(board);
@ -435,32 +436,33 @@ mod test {
},
Color::Black,
),
],
]
.into_iter(),
)
.unwrap();
assert!(board.group(&Coordinate { column: 18, row: 3 }).is_none());
assert_eq!(
board
.group(&Coordinate { column: 3, row: 3 })
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(4)
);
assert_eq!(
board
.group(&Coordinate { column: 0, row: 3 })
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(3)
);
assert_eq!(
board
.group(&Coordinate { column: 0, row: 0 })
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(2)
);
assert_eq!(
board
.group(&Coordinate { column: 18, row: 9 })
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(3)
);
assert_eq!(
@ -469,7 +471,7 @@ mod test {
column: 18,
row: 18
})
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(2)
);
}
@ -612,7 +614,7 @@ mod test {
for (board, coordinate, group, liberties) in test_cases {
assert_eq!(board.group(&coordinate), group.as_ref());
assert_eq!(
board.group(&coordinate).map(|g| board.liberties(g)),
board.group(&coordinate).map(|g| board.liberties(&g)),
liberties,
"{:?}",
coordinate
@ -686,11 +688,11 @@ mod test {
fn validate_group_comparisons() {
{
let b1 = Goban::from_coordinates(
vec![(Coordinate { column: 7, row: 9 }, Color::White)],
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
)
.unwrap();
let b2 = Goban::from_coordinates(
vec![(Coordinate { column: 7, row: 9 }, Color::White)],
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
)
.unwrap();
@ -702,14 +704,16 @@ mod test {
vec![
(Coordinate { column: 7, row: 9 }, Color::White),
(Coordinate { column: 8, row: 10 }, Color::White),
],
]
.into_iter(),
)
.unwrap();
let b2 = Goban::from_coordinates(
vec![
(Coordinate { column: 8, row: 10 }, Color::White),
(Coordinate { column: 7, row: 9 }, Color::White),
],
]
.into_iter(),
)
.unwrap();
@ -728,7 +732,8 @@ mod test {
(Coordinate { column: 10, row: 9 }, Color::Black),
(Coordinate { column: 9, row: 8 }, Color::Black),
(Coordinate { column: 9, row: 10 }, Color::Black),
],
]
.into_iter(),
)
.unwrap();

View File

@ -587,7 +587,8 @@ mod test {
(Coordinate { column: 17, row: 0 }, Color::White),
(Coordinate { column: 17, row: 1 }, Color::White),
(Coordinate { column: 18, row: 1 }, Color::White),
],
]
.into_iter(),
)
.unwrap();
state.current_player = Color::Black;
@ -611,7 +612,8 @@ mod test {
(Coordinate { column: 10, row: 9 }, Color::Black),
(Coordinate { column: 9, row: 8 }, Color::Black),
(Coordinate { column: 9, row: 10 }, Color::Black),
],
]
.into_iter(),
)
.unwrap();

View File

@ -132,10 +132,10 @@ impl GameReviewViewModel {
// the board state by applying the child.
pub fn next_move(&self) {
let mut inner = self.inner.write().unwrap();
let current_position = inner.current_position;
let current_position = inner.current_position.clone();
match current_position {
Some(current_position) => {
let current_id = current_position;
let current_id = current_position.clone();
let node = inner.game.trees[0].get(current_id).unwrap();
if let Some(next_id) = node.first_child().map(|child| child.node_id()) {
inner.current_position = Some(next_id);
@ -180,7 +180,7 @@ mod test {
where
F: FnOnce(GameReviewViewModel),
{
let records = sgf::parse_sgf_file(Path::new("../../sgf/test_data/branch_test.sgf"))
let records = sgf::parse_sgf_file(&Path::new("../../sgf/test_data/branch_test.sgf"))
.expect("to successfully load the test file");
let record = records[0]
.as_ref()

View File

@ -18,7 +18,7 @@ use crate::{CoreApi, ResourceManager};
use adw::prelude::*;
use glib::Propagation;
use gtk::EventControllerKey;
use gtk::{gdk::Key, EventControllerKey};
use otg_core::{
settings::{SettingsRequest, SettingsResponse},
CoreRequest, CoreResponse, GameReviewViewModel,

View File

@ -102,8 +102,14 @@ impl GameReview {
}
}
if let Some(ref mut goban) = *s.goban.borrow_mut() { goban.set_board_state(view.game_view()) };
if let Some(ref tree) = *s.review_tree.borrow() { tree.queue_draw() }
match *s.goban.borrow_mut() {
Some(ref mut goban) => goban.set_board_state(view.game_view()),
None => {}
};
match *s.review_tree.borrow() {
Some(ref tree) => tree.queue_draw(),
None => {}
}
Propagation::Stop
}
});
@ -163,6 +169,9 @@ impl GameReview {
*self.review_tree.borrow_mut() = Some(review_tree);
}
fn redraw(&self) {
}
pub fn widget(&self) -> gtk::Widget {
self.widget.clone().upcast::<gtk::Widget>()
}

10
otg/pwa/.eslintrc.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
env: {
node: true,
jest: true,
},
extends: ['plugin:@typescript-eslint/recommended', 'eslint:recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
root: true,
}

5
otg/pwa/jest.config.js Normal file
View File

@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

5210
otg/pwa/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
otg/pwa/package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "otg-pwa",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"lint": "eslint ./src/**/.ts",
"test": "jest",
"test:watch": "jest --watch"
},
"author": "Savanni D'Gerinel <savanni@luminescent-dreams.com>",
"license": "GPL-3.0-or-later",
"dependencies": {
"@typescript-eslint/eslint-plugin": "^7.10.0",
"@typescript-eslint/parser": "^7.10.0"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"http-server": "^14.1.1",
"jest": "^29.7.0",
"ts-jest": "^29.1.3"
}
}

View File

@ -0,0 +1,37 @@
import { c_to_k, f_to_k, k_to_c, k_to_f } from './conversions';
describe('temperature conversions', () => {
it('should convert celsius to kelvin', () => {
expect(c_to_k(0)).toBe(273.15);
expect(c_to_k(100)).toBe(373.15);
});
it('should convert fahrenheit to kelvin', () => {
expect(f_to_k(32)).toBe(273.15);
expect(f_to_k(212)).toBe(373.15);
});
it('should convert kelvin to celsius', () => {
expect(k_to_c(273.15)).toBe(0);
expect(k_to_c(373.15)).toBe(100);
});
it('should convert kelvin to fahrenheit', () => {
expect(k_to_f(273.15)).toBe(32);
expect(k_to_f(373.15)).toBe(212);
});
});
/*
import { getMessage } from './index'
describe('getMessage()', () => {
it('should return the correct message when called', () => {
expect(getMessage()).toBe('Hello, world')
})
it('should be super smart', () => {
expect(true).toBe(true)
})
})
*/

View File

@ -0,0 +1,6 @@
export const c_to_k = (value: number) => value + 273.15;
export const f_to_k = (value: number) => ((value - 32) * 5 / 9) + 273.15;
export const k_to_c = (value: number) => value - 273.15;
export const k_to_f = (value: number) => (value - 273.15) * 9 / 5 + 32;

18
otg/pwa/src/index.html Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>On The Grid</title>
<style>
:body {
font-family: "sans-serif";
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.js"></script>
</body>
</html>

83
otg/pwa/src/index.ts Normal file
View File

@ -0,0 +1,83 @@
import { c_to_k, f_to_k, k_to_c, k_to_f } from './conversions';
interface TemperatureChanged {
source: string,
value: number,
}
class TemperatureField extends HTMLElement {
static observedAttributes = ['value'];
value = 0;
inputElement: HTMLInputElement;
shadow: ShadowRoot;
constructor() {
super();
this.inputElement = document.createElement('input');
this.inputElement.type = "text";
this.shadow = this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.inputElement.onchange = ev => {
if (ev.target instanceof HTMLInputElement) {
let value = parseFloat(ev.target.value);
if (this.id == "f") {
const event = new CustomEvent('temperature-changed', { detail: { source: this.id, value: f_to_k(value) } });
this.dispatchEvent(event);
} else if (this.id == "c") {
const event = new CustomEvent('temperature-changed', { detail: { source: this.id, value: c_to_k(value) } });
this.dispatchEvent(event);
} else if (this.id == "k") {
const event = new CustomEvent('temperature-changed', { detail: { source: this.id, value } });
this.dispatchEvent(event);
}
}
}
}
render() {
this.shadow.appendChild(this.inputElement);
}
setTemperature(v: number) {
console.log("set the temperature on ", this.id, "to ", v);
}
}
class App {
temperatureK = 0;
updateTemperature(value: number) {
const f = document.getElementById("f");
if (f instanceof TemperatureField) {
f.setTemperature(value);
}
const c = document.getElementById("c");
if (c instanceof TemperatureField) {
c.setTemperature(value);
}
const k = document.getElementById("k");
if (k instanceof TemperatureField) {
k.setTemperature(value);
}
}
}
window.customElements.define('temperature-field', TemperatureField);
const app = new App();
document.getElementById("f")?.addEventListener('temperature-changed', ev => {
if (ev instanceof CustomEvent) { app.updateTemperature(ev.detail.value) }
});
document.getElementById("c")?.addEventListener('temperature-changed', ev => {
if (ev instanceof CustomEvent) { app.updateTemperature(ev.detail.value) }
});
document.getElementById("k")?.addEventListener('temperature-changed', ev => {
if (ev instanceof CustomEvent) { app.updateTemperature(ev.detail.value) }
});

View File

@ -1,15 +1,13 @@
{
"compilerOptions": {
"target": "es2016",
"strict": true,
"target": "ESNext",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["./visions.ts"]
}
}

View File

@ -1,12 +0,0 @@
[build]
target = "thumbv6m-none-eabi"
[target.thumbv6m-none-eabi]
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "llvm-args=--inline-threshold=5",
"-C", "no-vectorize-loops",
]
runner = "elf2uf2-rs -d"

View File

@ -1,11 +0,0 @@
[package]
name = "halloween-leds"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m-rt = "0.7.3"
embedded-hal = "1.0.0"
embedded-io = "0.6.1"
panic-halt = "1.0.0"
rp-pico = "0.9.0"

View File

@ -1,125 +0,0 @@
#![no_main]
#![no_std]
/// This application demonstrates using a Raspberry Pi Pico to control an individual SK9822 module.
/// Keep in mind that the Pico, though it accepts 5V for power, it runs on 3.3V logic. The GPIO
/// pins will emit only 3.3 volts, and the SK9822 needs 5V logic. So, make sure that the GPIO pins
/// run through a transistor or a logic level lhifter to go from 3.3V logic to 5V logic.
use embedded_hal::{delay::DelayNs, spi::SpiBus};
use panic_halt as _;
use rp_pico::{
entry,
hal::{
clocks::init_clocks_and_plls,
fugit::RateExtU32,
gpio::{
bank0::{Gpio10, Gpio11},
FunctionSpi, Pin, PullDown,
},
spi::Spi,
Clock, Sio, Timer, Watchdog,
},
pac, Pins,
};
const FPS: u32 = 60;
const MS_PER_FRAME: u32 = 1000 / FPS;
const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // MHz, https://forums.raspberrypi.com/viewtopic.php?t=356764
#[entry]
unsafe fn main() -> ! {
// rp_pico::pac::Peripherals is a reference to physical hardware defined on the Pico.
let mut peripherals = pac::Peripherals::take().unwrap();
// SIO inidcates "Single Cycle IO". I don't know what this means, but it could mean that this
// is a class of IO operations that can be run in a single clock cycle, such as switching a
// GPIO pin on or off.
let sio = Sio::new(peripherals.SIO);
// Many of the following systems require a watchdog. I do not know what this does, either, but
// it may be some failsafe software that will reset operations if the watchdog detects a lack
// of activity.
let mut watchdog = Watchdog::new(peripherals.WATCHDOG);
// Here we grab the GPIO pins in bank 0.
let pins = Pins::new(
peripherals.IO_BANK0,
peripherals.PADS_BANK0,
sio.gpio_bank0,
&mut peripherals.RESETS,
);
// Initialize an abstraction of the clock system with a batch of standard hardware clocks.
let clocks = init_clocks_and_plls(
XOSC_CRYSTAL_FREQ,
peripherals.XOSC,
peripherals.CLOCKS,
peripherals.PLL_SYS,
peripherals.PLL_USB,
&mut peripherals.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// An abstraction for a timer which we can use to delay the code.
let mut timer = Timer::new(peripherals.TIMER, &mut peripherals.RESETS, &clocks);
// Grab the clock and data pins for SPI1. For Clock pins and for Data pins, there are only two
// pins each on the Pico which can function for SPI1.
let spi_clk: Pin<Gpio10, FunctionSpi, PullDown> = pins.gpio10.into_function();
let spi_sdo: Pin<Gpio11, FunctionSpi, PullDown> = pins.gpio11.into_function();
// Now, create the SPI function abstraction for SPI1 with spi_clk and spi_sdo.
let mut spi = Spi::<_, _, _, 8>::new(peripherals.SPI1, (spi_sdo, spi_clk)).init(
&mut peripherals.RESETS,
// The SPI system uses the peripheral clock
clocks.peripheral_clock.freq(),
// Transmit data at a rate of 1Mbit.
1_u32.MHz(),
// Run with SPI Mode 1. This means that the clock line should start high and that data will
// be sampled starting at the first falling edge.
embedded_hal::spi::MODE_1,
);
// byte count: 4 for the start frame
// 1 * 4 for three lights with 4 bytes per light
// 4 for the end frame
// = 20 bytes
let mut lights: [u8; 12] = [0; 12];
// We just skip the first four bytes, because the start frame is four bytes of 0.
// Set the first byte of the one and only lamp. The first byte follows the pattern of three 1
// bits followed by five additional bits that indicate an overall brightness level of the
// pixel. The datasheet for the SK9822 doesn't specify the exact effect, but it does mean that
// the higher this number is, the brighter 255 means for an given LED in the array. 1 is the
// lowest brightness that emits light, and 31 is the highest supported brightness.
lights[4] = 0xe0 + 1;
// Set the Blue light of the dotstar to 255, assuming the dotstar frame format is RBG. Note
// that the standard SK9822 datasheed indicates that the format is BGR. Your mileage may vary.
lights[6] = 255;
// The end frame is four bytes of 255.
lights[8] = 0xff;
lights[9] = 0xff;
lights[10] = 0xff;
lights[11] = 0xff;
// The rest of this is just a stock pulsating animation which is slightly brightening and
// dimming the *blue* LED (on my set of dotstars).
let mut brightness = 1;
let mut step = 1;
loop {
if brightness == 64 && step == 1 {
step = -1;
} else if brightness == 1 && step == -1 {
step = 1;
};
lights[5] = brightness as u8;
brightness = brightness + step;
let _ = spi.write(lights.as_slice());
timer.delay_ms(MS_PER_FRAME);
}
}

View File

@ -33,9 +33,9 @@ use std::{error::Error, fmt};
/// statement.
pub trait FatalError: Error {}
/// ResultExt<A, FE, E> represents a return value that might be a success, might be a fatal error, or
/// Result<A, FE, E> represents a return value that might be a success, might be a fatal error, or
/// might be a normal handleable error.
pub enum ResultExt<A, E, FE> {
pub enum Result<A, E, FE> {
/// The operation was successful
Ok(A),
/// Ordinary errors. These should be handled and the application should recover gracefully.
@ -45,72 +45,72 @@ pub enum ResultExt<A, E, FE> {
Fatal(FE),
}
impl<A, E, FE> ResultExt<A, E, FE> {
impl<A, E, FE> Result<A, E, FE> {
/// Apply an infallible function to a successful value.
pub fn map<B, O>(self, mapper: O) -> ResultExt<B, E, FE>
pub fn map<B, O>(self, mapper: O) -> Result<B, E, FE>
where
O: FnOnce(A) -> B,
{
match self {
ResultExt::Ok(val) => ResultExt::Ok(mapper(val)),
ResultExt::Err(err) => ResultExt::Err(err),
ResultExt::Fatal(err) => ResultExt::Fatal(err),
Result::Ok(val) => Result::Ok(mapper(val)),
Result::Err(err) => Result::Err(err),
Result::Fatal(err) => Result::Fatal(err),
}
}
/// Apply a potentially fallible function to a successful value.
///
/// Like `Result.and_then`, the mapping function can itself fail.
pub fn and_then<B, O>(self, handler: O) -> ResultExt<B, E, FE>
pub fn and_then<B, O>(self, handler: O) -> Result<B, E, FE>
where
O: FnOnce(A) -> ResultExt<B, E, FE>,
O: FnOnce(A) -> Result<B, E, FE>,
{
match self {
ResultExt::Ok(val) => handler(val),
ResultExt::Err(err) => ResultExt::Err(err),
ResultExt::Fatal(err) => ResultExt::Fatal(err),
Result::Ok(val) => handler(val),
Result::Err(err) => Result::Err(err),
Result::Fatal(err) => Result::Fatal(err),
}
}
/// Map a normal error from one type to another. This is useful for converting an error from
/// one type to another, especially in re-throwing an underlying error. `?` syntax does not
/// work with `Result`, so you will likely need to use this a lot.
pub fn map_err<F, O>(self, mapper: O) -> ResultExt<A, F, FE>
pub fn map_err<F, O>(self, mapper: O) -> Result<A, F, FE>
where
O: FnOnce(E) -> F,
{
match self {
ResultExt::Ok(val) => ResultExt::Ok(val),
ResultExt::Err(err) => ResultExt::Err(mapper(err)),
ResultExt::Fatal(err) => ResultExt::Fatal(err),
Result::Ok(val) => Result::Ok(val),
Result::Err(err) => Result::Err(mapper(err)),
Result::Fatal(err) => Result::Fatal(err),
}
}
/// Provide a function to use to recover from (or simply re-throw) an error.
pub fn or_else<O, F>(self, handler: O) -> ResultExt<A, F, FE>
pub fn or_else<O, F>(self, handler: O) -> Result<A, F, FE>
where
O: FnOnce(E) -> ResultExt<A, F, FE>,
O: FnOnce(E) -> Result<A, F, FE>,
{
match self {
ResultExt::Ok(val) => ResultExt::Ok(val),
ResultExt::Err(err) => handler(err),
ResultExt::Fatal(err) => ResultExt::Fatal(err),
Result::Ok(val) => Result::Ok(val),
Result::Err(err) => handler(err),
Result::Fatal(err) => Result::Fatal(err),
}
}
}
/// Convert from a normal `Result` type to a `ResultExt` type. The error condition for a `Result` will
/// Convert from a normal `Result` type to a `Result` type. The error condition for a `Result` will
/// be treated as `Result::Err`, never `Result::Fatal`.
impl<A, E, FE> From<std::result::Result<A, E>> for ResultExt<A, E, FE> {
impl<A, E, FE> From<std::result::Result<A, E>> for Result<A, E, FE> {
fn from(r: std::result::Result<A, E>) -> Self {
match r {
Ok(val) => ResultExt::Ok(val),
Err(err) => ResultExt::Err(err),
Ok(val) => Result::Ok(val),
Err(err) => Result::Err(err),
}
}
}
impl<A, E, FE> fmt::Debug for ResultExt<A, E, FE>
impl<A, E, FE> fmt::Debug for Result<A, E, FE>
where
A: fmt::Debug,
FE: fmt::Debug,
@ -118,14 +118,14 @@ where
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResultExt::Ok(val) => f.write_fmt(format_args!("Result::Ok {:?}", val)),
ResultExt::Err(err) => f.write_fmt(format_args!("Result::Err {:?}", err)),
ResultExt::Fatal(err) => f.write_fmt(format_args!("Result::Fatal {:?}", err)),
Result::Ok(val) => f.write_fmt(format_args!("Result::Ok {:?}", val)),
Result::Err(err) => f.write_fmt(format_args!("Result::Err {:?}", err)),
Result::Fatal(err) => f.write_fmt(format_args!("Result::Fatal {:?}", err)),
}
}
}
impl<A, E, FE> PartialEq for ResultExt<A, E, FE>
impl<A, E, FE> PartialEq for Result<A, E, FE>
where
A: PartialEq,
FE: PartialEq,
@ -133,27 +133,27 @@ where
{
fn eq(&self, rhs: &Self) -> bool {
match (self, rhs) {
(ResultExt::Ok(val), ResultExt::Ok(rhs)) => val == rhs,
(ResultExt::Err(_), ResultExt::Err(_)) => true,
(ResultExt::Fatal(_), ResultExt::Fatal(_)) => true,
(Result::Ok(val), Result::Ok(rhs)) => val == rhs,
(Result::Err(_), Result::Err(_)) => true,
(Result::Fatal(_), Result::Fatal(_)) => true,
_ => false,
}
}
}
/// Convenience function to create an ok value.
pub fn ok<A, E: Error, FE: FatalError>(val: A) -> ResultExt<A, E, FE> {
ResultExt::Ok(val)
pub fn ok<A, E: Error, FE: FatalError>(val: A) -> Result<A, E, FE> {
Result::Ok(val)
}
/// Convenience function to create an error value.
pub fn error<A, E: Error, FE: FatalError>(err: E) -> ResultExt<A, E, FE> {
ResultExt::Err(err)
pub fn error<A, E: Error, FE: FatalError>(err: E) -> Result<A, E, FE> {
Result::Err(err)
}
/// Convenience function to create a fatal value.
pub fn fatal<A, E: Error, FE: FatalError>(err: FE) -> ResultExt<A, E, FE> {
ResultExt::Fatal(err)
pub fn fatal<A, E: Error, FE: FatalError>(err: FE) -> Result<A, E, FE> {
Result::Fatal(err)
}
/// Return early from the current function if the value is a fatal error.
@ -161,9 +161,9 @@ pub fn fatal<A, E: Error, FE: FatalError>(err: FE) -> ResultExt<A, E, FE> {
macro_rules! return_fatal {
($x:expr) => {
match $x {
ResultExt::Fatal(err) => return ResultExt::Fatal(err),
ResultExt::Err(err) => Err(err),
ResultExt::Ok(val) => Ok(val),
Result::Fatal(err) => return Result::Fatal(err),
Result::Err(err) => Err(err),
Result::Ok(val) => Ok(val),
}
};
}
@ -173,9 +173,9 @@ macro_rules! return_fatal {
macro_rules! return_error {
($x:expr) => {
match $x {
ResultExt::Ok(val) => val,
ResultExt::Err(err) => return ResultExt::Err(err),
ResultExt::Fatal(err) => return ResultExt::Fatal(err),
Result::Ok(val) => val,
Result::Err(err) => return Result::Err(err),
Result::Fatal(err) => return Result::Fatal(err),
}
};
}
@ -210,19 +210,19 @@ mod test {
#[test]
fn it_can_map_things() {
let success: ResultExt<i32, Error, FatalError> = ok(15);
let success: Result<i32, Error, FatalError> = ok(15);
assert_eq!(ok(16), success.map(|v| v + 1));
}
#[test]
fn it_can_chain_success() {
let success: ResultExt<i32, Error, FatalError> = ok(15);
let success: Result<i32, Error, FatalError> = ok(15);
assert_eq!(ok(16), success.and_then(|v| ok(v + 1)));
}
#[test]
fn it_can_handle_an_error() {
let failure: ResultExt<i32, Error, FatalError> = error(Error::Error);
let failure: Result<i32, Error, FatalError> = error(Error::Error);
assert_eq!(
ok::<i32, Error, FatalError>(16),
failure.or_else(|_| ok(16))
@ -231,7 +231,7 @@ mod test {
#[test]
fn early_exit_on_fatal() {
fn ok_func() -> ResultExt<i32, Error, FatalError> {
fn ok_func() -> Result<i32, Error, FatalError> {
let value = return_fatal!(ok::<i32, Error, FatalError>(15));
match value {
Ok(_) => ok(14),
@ -239,7 +239,7 @@ mod test {
}
}
fn err_func() -> ResultExt<i32, Error, FatalError> {
fn err_func() -> Result<i32, Error, FatalError> {
let value = return_fatal!(error::<i32, Error, FatalError>(Error::Error));
match value {
Ok(_) => panic!("shouldn't have gotten here"),
@ -247,7 +247,7 @@ mod test {
}
}
fn fatal_func() -> ResultExt<i32, Error, FatalError> {
fn fatal_func() -> Result<i32, Error, FatalError> {
let _ = return_fatal!(fatal::<i32, Error, FatalError>(FatalError::FatalError));
panic!("failed to bail");
}
@ -259,18 +259,18 @@ mod test {
#[test]
fn it_can_early_exit_on_all_errors() {
fn ok_func() -> ResultExt<i32, Error, FatalError> {
fn ok_func() -> Result<i32, Error, FatalError> {
let value = return_error!(ok::<i32, Error, FatalError>(15));
assert_eq!(value, 15);
ok(14)
}
fn err_func() -> ResultExt<i32, Error, FatalError> {
fn err_func() -> Result<i32, Error, FatalError> {
return_error!(error::<i32, Error, FatalError>(Error::Error));
panic!("failed to bail");
}
fn fatal_func() -> ResultExt<i32, Error, FatalError> {
fn fatal_func() -> Result<i32, Error, FatalError> {
return_error!(fatal::<i32, Error, FatalError>(FatalError::FatalError));
panic!("failed to bail");
}

View File

@ -1,4 +1,3 @@
[toolchain]
channel = "1.81.0"
channel = "1.77.0"
targets = [ "wasm32-unknown-unknown", "thumbv6m-none-eabi" ]
components = [ "rustfmt", "rust-analyzer", "clippy" ]

View File

@ -7,6 +7,5 @@ license = "GPL-3.0-only"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std = "1.13.0"
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4" }

View File

@ -66,8 +66,9 @@ impl Screenplay {
/// This function currently returns no errors, instead panicing if anything goes wrong.
pub fn new(gtk_app: &gtk::Application, screens: Vec<Screen>) -> Result<Self, Error> {
let window = gtk::ApplicationWindow::new(gtk_app);
window.show();
let (sender, receiver) = async_std::channel::unbounded();
let (sender, receiver) = gtk::glib::MainContext::channel(gtk::glib::Priority::DEFAULT);
let layout = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
@ -89,15 +90,11 @@ impl Screenplay {
layout.append(&frame);
listbox.connect_row_activated(move |_, row| {
let sender = sender.clone();
let row = row.clone();
glib::spawn_future_local(async move {
match row.index() {
-1 => sender.send(Action::Deselect).await,
idx => sender.send(Action::SelectPage(idx as usize)).await,
}
.unwrap()
});
match row.index() {
-1 => sender.send(Action::Deselect),
idx => sender.send(Action::SelectPage(idx as usize)),
}
.unwrap()
});
screens.iter().for_each(|Screen { title, .. }| {
@ -111,14 +108,8 @@ impl Screenplay {
let storybook = Self { frame, screens };
{
glib::spawn_future_local({
let mut storybook = storybook.clone();
async move {
while let Ok(msg) = receiver.recv().await {
storybook.process_action(msg);
}
}
});
let mut storybook = storybook.clone();
receiver.attach(None, move |message| storybook.process_action(message));
}
Ok(storybook)

View File

@ -1,18 +0,0 @@
- text: The distinguishing thing about magic is that it includes some kind of personal element. The person who is performing the magic is relevant to the magic. -- Ted Chang, Marie Brennan
position: top
transition:
secs: 1
nanos: 0
- text: Any sufficiently advanced technology is indistinguishable from magic. -- Arthur C. Clark.
position: middle
transition:
secs: 1
nanos: 0
- text: Science is our Magic.
position: bottom
transition:
secs: 1
nanos: 0

View File

@ -10,7 +10,7 @@ use nom::{
IResult, Parser,
};
use serde::{Deserialize, Serialize};
use std::{fmt::Display, num::ParseIntError, time::Duration};
use std::{fmt::Write, num::ParseIntError, time::Duration};
impl From<ParseSizeError> for Error {
fn from(_: ParseSizeError) -> Self {
@ -142,9 +142,9 @@ impl From<&GameType> for String {
}
}
impl Display for GameType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", String::from(self))
impl ToString for GameType {
fn to_string(&self) -> String {
String::from(self)
}
}
@ -189,15 +189,12 @@ pub struct Tree {
pub root: Node,
}
impl Display for Tree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({})", self.root)
impl ToString for Tree {
fn to_string(&self) -> String {
format!("({})", self.root.to_string())
}
}
struct Properties<'a>(&'a Vec<Property>);
struct Nodes<'a>(&'a Vec<Node>);
#[derive(Clone, Debug, PartialEq)]
pub struct Node {
pub properties: Vec<Property>,
@ -267,12 +264,8 @@ impl Node {
}
}
impl Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, ";{}", Properties(&self.properties))?;
write!(f, "{}", Nodes(&self.next))?;
Ok(())
/*
impl ToString for Node {
fn to_string(&self) -> String {
let props = self
.properties
.iter()
@ -288,36 +281,11 @@ impl Display for Node {
} else {
self.next
.iter()
.map(|node| write!(f, "({})", node.to_string()))
.map(|node| format!("({})", node.to_string()))
.collect::<Vec<String>>()
.join("")
};
write!(f, ";{}{}", props, next)
*/
}
}
impl Display for Properties<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for property in self.0.iter() {
write!(f, "{}", property)?;
}
Ok(())
}
}
impl Display for Nodes<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0.len() == 1 {
for node in self.0.iter() {
write!(f, "{}", node)?;
}
} else {
for node in self.0.iter() {
write!(f, "({})", node)?;
}
}
Ok(())
format!(";{}{}", props, next)
}
}
@ -492,96 +460,95 @@ pub struct UnknownProperty {
pub value: String,
}
impl Display for Property {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl ToString for Property {
fn to_string(&self) -> String {
match self {
Property::Move((color, Move::Move(mv))) => {
write!(f, "{}[{}]", color.abbreviation(), mv)
format!("{}[{}]", color.abbreviation(), mv)
}
Property::Move((color, Move::Pass)) => {
write!(f, "{}[]", color.abbreviation())
format!("{}[]", color.abbreviation())
}
Property::TimeLeft((color, time)) => {
write!(f, "{}[{}]", color.abbreviation(), time.as_secs())
format!("{}[{}]", color.abbreviation(), time.as_secs())
}
Property::Comment(value) => write!(f, "C[{}]", value),
Property::Annotation(Annotation::BadMove) => write!(f, "BM[]"),
Property::Annotation(Annotation::DoubtfulMove) => write!(f, "DO[]"),
Property::Annotation(Annotation::InterestingMove) => write!(f, "IT[]"),
Property::Annotation(Annotation::Tesuji) => write!(f, "TE[]"),
Property::Application(app) => write!(f, "AP[{}]", app),
Property::Charset(set) => write!(f, "CA[{}]", set),
Property::FileFormat(ff) => write!(f, "FF[{}]", ff),
Property::GameType(gt) => write!(f, "GM[{}]", gt),
Property::Comment(value) => format!("C[{}]", value),
Property::Annotation(Annotation::BadMove) => "BM[]".to_owned(),
Property::Annotation(Annotation::DoubtfulMove) => "DO[]".to_owned(),
Property::Annotation(Annotation::InterestingMove) => "IT[]".to_owned(),
Property::Annotation(Annotation::Tesuji) => "TE[]".to_owned(),
Property::Application(app) => format!("AP[{}]", app),
Property::Charset(set) => format!("CA[{}]", set),
Property::FileFormat(ff) => format!("FF[{}]", ff),
Property::GameType(gt) => format!("GM[{}]", gt.to_string()),
Property::VariationDisplay => unimplemented!(),
Property::BoardSize(Size { width, height }) => {
if width == height {
write!(f, "SZ[{}]", width)
format!("SZ[{}]", width)
} else {
write!(f, "SZ[{}:{}]", width, height)
format!("SZ[{}:{}]", width, height)
}
}
Property::SetupBlackStones(positions) => {
write!(f, "AB[{}]", positions.compressed_list(),)
format!("AB[{}]", positions.compressed_list(),)
}
Property::ClearStones(positions) => {
write!(f, "AE[{}]", positions.compressed_list(),)
format!("AE[{}]", positions.compressed_list(),)
}
Property::SetupWhiteStones(positions) => {
write!(f, "AW[{}]", positions.compressed_list(),)
format!("AW[{}]", positions.compressed_list(),)
}
Property::NextPlayer(color) => write!(f, "PL[{}]", color.abbreviation()),
Property::Evaluation(Evaluation::Even) => write!(f, "DM[]"),
Property::Evaluation(Evaluation::GoodForBlack) => write!(f, "GB[]"),
Property::Evaluation(Evaluation::GoodForWhite) => write!(f, "GW[]"),
Property::Evaluation(Evaluation::Unclear) => write!(f, "UC[]"),
Property::Hotspot => write!(f, "HO[]"),
Property::Value(value) => write!(f, "V[{}]", value),
Property::Annotator(value) => write!(f, "AN[{}]", value),
Property::BlackRank(value) => write!(f, "BR[{}]", value),
Property::BlackTeam(value) => write!(f, "BT[{}]", value),
Property::Copyright(value) => write!(f, "CP[{}]", value),
Property::NextPlayer(color) => format!("PL[{}]", color.abbreviation()),
Property::Evaluation(Evaluation::Even) => "DM[]".to_owned(),
Property::Evaluation(Evaluation::GoodForBlack) => "GB[]".to_owned(),
Property::Evaluation(Evaluation::GoodForWhite) => "GW[]".to_owned(),
Property::Evaluation(Evaluation::Unclear) => "UC[]".to_owned(),
Property::Hotspot => "HO[]".to_owned(),
Property::Value(value) => format!("V[{}]", value),
Property::Annotator(value) => format!("AN[{}]", value),
Property::BlackRank(value) => format!("BR[{}]", value),
Property::BlackTeam(value) => format!("BT[{}]", value),
Property::Copyright(value) => format!("CP[{}]", value),
Property::EventDates(_) => unimplemented!(),
Property::EventName(value) => write!(f, "EV[{}]", value),
Property::GameName(value) => write!(f, "GN[{}]", value),
Property::ExtraGameInformation(value) => write!(f, "GC[{}]", value),
Property::GameOpening(value) => write!(f, "ON[{}]", value),
Property::Overtime(value) => write!(f, "OT[{}]", value),
Property::BlackPlayer(value) => write!(f, "PB[{}]", value),
Property::GameLocation(value) => write!(f, "PC[{}]", value),
Property::WhitePlayer(value) => write!(f, "PW[{}]", value),
Property::EventName(value) => format!("EV[{}]", value),
Property::GameName(value) => format!("GN[{}]", value),
Property::ExtraGameInformation(value) => format!("GC[{}]", value),
Property::GameOpening(value) => format!("ON[{}]", value),
Property::Overtime(value) => format!("OT[{}]", value),
Property::BlackPlayer(value) => format!("PB[{}]", value),
Property::GameLocation(value) => format!("PC[{}]", value),
Property::WhitePlayer(value) => format!("PW[{}]", value),
Property::Result(_) => unimplemented!(),
Property::Round(value) => write!(f, "RO[{}]", value),
Property::Ruleset(value) => write!(f, "RU[{}]", value),
Property::Source(value) => write!(f, "SO[{}]", value),
Property::TimeLimit(value) => write!(f, "TM[{}]", value.as_secs()),
Property::User(value) => write!(f, "US[{}]", value),
Property::WhiteRank(value) => write!(f, "WR[{}]", value),
Property::WhiteTeam(value) => write!(f, "WT[{}]", value),
Property::Round(value) => format!("RO[{}]", value),
Property::Ruleset(value) => format!("RU[{}]", value),
Property::Source(value) => format!("SO[{}]", value),
Property::TimeLimit(value) => format!("TM[{}]", value.as_secs()),
Property::User(value) => format!("US[{}]", value),
Property::WhiteRank(value) => format!("WR[{}]", value),
Property::WhiteTeam(value) => format!("WT[{}]", value),
Property::Territory(Color::White, positions) => {
write!(f, "TW[{}]", Positions(positions))
positions
.iter()
.fold("TW".to_owned(), |mut output, Position(p)| {
let _ = write!(output, "{}", p);
output
})
}
Property::Territory(Color::Black, positions) => {
write!(f, "TB[{}]", Positions(positions))
positions
.iter()
.fold("TB".to_owned(), |mut output, Position(p)| {
let _ = write!(output, "{}", p);
output
})
}
Property::Unknown(UnknownProperty { ident, value }) => {
write!(f, "{}[{}]", ident, value)
format!("{}[{}]", ident, value)
}
}
}
}
struct Positions<'a>(&'a Vec<Position>);
impl Display for Positions<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for Position(p) in self.0.iter() {
write!(f, "{}", p)?;
}
Ok(())
}
}
pub fn parse_collection<'a, E: nom::error::ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, Vec<Tree>, E> {

View File

@ -53,7 +53,6 @@ pub enum Error {
// InvalidField,
// InvalidBoardSize,
Incomplete,
#[allow(dead_code)]
InvalidSgf(VerboseNomError),
}

View File

@ -1,7 +1,11 @@
#[cfg(test)]
mod tests {
use chrono::{DateTime, FixedOffset, NaiveDate, TimeZone};
use chrono_tz::America::{New_York, Phoenix};
use chrono_tz::{
America::{New_York, Phoenix},
Tz,
US::Mountain,
};
#[test]
fn it_saves_with_offset() {
@ -33,7 +37,7 @@ mod tests {
date.with_timezone(&New_York),
FixedOffset::west_opt(4 * 60 * 60)
.unwrap()
.with_ymd_and_hms(2023, 10, 14, 3, 0, 0)
.with_ymd_and_hms(2023, 10, 14, 03, 0, 0)
.unwrap()
);
assert_eq!(

View File

@ -62,7 +62,7 @@ impl<T> Tree<T> {
// Do a depth-first-search in order to get the path to a node. Start with a naive recursive
// implementation, then switch to a stack-based implementation in order to avoid exceeding the
// stack.
pub fn path_to<F>(&self, _f: F) -> Vec<Node<T>>
pub fn path_to<F>(&self, f: F) -> Vec<Node<T>>
where
F: FnOnce(&T) -> bool + Copy,
{
@ -206,20 +206,17 @@ mod tests {
assert!(tree2.find_bfs(|val| *val == "17").is_some());
}
/*
#[test]
fn path_to_on_empty_tree_returns_empty() {
let tree: Tree<&str> = Tree::default();
assert_eq!(tree.path_to(|val| *val == "i"), vec![]);
}
*/
// A
// B G H
// C I
// D E F
/*
#[test]
fn it_can_find_a_path_to_a_node() {
let (tree, a) = Tree::new("A");
@ -235,5 +232,4 @@ mod tests {
assert_eq!(tree.path_to(|val| *val == "z"), vec![]);
assert_eq!(tree.path_to(|val| *val == "i"), vec![a, h, i]);
}
*/
}

View File

@ -6,29 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std = { version = "1.13.0" }
async-trait = { version = "0.1.83" }
authdb = { path = "../../authdb/" }
axum = { version = "0.7.9", features = [ "macros" ] }
futures = { version = "0.3.31" }
include_dir = { version = "0.7.4" }
lazy_static = { version = "1.5.0" }
mime = { version = "0.3.17" }
mime_guess = { version = "2.0.5" }
pretty_env_logger = { version = "0.5.0" }
result-extended = { path = "../../result-extended" }
rusqlite = { version = "0.32.1" }
rusqlite_migration = { version = "1.3.1", features = ["from-directory"] }
serde = { version = "1" }
serde_json = { version = "*" }
thiserror = { version = "2.0.3" }
tokio = { version = "1", features = [ "full" ] }
tokio-stream = { version = "0.1.16" }
tower-http = { version = "0.6.2", features = ["cors"] }
typeshare = { version = "1.0.4" }
urlencoding = { version = "2.1.3" }
uuid = { version = "1.11.0", features = ["v4"] }
[dev-dependencies]
cool_asserts = "2.0.3"
axum-test = "16.4.1"
authdb = { path = "../../authdb/" }
http = { version = "1" }
serde_json = { version = "*" }
serde = { version = "1" }
tokio = { version = "1", features = [ "full" ] }
warp = { version = "0.3" }

View File

@ -1,23 +0,0 @@
version: '3'
tasks:
build:
cmds:
- cargo build
test:
cmds:
- cargo watch -x 'nextest run'
dev:
cmds:
- cargo watch -x run
lint:
cmds:
- cargo watch -x clippy
release:
cmds:
- task lint
- cargo build --release

View File

@ -1,42 +0,0 @@
CREATE TABLE users(
uuid TEXT PRIMARY KEY,
name TEXT UNIQUE,
password TEXT,
admin BOOLEAN,
enabled BOOLEAN
);
CREATE TABLE sessions(
id TEXT PRIMARY KEY,
user_id TEXT,
FOREIGN KEY(user_id) REFERENCES users(uuid)
);
CREATE TABLE games(
uuid TEXT PRIMARY KEY,
gm TEXT,
game_type TEXT,
name TEXT,
FOREIGN KEY(gm) REFERENCES users(uuid)
);
CREATE TABLE characters(
uuid TEXT PRIMARY KEY,
game TEXT,
data TEXT,
FOREIGN KEY(game) REFERENCES games(uuid)
);
CREATE TABLE roles(
user_id TEXT,
game_id TEXT,
role TEXT,
FOREIGN KEY(user_id) REFERENCES users(uuid),
FOREIGN KEY(game_id) REFERENCES games(uuid)
);
INSERT INTO users VALUES ('admin', 'admin', '', true, true);

View File

@ -1 +0,0 @@
{ "type_": "Candela", "name": "Soren Jensen", "pronouns": "he/him", "circle": "Circle of the Bluest Sky", "style": "dapper gentleman", "catalyst": "a cursed book", "question": "What were the contents of that book?", "nerve": { "type_": "nerve", "drives": { "current": 1, "max": 2 }, "resistances": { "current": 0, "max": 3 }, "move": { "gilded": false, "score": 2 }, "strike": { "gilded": false, "score": 1 }, "control": { "gilded": true, "score": 0 } }, "cunning": { "type_": "cunning", "drives": { "current": 1, "max": 1 }, "resistances": { "current": 0, "max": 3 }, "sway": { "gilded": false, "score": 0 }, "read": { "gilded": false, "score": 0 }, "hide": { "gilded": false, "score": 0 } }, "intuition": { "type_": "intuition", "drives": { "current": 0, "max": 0 }, "resistances": { "current": 0, "max": 3 }, "survey": { "gilded": false, "score": 0 }, "focus": { "gilded": false, "score": 0 }, "sense": { "gilded": false, "score": 0 } }, "role": "Slink", "role_abilities": [ "Scout: If you have time to observe a location, you can spend 1 Intuition to ask a question: What do I notice here that others do not see? What in this place might be of use to us? What path should we follow?" ], "specialty": "Detective", "specialty_abilities": [ "Mind Palace: When you want to figure out how two clues might relate or what path they should point you towards, burn 1 Intution resistance. The GM will give you the information you have deduced." ] }

View File

@ -1,156 +0,0 @@
use std::{
collections::{hash_map::Iter, HashMap}, fmt::{self, Display}, fs, io::Read, path::PathBuf
};
use mime::Mime;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use typeshare::typeshare;
#[derive(Debug, Error)]
pub enum Error {
#[error("Asset could not be found")]
NotFound,
#[error("Asset could not be opened")]
Inaccessible,
#[error("An unexpected IO error occured when retrieving an asset {0}")]
Unexpected(std::io::Error),
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
use std::io::ErrorKind::*;
match err.kind() {
NotFound => Error::NotFound,
PermissionDenied | UnexpectedEof => Error::Inaccessible,
_ => Error::Unexpected(err),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct AssetId(String);
impl AssetId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for AssetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AssetId({})", self.0)
}
}
impl From<&str> for AssetId {
fn from(s: &str) -> Self {
AssetId(s.to_owned())
}
}
impl From<String> for AssetId {
fn from(s: String) -> Self {
AssetId(s)
}
}
pub struct AssetIter<'a>(Iter<'a, AssetId, String>);
impl<'a> Iterator for AssetIter<'a> {
type Item = (&'a AssetId, &'a String);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub trait Assets {
fn assets(&self) -> AssetIter;
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error>;
}
pub struct FsAssets {
assets: HashMap<AssetId, String>,
}
impl FsAssets {
pub fn new(path: PathBuf) -> Self {
let dir = fs::read_dir(path).unwrap();
let mut assets = HashMap::new();
for dir_ent in dir {
let path = dir_ent.unwrap().path();
let file_name = path.file_name().unwrap().to_str().unwrap();
assets.insert(AssetId::from(file_name), path.to_str().unwrap().to_owned());
}
Self {
assets,
}
}
}
impl Assets for FsAssets {
fn assets(&self) -> AssetIter {
AssetIter(self.assets.iter())
}
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
let path = match self.assets.get(&asset_id) {
Some(asset) => Ok(asset),
None => Err(Error::NotFound),
}?;
let mime = mime_guess::from_path(path).first().unwrap();
let mut content: Vec<u8> = Vec::new();
let mut file = std::fs::File::open(path)?;
file.read_to_end(&mut content)?;
Ok((mime, content))
}
}
#[cfg(test)]
pub mod mocks {
use std::collections::HashMap;
use super::*;
pub struct MemoryAssets {
asset_paths: HashMap<AssetId, String>,
assets: HashMap<AssetId, Vec<u8>>,
}
impl MemoryAssets {
pub fn new(data: Vec<(AssetId, String, Vec<u8>)>) -> Self {
let mut asset_paths = HashMap::new();
let mut assets = HashMap::new();
data.into_iter().for_each(|(asset, path, data)| {
asset_paths.insert(asset.clone(), path);
assets.insert(asset, data);
});
Self {
asset_paths,
assets,
}
}
}
impl Assets for MemoryAssets {
fn assets(&self) -> AssetIter<'_> {
AssetIter(self.asset_paths.iter())
}
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
match (self.asset_paths.get(&asset_id), self.assets.get(&asset_id)) {
(Some(path), Some(data)) => {
let mime = mime_guess::from_path(path).first().unwrap();
Ok((mime, data.to_vec()))
}
_ => Err(Error::NotFound),
}
}
}
}

View File

@ -1,438 +0,0 @@
use std::{collections::HashMap, sync::Arc};
use async_std::sync::RwLock;
use mime::Mime;
use result_extended::{error, fatal, ok, return_error, ResultExt};
use serde::Serialize;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use typeshare::typeshare;
use uuid::Uuid;
use crate::{
asset_db::{self, AssetId, Assets},
database::{CharacterId, Database, GameId, SessionId, UserId}, types::{AppError, FatalError, GameOverview, Message, Rgb, Tabletop, User, UserProfile},
};
const DEFAULT_BACKGROUND_COLOR: Rgb = Rgb {
red: 0xca,
green: 0xb9,
blue: 0xbb,
};
#[derive(Clone, Serialize)]
#[typeshare]
pub struct Status {
pub admin_enabled: bool,
}
#[derive(Debug)]
struct WebsocketClient {
sender: Option<UnboundedSender<Message>>,
}
pub struct AppState {
pub asset_store: Box<dyn Assets + Sync + Send + 'static>,
pub db: Box<dyn Database + Sync + Send + 'static>,
pub clients: HashMap<String, WebsocketClient>,
pub tabletop: Tabletop,
}
#[derive(Clone)]
pub struct Core(Arc<RwLock<AppState>>);
impl Core {
pub fn new<A, DB>(assetdb: A, db: DB) -> Self
where
A: Assets + Sync + Send + 'static,
DB: Database + Sync + Send + 'static,
{
Self(Arc::new(RwLock::new(AppState {
asset_store: Box::new(assetdb),
db: Box::new(db),
clients: HashMap::new(),
tabletop: Tabletop {
background_color: DEFAULT_BACKGROUND_COLOR,
background_image: None,
},
})))
}
pub async fn status(&self) -> ResultExt<Status, AppError, FatalError> {
let state = self.0.write().await;
let admin_user = return_error!(match state.db.user(&UserId::from("admin")).await {
Ok(Some(admin_user)) => ok(admin_user),
Ok(None) => {
return ok(Status {
admin_enabled: false,
});
}
Err(err) => fatal(err),
});
ok(Status {
admin_enabled: !admin_user.password.is_empty(),
})
}
pub async fn register_client(&self) -> String {
let mut state = self.0.write().await;
let uuid = Uuid::new_v4().simple().to_string();
let client = WebsocketClient { sender: None };
state.clients.insert(uuid.clone(), client);
uuid
}
pub async fn unregister_client(&self, client_id: String) {
let mut state = self.0.write().await;
let _ = state.clients.remove(&client_id);
}
pub async fn connect_client(&self, client_id: String) -> UnboundedReceiver<Message> {
let mut state = self.0.write().await;
match state.clients.get_mut(&client_id) {
Some(client) => {
let (tx, rx) = unbounded_channel();
client.sender = Some(tx);
rx
}
None => {
unimplemented!();
}
}
}
pub async fn user_by_username(
&self,
username: &str,
) -> ResultExt<Option<User>, AppError, FatalError> {
let state = self.0.read().await;
match state.db.user_by_username(username).await {
Ok(Some(user_row)) => ok(Some(User::from(user_row))),
Ok(None) => ok(None),
Err(err) => fatal(err),
}
}
pub async fn list_users(&self) -> ResultExt<Vec<User>, AppError, FatalError> {
let users = self.0.write().await.db.users().await;
match users {
Ok(users) => ok(users.into_iter().map(User::from).collect()),
Err(err) => fatal(err),
}
}
pub async fn user(&self, user_id: UserId) -> ResultExt<Option<UserProfile>, AppError, FatalError> {
let users = return_error!(self.list_users().await);
let games = return_error!(self.list_games().await);
let user = match users.into_iter().find(|user| user.id == user_id) {
Some(user) => user,
None => return ok(None),
};
let user_games = games.into_iter().filter(|g| g.gm == user.id).collect();
ok(Some(UserProfile {
id: user.id,
name: user.name,
games: user_games,
is_admin: user.admin,
}))
}
pub async fn create_user(&self, username: &str) -> ResultExt<UserId, AppError, FatalError> {
let state = self.0.read().await;
match return_error!(self.user_by_username(username).await) {
Some(_) => error(AppError::UsernameUnavailable),
None => match state.db.save_user(None, username, "", false, true).await {
Ok(user_id) => ok(user_id),
Err(err) => fatal(err),
},
}
}
pub async fn list_games(&self) -> ResultExt<Vec<GameOverview>, AppError, FatalError> {
let games = self.0.read().await.db.games().await;
match games {
// Ok(games) => ok(games.into_iter().map(|g| Game::from(g)).collect()),
Ok(games) => ok(games.into_iter().map(GameOverview::from).collect()),
Err(err) => fatal(err),
}
}
pub async fn create_game(&self, gm: &UserId, game_type: &str, game_name: &str) -> ResultExt<GameId, AppError, FatalError> {
let state = self.0.read().await;
match state.db.save_game(None, gm, game_type, game_name).await {
Ok(game_id) => ok(game_id),
Err(err) => fatal(err),
}
}
pub async fn tabletop(&self) -> Tabletop {
self.0.read().await.tabletop.clone()
}
pub async fn get_asset(
&self,
asset_id: AssetId,
) -> ResultExt<(Mime, Vec<u8>), AppError, FatalError> {
ResultExt::from(
self.0
.read()
.await
.asset_store
.get(asset_id.clone())
.map_err(|err| match err {
asset_db::Error::NotFound => AppError::NotFound(format!("{}", asset_id)),
asset_db::Error::Inaccessible => {
AppError::Inaccessible(format!("{}", asset_id))
}
asset_db::Error::Unexpected(err) => AppError::Inaccessible(format!("{}", err)),
}),
)
}
pub async fn available_images(&self) -> Vec<AssetId> {
self.0
.read()
.await
.asset_store
.assets()
.filter_map(
|(asset_id, value)| match mime_guess::from_path(value).first() {
Some(mime) if mime.type_() == mime::IMAGE => Some(asset_id.clone()),
_ => None,
},
)
.collect()
}
pub async fn set_background_image(
&self,
asset: AssetId,
) -> ResultExt<(), AppError, FatalError> {
let tabletop = {
let mut state = self.0.write().await;
state.tabletop.background_image = Some(asset.clone());
state.tabletop.clone()
};
self.publish(Message::UpdateTabletop(tabletop)).await;
ok(())
}
pub async fn get_charsheet(
&self,
id: CharacterId,
) -> ResultExt<Option<serde_json::Value>, AppError, FatalError> {
let state = self.0.write().await;
let cr = state.db.character(&id).await;
match cr {
Ok(Some(row)) => ok(Some(row.data)),
Ok(None) => ok(None),
Err(err) => fatal(err),
}
}
pub async fn publish(&self, message: Message) {
let state = self.0.read().await;
state.clients.values().for_each(|client| {
if let Some(ref sender) = client.sender {
let _ = sender.send(message.clone());
}
});
}
pub async fn save_user(
&self,
uuid: Option<UserId>,
username: &str,
password: &str,
admin: bool,
enabled: bool,
) -> ResultExt<UserId, AppError, FatalError> {
let state = self.0.read().await;
match state
.db
.save_user(uuid, username, password, admin, enabled)
.await
{
Ok(uuid) => ok(uuid),
Err(err) => fatal(err),
}
}
pub async fn set_password(
&self,
uuid: UserId,
password: String,
) -> ResultExt<(), AppError, FatalError> {
let state = self.0.write().await;
let user = match state.db.user(&uuid).await {
Ok(Some(row)) => row,
Ok(None) => return error(AppError::NotFound(uuid.as_str().to_owned())),
Err(err) => return fatal(err),
};
match state
.db
.save_user(Some(uuid), &user.name, &password, user.admin, user.enabled)
.await
{
Ok(_) => ok(()),
Err(err) => fatal(err),
}
}
pub async fn auth(
&self,
username: &str,
password: &str,
) -> ResultExt<SessionId, AppError, FatalError> {
let state = self.0.write().await;
match state.db.user_by_username(username).await {
Ok(Some(row)) if (row.password == password) => {
let session_id = state.db.create_session(&row.id).await.unwrap();
ok(session_id)
}
Ok(_) => error(AppError::AuthFailed),
Err(err) => fatal(err),
}
}
pub async fn session(&self, session_id: &SessionId) -> ResultExt<Option<User>, AppError, FatalError> {
let state = self.0.read().await;
match state.db.session(session_id).await {
Ok(Some(user_row)) => ok(Some(User::from(user_row))),
Ok(None) => ok(None),
Err(fatal_error) => fatal(fatal_error),
}
}
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use super::*;
use cool_asserts::assert_matches;
use crate::{asset_db::mocks::MemoryAssets, database::DbConn};
async fn test_core() -> Core {
let assets = MemoryAssets::new(vec![
(
AssetId::from("asset_1"),
"asset_1.png".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_2"),
"asset_2.jpg".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_3"),
"asset_3".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_4"),
"asset_4".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_5"),
"asset_5".to_owned(),
String::from("abcdefg").into_bytes(),
),
]);
let memory_db: Option<PathBuf> = None;
let conn = DbConn::new(memory_db);
conn.save_user(Some(UserId::from("admin")), "admin", "aoeu", true, true)
.await
.unwrap();
conn.save_user(None, "gm_1", "aoeu", false, true)
.await
.unwrap();
Core::new(assets, conn)
}
#[tokio::test]
async fn it_lists_available_images() {
let core = test_core().await;
let image_paths = core.available_images().await;
assert_eq!(image_paths.len(), 2);
}
#[tokio::test]
async fn it_retrieves_an_asset() {
let core = test_core().await;
assert_matches!(core.get_asset(AssetId::from("asset_1")).await, ResultExt::Ok((mime, data)) => {
assert_eq!(mime.type_(), mime::IMAGE);
assert_eq!(data, "abcdefg".as_bytes());
});
}
#[tokio::test]
async fn it_can_retrieve_the_default_tabletop() {
let core = test_core().await;
assert_matches!(core.tabletop().await, Tabletop{ background_color, background_image } => {
assert_eq!(background_color, DEFAULT_BACKGROUND_COLOR);
assert_eq!(background_image, None);
});
}
#[tokio::test]
async fn it_can_change_the_tabletop_background() {
let core = test_core().await;
assert_matches!(
core.set_background_image(AssetId::from("asset_1")).await,
ResultExt::Ok(())
);
assert_matches!(core.tabletop().await, Tabletop{ background_color, background_image } => {
assert_eq!(background_color, DEFAULT_BACKGROUND_COLOR);
assert_eq!(background_image, Some(AssetId::from("asset_1")));
});
}
#[tokio::test]
async fn it_sends_notices_to_clients_on_tabletop_change() {
let core = test_core().await;
let client_id = core.register_client().await;
let mut receiver = core.connect_client(client_id).await;
assert_matches!(
core.set_background_image(AssetId::from("asset_1")).await,
ResultExt::Ok(())
);
match receiver.recv().await {
Some(Message::UpdateTabletop(Tabletop {
background_color,
background_image,
})) => {
assert_eq!(background_color, DEFAULT_BACKGROUND_COLOR);
assert_eq!(background_image, Some(AssetId::from("asset_1")));
}
None => panic!("receiver did not get a message"),
}
}
#[tokio::test]
async fn it_creates_a_sessionid_on_successful_auth() {
let core = test_core().await;
match core.auth("admin", "aoeu").await {
ResultExt::Ok(session_id) => {
let st = core.0.read().await;
match st.db.session(&session_id).await {
Ok(Some(user_row)) => assert_eq!(user_row.name, "admin"),
Ok(None) => panic!("no matching user row for the session id"),
Err(err) => panic!("{}", err),
}
}
ResultExt::Err(err) => panic!("{}", err),
ResultExt::Fatal(err) => panic!("{}", err),
}
}
}

View File

@ -1,386 +0,0 @@
use std::path::Path;
use async_std::channel::Receiver;
use include_dir::{include_dir, Dir};
use lazy_static::lazy_static;
use rusqlite::Connection;
use rusqlite_migration::Migrations;
use crate::{
database::{DatabaseResponse, Request},
types::FatalError,
};
use super::{
types::GameId, CharacterId, CharsheetRow, DatabaseRequest, GameRow, SessionId, UserId, UserRow
};
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations");
lazy_static! {
static ref MIGRATIONS: Migrations<'static> =
Migrations::from_directory(&MIGRATIONS_DIR).unwrap();
}
pub struct DiskDb {
conn: Connection,
}
impl DiskDb {
pub fn new<P>(path: Option<P>) -> Result<Self, FatalError>
where
P: AsRef<Path>,
{
let mut conn = match path {
None => Connection::open(":memory:").expect("to create a memory connection"),
Some(path) => Connection::open(path).expect("to create connection"),
};
MIGRATIONS
.to_latest(&mut conn)
.map_err(|err| FatalError::DatabaseMigrationFailure(format!("{}", err)))?;
// setup_test_database(&conn)?;
Ok(DiskDb { conn })
}
pub fn user(&self, id: &UserId) -> Result<Option<UserRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT uuid, name, password, admin, enabled FROM users WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt
.query_map([id.as_str()], |row| {
Ok(UserRow {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(id.as_str().to_owned())),
}
}
pub fn user_by_username(&self, username: &str) -> Result<Option<UserRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT uuid, name, password, admin, enabled FROM users WHERE name=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt
.query_map([username], |row| {
Ok(UserRow {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(username.to_owned())),
}
}
pub fn save_user(
&self,
user_id: Option<UserId>,
name: &str,
password: &str,
admin: bool,
enabled: bool,
) -> Result<UserId, FatalError> {
match user_id {
None => {
let user_id = UserId::default();
let mut stmt = self
.conn
.prepare("INSERT INTO users VALUES (?, ?, ?, ?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((user_id.as_str(), name, password, admin, enabled))
.unwrap();
Ok(user_id)
}
Some(user_id) => {
let mut stmt = self
.conn
.prepare("UPDATE users SET name=?, password=?, admin=?, enabled=? WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((name, password, admin, enabled, user_id.as_str()))
.unwrap();
Ok(user_id)
}
}
}
pub fn users(&self) -> Result<Vec<UserRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT * FROM users")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items = stmt
.query_map([], |row| {
Ok(UserRow {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
.unwrap();
Ok(items)
}
pub fn save_game(
&self,
game_id: Option<GameId>,
gm: &UserId,
game_type: &str,
name: &str,
) -> Result<GameId, FatalError> {
match game_id {
None => {
let game_id = GameId::new();
let mut stmt = self
.conn
.prepare("INSERT INTO games VALUES (?, ?, ?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((game_id.as_str(), gm.as_str(), game_type, name))
.unwrap();
Ok(game_id)
}
Some(game_id) => {
let mut stmt = self
.conn
.prepare("UPDATE games SET gm=? game_type=? name=? WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((gm.as_str(), game_type, name, game_id.as_str()))
.unwrap();
Ok(game_id)
}
}
}
pub fn games(&self) -> Result<Vec<GameRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT * FROM games")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items = stmt
.query_map([], |row| {
Ok(GameRow {
id: row.get(0).unwrap(),
gm: row.get(1).unwrap(),
game_type: row.get(2).unwrap(),
name: row.get(3).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<GameRow>, rusqlite::Error>>()
.unwrap();
Ok(items)
}
pub fn session(&self, session_id: &SessionId) -> Result<Option<UserRow>, FatalError> {
let mut stmt = self.conn
.prepare("SELECT u.uuid, u.name, u.password, u.admin, u.enabled FROM sessions s INNER JOIN users u ON u.uuid = s.user_id WHERE s.id = ?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<UserRow> = stmt
.query_map([session_id.as_str()], |row| {
Ok(UserRow {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
enabled: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(
session_id.as_str().to_owned(),
)),
}
}
fn create_session(&self, user_id: &UserId) -> Result<SessionId, FatalError> {
match self.user(user_id) {
Ok(Some(_)) => {
let mut stmt = self
.conn
.prepare("INSERT INTO sessions VALUES (?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let session_id = SessionId::new();
stmt.execute((session_id.as_str(), user_id.as_str()))
.unwrap();
Ok(session_id)
}
Ok(None) => Err(FatalError::DatabaseKeyMissing),
Err(err) => Err(err),
}
}
pub fn character(&self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT uuid, game, data FROM characters WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<CharsheetRow> = stmt
.query_map([id.as_str()], |row| {
let data: String = row.get(2).unwrap();
Ok(CharsheetRow {
id: row.get(0).unwrap(),
game: row.get(1).unwrap(),
data: serde_json::from_str(&data).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<CharsheetRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(id.as_str().to_owned())),
}
}
pub fn save_character(
&self,
char_id: Option<CharacterId>,
game: GameId,
character: serde_json::Value,
) -> std::result::Result<CharacterId, FatalError> {
match char_id {
None => {
let char_id = CharacterId::new();
let mut stmt = self
.conn
.prepare("INSERT INTO characters VALUES (?, ?, ?)")
.unwrap();
stmt.execute((char_id.as_str(), game.as_str(), character.to_string()))
.unwrap();
Ok(char_id)
}
Some(char_id) => {
let mut stmt = self
.conn
.prepare("UPDATE characters SET data=? WHERE uuid=?")
.unwrap();
stmt.execute((character.to_string(), char_id.as_str()))
.unwrap();
Ok(char_id)
}
}
}
}
pub async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
while let Ok(DatabaseRequest { tx, req }) = requestor.recv().await {
match req {
Request::Charsheet(id) => {
let sheet = db.character(id);
match sheet {
Ok(sheet) => {
tx.send(DatabaseResponse::Charsheet(sheet)).await.unwrap();
}
_ => unimplemented!("errors for Charsheet"),
}
}
Request::CreateSession(id) => {
let session_id = db.create_session(&id).unwrap();
tx.send(DatabaseResponse::CreateSession(session_id))
.await
.unwrap();
}
Request::Games => {
match db.games() {
Ok(games) => tx.send(DatabaseResponse::Games(games)).await.unwrap(),
_ => unimplemented!("errors for Request::Games"),
}
}
Request::Game(_game_id) => {
unimplemented!("Request::Game handler");
}
Request::SaveGame(game_id, user_id, game_type, game_name) => {
let game_id = db.save_game(game_id, &user_id, &game_type, &game_name);
match game_id {
Ok(game_id) => {
tx.send(DatabaseResponse::SaveGame(game_id)).await.unwrap();
}
err => panic!("{:?}", err),
}
}
Request::User(uid) => {
let user = db.user(&uid);
match user {
Ok(user) => {
tx.send(DatabaseResponse::User(user)).await.unwrap();
}
err => panic!("{:?}", err),
}
}
Request::UserByUsername(username) => {
let user = db.user_by_username(&username);
match user {
Ok(user) => tx.send(DatabaseResponse::User(user)).await.unwrap(),
err => panic!("{:?}", err),
}
}
Request::SaveUser(user_id, username, password, admin, enabled) => {
let user_id = db.save_user(
user_id,
username.as_ref(),
password.as_ref(),
admin,
enabled,
);
match user_id {
Ok(user_id) => {
tx.send(DatabaseResponse::SaveUser(user_id)).await.unwrap();
}
err => panic!("{:?}", err),
}
}
Request::Session(session_id) => {
let user = db.session(&session_id);
match user {
Ok(user) => tx.send(DatabaseResponse::Session(user)).await.unwrap(),
err => panic!("{:?}", err),
}
}
Request::Users => {
let users = db.users();
match users {
Ok(users) => {
tx.send(DatabaseResponse::Users(users)).await.unwrap();
}
_ => unimplemented!(),
}
}
}
}
}

View File

@ -1,228 +0,0 @@
mod disk_db;
mod types;
use std::path::Path;
use async_std::channel::{bounded, Sender};
use async_trait::async_trait;
use disk_db::{db_handler, DiskDb};
pub use types::{CharacterId, CharsheetRow, GameId, GameRow, SessionId, UserId, UserRow};
use crate::types::FatalError;
#[derive(Debug)]
enum Request {
Charsheet(CharacterId),
CreateSession(UserId),
Games,
Game(GameId),
SaveGame(Option<GameId>, UserId, String, String),
SaveUser(Option<UserId>, String, String, bool, bool),
Session(SessionId),
User(UserId),
UserByUsername(String),
Users,
}
#[derive(Debug)]
struct DatabaseRequest {
tx: Sender<DatabaseResponse>,
req: Request,
}
#[derive(Debug)]
enum DatabaseResponse {
Charsheet(Option<CharsheetRow>),
CreateSession(SessionId),
Games(Vec<GameRow>),
Game(Option<GameRow>),
SaveGame(GameId),
SaveUser(UserId),
Session(Option<UserRow>),
User(Option<UserRow>),
Users(Vec<UserRow>),
}
#[async_trait]
pub trait Database: Send + Sync {
async fn users(&self) -> Result<Vec<UserRow>, FatalError>;
async fn user(&self, _: &UserId) -> Result<Option<UserRow>, FatalError>;
async fn user_by_username(&self, _: &str) -> Result<Option<UserRow>, FatalError>;
async fn save_user(
&self,
user_id: Option<UserId>,
name: &str,
password: &str,
admin: bool,
enabled: bool,
) -> Result<UserId, FatalError>;
async fn games(&self) -> Result<Vec<GameRow>, FatalError>;
async fn game(&self, _: &GameId) -> Result<Option<GameRow>, FatalError>;
async fn save_game(
&self,
game_id: Option<GameId>,
gm: &UserId,
game_type: &str,
game_name: &str,
) -> Result<GameId, FatalError>;
async fn character(&self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError>;
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError>;
}
pub struct DbConn {
conn: Sender<DatabaseRequest>,
handle: tokio::task::JoinHandle<()>,
}
impl DbConn {
pub fn new<P>(path: Option<P>) -> Self
where
P: AsRef<Path>,
{
let (tx, rx) = bounded::<DatabaseRequest>(5);
let db = DiskDb::new(path).unwrap();
let handle = tokio::spawn(async move {
db_handler(db, rx).await;
});
Self { conn: tx, handle }
}
}
macro_rules! send_request {
($s:expr, $req:expr, $resp_h:pat => $block:expr) => {{
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest { tx, req: $req };
match $s.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx
.recv()
.await
.map_err(|_| FatalError::DatabaseConnectionLost)
{
Ok($resp_h) => $block,
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}};
}
#[async_trait]
impl Database for DbConn {
async fn users(&self) -> Result<Vec<UserRow>, FatalError> {
send_request!(self, Request::Users, DatabaseResponse::Users(lst) => Ok(lst))
}
async fn user(&self, uid: &UserId) -> Result<Option<UserRow>, FatalError> {
send_request!(self, Request::User(uid.clone()), DatabaseResponse::User(user) => Ok(user))
}
async fn user_by_username(&self, username: &str) -> Result<Option<UserRow>, FatalError> {
send_request!(self, Request::UserByUsername(username.to_owned()), DatabaseResponse::User(user) => Ok(user))
}
async fn save_user(
&self,
user_id: Option<UserId>,
name: &str,
password: &str,
admin: bool,
enabled: bool,
) -> Result<UserId, FatalError> {
send_request!(self,
Request::SaveUser(
user_id,
name.to_owned(),
password.to_owned(),
admin,
enabled,
),
DatabaseResponse::SaveUser(user_id) => Ok(user_id))
}
async fn games(&self) -> Result<Vec<GameRow>, FatalError> {
send_request!(self, Request::Games, DatabaseResponse::Games(lst) => Ok(lst))
}
async fn game(&self, game_id: &GameId) -> Result<Option<GameRow>, FatalError> {
send_request!(self, Request::Game(game_id.clone()), DatabaseResponse::Game(game) => Ok(game))
}
async fn save_game(
&self,
game_id: Option<GameId>,
user_id: &UserId,
game_type: &str,
game_name: &str,
) -> Result<GameId, FatalError> {
send_request!(self, Request::SaveGame(game_id, user_id.to_owned(), game_type.to_owned(), game_name.to_owned()), DatabaseResponse::SaveGame(game_id) => Ok(game_id))
}
async fn character(&self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
send_request!(self, Request::Charsheet(id.to_owned()), DatabaseResponse::Charsheet(row) => Ok(row))
}
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError> {
send_request!(self, Request::Session(id.to_owned()), DatabaseResponse::Session(row) => Ok(row))
}
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError> {
send_request!(self, Request::CreateSession(id.to_owned()), DatabaseResponse::CreateSession(session_id) => Ok(session_id))
}
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use cool_asserts::assert_matches;
use disk_db::DiskDb;
use types::GameId;
use super::*;
const SOREN: &str = r#"{ "type_": "Candela", "name": "Soren Jensen", "pronouns": "he/him", "circle": "Circle of the Bluest Sky", "style": "dapper gentleman", "catalyst": "a cursed book", "question": "What were the contents of that book?", "nerve": { "type_": "nerve", "drives": { "current": 1, "max": 2 }, "resistances": { "current": 0, "max": 3 }, "move": { "gilded": false, "score": 2 }, "strike": { "gilded": false, "score": 1 }, "control": { "gilded": true, "score": 0 } }, "cunning": { "type_": "cunning", "drives": { "current": 1, "max": 1 }, "resistances": { "current": 0, "max": 3 }, "sway": { "gilded": false, "score": 0 }, "read": { "gilded": false, "score": 0 }, "hide": { "gilded": false, "score": 0 } }, "intuition": { "type_": "intuition", "drives": { "current": 0, "max": 0 }, "resistances": { "current": 0, "max": 3 }, "survey": { "gilded": false, "score": 0 }, "focus": { "gilded": false, "score": 0 }, "sense": { "gilded": false, "score": 0 } }, "role": "Slink", "role_abilities": [ "Scout: If you have time to observe a location, you can spend 1 Intuition to ask a question: What do I notice here that others do not see? What in this place might be of use to us? What path should we follow?" ], "specialty": "Detective", "specialty_abilities": [ "Mind Palace: When you want to figure out how two clues might relate or what path they should point you towards, burn 1 Intution resistance. The GM will give you the information you have deduced." ] }"#;
fn setup_db() -> (DiskDb, GameId) {
let no_path: Option<PathBuf> = None;
let db = DiskDb::new(no_path).unwrap();
db.save_user(Some(UserId::from("admin")), "admin", "abcdefg", true, true)
.unwrap();
let game_id = db.save_game(None, &UserId::from("admin"), "Candela", "Circle of the Winter Solstice").unwrap();
(db, game_id)
}
#[test]
fn it_can_retrieve_a_character() {
let (db, game_id) = setup_db();
assert_matches!(db.character(CharacterId::from("1")), Ok(None));
let js: serde_json::Value = serde_json::from_str(SOREN).unwrap();
let soren_id = db.save_character(None, game_id, js.clone()).unwrap();
assert_matches!(db.character(soren_id).unwrap(), Some(CharsheetRow{ data, .. }) => assert_eq!(js, data));
}
#[tokio::test]
async fn it_can_retrieve_a_character_through_conn() {
let memory_db: Option<PathBuf> = None;
let conn = DbConn::new(memory_db);
assert_matches!(conn.character(&CharacterId::from("1")).await, Ok(None));
}
}

View File

@ -1,200 +0,0 @@
use std::fmt;
use rusqlite::types::{FromSql, FromSqlResult, ValueRef};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use uuid::Uuid;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct UserId(String);
impl UserId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for UserId {
fn default() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
}
impl From<&str> for UserId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for UserId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for UserId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
impl fmt::Display for UserId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&self.0)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct SessionId(String);
impl SessionId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for SessionId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for SessionId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for SessionId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
impl fmt::Display for SessionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&self.0)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct GameId(String);
impl GameId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for GameId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for GameId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for GameId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct CharacterId(String);
impl CharacterId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for CharacterId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for CharacterId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for CharacterId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug)]
pub struct UserRow {
pub id: UserId,
pub name: String,
pub password: String,
pub admin: bool,
pub enabled: bool,
}
#[derive(Clone, Debug)]
pub struct Role {
userid: UserId,
gameid: GameId,
role: String,
}
#[derive(Clone, Debug)]
pub struct GameRow {
pub id: GameId,
pub gm: UserId,
pub game_type: String,
pub name: String,
}
#[derive(Clone, Debug)]
pub struct CharsheetRow {
pub id: String,
pub game: GameId,
pub data: serde_json::Value,
}
#[derive(Clone, Debug)]
pub struct SessionRow {
id: SessionId,
user_id: SessionId,
}

View File

@ -0,0 +1,24 @@
use authdb::{AuthDB, AuthToken};
use http::{response::Response, status::StatusCode, Error};
pub async fn handle_auth(
auth_ctx: &AuthDB,
auth_token: AuthToken,
) -> Result<http::Response<String>, Error> {
match auth_ctx.authenticate(auth_token).await {
Ok(Some(session)) => match serde_json::to_string(&session) {
Ok(session_token) => Response::builder()
.status(StatusCode::OK)
.body(session_token),
Err(_) => Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body("".to_owned()),
},
Ok(None) => Response::builder()
.status(StatusCode::UNAUTHORIZED)
.body("".to_owned()),
Err(_) => Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body("".to_owned()),
}
}

View File

@ -1,29 +0,0 @@
use axum::http::HeaderMap;
use result_extended::ResultExt;
use serde::{Deserialize, Serialize};
use crate::{
core::Core,
database::GameId,
types::{AppError, FatalError},
};
use super::auth_required;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct CreateGameRequest {
pub game_type: String,
pub game_name: String,
}
pub async fn create_game(
core: Core,
headers: HeaderMap,
req: CreateGameRequest,
) -> ResultExt<GameId, AppError, FatalError> {
auth_required(core.clone(), headers, |user| async move {
core.create_game(&user.id, &req.game_type, &req.game_name)
.await
})
.await
}

View File

@ -1,247 +0,0 @@
mod game_management;
mod user_management;
use axum::{http::StatusCode, Json};
use futures::Future;
pub use game_management::*;
pub use user_management::*;
use result_extended::ResultExt;
use serde::{Deserialize, Serialize};
use crate::{
core::Core,
types::{AppError, FatalError},
};
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct HealthCheck {
pub ok: bool,
}
pub async fn wrap_handler<F, A, Fut>(f: F) -> (StatusCode, Json<Option<A>>)
where
F: FnOnce() -> Fut,
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
{
match f().await {
ResultExt::Ok(val) => (StatusCode::OK, Json(Some(val))),
ResultExt::Err(AppError::BadRequest) => (StatusCode::BAD_REQUEST, Json(None)),
ResultExt::Err(AppError::CouldNotCreateObject) => (StatusCode::BAD_REQUEST, Json(None)),
ResultExt::Err(AppError::NotFound(_)) => (StatusCode::NOT_FOUND, Json(None)),
ResultExt::Err(AppError::Inaccessible(_)) => (StatusCode::NOT_FOUND, Json(None)),
ResultExt::Err(AppError::PermissionDenied) => (StatusCode::FORBIDDEN, Json(None)),
ResultExt::Err(AppError::AuthFailed) => (StatusCode::UNAUTHORIZED, Json(None)),
ResultExt::Err(AppError::JsonError(_)) => (StatusCode::INTERNAL_SERVER_ERROR, Json(None)),
ResultExt::Err(AppError::UnexpectedError(_)) => {
(StatusCode::INTERNAL_SERVER_ERROR, Json(None))
}
ResultExt::Err(AppError::UsernameUnavailable) => (StatusCode::BAD_REQUEST, Json(None)),
ResultExt::Fatal(err) => {
panic!("The server encountered a fatal error: {}", err);
}
}
}
pub async fn healthcheck(core: Core) -> Vec<u8> {
match core.status().await {
ResultExt::Ok(s) => serde_json::to_vec(&HealthCheck {
ok: s.admin_enabled,
})
.unwrap(),
ResultExt::Err(_) => serde_json::to_vec(&HealthCheck { ok: false }).unwrap(),
ResultExt::Fatal(err) => panic!("{}", err),
}
}
/*
pub async fn handle_file(core: Core, asset_id: AssetId) -> impl Reply {
handler(async move {
let (mime, bytes) = return_error!(core.get_asset(asset_id).await);
ok(Response::builder()
.header("content-type", mime.to_string())
.body(bytes)
.unwrap())
})
.await
}
pub async fn handle_available_images(core: Core) -> impl Reply {
handler(async move {
let image_paths: Vec<String> = core
.available_images()
.await
.into_iter()
.map(|path| format!("{}", path.as_str()))
.collect();
ok(Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Content-Type", "application/json")
.body(serde_json::to_vec(&image_paths).unwrap())
.unwrap())
})
.await
}
#[derive(Deserialize, Serialize)]
pub struct RegisterRequest {}
#[derive(Deserialize, Serialize)]
pub struct RegisterResponse {
url: String,
}
pub async fn handle_register_client(core: Core, _request: RegisterRequest) -> impl Reply {
handler(async move {
let client_id = core.register_client().await;
ok(Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Content-Type", "application/json")
.body(
serde_json::to_vec(&RegisterResponse {
url: format!("ws://127.0.0.1:8001/ws/{}", client_id),
})
.unwrap(),
)
.unwrap())
})
.await
}
pub async fn handle_unregister_client(core: Core, client_id: String) -> impl Reply {
handler(async move {
core.unregister_client(client_id).await;
ok(Response::builder()
.status(StatusCode::NO_CONTENT)
.body(vec![])
.unwrap())
})
.await
}
pub async fn handle_connect_websocket(
core: Core,
ws: warp::ws::Ws,
client_id: String,
) -> impl Reply {
ws.on_upgrade(move |socket| {
println!("upgrading websocket");
let core = core.clone();
async move {
let (mut ws_sender, _) = socket.split();
let mut receiver = core.connect_client(client_id.clone()).await;
tokio::task::spawn(async move {
let tabletop = core.tabletop().await;
let _ = ws_sender
.send(Message::text(
serde_json::to_string(&crate::types::Message::UpdateTabletop(tabletop))
.unwrap(),
))
.await;
while let Some(msg) = receiver.recv().await {
println!("Relaying message: {:?}", msg);
let _ = ws_sender
.send(Message::text(serde_json::to_string(&msg).unwrap()))
.await;
}
println!("process ended for id {}", client_id);
});
}
})
}
pub async fn handle_set_background_image(core: Core, image_name: String) -> impl Reply {
handler(async move {
let _ = core.set_background_image(AssetId::from(image_name)).await;
ok(Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "*")
.header("Content-Type", "application/json")
.body(vec![])
.unwrap())
})
.await
}
pub async fn handle_get_users(core: Core) -> Response<Vec<u8>> {
unimplemented!()
/*
handler(async move {
let users = match core.list_users().await {
ResultExt::Ok(users) => users,
ResultExt::Err(err) => return ResultExt::Err(err),
ResultExt::Fatal(err) => return ResultExt::Fatal(err),
};
ok(Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Content-Type", "application/json")
.body(serde_json::to_vec(&users).unwrap())
.unwrap())
})
.await
*/
}
pub async fn handle_get_games(core: Core) -> impl Reply {
handler(async move {
let games = match core.list_games().await {
ResultExt::Ok(games) => games,
ResultExt::Err(err) => return ResultExt::Err(err),
ResultExt::Fatal(err) => return ResultExt::Fatal(err),
};
ok(Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Content-Type", "application/json")
.body(serde_json::to_vec(&games).unwrap())
.unwrap())
})
.await
}
pub async fn handle_get_charsheet(core: Core, charid: String) -> impl Reply {
handler(async move {
let sheet = match core.get_charsheet(CharacterId::from(charid)).await {
ResultExt::Ok(sheet) => sheet,
ResultExt::Err(err) => return ResultExt::Err(err),
ResultExt::Fatal(err) => return ResultExt::Fatal(err),
};
match sheet {
Some(sheet) => ok(Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Content-Type", "application/json")
.body(serde_json::to_vec(&sheet).unwrap())
.unwrap()),
None => ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(vec![])
.unwrap()),
}
})
.await
}
pub async fn handle_set_admin_password(core: Core, password: String) -> impl Reply {
handler(async move {
let status = return_error!(core.status().await);
if status.admin_enabled {
return error(AppError::PermissionDenied);
}
core.set_password(UserId::from("admin"), password).await;
ok(Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "*")
.header("Content-Type", "application/json")
.body(vec![])
.unwrap())
})
.await
}
*/

View File

@ -1,136 +0,0 @@
use axum::{
http::HeaderMap,
Json,
};
use futures::Future;
use result_extended::{error, ok, return_error, ResultExt};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::{
core::Core,
database::{SessionId, UserId},
types::{AppError, FatalError, User, UserProfile},
};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[typeshare]
pub struct AuthRequest {
pub username: String,
pub password: String,
}
#[derive(Deserialize, Serialize)]
#[typeshare]
pub struct CreateUserRequest {
pub username: String,
}
#[derive(Deserialize, Serialize)]
#[typeshare]
pub struct SetPasswordRequest {
pub password_1: String,
pub password_2: String,
}
async fn check_session(
core: &Core,
headers: HeaderMap,
) -> ResultExt<Option<User>, AppError, FatalError> {
match headers.get("Authorization") {
Some(token) => {
match token
.to_str()
.unwrap()
.split(" ")
.collect::<Vec<&str>>()
.as_slice()
{
[_schema, token] => core.session(&SessionId::from(token.to_owned())).await,
_ => error(AppError::BadRequest),
}
}
None => ok(None),
}
}
pub async fn auth_required<F, A, Fut>(
core: Core,
headers: HeaderMap,
f: F,
) -> ResultExt<A, AppError, FatalError>
where
F: FnOnce(User) -> Fut,
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
{
match return_error!(check_session(&core, headers).await) {
Some(user) => f(user).await,
None => error(AppError::AuthFailed),
}
}
pub async fn admin_required<F, A, Fut>(
core: Core,
headers: HeaderMap,
f: F,
) -> ResultExt<A, AppError, FatalError>
where
F: FnOnce(User) -> Fut,
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
{
match return_error!(check_session(&core, headers).await) {
Some(user) => {
if user.admin {
f(user).await
} else {
error(AppError::PermissionDenied)
}
}
None => error(AppError::AuthFailed),
}
}
pub async fn check_password(
core: Core,
req: Json<AuthRequest>,
) -> ResultExt<SessionId, AppError, FatalError> {
let Json(AuthRequest { username, password }) = req;
core.auth(&username, &password).await
}
pub async fn get_user(
core: Core,
headers: HeaderMap,
user_id: Option<UserId>,
) -> ResultExt<Option<UserProfile>, AppError, FatalError> {
auth_required(core.clone(), headers, |user| async move {
match user_id {
Some(user_id) => core.user(user_id).await,
None => core.user(user.id).await,
}
}).await
}
pub async fn create_user(
core: Core,
headers: HeaderMap,
req: CreateUserRequest,
) -> ResultExt<UserId, AppError, FatalError> {
admin_required(core.clone(), headers, |_admin| async {
core.create_user(&req.username).await
}).await
}
pub async fn set_password(
core: Core,
headers: HeaderMap,
req: SetPasswordRequest,
) -> ResultExt<(), AppError, FatalError> {
auth_required(core.clone(), headers, |user| async {
if req.password_1 == req.password_2 {
core.set_password(user.id, req.password_1).await
} else {
error(AppError::BadRequest)
}
}).await
}

View File

@ -1,33 +1,105 @@
use core::Core;
use std::path::PathBuf;
use authdb::{AuthDB, AuthError, AuthToken, SessionToken, Username};
use std::{
convert::Infallible,
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
sync::Arc,
};
use warp::{
header,
http::StatusCode,
reply::{Json, Reply},
Filter,
};
use asset_db::FsAssets;
use database::DbConn;
mod asset_db;
mod core;
mod database;
mod handlers;
mod routes;
mod types;
use handlers::handle_auth;
#[derive(Debug)]
struct Unauthorized;
impl warp::reject::Reject for Unauthorized {}
#[derive(Debug)]
struct AuthDBError(AuthError);
impl warp::reject::Reject for AuthDBError {}
fn with_session(
auth_ctx: Arc<AuthDB>,
) -> impl Filter<Extract = (Username,), Error = warp::Rejection> + Clone {
header("authentication").and_then({
move |value: String| {
let auth_ctx = auth_ctx.clone();
async move {
match auth_ctx.validate_session(SessionToken::from(value)).await {
Ok(Some(username)) => Ok(username),
Ok(None) => Err(warp::reject::custom(Unauthorized)),
Err(err) => Err(warp::reject::custom(AuthDBError(err))),
}
}
}
})
}
fn route_echo_unauthenticated() -> impl Filter<Extract = (Json,), Error = warp::Rejection> + Clone {
warp::path!("api" / "v1" / "echo" / String).map(|param: String| {
println!("param: {}", param);
warp::reply::json(&vec!["unauthenticated", param.as_str()])
})
}
fn route_authenticate(
auth_ctx: Arc<AuthDB>,
) -> impl Filter<Extract = (Json,), Error = warp::Rejection> + Clone {
let auth_ctx = auth_ctx.clone();
warp::path!("api" / "v1" / "auth")
.and(warp::post())
.and(warp::body::json())
.map(move |param: AuthToken| {
let res = handle_auth(&auth_ctx, param.clone());
warp::reply::json(&param)
})
}
fn route_echo_authenticated(
auth_ctx: Arc<AuthDB>,
) -> impl Filter<Extract = (Json,), Error = warp::Rejection> + Clone {
warp::path!("api" / "v1" / "echo" / String)
.and(with_session(auth_ctx.clone()))
.map(move |param: String, username: Username| {
println!("param: {:?}", username);
println!("param: {}", param);
warp::reply::json(&vec!["authenticated", username.as_str(), param.as_str()])
})
}
async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible> {
if let Some(Unauthorized) = err.find() {
Ok(warp::reply::with_status(
"".to_owned(),
StatusCode::UNAUTHORIZED,
))
} else {
Ok(warp::reply::with_status(
"".to_owned(),
StatusCode::INTERNAL_SERVER_ERROR,
))
}
}
#[tokio::main]
pub async fn main() {
/*
pretty_env_logger::init();
let unauthenticated_endpoints = route_healthcheck().or(route_authenticate(core.clone()));
let authenticated_endpoints = route_image(core.clone());
*/
let conn = DbConn::new(Some("/home/savanni/game.db"));
let core = Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn);
let app = routes::routes(core);
let listener = tokio::net::TcpListener::bind("127.0.0.1:8001")
let auth_db = AuthDB::new(PathBuf::from("./auth_db.sqlite"))
.await
.unwrap();
.expect("AuthDB should initialize");
let auth_ctx: Arc<AuthDB> = Arc::new(auth_db);
axum::serve(listener, app).await.unwrap();
let filter = route_echo_authenticated(auth_ctx.clone())
.or(route_authenticate(auth_ctx.clone()))
.or(route_echo_unauthenticated())
.recover(handle_rejection);
let server = warp::serve(filter);
server
.run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8001))
.await;
}

Some files were not shown because too many files have changed in this diff Show More