legion_prof_viewer 0.8.1

Profiler UI frontend component for Legion Prof
Documentation
use std::fs::File;
use std::path::{Path, PathBuf};
use std::sync::Mutex;

use serde::Deserialize;

use crate::data::{
    self, DataSource, DataSourceDescription, DataSourceInfo, EntryID, SlotMetaTile, SlotTile,
    SummaryTile, TileID,
};
use crate::http::schema::TileRequestRef;

pub struct FileDataSource {
    basedir: PathBuf,
    info: Mutex<Option<DataSourceInfo>>,
}

impl FileDataSource {
    pub fn new(basedir: impl AsRef<Path>) -> Self {
        Self {
            basedir: basedir.as_ref().to_owned(),
            info: Mutex::new(None),
        }
    }

    fn read_file<T>(&self, path: impl AsRef<Path>) -> data::Result<T>
    where
        T: for<'a> Deserialize<'a>,
    {
        let path = path.as_ref();
        let f = File::open(path)
            .map_err(|e| format!("error opening file '{}': {}", path.display(), e))?;
        let f = zstd::Decoder::new(f).map_err(|e| e.to_string())?;
        ciborium::from_reader(f).map_err(|e| e.to_string())
    }
}

impl DataSource for FileDataSource {
    fn fetch_description(&self) -> DataSourceDescription {
        DataSourceDescription {
            source_locator: vec![String::from(self.basedir.to_string_lossy())],
        }
    }

    fn fetch_info(&self) -> data::Result<DataSourceInfo> {
        let path = self.basedir.join("info");
        let result = self.read_file::<DataSourceInfo>(&path);
        if let Ok(info) = &result {
            *self.info.lock().unwrap() = Some(info.clone());
        }
        result
    }

    fn fetch_summary_tile(
        &self,
        entry_id: &EntryID,
        tile_id: TileID,
        _full: bool,
    ) -> data::Result<SummaryTile> {
        let req = TileRequestRef { entry_id, tile_id };
        let mut path = self.basedir.join("summary_tile");
        path.push(req.to_slug());
        self.read_file::<SummaryTile>(&path)
    }

    fn fetch_slot_tile(
        &self,
        entry_id: &EntryID,
        tile_id: TileID,
        _full: bool,
    ) -> data::Result<SlotTile> {
        {
            let Some(ref info) = *self.info.lock().unwrap() else {
                panic!("Must call fetch_info before calling other fetch methods");
            };
            if info.is_empty_tile(entry_id, tile_id) {
                return Ok(SlotTile {
                    entry_id: entry_id.to_owned(),
                    tile_id,
                    data: Default::default(),
                });
            }
        }

        let req = TileRequestRef { entry_id, tile_id };
        let mut path = self.basedir.join("slot_tile");
        path.push(req.to_slug());
        self.read_file::<SlotTile>(&path)
    }

    fn fetch_slot_meta_tile(
        &self,
        entry_id: &EntryID,
        tile_id: TileID,
        _full: bool,
    ) -> data::Result<SlotMetaTile> {
        {
            let Some(ref info) = *self.info.lock().unwrap() else {
                panic!("Must call fetch_info before calling other fetch methods");
            };
            if info.is_empty_tile(entry_id, tile_id) {
                return Ok(SlotMetaTile {
                    entry_id: entry_id.to_owned(),
                    tile_id,
                    data: Default::default(),
                });
            }
        }

        let req = TileRequestRef { entry_id, tile_id };
        let mut path = self.basedir.join("slot_meta_tile");
        path.push(req.to_slug());
        self.read_file::<SlotMetaTile>(&path)
    }
}