file.rs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. use std::{
  2. ffi::OsString,
  3. fs,
  4. fs::File,
  5. io,
  6. io::{Read, Write},
  7. path::{Path, PathBuf},
  8. str,
  9. time::SystemTime,
  10. };
  11. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
  12. pub struct FileId(pub(crate) String);
  13. impl std::convert::From<String> for FileId {
  14. fn from(s: String) -> Self { FileId(s) }
  15. }
  16. #[derive(Debug, Clone, Copy)]
  17. pub enum CharacterEncoding {
  18. Utf8,
  19. Utf8WithBom,
  20. }
  21. const UTF8_BOM: &str = "\u{feff}";
  22. impl CharacterEncoding {
  23. pub(crate) fn guess(s: &[u8]) -> Self {
  24. if s.starts_with(UTF8_BOM.as_bytes()) {
  25. CharacterEncoding::Utf8WithBom
  26. } else {
  27. CharacterEncoding::Utf8
  28. }
  29. }
  30. }
  31. #[derive(Debug)]
  32. pub enum FileError {
  33. Io(io::Error, PathBuf),
  34. UnknownEncoding(PathBuf),
  35. HasChanged(PathBuf),
  36. }
  37. #[derive(Clone, Debug)]
  38. pub struct FileInfo {
  39. pub path: PathBuf,
  40. pub modified_time: Option<SystemTime>,
  41. pub has_changed: bool,
  42. pub encoding: CharacterEncoding,
  43. }
  44. pub(crate) fn try_load_file<P>(path: P) -> Result<(String, FileInfo), FileError>
  45. where
  46. P: AsRef<Path>,
  47. {
  48. let mut f =
  49. File::open(path.as_ref()).map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?;
  50. let mut bytes = Vec::new();
  51. f.read_to_end(&mut bytes)
  52. .map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?;
  53. let encoding = CharacterEncoding::guess(&bytes);
  54. let s = try_decode(bytes, encoding, path.as_ref())?;
  55. let info = FileInfo {
  56. encoding,
  57. path: path.as_ref().to_owned(),
  58. modified_time: get_modified_time(&path),
  59. has_changed: false,
  60. };
  61. Ok((s, info))
  62. }
  63. pub(crate) fn try_save(
  64. path: &Path,
  65. text: &str,
  66. encoding: CharacterEncoding,
  67. _file_info: Option<&FileInfo>,
  68. ) -> io::Result<()> {
  69. let tmp_extension = path.extension().map_or_else(
  70. || OsString::from("swp"),
  71. |ext| {
  72. let mut ext = ext.to_os_string();
  73. ext.push(".swp");
  74. ext
  75. },
  76. );
  77. let tmp_path = &path.with_extension(tmp_extension);
  78. let mut f = File::create(tmp_path)?;
  79. match encoding {
  80. CharacterEncoding::Utf8WithBom => f.write_all(UTF8_BOM.as_bytes())?,
  81. CharacterEncoding::Utf8 => (),
  82. }
  83. f.write_all(text.as_bytes())?;
  84. fs::rename(tmp_path, path)?;
  85. Ok(())
  86. }
  87. pub(crate) fn try_decode(
  88. bytes: Vec<u8>,
  89. encoding: CharacterEncoding,
  90. path: &Path,
  91. ) -> Result<String, FileError> {
  92. match encoding {
  93. CharacterEncoding::Utf8 => {
  94. Ok(String::from(str::from_utf8(&bytes).map_err(|_e| {
  95. FileError::UnknownEncoding(path.to_owned())
  96. })?))
  97. },
  98. CharacterEncoding::Utf8WithBom => {
  99. let s = String::from_utf8(bytes)
  100. .map_err(|_e| FileError::UnknownEncoding(path.to_owned()))?;
  101. Ok(String::from(&s[UTF8_BOM.len()..]))
  102. },
  103. }
  104. }
  105. #[allow(dead_code)]
  106. pub(crate) fn create_dir_if_not_exist(dir: &str) -> Result<(), io::Error> {
  107. let _ = fs::create_dir_all(dir)?;
  108. Ok(())
  109. }
  110. pub(crate) fn get_modified_time<P: AsRef<Path>>(path: P) -> Option<SystemTime> {
  111. File::open(path)
  112. .and_then(|f| f.metadata())
  113. .and_then(|meta| meta.modified())
  114. .ok()
  115. }