mirror of
				https://github.com/Djeeberjr/fw-anwesenheit.git
				synced 2025-11-03 23:24:10 +00:00 
			
		
		
		
	changed storage method of ID mappings
This commit is contained in:
		
							parent
							
								
									6cc6df6298
								
							
						
					
					
						commit
						07d51264f9
					
				@ -1,10 +1,14 @@
 | 
			
		||||
use alloc::vec::Vec;
 | 
			
		||||
use core::str::from_utf8;
 | 
			
		||||
 | 
			
		||||
use alloc::{string::ToString, vec::Vec};
 | 
			
		||||
use embassy_time::Delay;
 | 
			
		||||
use embedded_hal_bus::spi::ExclusiveDevice;
 | 
			
		||||
use embedded_sdmmc::{SdCard, ShortFileName, TimeSource, Timestamp, VolumeIdx, VolumeManager};
 | 
			
		||||
use esp_hal::{Blocking, gpio::Output, spi::master::Spi};
 | 
			
		||||
 | 
			
		||||
use crate::store::{AttendanceDay, IDMapping, day::Day, persistence::Persistence};
 | 
			
		||||
use crate::store::{
 | 
			
		||||
    AttendanceDay, day::Day, mapping_loader::Name, persistence::Persistence, tally_id::TallyID,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub struct DummyTimesource;
 | 
			
		||||
 | 
			
		||||
@ -39,16 +43,46 @@ pub struct SDCardPersistence {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SDCardPersistence {
 | 
			
		||||
    const MAPPING_FILENAME: &'static str = "MAPPING.JS";
 | 
			
		||||
    const MAPPING_DIRNAME: &'static str = "MAPPINGS";
 | 
			
		||||
 | 
			
		||||
    fn generate_filename(day: Day) -> ShortFileName {
 | 
			
		||||
    fn generate_filename_for_day(day: Day) -> ShortFileName {
 | 
			
		||||
        let basename = day.to_string();
 | 
			
		||||
        let mut filename: heapless::String<11> = heapless::String::new();
 | 
			
		||||
        filename.push_str(&basename).unwrap();
 | 
			
		||||
        filename.push_str(".js").unwrap();
 | 
			
		||||
        filename.push_str(".JS").unwrap();
 | 
			
		||||
 | 
			
		||||
        ShortFileName::create_from_str(&filename).unwrap()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn generate_path_for_id(id: TallyID) -> (ShortFileName, ShortFileName) {
 | 
			
		||||
        let basename: heapless::String<12> = id.into();
 | 
			
		||||
        let (dir, file) = basename.split_at(6);
 | 
			
		||||
 | 
			
		||||
        let mut filename: heapless::String<11> = heapless::String::new();
 | 
			
		||||
        filename.push_str(file).unwrap();
 | 
			
		||||
        filename.push_str(".JS").unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut dirname: heapless::String<11> = heapless::String::new();
 | 
			
		||||
        dirname.push_str(dir).unwrap();
 | 
			
		||||
 | 
			
		||||
        (
 | 
			
		||||
            ShortFileName::create_from_str(&dirname).unwrap(),
 | 
			
		||||
            ShortFileName::create_from_str(&filename).unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_tallyid_from_path(dirname: &ShortFileName, filename: &ShortFileName) -> Option<TallyID> {
 | 
			
		||||
        let mut id_str: heapless::String<12> = heapless::String::new();
 | 
			
		||||
 | 
			
		||||
        id_str.push_str(&dirname.to_string()).unwrap();
 | 
			
		||||
        id_str
 | 
			
		||||
            .push_str(from_utf8(filename.base_name()).unwrap())
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let id: TallyID = id_str.try_into().unwrap();
 | 
			
		||||
 | 
			
		||||
        Some(id)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Persistence for SDCardPersistence {
 | 
			
		||||
@ -56,7 +90,7 @@ impl Persistence for SDCardPersistence {
 | 
			
		||||
        let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
 | 
			
		||||
        let mut root_dir = vol_0.open_root_dir().unwrap();
 | 
			
		||||
 | 
			
		||||
        let filename = Self::generate_filename(day);
 | 
			
		||||
        let filename = Self::generate_filename_for_day(day);
 | 
			
		||||
        let file = root_dir.open_file_in_dir(filename, embedded_sdmmc::Mode::ReadOnly);
 | 
			
		||||
 | 
			
		||||
        if file.is_err() {
 | 
			
		||||
@ -78,7 +112,7 @@ impl Persistence for SDCardPersistence {
 | 
			
		||||
        let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
 | 
			
		||||
        let mut root_dir = vol_0.open_root_dir().unwrap();
 | 
			
		||||
 | 
			
		||||
        let filename = Self::generate_filename(day);
 | 
			
		||||
        let filename = Self::generate_filename_for_day(day);
 | 
			
		||||
 | 
			
		||||
        let mut file = root_dir
 | 
			
		||||
            .open_file_in_dir(filename, embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
 | 
			
		||||
@ -90,45 +124,6 @@ impl Persistence for SDCardPersistence {
 | 
			
		||||
        file.close().unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn load_mapping(&mut self) -> Option<crate::store::IDMapping> {
 | 
			
		||||
        let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
 | 
			
		||||
        let mut root_dir = vol_0.open_root_dir().unwrap();
 | 
			
		||||
 | 
			
		||||
        let file =
 | 
			
		||||
            root_dir.open_file_in_dir(Self::MAPPING_FILENAME, embedded_sdmmc::Mode::ReadOnly);
 | 
			
		||||
 | 
			
		||||
        if file.is_err() {
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut open_file = file.unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut read_buffer: [u8; 1024] = [0; 1024];
 | 
			
		||||
        let read = open_file.read(&mut read_buffer).unwrap();
 | 
			
		||||
        open_file.close().unwrap();
 | 
			
		||||
 | 
			
		||||
        let mapping: IDMapping = serde_json::from_slice(&read_buffer[..read]).unwrap();
 | 
			
		||||
 | 
			
		||||
        Some(mapping)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn save_mapping(&mut self, data: &crate::store::IDMapping) {
 | 
			
		||||
        let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
 | 
			
		||||
        let mut root_dir = vol_0.open_root_dir().unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut file = root_dir
 | 
			
		||||
            .open_file_in_dir(
 | 
			
		||||
                Self::MAPPING_FILENAME,
 | 
			
		||||
                embedded_sdmmc::Mode::ReadWriteCreateOrTruncate,
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        file.write(&serde_json::to_vec(data).unwrap()).unwrap();
 | 
			
		||||
 | 
			
		||||
        file.flush().unwrap();
 | 
			
		||||
        file.close().unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn list_days(&mut self) -> Vec<Day> {
 | 
			
		||||
        let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
 | 
			
		||||
        let mut root_dir = vol_0.open_root_dir().unwrap();
 | 
			
		||||
@ -148,4 +143,92 @@ impl Persistence for SDCardPersistence {
 | 
			
		||||
 | 
			
		||||
        days
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn load_mapping_for_id(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        id: crate::store::tally_id::TallyID,
 | 
			
		||||
    ) -> Option<crate::store::mapping_loader::Name> {
 | 
			
		||||
        let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
 | 
			
		||||
        let mut root_dir = vol_0.open_root_dir().unwrap();
 | 
			
		||||
        let mut mapping_dir = root_dir.open_dir(Self::MAPPING_DIRNAME).unwrap();
 | 
			
		||||
 | 
			
		||||
        let (dirname, filename) = Self::generate_path_for_id(id);
 | 
			
		||||
 | 
			
		||||
        let mut dir = mapping_dir.open_dir(dirname).unwrap();
 | 
			
		||||
        let mut file = dir
 | 
			
		||||
            .open_file_in_dir(filename, embedded_sdmmc::Mode::ReadOnly)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut read_buffer: [u8; 1024] = [0; 1024];
 | 
			
		||||
        let read_bytes = file.read(&mut read_buffer).unwrap();
 | 
			
		||||
        file.close().unwrap();
 | 
			
		||||
 | 
			
		||||
        let mapping: Name = serde_json::from_slice(&read_buffer[..read_bytes]).unwrap();
 | 
			
		||||
 | 
			
		||||
        Some(mapping)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn save_mapping_for_id(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        id: crate::store::tally_id::TallyID,
 | 
			
		||||
        name: crate::store::mapping_loader::Name,
 | 
			
		||||
    ) {
 | 
			
		||||
        let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
 | 
			
		||||
        let mut root_dir = vol_0.open_root_dir().unwrap();
 | 
			
		||||
        let mut mapping_dir = root_dir.open_dir(Self::MAPPING_DIRNAME).unwrap();
 | 
			
		||||
 | 
			
		||||
        let (dirname, filename) = Self::generate_path_for_id(id);
 | 
			
		||||
 | 
			
		||||
        let mut dir = if let Ok(dir) = mapping_dir.open_dir(&dirname) {
 | 
			
		||||
            dir
 | 
			
		||||
        } else {
 | 
			
		||||
            mapping_dir.make_dir_in_dir(&dirname).unwrap();
 | 
			
		||||
            mapping_dir.open_dir(&dirname).unwrap()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let mut file = dir
 | 
			
		||||
            .open_file_in_dir(filename, embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        file.write(&serde_json::to_vec(&name).unwrap()).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn list_mappings(&mut self) -> Vec<TallyID> {
 | 
			
		||||
        let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
 | 
			
		||||
        let mut root_dir = vol_0.open_root_dir().unwrap();
 | 
			
		||||
        let mut mapping_dir = root_dir.open_dir(Self::MAPPING_DIRNAME).unwrap();
 | 
			
		||||
        let mut ids: Vec<TallyID> = Vec::new();
 | 
			
		||||
 | 
			
		||||
        let mut dir_names = Vec::new();
 | 
			
		||||
        mapping_dir
 | 
			
		||||
            .iterate_dir(|entry| {
 | 
			
		||||
                if entry.attributes.is_directory()
 | 
			
		||||
                    && entry.name.to_string() != "."
 | 
			
		||||
                    && entry.name.to_string() != ".."
 | 
			
		||||
                {
 | 
			
		||||
                    dir_names.push(entry.name.clone());
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        for dirname in dir_names {
 | 
			
		||||
            if let Ok(mut subdir) = mapping_dir.open_dir(&dirname) {
 | 
			
		||||
                let mut file_names = Vec::new();
 | 
			
		||||
                subdir
 | 
			
		||||
                    .iterate_dir(|file_entry| {
 | 
			
		||||
                        if !file_entry.attributes.is_directory() {
 | 
			
		||||
                            file_names.push(file_entry.name.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
 | 
			
		||||
                for filename in file_names {
 | 
			
		||||
                    let id = Self::get_tallyid_from_path(&dirname, &filename);
 | 
			
		||||
                    ids.push(id.unwrap());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ids
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/main.rs
									
									
									
									
									
								
							@ -2,7 +2,6 @@
 | 
			
		||||
#![no_main]
 | 
			
		||||
#![feature(type_alias_impl_trait)]
 | 
			
		||||
#![feature(impl_trait_in_assoc_type)]
 | 
			
		||||
#![warn(clippy::unwrap_used)]
 | 
			
		||||
 | 
			
		||||
use alloc::rc::Rc;
 | 
			
		||||
use embassy_executor::Spawner;
 | 
			
		||||
@ -26,7 +25,7 @@ extern crate alloc;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    init::{hardware::AppHardware, sd_card::SDCardPersistence},
 | 
			
		||||
    store::{IDStore, day::Day, tally_id::TallyID},
 | 
			
		||||
    store::{IDStore, day::Day, mapping_loader::MappingLoader, tally_id::TallyID},
 | 
			
		||||
    webserver::start_webserver,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -54,9 +53,15 @@ async fn main(spawner: Spawner) -> ! {
 | 
			
		||||
    let mut rtc = drivers::rtc::RTCClock::new(app_hardware.i2c).await;
 | 
			
		||||
 | 
			
		||||
    let current_day: Day = rtc.get_time().await.into();
 | 
			
		||||
    let store: UsedStore = IDStore::new_from_storage(app_hardware.sdcard, current_day).await;
 | 
			
		||||
 | 
			
		||||
    let shared_sdcard: Rc<Mutex<CriticalSectionRawMutex, SDCardPersistence>> =
 | 
			
		||||
        Rc::new(Mutex::new(app_hardware.sdcard));
 | 
			
		||||
 | 
			
		||||
    let store: UsedStore = IDStore::new_from_storage(shared_sdcard.clone(), current_day).await;
 | 
			
		||||
    let shared_store = Rc::new(Mutex::new(store));
 | 
			
		||||
 | 
			
		||||
    let mapping_loader = MappingLoader::new(shared_sdcard.clone());
 | 
			
		||||
 | 
			
		||||
    let chan: &'static mut TallyChannel = CHAN.init(PubSubChannel::new());
 | 
			
		||||
    let publisher: TallyPublisher = chan.publisher().unwrap();
 | 
			
		||||
    let mut sub: TallySubscriber = chan.subscriber().unwrap();
 | 
			
		||||
@ -68,6 +73,7 @@ async fn main(spawner: Spawner) -> ! {
 | 
			
		||||
        app_hardware.network_stack,
 | 
			
		||||
        shared_store.clone(),
 | 
			
		||||
        chan,
 | 
			
		||||
        mapping_loader,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /****************************** Spawning tasks ***********************************/
 | 
			
		||||
 | 
			
		||||
@ -1,33 +0,0 @@
 | 
			
		||||
use alloc::collections::BTreeMap;
 | 
			
		||||
use alloc::string::String;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::store::tally_id::TallyID;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Name {
 | 
			
		||||
    pub first: String,
 | 
			
		||||
    pub last: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct IDMapping {
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    id_map: BTreeMap<TallyID, Name>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IDMapping {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        IDMapping {
 | 
			
		||||
            id_map: BTreeMap::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn map(&self, id: &TallyID) -> Option<&Name> {
 | 
			
		||||
        self.id_map.get(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_mapping(&mut self, id: TallyID, name: Name) {
 | 
			
		||||
        self.id_map.insert(id, name);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,10 @@
 | 
			
		||||
use alloc::rc::Rc;
 | 
			
		||||
use alloc::vec::Vec;
 | 
			
		||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 | 
			
		||||
use embassy_sync::mutex::Mutex;
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
use super::IDMapping;
 | 
			
		||||
use crate::store::day::Day;
 | 
			
		||||
use crate::store::persistence::Persistence;
 | 
			
		||||
use crate::store::tally_id::TallyID;
 | 
			
		||||
@ -34,40 +36,36 @@ impl AttendanceDay {
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct IDStore<T: Persistence> {
 | 
			
		||||
    pub current_day: AttendanceDay,
 | 
			
		||||
    pub mapping: IDMapping,
 | 
			
		||||
    persistence_layer: T,
 | 
			
		||||
    current_day: AttendanceDay,
 | 
			
		||||
    persistence_layer: Rc<Mutex<CriticalSectionRawMutex, T>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: Persistence> IDStore<T> {
 | 
			
		||||
    pub async fn new_from_storage(mut persistence_layer: T, current_date: Day) -> Self {
 | 
			
		||||
        let mapping = match persistence_layer.load_mapping().await {
 | 
			
		||||
            Some(map) => map,
 | 
			
		||||
            None => IDMapping::new(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    pub async fn new_from_storage(
 | 
			
		||||
        persistence_layer: Rc<Mutex<CriticalSectionRawMutex, T>>,
 | 
			
		||||
        current_date: Day,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let day = persistence_layer
 | 
			
		||||
            .lock()
 | 
			
		||||
            .await
 | 
			
		||||
            .load_day(current_date)
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap_or(AttendanceDay::new(current_date));
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            current_day: day,
 | 
			
		||||
            mapping,
 | 
			
		||||
            persistence_layer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn persist_day(&mut self) {
 | 
			
		||||
        self.persistence_layer
 | 
			
		||||
            .lock()
 | 
			
		||||
            .await
 | 
			
		||||
            .save_day(self.current_day.date, &self.current_day)
 | 
			
		||||
            .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn persist_mapping(&mut self) {
 | 
			
		||||
        self.persistence_layer.save_mapping(&self.mapping).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a new id for the current day
 | 
			
		||||
    /// Returns false if ID is already present at the current day.
 | 
			
		||||
    pub async fn add_id(&mut self, id: TallyID, current_date: Day) -> bool {
 | 
			
		||||
@ -95,11 +93,11 @@ impl<T: Persistence> IDStore<T> {
 | 
			
		||||
            return Some(self.current_day.clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.persistence_layer.load_day(day).await
 | 
			
		||||
        self.persistence_layer.lock().await.load_day(day).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn list_days_in_timespan(&mut self, from: Day, to: Day) -> Vec<Day> {
 | 
			
		||||
        let all_days = self.persistence_layer.list_days().await;
 | 
			
		||||
        let all_days = self.persistence_layer.lock().await.list_days().await;
 | 
			
		||||
 | 
			
		||||
        all_days
 | 
			
		||||
            .into_iter()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								src/store/mapping_loader.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/store/mapping_loader.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
use alloc::{rc::Rc, string::String, vec::Vec};
 | 
			
		||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::store::{persistence::Persistence, tally_id::TallyID};
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Name {
 | 
			
		||||
    pub first: String,
 | 
			
		||||
    pub last: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct MappingLoader<T: Persistence>(Rc<Mutex<CriticalSectionRawMutex, T>>);
 | 
			
		||||
 | 
			
		||||
impl<T: Persistence> MappingLoader<T> {
 | 
			
		||||
    pub fn new(persistence_layer: Rc<Mutex<CriticalSectionRawMutex, T>>) -> Self {
 | 
			
		||||
        Self(persistence_layer)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_mapping(&self, id: TallyID) -> Option<Name> {
 | 
			
		||||
        self.0.lock().await.load_mapping_for_id(id).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn set_mapping(&self, id: TallyID, name: Name) {
 | 
			
		||||
        self.0.lock().await.save_mapping_for_id(id, name).await;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn list_mappings(&self) -> Vec<TallyID> {
 | 
			
		||||
        self.0.lock().await.list_mappings().await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,9 +1,7 @@
 | 
			
		||||
pub use id_mapping::{IDMapping, Name};
 | 
			
		||||
pub use id_store::{IDStore,AttendanceDay};
 | 
			
		||||
 | 
			
		||||
mod id_mapping;
 | 
			
		||||
pub mod persistence;
 | 
			
		||||
mod id_store;
 | 
			
		||||
pub mod tally_id;
 | 
			
		||||
pub mod day;
 | 
			
		||||
 | 
			
		||||
pub mod mapping_loader;
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,13 @@
 | 
			
		||||
use alloc::vec::Vec;
 | 
			
		||||
 | 
			
		||||
use crate::store::{IDMapping, day::Day, id_store::AttendanceDay};
 | 
			
		||||
use crate::store::{day::Day, id_store::AttendanceDay, mapping_loader::Name, tally_id::TallyID};
 | 
			
		||||
 | 
			
		||||
pub trait Persistence {
 | 
			
		||||
    async fn load_day(&mut self, day: Day) -> Option<AttendanceDay>;
 | 
			
		||||
    async fn save_day(&mut self, day: Day, data: &AttendanceDay);
 | 
			
		||||
    async fn list_days(&mut self) -> Vec<Day>;
 | 
			
		||||
 | 
			
		||||
    async fn load_mapping(&mut self) -> Option<IDMapping>;
 | 
			
		||||
    async fn save_mapping(&mut self, data: &IDMapping);
 | 
			
		||||
    async fn load_mapping_for_id(&mut self, id:TallyID ) -> Option<Name>;
 | 
			
		||||
    async fn save_mapping_for_id(&mut self, id:TallyID, name: Name);
 | 
			
		||||
    async fn list_mappings(&mut self) -> Vec<TallyID>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ use picoserve::{
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    store::{Name, day::Day, tally_id::TallyID},
 | 
			
		||||
    store::{day::Day, mapping_loader::Name, tally_id::TallyID},
 | 
			
		||||
    webserver::{app::AppState, sse::IDEvents},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -28,10 +28,25 @@ pub struct QueryDay {
 | 
			
		||||
    day: Option<u32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize)]
 | 
			
		||||
pub struct QueryMapping {
 | 
			
		||||
    id: TallyID,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GET /api/mappings
 | 
			
		||||
pub async fn get_mappings(State(state): State<AppState>) -> impl IntoResponse {
 | 
			
		||||
    let loader = state.mapping_loader.lock().await;
 | 
			
		||||
    response::Json(loader.list_mappings().await)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GET /api/mapping
 | 
			
		||||
pub async fn get_mapping(State(state): State<AppState>) -> impl IntoResponse {
 | 
			
		||||
    let store = state.store.lock().await;
 | 
			
		||||
    response::Json(store.mapping.clone())
 | 
			
		||||
pub async fn get_mapping(
 | 
			
		||||
    State(state): State<AppState>,
 | 
			
		||||
    Query(QueryMapping { id }): Query<QueryMapping>,
 | 
			
		||||
) -> impl IntoResponse {
 | 
			
		||||
    let loader = state.mapping_loader.lock().await;
 | 
			
		||||
    let mapping = loader.get_mapping(id).await;
 | 
			
		||||
    response::Json(mapping)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// POST /api/mapping
 | 
			
		||||
@ -39,9 +54,8 @@ pub async fn add_mapping(
 | 
			
		||||
    State(state): State<AppState>,
 | 
			
		||||
    Json(data): Json<NewMapping>,
 | 
			
		||||
) -> impl IntoResponse {
 | 
			
		||||
    let mut store = state.store.lock().await;
 | 
			
		||||
    store.mapping.add_mapping(data.id, data.name);
 | 
			
		||||
    store.persist_mapping().await;
 | 
			
		||||
    let loader = state.mapping_loader.lock().await;
 | 
			
		||||
    loader.set_mapping(data.id, data.name).await;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SSE /api/idevent
 | 
			
		||||
 | 
			
		||||
@ -4,8 +4,10 @@ use picoserve::{AppWithStateBuilder, routing::get};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    TallyChannel, UsedStore,
 | 
			
		||||
    init::sd_card::SDCardPersistence,
 | 
			
		||||
    store::mapping_loader::MappingLoader,
 | 
			
		||||
    webserver::{
 | 
			
		||||
        api::{add_mapping, get_day, get_days, get_idevent, get_mapping},
 | 
			
		||||
        api::{add_mapping, get_day, get_days, get_idevent, get_mapping, get_mappings},
 | 
			
		||||
        assets::Assets,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
@ -14,6 +16,7 @@ use crate::{
 | 
			
		||||
pub struct AppState {
 | 
			
		||||
    pub store: Rc<Mutex<CriticalSectionRawMutex, UsedStore>>,
 | 
			
		||||
    pub chan: &'static TallyChannel,
 | 
			
		||||
    pub mapping_loader: Rc<Mutex<CriticalSectionRawMutex, MappingLoader<SDCardPersistence>>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct AppProps;
 | 
			
		||||
@ -25,6 +28,7 @@ impl AppWithStateBuilder for AppProps {
 | 
			
		||||
    fn build_app(self) -> picoserve::Router<Self::PathRouter, AppState> {
 | 
			
		||||
        picoserve::Router::from_service(Assets)
 | 
			
		||||
            .route("/api/mapping", get(get_mapping).post(add_mapping))
 | 
			
		||||
            .route("/api/mappings", get(get_mappings))
 | 
			
		||||
            .route("/api/idevent", get(get_idevent))
 | 
			
		||||
            .route("/api/days", get(get_days))
 | 
			
		||||
            .route("/api/day", get(get_day))
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,8 @@ use static_cell::make_static;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    TallyChannel, UsedStore,
 | 
			
		||||
    init::sd_card::SDCardPersistence,
 | 
			
		||||
    store::mapping_loader::{self, MappingLoader},
 | 
			
		||||
    webserver::app::{AppProps, AppState},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -23,10 +25,16 @@ pub fn start_webserver(
 | 
			
		||||
    stack: Stack<'static>,
 | 
			
		||||
    store: Rc<Mutex<CriticalSectionRawMutex, UsedStore>>,
 | 
			
		||||
    chan: &'static TallyChannel,
 | 
			
		||||
    mapping_loader: MappingLoader<SDCardPersistence>,
 | 
			
		||||
) {
 | 
			
		||||
    let app = make_static!(AppProps.build_app());
 | 
			
		||||
 | 
			
		||||
    let state = make_static!(AppState { store, chan });
 | 
			
		||||
    let shared_mapping_loader = Rc::new(Mutex::new(mapping_loader));
 | 
			
		||||
    let state = make_static!(AppState {
 | 
			
		||||
        store,
 | 
			
		||||
        chan,
 | 
			
		||||
        mapping_loader: shared_mapping_loader
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let config = make_static!(picoserve::Config::new(picoserve::Timeouts {
 | 
			
		||||
        start_read_request: Some(Duration::from_secs(5)),
 | 
			
		||||
 | 
			
		||||
@ -7,21 +7,29 @@ export interface Name {
 | 
			
		||||
  last: string,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stupidSerdeFix(pairs: [string, Name][]): IDMap {
 | 
			
		||||
  const map: IDMap = {};
 | 
			
		||||
  for (const [key, value] of pairs) {
 | 
			
		||||
    map[key] = value;
 | 
			
		||||
async function fetchIDList(): Promise<string[]> {
 | 
			
		||||
  let res = await fetch("/api/mappings");
 | 
			
		||||
  let data = await res.json();
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  return map;
 | 
			
		||||
async function fetchID(id: string): Promise<Name> {
 | 
			
		||||
  let res = await fetch("/api/mapping?id=" + id);
 | 
			
		||||
  let data = await res.json();
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function fetchMapping(): Promise<IDMap> {
 | 
			
		||||
  let res = await fetch("/api/mapping");
 | 
			
		||||
  let ids = await fetchIDList();
 | 
			
		||||
 | 
			
		||||
  let data = await res.json();
 | 
			
		||||
  let map: IDMap = {};
 | 
			
		||||
 | 
			
		||||
  return stupidSerdeFix(data);
 | 
			
		||||
  for (const id of ids) {
 | 
			
		||||
    let id_name = await fetchID(id);
 | 
			
		||||
    map[id] = id_name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return map;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function addMapping(id: string, firstName: string, lastName: string) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user