diff --git a/src/database/stores/fs/mod.rs b/src/database/stores/fs/mod.rs index f885e99..5febab2 100644 --- a/src/database/stores/fs/mod.rs +++ b/src/database/stores/fs/mod.rs @@ -21,6 +21,7 @@ use diesel::{Connection, QueryDsl, RunQueryDsl, SqliteConnection}; use log::{debug, error, info, warn}; use lru::LruCache; use rayon::prelude::*; +use serde_json::json; use std::borrow::Borrow; use std::convert::{TryFrom, TryInto}; use std::path::PathBuf; @@ -693,6 +694,50 @@ impl UpStore for FsStore { Err(err) => Err(StoreError::Unknown(err.to_string())), } } + + fn stats(&self) -> std::result::Result { + let files = self + .retrieve_all_files() + .map_err(|e| StoreError::Unknown(e.to_string()))?; + + let mut files_by_hash = std::collections::HashMap::new(); + for file in &files { + if !files_by_hash.contains_key(&file.hash) { + files_by_hash.insert(&file.hash, vec![]); + } + files_by_hash.get_mut(&file.hash).unwrap().push(file); + } + + for paths in files_by_hash.values_mut() { + paths.sort_unstable_by_key(|f| !f.valid); + } + + let mut blobs = files_by_hash + .iter() + .map(|(hash, files)| { + json!({ + "hash": hash, + "size": files[0].size, + "paths": files.iter().map(|f| json!({ + "added": f.added, + "valid": f.valid, + "path": f.path + })).collect::() + }) + }) + .collect::>(); + + blobs.sort_unstable_by_key(|f| f["size"].as_u64().unwrap()); + blobs.reverse(); + + Ok(json!({ + "totals": { + "count": files_by_hash.len(), + "size": files_by_hash.iter().map(|(_, f)| f[0].size as u64).sum::() + }, + "blobs": blobs + })) + } } #[cfg(test)] diff --git a/src/database/stores/mod.rs b/src/database/stores/mod.rs index 9e65b81..6514c82 100644 --- a/src/database/stores/mod.rs +++ b/src/database/stores/mod.rs @@ -49,6 +49,7 @@ pub enum UpdatePathOutcome { Removed(PathBuf), Failed(PathBuf, StoreError), } + pub trait UpStore { fn retrieve(&self, hash: &Hash) -> Result>; fn retrieve_all(&self) -> Result>; @@ -64,4 +65,5 @@ pub trait UpStore { job_container: JobContainer, initial: bool, ) -> Result>; + fn stats(&self) -> Result; } diff --git a/src/routes.rs b/src/routes.rs index 77bb4e7..3bd8c9e 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -720,48 +720,9 @@ pub async fn api_refresh( #[get("/api/store")] pub async fn store_info(state: web::Data) -> Result { - // let connection = state.upend.connection().map_err(ErrorInternalServerError)?; - // let files = web::block(move || connection.retrieve_all_files()) - // .await - // .map_err(ErrorInternalServerError)?; - // let mut files_by_hash = HashMap::new(); - // for file in &files { - // if !files_by_hash.contains_key(&file.hash) { - // files_by_hash.insert(&file.hash, vec![]); - // } - // files_by_hash.get_mut(&file.hash).unwrap().push(file); - // } - - // for paths in files_by_hash.values_mut() { - // paths.sort_unstable_by_key(|f| !f.valid); - // } - - // let mut blobs = files_by_hash - // .iter() - // .map(|(hash, files)| { - // json!({ - // "hash": hash, - // "size": files[0].size, - // "paths": files.iter().map(|f| json!({ - // "added": f.added, - // "valid": f.valid, - // "path": f.path - // })).collect::() - // }) - // }) - // .collect::>(); - - // blobs.sort_unstable_by_key(|f| f["size"].as_u64().unwrap()); - // blobs.reverse(); - - // Ok(HttpResponse::Ok().json(json!({ - // "totals": { - // "count": files_by_hash.len(), - // "size": files_by_hash.iter().map(|(_, f)| f[0].size as u64).sum::() - // }, - // "blobs": blobs - // }))) - todo!(); + Ok(HttpResponse::Ok().json(json!({ + "main": state.store.stats().map_err(ErrorInternalServerError)? + }))) } #[derive(Deserialize)] diff --git a/webui/src/lib/api.ts b/webui/src/lib/api.ts index cf4a074..a2fe5d7 100644 --- a/webui/src/lib/api.ts +++ b/webui/src/lib/api.ts @@ -134,7 +134,7 @@ export async function fetchInfo(): Promise { return await response.json(); } -export async function fetchStoreInfo(): Promise { +export async function fetchStoreInfo(): Promise<{ [key: string]: StoreInfo }> { const response = await fetch(`${API_URL}/store`); return await response.json(); } diff --git a/webui/src/views/Store.svelte b/webui/src/views/Store.svelte index 619af75..aed527f 100644 --- a/webui/src/views/Store.svelte +++ b/webui/src/views/Store.svelte @@ -5,61 +5,64 @@ import Spinner from "../components/utils/Spinner.svelte"; import { fetchStoreInfo } from "../lib/api"; - const store = fetchStoreInfo(); + const stores = fetchStoreInfo();
-

Store

- {#await store} +

Stores

+ {#await stores} - {:then store} -
- {store.totals.count} blobs, - {filesize(store.totals.size)} -
- - - - - - - - - {#each store.blobs as blob} - - - - - - - - - {#each blob.paths.slice(1) as path} - - - + {:then stores} + {#each Object.entries(stores) as [key, store] (key)} +

{key}

+
+ {store.totals.count} blobs, + {filesize(store.totals.size)} +
+
HashSizePathAddedValid
{filesize(blob.size)}{blob.paths[0].path}{blob.paths[0].added} - {#if blob.paths[0].valid} - - {:else} - - {/if} -
- - {path.path}{path.added}
+ + + + + + + + {#each store.blobs as blob} + + + + + + - {/each} - - {/each} -
HashSizePathAddedValid
{filesize(blob.size)}{blob.paths[0].path}{blob.paths[0].added} - {#if path.valid} + {#if blob.paths[0].valid} {:else} {/if}
+ {#each blob.paths.slice(1) as path} + + + + {path.path} + {path.added} + + {#if path.valid} + + {:else} + + {/if} + + + {/each} + + {/each} + + {/each} {:catch error}
{error}