use crate::model::filesystem::FileSystem;
use crate::model::piece_tree::{BufferLocation, LeafData, PieceTree, PieceTreeNode, StringBuffer};
use crate::model::piece_tree_diff::PieceTreeDiff;
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub struct Persistence {
fs: Arc<dyn FileSystem + Send + Sync>,
file_path: Option<PathBuf>,
modified: bool,
recovery_pending: bool,
saved_root: Arc<PieceTreeNode>,
saved_file_size: Option<usize>,
}
impl Persistence {
pub fn new(
fs: Arc<dyn FileSystem + Send + Sync>,
file_path: Option<PathBuf>,
saved_root: Arc<PieceTreeNode>,
saved_file_size: Option<usize>,
) -> Self {
Self {
fs,
file_path,
modified: false,
recovery_pending: false,
saved_root,
saved_file_size,
}
}
pub fn fs(&self) -> &Arc<dyn FileSystem + Send + Sync> {
&self.fs
}
pub fn set_fs(&mut self, fs: Arc<dyn FileSystem + Send + Sync>) {
self.fs = fs;
}
pub fn file_path(&self) -> Option<&Path> {
self.file_path.as_deref()
}
pub fn file_path_owned(&self) -> Option<PathBuf> {
self.file_path.clone()
}
pub fn set_file_path(&mut self, path: PathBuf) {
self.file_path = Some(path);
}
pub fn clear_file_path(&mut self) {
self.file_path = None;
}
pub fn is_modified(&self) -> bool {
self.modified
}
pub fn set_modified(&mut self, modified: bool) {
self.modified = modified;
}
pub fn clear_modified(&mut self) {
self.modified = false;
}
pub fn is_recovery_pending(&self) -> bool {
self.recovery_pending
}
pub fn set_recovery_pending(&mut self, pending: bool) {
self.recovery_pending = pending;
}
pub(super) fn mark_dirty(&mut self) {
self.modified = true;
self.recovery_pending = true;
}
pub fn saved_root(&self) -> &Arc<PieceTreeNode> {
&self.saved_root
}
pub fn set_saved_root(&mut self, root: Arc<PieceTreeNode>) {
self.saved_root = root;
}
pub fn saved_file_size(&self) -> Option<usize> {
self.saved_file_size
}
pub fn set_saved_file_size(&mut self, size: Option<usize>) {
self.saved_file_size = size;
}
pub fn mark_saved_snapshot(&mut self, piece_tree: &PieceTree) {
self.saved_root = piece_tree.root();
self.modified = false;
}
pub fn refresh_saved_root_if_unmodified(&mut self, piece_tree: &PieceTree) {
if !self.modified {
self.saved_root = piece_tree.root();
}
}
pub fn apply_chunk_load_to_saved_root(
&mut self,
old_buffer_id: usize,
chunk_offset_in_buffer: usize,
chunk_bytes: usize,
new_buffer_id: usize,
) {
let mut leaves = Vec::new();
self.saved_root.collect_leaves(&mut leaves);
let mut modified = false;
let mut new_leaves: Vec<LeafData> = Vec::with_capacity(leaves.len() + 2);
for leaf in &leaves {
if leaf.location.buffer_id() != old_buffer_id {
new_leaves.push(*leaf);
continue;
}
let leaf_start = leaf.offset;
let leaf_end = leaf.offset + leaf.bytes;
let chunk_start = chunk_offset_in_buffer;
let chunk_end = chunk_offset_in_buffer + chunk_bytes;
if chunk_start >= leaf_end || chunk_end <= leaf_start {
new_leaves.push(*leaf);
continue;
}
modified = true;
if chunk_start > leaf_start {
new_leaves.push(LeafData::new(
leaf.location,
leaf.offset,
chunk_start - leaf_start,
None, ));
}
let actual_start = chunk_start.max(leaf_start);
let actual_end = chunk_end.min(leaf_end);
let offset_in_chunk = actual_start - chunk_start;
new_leaves.push(LeafData::new(
BufferLocation::Added(new_buffer_id),
offset_in_chunk,
actual_end - actual_start,
None,
));
if chunk_end < leaf_end {
new_leaves.push(LeafData::new(
leaf.location,
chunk_end,
leaf_end - chunk_end,
None,
));
}
}
if modified {
self.saved_root = PieceTree::from_leaves(&new_leaves).root();
}
}
pub fn diff_since_saved(
&self,
piece_tree: &PieceTree,
buffers: &[StringBuffer],
) -> PieceTreeDiff {
if !self.modified {
return PieceTreeDiff {
equal: true,
byte_ranges: Vec::new(),
nodes_visited: 0,
};
}
if Arc::ptr_eq(&self.saved_root, &piece_tree.root()) {
return PieceTreeDiff {
equal: true,
byte_ranges: Vec::new(),
nodes_visited: 0,
};
}
let structure_diff = self.diff_trees_by_structure(piece_tree);
if structure_diff.equal {
return structure_diff;
}
let total_changed_bytes: usize = structure_diff
.byte_ranges
.iter()
.map(|r| r.end.saturating_sub(r.start))
.sum();
const MAX_VERIFY_BYTES: usize = 64 * 1024;
if total_changed_bytes <= MAX_VERIFY_BYTES && !structure_diff.byte_ranges.is_empty() {
if self.verify_content_differs_in_ranges(
&structure_diff.byte_ranges,
piece_tree,
buffers,
) {
return structure_diff;
} else {
return PieceTreeDiff {
equal: true,
byte_ranges: Vec::new(),
nodes_visited: structure_diff.nodes_visited,
};
}
}
structure_diff
}
pub fn diff_trees_by_structure(&self, piece_tree: &PieceTree) -> PieceTreeDiff {
crate::model::piece_tree_diff::diff_piece_trees(&self.saved_root, &piece_tree.root())
}
fn verify_content_differs_in_ranges(
&self,
byte_ranges: &[std::ops::Range<usize>],
piece_tree: &PieceTree,
buffers: &[StringBuffer],
) -> bool {
let saved_bytes = tree_total_bytes(&self.saved_root);
let current_bytes = piece_tree.total_bytes();
if saved_bytes != current_bytes {
return true;
}
for range in byte_ranges {
if range.start >= range.end {
continue;
}
let saved_slice =
extract_range_from_tree(&self.saved_root, range.start, range.end, buffers);
let current_slice = get_text_range(piece_tree, buffers, range.start, range.len());
match (saved_slice, current_slice) {
(Some(saved), Some(current)) => {
if saved != current {
return true;
}
}
_ => {
return true;
}
}
}
false
}
}
fn tree_total_bytes(root: &Arc<PieceTreeNode>) -> usize {
match root.as_ref() {
PieceTreeNode::Internal {
left_bytes, right, ..
} => left_bytes + tree_total_bytes(right),
PieceTreeNode::Leaf { bytes, .. } => *bytes,
}
}
fn extract_range_from_tree(
root: &Arc<PieceTreeNode>,
start: usize,
end: usize,
buffers: &[StringBuffer],
) -> Option<Vec<u8>> {
let mut result = Vec::with_capacity(end.saturating_sub(start));
collect_range_from_node(root, start, end, 0, buffers, &mut result)?;
Some(result)
}
fn collect_range_from_node(
node: &Arc<PieceTreeNode>,
range_start: usize,
range_end: usize,
node_offset: usize,
buffers: &[StringBuffer],
result: &mut Vec<u8>,
) -> Option<()> {
match node.as_ref() {
PieceTreeNode::Internal {
left_bytes,
left,
right,
..
} => {
let left_end = node_offset + left_bytes;
if range_start < left_end {
collect_range_from_node(
left,
range_start,
range_end,
node_offset,
buffers,
result,
)?;
}
if range_end > left_end {
collect_range_from_node(right, range_start, range_end, left_end, buffers, result)?;
}
}
PieceTreeNode::Leaf {
location,
offset,
bytes,
..
} => {
let node_end = node_offset + bytes;
if range_start < node_end && range_end > node_offset {
let buf = buffers.get(location.buffer_id())?;
let data = buf.get_data()?;
let leaf_start = range_start.saturating_sub(node_offset);
let leaf_end = (range_end - node_offset).min(*bytes);
if leaf_start < leaf_end {
let slice = data.get(*offset + leaf_start..*offset + leaf_end)?;
result.extend_from_slice(slice);
}
}
}
}
Some(())
}
fn get_text_range(
piece_tree: &PieceTree,
buffers: &[StringBuffer],
offset: usize,
bytes: usize,
) -> Option<Vec<u8>> {
if bytes == 0 {
return Some(Vec::new());
}
let mut result = Vec::with_capacity(bytes);
let end_offset = offset + bytes;
let mut collected = 0;
for piece_view in piece_tree.iter_pieces_in_range(offset, end_offset) {
let buffer_id = piece_view.location.buffer_id();
if let Some(buffer) = buffers.get(buffer_id) {
let piece_start_in_doc = piece_view.doc_offset;
let piece_end_in_doc = piece_view.doc_offset + piece_view.bytes;
let read_start = offset.max(piece_start_in_doc);
let read_end = end_offset.min(piece_end_in_doc);
if read_end > read_start {
let offset_in_piece = read_start - piece_start_in_doc;
let bytes_to_read = read_end - read_start;
let buffer_start = piece_view.buffer_offset + offset_in_piece;
let buffer_end = buffer_start + bytes_to_read;
let data = buffer.get_data()?;
if buffer_end <= data.len() {
result.extend_from_slice(&data[buffer_start..buffer_end]);
collected += bytes_to_read;
if collected >= bytes {
break;
}
}
}
}
}
Some(result)
}