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
20pub struct SavePlan {
24 pub text: Rope,
25 pub origin: Option<PathBuf>,
26 pub target: PathBuf,
27 pub revision: BufferRevision,
28 pub baseline: Option<SystemTime>,
29 pub new_name: bool,
30 pub force: bool,
31}
32
33#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
34pub struct SaveReceipt {
35 #[serde(with = "crate::path_serde::option")]
36 origin: Option<PathBuf>,
37 #[serde(with = "crate::path_serde")]
38 target: PathBuf,
39 #[serde(with = "crate::path_serde")]
40 canonical: PathBuf,
41 revision: BufferRevision,
42 stamp: Option<SystemTime>,
43}
44
45impl SaveReceipt {
46 pub fn from_store(
50 origin: Option<PathBuf>,
51 target: PathBuf,
52 canonical: PathBuf,
53 revision: BufferRevision,
54 stamp: Option<SystemTime>,
55 ) -> Self {
56 Self {
57 origin,
58 target,
59 canonical,
60 revision,
61 stamp,
62 }
63 }
64}
65
66impl Buffer {
67 pub fn adopt_file_binding(&mut self, observed: &Self) {
70 self.path = observed.path.clone();
71 self.file_identity = observed.file_identity.clone();
72 self.disk_stamp = observed.disk_stamp;
73 }
74
75 pub fn relocate_file_binding(&mut self, canonical: PathBuf, stamp: Option<SystemTime>) {
80 self.path = Some(canonical.clone());
81 self.file_identity = Some(canonical);
82 self.disk_stamp = stamp.or(self.disk_stamp);
83 }
84
85 pub fn detach_file_binding(&mut self) {
87 self.path = None;
88 self.file_identity = None;
89 self.disk_stamp = None;
90 }
91
92 pub fn prepare_save(&self, target: Option<PathBuf>, force: bool) -> io::Result<SaveRequest> {
93 if !crate::mutguard::save_admitted(self.readonly, force) {
95 return Err(io::Error::new(
96 io::ErrorKind::PermissionDenied,
97 "readonly buffer — :w! to force",
98 ));
99 }
100 let new_name = target.is_some();
101 let target = target.or_else(|| self.path.clone()).ok_or_else(|| {
102 io::Error::new(
103 io::ErrorKind::NotFound,
104 "no file name — :w {path} to name it",
105 )
106 })?;
107 Ok(SaveRequest {
108 text: self.snapshot(),
109 origin: self.path.clone(),
110 target,
111 revision: self.revision(),
112 baseline: self.disk_stamp,
113 new_name,
114 force,
115 })
116 }
117
118 pub fn accept_save(&mut self, receipt: SaveReceipt) -> bool {
121 if self.path != receipt.origin {
122 return false;
123 }
124 let current = self.acknowledge_saved_revision(receipt.revision);
125 if current {
126 self.path = Some(receipt.target);
127 self.disk_stamp = receipt.stamp;
128 self.file_identity = Some(receipt.canonical);
129 } else if self.path.as_ref() == Some(&receipt.target) {
130 self.disk_stamp = receipt.stamp;
131 }
132 current
133 }
134
135 pub fn acknowledge_saved_revision(&mut self, revision: BufferRevision) -> bool {
138 let current = crate::mutguard::save_ack_is_current(revision.get(), self.revision().get());
140 if current {
141 self.dirty = false;
142 }
143 current
144 }
145}
146
147impl SaveRequest {
148 pub fn into_plan(self) -> SavePlan {
151 SavePlan {
152 text: self.text,
153 origin: self.origin,
154 target: self.target,
155 revision: self.revision,
156 baseline: self.baseline,
157 new_name: self.new_name,
158 force: self.force,
159 }
160 }
161 pub fn execute(self) -> io::Result<SaveReceipt> {
163 let target = if self.new_name {
164 self.target.clone()
165 } else {
166 fs::canonicalize(&self.target).unwrap_or_else(|_| self.target.clone())
167 };
168 let current = match fs::metadata(&target) {
169 Ok(metadata) => Some(metadata.modified()?),
170 Err(error) if error.kind() == io::ErrorKind::NotFound => None,
171 Err(error) => return Err(error),
172 };
173 if !self.force && self.new_name && current.is_some() {
174 return Err(io::Error::new(
175 io::ErrorKind::PermissionDenied,
176 "file exists — :w! to overwrite",
177 ));
178 }
179 if !self.force && !self.new_name && current != self.baseline {
180 return Err(io::Error::new(
181 io::ErrorKind::PermissionDenied,
182 "file changed on disk — :w! to force",
183 ));
184 }
185 write_atomic(&target, &self.text, !self.new_name || self.force)?;
186 let stamp = Some(fs::metadata(&target)?.modified()?);
187 let canonical = fs::canonicalize(&target)?;
188 Ok(SaveReceipt {
189 origin: self.origin,
190 target: self.target,
191 canonical,
192 revision: self.revision,
193 stamp,
194 })
195 }
196}
197
198fn write_atomic(target: &Path, contents: &Rope, overwrite: bool) -> io::Result<()> {
199 let parent = target
200 .parent()
201 .filter(|path| !path.as_os_str().is_empty())
202 .unwrap_or_else(|| Path::new("."));
203 let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
204 match fs::metadata(target) {
205 Ok(metadata) => temporary
206 .as_file()
207 .set_permissions(metadata.permissions())?,
208 Err(error) if error.kind() == io::ErrorKind::NotFound => {}
209 Err(error) => return Err(error),
210 }
211 for chunk in contents.chunks() {
212 temporary.write_all(chunk.as_bytes())?;
213 }
214 temporary.as_file().sync_all()?;
215 let result = if overwrite {
216 temporary.persist(target)
217 } else {
218 temporary.persist_noclobber(target)
219 };
220 result.map_err(|error| error.error)?;
221 #[cfg(unix)]
222 fs::File::open(parent)?.sync_all()?;
223 Ok(())
224}