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 self.readonly && !force {
60 return Err(io::Error::new(
61 io::ErrorKind::PermissionDenied,
62 "readonly buffer — :w! to force",
63 ));
64 }
65 let new_name = target.is_some();
66 let target = target.or_else(|| self.path.clone()).ok_or_else(|| {
67 io::Error::new(
68 io::ErrorKind::NotFound,
69 "no file name — :w {path} to name it",
70 )
71 })?;
72 Ok(SaveRequest {
73 text: self.snapshot(),
74 origin: self.path.clone(),
75 target,
76 revision: self.revision(),
77 baseline: self.disk_stamp,
78 new_name,
79 force,
80 })
81 }
82
83 pub fn accept_save(&mut self, receipt: SaveReceipt) -> bool {
86 if self.path != receipt.origin {
87 return false;
88 }
89 let current = self.acknowledge_saved_revision(receipt.revision);
90 if current {
91 self.path = Some(receipt.target);
92 self.disk_stamp = receipt.stamp;
93 self.file_identity = Some(receipt.canonical);
94 } else if self.path.as_ref() == Some(&receipt.target) {
95 self.disk_stamp = receipt.stamp;
96 }
97 current
98 }
99
100 pub fn acknowledge_saved_revision(&mut self, revision: BufferRevision) -> bool {
103 let current = self.revision() == revision;
104 if current {
105 self.dirty = false;
106 }
107 current
108 }
109}
110
111impl SaveRequest {
112 pub fn execute(self) -> io::Result<SaveReceipt> {
114 let target = if self.new_name {
115 self.target.clone()
116 } else {
117 fs::canonicalize(&self.target).unwrap_or_else(|_| self.target.clone())
118 };
119 let current = match fs::metadata(&target) {
120 Ok(metadata) => Some(metadata.modified()?),
121 Err(error) if error.kind() == io::ErrorKind::NotFound => None,
122 Err(error) => return Err(error),
123 };
124 if !self.force && self.new_name && current.is_some() {
125 return Err(io::Error::new(
126 io::ErrorKind::PermissionDenied,
127 "file exists — :w! to overwrite",
128 ));
129 }
130 if !self.force && !self.new_name && current != self.baseline {
131 return Err(io::Error::new(
132 io::ErrorKind::PermissionDenied,
133 "file changed on disk — :w! to force",
134 ));
135 }
136 write_atomic(&target, &self.text, !self.new_name || self.force)?;
137 let stamp = Some(fs::metadata(&target)?.modified()?);
138 let canonical = fs::canonicalize(&target)?;
139 Ok(SaveReceipt {
140 origin: self.origin,
141 target: self.target,
142 canonical,
143 revision: self.revision,
144 stamp,
145 })
146 }
147}
148
149fn write_atomic(target: &Path, contents: &Rope, overwrite: bool) -> io::Result<()> {
150 let parent = target
151 .parent()
152 .filter(|path| !path.as_os_str().is_empty())
153 .unwrap_or_else(|| Path::new("."));
154 let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
155 match fs::metadata(target) {
156 Ok(metadata) => temporary
157 .as_file()
158 .set_permissions(metadata.permissions())?,
159 Err(error) if error.kind() == io::ErrorKind::NotFound => {}
160 Err(error) => return Err(error),
161 }
162 for chunk in contents.chunks() {
163 temporary.write_all(chunk.as_bytes())?;
164 }
165 temporary.as_file().sync_all()?;
166 let result = if overwrite {
167 temporary.persist(target)
168 } else {
169 temporary.persist_noclobber(target)
170 };
171 result.map_err(|error| error.error)?;
172 #[cfg(unix)]
173 fs::File::open(parent)?.sync_all()?;
174 Ok(())
175}