[ENG-588] Fix total capacity on macOS (#882)

* correct macos root volume handling

* DiskType enum
This commit is contained in:
Brendan Allan 2023-05-30 08:25:35 +02:00 committed by GitHub
parent cc308c349f
commit 8226d5e9f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 50 deletions

View file

@ -44,7 +44,7 @@ const StatItem: FC<{ title: string; bytes: bigint }> = ({ title, bytes }) => {
};
const OverviewStats = () => {
const { data: libraryStatistics } = useLibraryQuery(['library.getStatistics'], {
const { data: libraryStatistics } = useLibraryQuery(['library.statistics'], {
initialData: { ...EMPTY_STATISTICS }
});

View file

@ -24,7 +24,7 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
|ctx, _: ()| async move { ctx.library_manager.get_all_libraries_config().await },
)
})
.procedure("getStatistics", {
.procedure("statistics", {
R.with2(library()).query(|(_, library), _: ()| async move {
let _statistics = library
.db
@ -39,6 +39,7 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
let mut available_capacity: u64 = 0;
let mut total_capacity: u64 = 0;
if let Ok(volumes) = volumes {
for volume in volumes {
total_capacity += volume.total_capacity;

View file

@ -6,12 +6,29 @@ use crate::{
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use specta::Type;
use std::process::Command;
use std::{fmt::Display, process::Command};
use sysinfo::{DiskExt, System, SystemExt};
use thiserror::Error;
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
pub enum DiskType {
SSD,
HDD,
Removable,
}
impl Display for DiskType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::SSD => "SSD",
Self::HDD => "HDD",
Self::Removable => "Removable",
})
}
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Default, Clone, Type)]
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
pub struct Volume {
pub name: String,
pub mount_point: String,
@ -22,7 +39,7 @@ pub struct Volume {
#[serde_as(as = "DisplayFromStr")]
pub available_capacity: u64,
pub is_removable: bool,
pub disk_type: Option<String>,
pub disk_type: Option<DiskType>,
pub file_system: Option<String>,
pub is_root_filesystem: bool,
}
@ -46,6 +63,13 @@ pub async fn save_volume(library: &Library) -> Result<(), VolumeError> {
// enter all volumes associate with this client add to db
for volume in volumes {
let params = vec![
disk_type::set(volume.disk_type.map(|t| t.to_string())),
filesystem::set(volume.file_system.clone()),
total_bytes_capacity::set(volume.total_capacity.to_string()),
total_bytes_available::set(volume.available_capacity.to_string()),
];
library
.db
.volume()
@ -59,19 +83,9 @@ pub async fn save_volume(library: &Library) -> Result<(), VolumeError> {
library.node_local_id,
volume.name,
volume.mount_point,
vec![
disk_type::set(volume.disk_type.clone()),
filesystem::set(volume.file_system.clone()),
total_bytes_capacity::set(volume.total_capacity.to_string()),
total_bytes_available::set(volume.available_capacity.to_string()),
],
params.clone(),
),
vec![
disk_type::set(volume.disk_type),
filesystem::set(volume.file_system),
total_bytes_capacity::set(volume.total_capacity.to_string()),
total_bytes_available::set(volume.available_capacity.to_string()),
],
params,
)
.exec()
.await?;
@ -88,27 +102,20 @@ pub fn get_volumes() -> Result<Vec<Volume>, VolumeError> {
.iter()
.filter_map(|disk| {
let mut total_capacity = disk.total_space();
let mut mount_point = disk.mount_point().to_str().unwrap_or("/").to_string();
let mount_point = disk.mount_point().to_str().unwrap_or("/").to_string();
let available_capacity = disk.available_space();
let mut name = disk.name().to_str().unwrap_or("Volume").to_string();
let name = disk.name().to_str().unwrap_or("Volume").to_string();
let is_removable = disk.is_removable();
let file_system = String::from_utf8(disk.file_system().to_vec())
.unwrap_or_else(|_| "Err".to_string());
let disk_type = match disk.type_() {
sysinfo::DiskType::SSD => "SSD".to_string(),
sysinfo::DiskType::HDD => "HDD".to_string(),
_ => "Removable Disk".to_string(),
sysinfo::DiskType::SSD => DiskType::SSD,
sysinfo::DiskType::HDD => DiskType::HDD,
_ => DiskType::Removable,
};
if cfg!(target_os = "macos") && mount_point == "/"
|| mount_point == "/System/Volumes/Data"
{
name = "Macintosh HD".to_string();
mount_point = "/".to_string();
}
if total_capacity < available_capacity && cfg!(target_os = "windows") {
let mut caption = mount_point.clone();
caption.pop();

View file

@ -81,7 +81,7 @@ export default () => {
const platform = usePlatform();
const { library } = useLibraryContext();
const stats = useLibraryQuery(['library.getStatistics'], {
const stats = useLibraryQuery(['library.statistics'], {
initialData: { ...EMPTY_STATISTICS }
});
mounted = true;

View file

@ -16,8 +16,8 @@ export type Procedures = {
{ key: "keys.isUnlocked", input: LibraryArgs<null>, result: boolean } |
{ key: "keys.list", input: LibraryArgs<null>, result: StoredKey[] } |
{ key: "keys.listMounted", input: LibraryArgs<null>, result: string[] } |
{ key: "library.getStatistics", input: LibraryArgs<null>, result: Statistics } |
{ key: "library.list", input: never, result: LibraryConfigWrapped[] } |
{ key: "library.statistics", input: LibraryArgs<null>, result: Statistics } |
{ key: "locations.get", input: LibraryArgs<number>, result: Location | null } |
{ key: "locations.getWithRules", input: LibraryArgs<number>, result: LocationWithIndexerRules | null } |
{ key: "locations.indexer_rules.get", input: LibraryArgs<number>, result: IndexerRule } |
@ -98,6 +98,8 @@ export type PeerMetadata = { name: string; operating_system: OperatingSystem | n
export type MasterPasswordChangeArgs = { password: Protected<string>; algorithm: Algorithm; hashing_algorithm: HashingAlgorithm }
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
/**
* NodeConfig is the configuration for a node. This is shared between all libraries and is stored in a JSON file on disk.
*/
@ -138,10 +140,10 @@ export type Params = "Standard" | "Hardened" | "Paranoid"
*/
export type LocationUpdateArgs = { id: number; name: string | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; indexer_rules_ids: number[] }
export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string }
export type SortOrder = "Asc" | "Desc"
export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null }
/**
* Represents the operating system which the remote peer is running.
* This is not used internally and predominantly is designed to be used for display purposes by the embedding application.
@ -161,12 +163,10 @@ export type OnboardingConfig = { password: Protected<string>; algorithm: Algorit
export type FileDecryptorJobInit = { location_id: number; path_id: number; mount_associated_key: boolean; output_path: string | null; password: string | null; save_to_library: boolean | null }
export type Volume = { name: string; mount_point: string; total_capacity: string; available_capacity: string; is_removable: boolean; disk_type: string | null; file_system: string | null; is_root_filesystem: boolean }
export type Statistics = { id: number; date_captured: string; total_object_count: number; library_db_size: string; total_bytes_used: string; total_bytes_capacity: string; total_unique_bytes: string; total_bytes_free: string; preview_media_bytes: string }
export type TagCreateArgs = { name: string; color: string }
export type EditLibraryArgs = { id: string; name: string | null; description: string | null }
export type LightScanArgs = { location_id: number; sub_path: string }
export type FileEraserJobInit = { location_id: number; path_id: number; passes: string }
@ -182,6 +182,8 @@ export type UnlockKeyManagerArgs = { password: Protected<string>; secret_key: Pr
export type NodeState = ({ id: string; name: string; p2p_port: number | null; p2p_email: string | null; p2p_img_url: string | null }) & { data_path: string }
export type EditLibraryArgs = { id: string; name: string | null; description: string | null }
export type SetNoteArgs = { id: number; note: string | null }
export type InvalidateOperationEvent = { key: string; arg: any; result: any | null }
@ -204,18 +206,18 @@ export type Category = "Recents" | "Favorites" | "Photos" | "Videos" | "Movies"
export type FileCopierJobInit = { source_location_id: number; source_path_id: number; target_location_id: number; target_path: string; target_file_name_suffix: string | null }
export type DiskType = "SSD" | "HDD" | "Removable"
export type SetFavoriteArgs = { id: number; favorite: boolean }
export type FilePathFilterArgs = { locationId?: number | null; search?: string; extension?: string | null; createdAt?: OptionalRange<string>; path?: string | null; object?: ObjectFilterArgs | null }
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null }
export type Volume = { name: string; mount_point: string; total_capacity: string; available_capacity: string; is_removable: boolean; disk_type: DiskType | null; file_system: string | null; is_root_filesystem: boolean }
export type FilePathSearchOrdering = { name: SortOrder } | { sizeInBytes: SortOrder } | { dateCreated: SortOrder } | { dateModified: SortOrder } | { dateIndexed: SortOrder } | { object: ObjectSearchOrdering }
export type IndexerRule = { id: number; name: string; default: boolean; rules_per_kind: number[]; date_created: string; date_modified: string }
export type BuildInfo = { version: string; commit: string }
export type IdentifyUniqueFilesArgs = { id: number; path: string }
@ -225,7 +227,7 @@ export type IdentifyUniqueFilesArgs = { id: number; path: string }
*/
export type Algorithm = "XChaCha20Poly1305" | "Aes256Gcm"
export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string }
export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string }
export type OwnedOperationItem = { id: any; data: OwnedOperationData }
@ -244,6 +246,10 @@ export type MaybeNot<T> = T | { not: T }
export type SpacedropArgs = { peer_id: PeerId; file_path: string[] }
export type Object = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null }
export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string }
export type JobReport = { id: string; name: string; action: string | null; data: number[] | null; metadata: any | null; is_background: boolean; errors_text: string[]; created_at: string | null; started_at: string | null; completed_at: string | null; parent_id: string | null; status: JobStatus; task_count: number; completed_task_count: number; message: string; estimated_completion: string }
export type ObjectFilterArgs = { favorite?: boolean | null; hidden?: boolean | null; dateAccessed?: MaybeNot<string | null> | null; kind?: number[]; tags?: number[] }
@ -258,6 +264,8 @@ export type RelationOperationData = "Create" | { Update: { field: string; value:
export type FileDeleterJobInit = { location_id: number; path_id: number }
export type CreateLibraryArgs = { name: string }
/**
* `IndexerRuleCreateArgs` is the argument received from the client using rspc to create a new indexer rule.
* Note that `rules` field is a vector of tuples of `RuleKind` and `parameters`.
@ -276,6 +284,8 @@ export type KeyAddArgs = { algorithm: Algorithm; hashing_algorithm: HashingAlgor
export type OptionalRange<T> = { from: T | null; to: T | null }
export type IndexerRule = { id: number; name: string; default: boolean; rules_per_kind: number[]; date_created: string; date_modified: string }
export type FileEncryptorJobInit = { location_id: number; path_id: number; key_uuid: string; algorithm: Algorithm; metadata: boolean; preview_media: boolean; output_path: string | null }
/**
@ -294,12 +304,12 @@ export type LibraryArgs<T> = { library_id: string; arg: T }
export type FileCutterJobInit = { source_location_id: number; source_path_id: number; target_location_id: number; target_path: string }
export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string }
export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMany: { values: ([any, { [key: string]: any }])[]; skip_duplicates: boolean } } | { Update: { [key: string]: any } } | "Delete"
export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | null
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
export type TagUpdateArgs = { id: number; name: string | null; color: string | null }
export type ObjectValidatorArgs = { id: number; path: string }
@ -326,20 +336,12 @@ export type LibraryConfig = { name: string; description: string }
export type SearchData<T> = { cursor: number[] | null; items: T[] }
export type CreateLibraryArgs = { name: string }
export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string }
export type AutomountUpdateArgs = { uuid: string; status: boolean }
export type Statistics = { id: number; date_captured: string; total_object_count: number; library_db_size: string; total_bytes_used: string; total_bytes_capacity: string; total_unique_bytes: string; total_bytes_free: string; preview_media_bytes: string }
export type Protected<T> = T
export type RestoreBackupArgs = { password: Protected<string>; secret_key: Protected<string>; path: string }
export type Object = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null }
export type RelationOperation = { relation_item: string; relation_group: string; relation: string; data: RelationOperationData }
/**