| 190 |
190 |
|
Ok(())
|
| 191 |
191 |
|
}
|
| 192 |
192 |
|
|
|
193 |
+ |
/// Check for sibling name conflicts, handling both root (NULL parent) and
|
|
194 |
+ |
/// non-root cases. Excludes `exclude_id` so a node doesn't conflict with itself
|
|
195 |
+ |
/// (needed for rename).
|
|
196 |
+ |
fn check_sibling_name_conflict(
|
|
197 |
+ |
db: &Database,
|
|
198 |
+ |
vfs_id: VfsId,
|
|
199 |
+ |
parent_id: Option<NodeId>,
|
|
200 |
+ |
name: &str,
|
|
201 |
+ |
exclude_id: NodeId,
|
|
202 |
+ |
) -> Result<()> {
|
|
203 |
+ |
let count: i64 = match parent_id {
|
|
204 |
+ |
None => db.conn().query_row(
|
|
205 |
+ |
"SELECT COUNT(*) FROM vfs_nodes \
|
|
206 |
+ |
WHERE vfs_id = ?1 AND parent_id IS NULL AND name = ?2 AND id != ?3",
|
|
207 |
+ |
rusqlite::params![vfs_id, name, exclude_id],
|
|
208 |
+ |
|row| row.get(0),
|
|
209 |
+ |
)?,
|
|
210 |
+ |
Some(pid) => db.conn().query_row(
|
|
211 |
+ |
"SELECT COUNT(*) FROM vfs_nodes \
|
|
212 |
+ |
WHERE parent_id = ?1 AND name = ?2 AND id != ?3",
|
|
213 |
+ |
rusqlite::params![pid, name, exclude_id],
|
|
214 |
+ |
|row| row.get(0),
|
|
215 |
+ |
)?,
|
|
216 |
+ |
};
|
|
217 |
+ |
if count > 0 {
|
|
218 |
+ |
return Err(CoreError::NameConflict(name.to_string()));
|
|
219 |
+ |
}
|
|
220 |
+ |
Ok(())
|
|
221 |
+ |
}
|
|
222 |
+ |
|
| 193 |
223 |
|
/// Create a directory node under the given parent (or at root if `None`). Returns the new node ID.
|
| 194 |
224 |
|
#[instrument(skip_all)]
|
| 195 |
225 |
|
pub fn create_directory(
|
| 284 |
314 |
|
})
|
| 285 |
315 |
|
}
|
| 286 |
316 |
|
|
| 287 |
|
- |
/// Rename a VFS node. Returns `NodeNotFound` if the ID doesn't exist.
|
|
317 |
+ |
/// Rename a VFS node. Returns `NodeNotFound` if the ID doesn't exist,
|
|
318 |
+ |
/// `NameConflict` if a sibling with the same name already exists.
|
| 288 |
319 |
|
#[instrument(skip_all)]
|
| 289 |
320 |
|
pub fn rename_node(db: &Database, id: NodeId, new_name: &str) -> Result<()> {
|
| 290 |
321 |
|
validate_node_name(new_name)?;
|
|
322 |
+ |
let node = get_node(db, id)?;
|
|
323 |
+ |
check_sibling_name_conflict(db, node.vfs_id, node.parent_id, new_name, id)?;
|
| 291 |
324 |
|
let changed = db.conn().execute(
|
| 292 |
325 |
|
"UPDATE vfs_nodes SET name = ?1 WHERE id = ?2",
|
| 293 |
326 |
|
rusqlite::params![new_name, id],
|
| 300 |
333 |
|
|
| 301 |
334 |
|
/// Move a VFS node to a new parent directory (or root if `None`).
|
| 302 |
335 |
|
///
|
| 303 |
|
- |
/// Returns an error if the move would create a circular parent reference
|
| 304 |
|
- |
/// (e.g. moving a node to be a child of one of its own descendants).
|
|
336 |
+ |
/// Returns an error if the move would create a circular parent reference,
|
|
337 |
+ |
/// cross a VFS boundary, or conflict with an existing sibling name.
|
| 305 |
338 |
|
#[instrument(skip_all)]
|
| 306 |
339 |
|
pub fn move_node(db: &Database, id: NodeId, new_parent_id: Option<NodeId>) -> Result<()> {
|
|
340 |
+ |
let node = get_node(db, id)?;
|
|
341 |
+ |
|
|
342 |
+ |
// Reject cross-VFS moves.
|
|
343 |
+ |
if let Some(parent) = new_parent_id {
|
|
344 |
+ |
let parent_node = get_node(db, parent)?;
|
|
345 |
+ |
if parent_node.vfs_id != node.vfs_id {
|
|
346 |
+ |
return Err(CoreError::Internal(
|
|
347 |
+ |
"cannot move a node to a different VFS".to_string(),
|
|
348 |
+ |
));
|
|
349 |
+ |
}
|
|
350 |
+ |
}
|
|
351 |
+ |
|
| 307 |
352 |
|
// Check for circular reference: walk from new_parent_id up to root.
|
| 308 |
353 |
|
// If we encounter `id` along the way, the move would create a cycle.
|
| 309 |
354 |
|
if let Some(parent) = new_parent_id {
|
| 314 |
359 |
|
"move would create a circular parent reference".to_string(),
|
| 315 |
360 |
|
));
|
| 316 |
361 |
|
}
|
| 317 |
|
- |
let node = get_node(db, cur_id)?;
|
| 318 |
|
- |
current = node.parent_id;
|
|
362 |
+ |
let cur_node = get_node(db, cur_id)?;
|
|
363 |
+ |
current = cur_node.parent_id;
|
| 319 |
364 |
|
}
|
| 320 |
365 |
|
}
|
| 321 |
366 |
|
|
|
367 |
+ |
// Check for name conflicts at the destination.
|
|
368 |
+ |
check_sibling_name_conflict(db, node.vfs_id, new_parent_id, &node.name, id)?;
|
|
369 |
+ |
|
| 322 |
370 |
|
let changed = db.conn().execute(
|
| 323 |
371 |
|
"UPDATE vfs_nodes SET parent_id = ?1 WHERE id = ?2",
|
| 324 |
372 |
|
rusqlite::params![new_parent_id, id],
|