use std::ffi::{OsStr, OsString};
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::path::Path;
use crate::file_metadata::RawMetadata;
use crate::tree::DescendantsRow;
use crate::{FileMode, Gid, Uid};
use crate::{
FileOrigin, RootId,
block::FileId as StoreFileId,
file_metadata::{Device, FileKind},
path::NormalizedPath,
sql::FileDiscriminant,
tree::ChildrenRow,
};
use super::store::SqlStore;
#[derive(Debug, Clone, Copy)]
pub struct AncestorPathId(pub(crate) i64);
impl<'conn> SqlStore<'conn> {
pub fn get_ancestor_id(
&self,
root_id: RootId,
base: &NormalizedPath,
) -> crate::Result<AncestorPathId> {
let root_row_id: i64 = self.db.query_row(
r#"
SELECT
id
FROM
liteboxfs_roots
WHERE
uuid = ?;
"#,
rusqlite::params![root_id.to_string()],
|row| row.get(0),
)?;
let path_id = self.resolve_path_id(root_row_id, root_id, base)?;
let kind: FileDiscriminant = self.db.query_row(
r#"
SELECT
liteboxfs_files.kind
FROM
liteboxfs_paths
JOIN
liteboxfs_files ON liteboxfs_paths.file = liteboxfs_files.id
WHERE
liteboxfs_paths.id = ?;
"#,
rusqlite::params![path_id],
|row| row.get(0),
)?;
if kind != FileDiscriminant::Dir {
return Err(crate::Error::NotADirectory {
file: FileOrigin::Litebox {
root: root_id,
locator: base.to_path_buf().into(),
},
});
}
Ok(AncestorPathId(path_id))
}
pub fn get_all_children(&self, ancestor_id: AncestorPathId) -> crate::Result<Vec<ChildrenRow>> {
let mut stmt = self.db.prepare_cached(
r#"
SELECT
liteboxfs_paths.name,
liteboxfs_files.id,
liteboxfs_files.kind,
liteboxfs_files.mode,
liteboxfs_files.atime,
liteboxfs_files.mtime,
liteboxfs_files.ctime,
liteboxfs_files.btime,
liteboxfs_files.uid,
liteboxfs_files.gid,
liteboxfs_files.major,
liteboxfs_files.minor,
liteboxfs_files.target
FROM
liteboxfs_paths
JOIN
liteboxfs_files ON liteboxfs_paths.file = liteboxfs_files.id
WHERE
liteboxfs_paths.parent = ?1
ORDER BY
liteboxfs_paths.name ASC;
"#,
)?;
Ok(stmt
.query_map(rusqlite::params![ancestor_id.0], |row| {
let name_bytes: Vec<u8> = row.get(0)?;
let file_id: i64 = row.get(1)?;
let metadata = RawMetadata {
discriminant: row.get(2)?,
mode: FileMode::from_bits_truncate(row.get(3)?),
atime: row.get(4)?,
mtime: row.get(5)?,
ctime: row.get(6)?,
btime: row.get(7)?,
uid: Uid::from_raw(row.get(8)?),
gid: Gid::from_raw(row.get(9)?),
};
let device_major: Option<u64> = row.get(10)?;
let device_minor: Option<u64> = row.get(11)?;
let symlink_target: Option<Vec<u8>> = row.get(12)?;
let name = OsString::from_vec(name_bytes);
let file_id = StoreFileId::from(file_id);
let kind = match metadata.discriminant {
FileDiscriminant::Regular => FileKind::Regular,
FileDiscriminant::Dir => FileKind::Dir,
FileDiscriminant::Pipe => FileKind::Pipe,
FileDiscriminant::Symlink => FileKind::Symlink {
target: Path::new(OsStr::from_bytes(
symlink_target
.as_deref()
.expect("Symlink should have a target."),
))
.to_path_buf(),
},
FileDiscriminant::Block => FileKind::Block {
dev: Device::new(
device_major.expect("Block device should have a major number."),
device_minor.expect("Block device should have a minor number."),
),
},
FileDiscriminant::Char => FileKind::Char {
dev: Device::new(
device_major.expect("Char device should have a major number."),
device_minor.expect("Char device should have a minor number."),
),
},
};
Ok(ChildrenRow {
name,
file_id,
kind,
metadata,
})
})?
.collect::<Result<Vec<_>, _>>()?)
}
pub fn get_all_descendants(
&self,
ancestor_id: AncestorPathId,
) -> crate::Result<Vec<DescendantsRow>> {
let mut stmt = self.db.prepare_cached(
r#"
WITH RECURSIVE descendants(id, file, name, parent_id, depth) AS (
SELECT
liteboxfs_paths.id,
liteboxfs_paths.file,
liteboxfs_paths.name,
liteboxfs_paths.parent,
0
FROM
liteboxfs_paths
WHERE
liteboxfs_paths.parent = ?1
UNION ALL
SELECT
liteboxfs_paths.id,
liteboxfs_paths.file,
liteboxfs_paths.name,
liteboxfs_paths.parent,
descendants.depth + 1
FROM
liteboxfs_paths
JOIN
descendants ON liteboxfs_paths.parent = descendants.id
)
SELECT
descendants.id,
descendants.name,
descendants.parent_id,
liteboxfs_files.id,
liteboxfs_files.kind,
liteboxfs_files.mode,
liteboxfs_files.atime,
liteboxfs_files.mtime,
liteboxfs_files.ctime,
liteboxfs_files.btime,
liteboxfs_files.uid,
liteboxfs_files.gid,
liteboxfs_files.major,
liteboxfs_files.minor,
liteboxfs_files.target
FROM
descendants
JOIN
liteboxfs_files ON descendants.file = liteboxfs_files.id
ORDER BY
descendants.depth ASC,
descendants.id ASC;
"#,
)?;
Ok(stmt
.query_map(rusqlite::params![ancestor_id.0], |row| {
let path_id: i64 = row.get(0)?;
let name_bytes: Vec<u8> = row.get(1)?;
let parent_path_id: i64 = row.get(2)?;
let file_id: i64 = row.get(3)?;
let metadata = RawMetadata {
discriminant: row.get(4)?,
mode: FileMode::from_bits_truncate(row.get(5)?),
atime: row.get(6)?,
mtime: row.get(7)?,
ctime: row.get(8)?,
btime: row.get(9)?,
uid: Uid::from_raw(row.get(10)?),
gid: Gid::from_raw(row.get(11)?),
};
let device_major: Option<u64> = row.get(12)?;
let device_minor: Option<u64> = row.get(13)?;
let symlink_target: Option<Vec<u8>> = row.get(14)?;
let name = OsString::from_vec(name_bytes);
let file_id = StoreFileId::from(file_id);
let kind = match metadata.discriminant {
FileDiscriminant::Regular => FileKind::Regular,
FileDiscriminant::Dir => FileKind::Dir,
FileDiscriminant::Pipe => FileKind::Pipe,
FileDiscriminant::Symlink => FileKind::Symlink {
target: Path::new(OsStr::from_bytes(
symlink_target
.as_deref()
.expect("Symlink should have a target."),
))
.to_path_buf(),
},
FileDiscriminant::Block => FileKind::Block {
dev: Device::new(
device_major.expect("Block device should have a major number."),
device_minor.expect("Block device should have a minor number."),
),
},
FileDiscriminant::Char => FileKind::Char {
dev: Device::new(
device_major.expect("Char device should have a major number."),
device_minor.expect("Char device should have a minor number."),
),
},
};
Ok(DescendantsRow {
path_id,
name,
parent_path_id,
file_id,
kind,
metadata,
})
})?
.collect::<Result<Vec<_>, _>>()?)
}
}