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.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 self.dirty = false;
70 } else if self.path.as_ref() == Some(&receipt.target) {
71 self.disk_stamp = receipt.stamp;
72 }
73 current
74 }
75}
76
77impl SaveRequest {
78 pub fn execute(self) -> io::Result<SaveReceipt> {
80 let target = if self.new_name {
81 self.target.clone()
82 } else {
83 fs::canonicalize(&self.target).unwrap_or_else(|_| self.target.clone())
84 };
85 let current = match fs::metadata(&target) {
86 Ok(metadata) => Some(metadata.modified()?),
87 Err(error) if error.kind() == io::ErrorKind::NotFound => None,
88 Err(error) => return Err(error),
89 };
90 if !self.force && self.new_name && current.is_some() {
91 return Err(io::Error::new(
92 io::ErrorKind::PermissionDenied,
93 "file exists — :w! to overwrite",
94 ));
95 }
96 if !self.force && !self.new_name && current != self.baseline {
97 return Err(io::Error::new(
98 io::ErrorKind::PermissionDenied,
99 "file changed on disk — :w! to force",
100 ));
101 }
102 write_atomic(&target, &self.text, !self.new_name || self.force)?;
103 let stamp = Some(fs::metadata(&target)?.modified()?);
104 let canonical = fs::canonicalize(&target)?;
105 Ok(SaveReceipt {
106 origin: self.origin,
107 target: self.target,
108 canonical,
109 revision: self.revision,
110 stamp,
111 })
112 }
113}
114
115fn write_atomic(target: &Path, contents: &Rope, overwrite: bool) -> io::Result<()> {
116 let parent = target
117 .parent()
118 .filter(|path| !path.as_os_str().is_empty())
119 .unwrap_or_else(|| Path::new("."));
120 let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
121 match fs::metadata(target) {
122 Ok(metadata) => temporary
123 .as_file()
124 .set_permissions(metadata.permissions())?,
125 Err(error) if error.kind() == io::ErrorKind::NotFound => {}
126 Err(error) => return Err(error),
127 }
128 for chunk in contents.chunks() {
129 temporary.write_all(chunk.as_bytes())?;
130 }
131 temporary.as_file().sync_all()?;
132 let result = if overwrite {
133 temporary.persist(target)
134 } else {
135 temporary.persist_noclobber(target)
136 };
137 result.map_err(|error| error.error)?;
138 #[cfg(unix)]
139 fs::File::open(parent)?.sync_all()?;
140 Ok(())
141}