probably the wrong solution

This commit is contained in:
Azalea Colburn 2025-03-15 20:02:12 -07:00
parent 79de8659b4
commit 5fbacb998d
No known key found for this signature in database
2 changed files with 94 additions and 19 deletions

View File

@ -19,6 +19,18 @@ pub fn data_dir() -> Result<PathBuf> {
Ok(dir)
}
pub fn bookmarks_dir() -> Result<PathBuf> {
let dir = match env::var_os("_ZO_BOOKMARKS_DIR") {
Some(path) => PathBuf::from(path),
None => dirs::data_local_dir()
.context("could not find bookmarks directory, please set _ZO_BOOKMARKS_DIR manually")?
.join("zoxide"),
};
ensure!(dir.is_absolute(), "_ZO_BOOKMARKS_DIR must be an absolute path");
Ok(dir)
}
pub fn echo() -> bool {
env::var_os("_ZO_ECHO").is_some_and(|var| var == "1")
}

View File

@ -33,35 +33,64 @@ impl Database {
pub fn open() -> Result<Self> {
let data_dir = config::data_dir()?;
let bookmarks_dir: PathBuf = config::bookmarks_dir()?;
Self::open_dir(data_dir)
Self::open_dir(data_dir, bookmarks_dir)
}
pub fn open_dir(data_dir: impl AsRef<Path>) -> Result<Self> {
pub fn open_dir(data_dir: impl AsRef<Path>, bookmarks_dir: impl AsRef<Path>) -> Result<Self> {
let data_dir = data_dir.as_ref();
let path = data_dir.join("db.zo");
let path = fs::canonicalize(&path).unwrap_or(path);
match fs::read(&path)
let bookmarks_dir = bookmarks_dir.as_ref();
let bookmarks_path = bookmarks_dir.join("db_bm.zo");
let bookmarks_path = fs::canonicalize(&bookmarks_path).unwrap_or(bookmarks_path);
match fs::read(&path) {
Ok(bytes) => Self::try_new(
match (fs::read(&path), fs::read(&bookmarks_path)) {
(Ok(bytes), Ok(bookmarks_bytes)) => Self::try_new(
path,
bytes,
|bytes| Self::deserialize(bytes),
|_| Self::deserialize_bookmarks(bytes),
bookmarks_bytes,
|bookmarks_bytes| Self::deserialize_bookmarks(bookmarks_bytes),
false,
),
Err(e) if e.kind() == io::ErrorKind::NotFound => {
(Err(e), _) if e.kind() == io::ErrorKind::NotFound => {
// Create data directory, but don't create any file yet. The file will be
// created later by [`Database::save`] if any data is modified.
fs::create_dir_all(data_dir).with_context(|| {
format!("unable to create data directory: {}", data_dir.display())
})?;
Ok(Self::new(path, Vec::new(), |_| Vec::new(), |_| HashMap::new(), false))
Ok(Self::new(
path,
Vec::new(),
|_| Vec::new(),
Vec::new(),
|_| HashMap::new(),
false,
))
}
Err(e) => {
(_, Err(e)) if e.kind() == io::ErrorKind::NotFound => {
// Create data directory, but don't create any file yet. The file will be
// created later by [`Database::save`] if any data is modified.
fs::create_dir_all(data_dir).with_context(|| {
format!("unable to create bookmarks directory: {}", data_dir.display())
})?;
Ok(Self::new(
path,
Vec::new(),
|_| Vec::new(),
Vec::new(),
|_| HashMap::new(),
false,
))
}
(Err(e), _) => {
Err(e).with_context(|| format!("could not read from database: {}", path.display()))
}
(_, Err(e)) => Err(e).with_context(|| {
format!("could not read from bookmarks database: {}", bookmarks_path.display())
}),
}
}
@ -243,6 +272,33 @@ impl Database {
Ok(dirs)
}
fn deserialize_bookmarks(bytes: &[u8]) -> Result<HashMap<String, Dir>> {
// Assume a maximum size for the database. This prevents bincode from throwing
// strange errors when it encounters invalid data.
const MAX_SIZE: u64 = 32 << 20; // 32 MiB
let deserializer = &mut bincode::options().with_fixint_encoding().with_limit(MAX_SIZE);
// Split bytes into sections.
let version_size = deserializer.serialized_size(&Self::VERSION).unwrap() as _;
if bytes.len() < version_size {
bail!("could not deserialize database: corrupted data");
}
let (bytes_version, bytes_dirs) = bytes.split_at(version_size);
// Deserialize sections.
let version = deserializer.deserialize(bytes_version)?;
let dirs = match version {
Self::VERSION => {
deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
}
version => {
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
}
};
Ok(dirs)
}
}
#[cfg(test)]
@ -255,15 +311,18 @@ mod tests {
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800;
let bookmarks_dir = tempfile::tempdir().unwrap();
let bookmarks_path = if cfg!(windows) { r"C:\foo\bar2" } else { "/foo/bar2" };
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
db.add(path, 1.0, now);
db.add(path, 1.0, now);
let mut db = Database::open_dir(data_dir.path(), bookmarks_dir.path()).unwrap();
db.add(bookmarks_path, 1.0, now);
db.add(bookmarks_path, 1.0, now);
db.save().unwrap();
}
{
let db = Database::open_dir(data_dir.path()).unwrap();
let db = Database::open_dir(data_dir.path(), bookmarks_dir.path()).unwrap();
assert_eq!(db.dirs().len(), 1);
let dir = &db.dirs()[0];
@ -279,22 +338,26 @@ mod tests {
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800;
let bookmarks_dir = tempfile::tempdir().unwrap();
let bookmarks_path = if cfg!(windows) { r"C:\foo\bar2" } else { "/foo/bar2" };
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
db.add(path, 1.0, now);
let mut db = Database::open_dir(data_dir.path(), bookmarks_dir.path()).unwrap();
db.add(bookmarks_path, 1.0, now);
db.add(bookmarks_path, 1.0, now);
db.save().unwrap();
}
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
assert!(db.remove(path));
let mut db = Database::open_dir(data_dir.path(), bookmarks_dir.path()).unwrap();
assert!(db.remove(bookmarks_path));
db.save().unwrap();
}
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut db = Database::open_dir(data_dir.path(), bookmarks_dir.path()).unwrap();
assert!(db.dirs().is_empty());
assert!(!db.remove(path));
assert!(!db.remove(bookmarks_path));
db.save().unwrap();
}
}