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 prepare_save(&self, target: Option<PathBuf>, force: bool) -> io::Result<SaveRequest> {
34 if self.readonly && !force {
35 return Err(io::Error::new(
36 io::ErrorKind::PermissionDenied,
37 "readonly buffer — :w! to force",
38 ));
39 }
40 let new_name = target.is_some();
41 let target = target.or_else(|| self.path.clone()).ok_or_else(|| {
42 io::Error::new(
43 io::ErrorKind::NotFound,
44 "no file name — :w {path} to name it",
45 )
46 })?;
47 Ok(SaveRequest {
48 text: self.snapshot(),
49 origin: self.path.clone(),
50 target,
51 revision: self.revision(),
52 baseline: self.disk_stamp,
53 new_name,
54 force,
55 })
56 }
57
58 pub fn accept_save(&mut self, receipt: SaveReceipt) -> bool {
61 if self.path != receipt.origin {
62 return false;
63 }
64 let current = self.acknowledge_saved_revision(receipt.revision);
65 if current {
66 self.path = Some(receipt.target);
67 self.disk_stamp = receipt.stamp;
68 self.file_identity = Some(receipt.canonical);
69 } else if self.path.as_ref() == Some(&receipt.target) {
70 self.disk_stamp = receipt.stamp;
71 }
72 current
73 }
74
75 pub fn acknowledge_saved_revision(&mut self, revision: BufferRevision) -> bool {
78 let current = self.revision() == revision;
79 if current {
80 self.dirty = false;
81 }
82 current
83 }
84}
85
86impl SaveRequest {
87 pub fn execute(self) -> io::Result<SaveReceipt> {
89 let target = if self.new_name {
90 self.target.clone()
91 } else {
92 fs::canonicalize(&self.target).unwrap_or_else(|_| self.target.clone())
93 };
94 let current = match fs::metadata(&target) {
95 Ok(metadata) => Some(metadata.modified()?),
96 Err(error) if error.kind() == io::ErrorKind::NotFound => None,
97 Err(error) => return Err(error),
98 };
99 if !self.force && self.new_name && current.is_some() {
100 return Err(io::Error::new(
101 io::ErrorKind::PermissionDenied,
102 "file exists — :w! to overwrite",
103 ));
104 }
105 if !self.force && !self.new_name && current != self.baseline {
106 return Err(io::Error::new(
107 io::ErrorKind::PermissionDenied,
108 "file changed on disk — :w! to force",
109 ));
110 }
111 write_atomic(&target, &self.text, !self.new_name || self.force)?;
112 let stamp = Some(fs::metadata(&target)?.modified()?);
113 let canonical = fs::canonicalize(&target)?;
114 Ok(SaveReceipt {
115 origin: self.origin,
116 target: self.target,
117 canonical,
118 revision: self.revision,
119 stamp,
120 })
121 }
122}
123
124fn write_atomic(target: &Path, contents: &Rope, overwrite: bool) -> io::Result<()> {
125 let parent = target
126 .parent()
127 .filter(|path| !path.as_os_str().is_empty())
128 .unwrap_or_else(|| Path::new("."));
129 let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
130 match fs::metadata(target) {
131 Ok(metadata) => temporary
132 .as_file()
133 .set_permissions(metadata.permissions())?,
134 Err(error) if error.kind() == io::ErrorKind::NotFound => {}
135 Err(error) => return Err(error),
136 }
137 for chunk in contents.chunks() {
138 temporary.write_all(chunk.as_bytes())?;
139 }
140 temporary.as_file().sync_all()?;
141 let result = if overwrite {
142 temporary.persist(target)
143 } else {
144 temporary.persist_noclobber(target)
145 };
146 result.map_err(|error| error.error)?;
147 #[cfg(unix)]
148 fs::File::open(parent)?.sync_all()?;
149 Ok(())
150}