1use super::Buffer;
3use crate::id::BufferRevision;
4use ropey::Rope;
5use std::fs;
6use std::io::{self, Write};
7use std::path::{Path, PathBuf};
8use std::time::SystemTime;
9
10pub struct SaveRequest {
11 text: Rope,
12 origin: Option<PathBuf>,
13 target: PathBuf,
14 revision: BufferRevision,
15 baseline: Option<SystemTime>,
16 new_name: bool,
17 force: bool,
18}
19
20#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
21pub struct SaveReceipt {
22 #[serde(with = "crate::path_serde::option")]
23 origin: Option<PathBuf>,
24 #[serde(with = "crate::path_serde")]
25 target: PathBuf,
26 #[serde(with = "crate::path_serde")]
27 canonical: PathBuf,
28 revision: BufferRevision,
29 stamp: Option<SystemTime>,
30}
31
32impl Buffer {
33 pub fn adopt_file_binding(&mut self, observed: &Self) {
36 self.path = observed.path.clone();
37 self.file_identity = observed.file_identity.clone();
38 self.disk_stamp = observed.disk_stamp;
39 }
40
41 pub fn relocate_file_binding(&mut self, canonical: PathBuf, stamp: Option<SystemTime>) {
46 self.path = Some(canonical.clone());
47 self.file_identity = Some(canonical);
48 self.disk_stamp = stamp.or(self.disk_stamp);
49 }
50
51 pub fn detach_file_binding(&mut self) {
53 self.path = None;
54 self.file_identity = None;
55 self.disk_stamp = None;
56 }
57
58 pub fn prepare_save(&self, target: Option<PathBuf>, force: bool) -> io::Result<SaveRequest> {
59 if !crate::mutguard::save_admitted(self.readonly, force) {
61 return Err(io::Error::new(
62 io::ErrorKind::PermissionDenied,
63 "readonly buffer — :w! to force",
64 ));
65 }
66 let new_name = target.is_some();
67 let target = target.or_else(|| self.path.clone()).ok_or_else(|| {
68 io::Error::new(
69 io::ErrorKind::NotFound,
70 "no file name — :w {path} to name it",
71 )
72 })?;
73 Ok(SaveRequest {
74 text: self.snapshot(),
75 origin: self.path.clone(),
76 target,
77 revision: self.revision(),
78 baseline: self.disk_stamp,
79 new_name,
80 force,
81 })
82 }
83
84 pub fn accept_save(&mut self, receipt: SaveReceipt) -> bool {
87 if self.path != receipt.origin {
88 return false;
89 }
90 let current = self.acknowledge_saved_revision(receipt.revision);
91 if current {
92 self.path = Some(receipt.target);
93 self.disk_stamp = receipt.stamp;
94 self.file_identity = Some(receipt.canonical);
95 } else if self.path.as_ref() == Some(&receipt.target) {
96 self.disk_stamp = receipt.stamp;
97 }
98 current
99 }
100
101 pub fn acknowledge_saved_revision(&mut self, revision: BufferRevision) -> bool {
104 let current = crate::mutguard::save_ack_is_current(revision.get(), self.revision().get());
106 if current {
107 self.dirty = false;
108 }
109 current
110 }
111}
112
113impl SaveRequest {
114 pub fn execute(self) -> io::Result<SaveReceipt> {
116 let target = if self.new_name {
117 self.target.clone()
118 } else {
119 fs::canonicalize(&self.target).unwrap_or_else(|_| self.target.clone())
120 };
121 let current = match fs::metadata(&target) {
122 Ok(metadata) => Some(metadata.modified()?),
123 Err(error) if error.kind() == io::ErrorKind::NotFound => None,
124 Err(error) => return Err(error),
125 };
126 if !self.force && self.new_name && current.is_some() {
127 return Err(io::Error::new(
128 io::ErrorKind::PermissionDenied,
129 "file exists — :w! to overwrite",
130 ));
131 }
132 if !self.force && !self.new_name && current != self.baseline {
133 return Err(io::Error::new(
134 io::ErrorKind::PermissionDenied,
135 "file changed on disk — :w! to force",
136 ));
137 }
138 write_atomic(&target, &self.text, !self.new_name || self.force)?;
139 let stamp = Some(fs::metadata(&target)?.modified()?);
140 let canonical = fs::canonicalize(&target)?;
141 Ok(SaveReceipt {
142 origin: self.origin,
143 target: self.target,
144 canonical,
145 revision: self.revision,
146 stamp,
147 })
148 }
149}
150
151fn write_atomic(target: &Path, contents: &Rope, overwrite: bool) -> io::Result<()> {
152 let parent = target
153 .parent()
154 .filter(|path| !path.as_os_str().is_empty())
155 .unwrap_or_else(|| Path::new("."));
156 let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
157 match fs::metadata(target) {
158 Ok(metadata) => temporary
159 .as_file()
160 .set_permissions(metadata.permissions())?,
161 Err(error) if error.kind() == io::ErrorKind::NotFound => {}
162 Err(error) => return Err(error),
163 }
164 for chunk in contents.chunks() {
165 temporary.write_all(chunk.as_bytes())?;
166 }
167 temporary.as_file().sync_all()?;
168 let result = if overwrite {
169 temporary.persist(target)
170 } else {
171 temporary.persist_noclobber(target)
172 };
173 result.map_err(|error| error.error)?;
174 #[cfg(unix)]
175 fs::File::open(parent)?.sync_all()?;
176 Ok(())
177}