mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 13:58:50 +08:00
fix(core): onenote import (#15332)
#### PR Dependency Tree * **PR #15332** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added improved Windows support for importing OneNote files, including `.one`, `.onetoc2`, and `.onepkg` formats. * Enabled reliable handling of Windows paths, including UNC and verbatim paths. * Added filesystem operations for reading, writing, discovering, and opening OneNote content on Windows. * **Bug Fixes** * Improved file access and path resolution during OneNote imports on Windows. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Generated
+1
@@ -165,6 +165,7 @@ dependencies = [
|
||||
"affine_nbstore",
|
||||
"affine_preview",
|
||||
"affine_sqlite_v1",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"hex",
|
||||
"infer",
|
||||
|
||||
@@ -25,6 +25,7 @@ resolver = "3"
|
||||
base64 = "0.22.1"
|
||||
base64-simd = "0.8"
|
||||
block2 = "0.6"
|
||||
bytes = "1"
|
||||
chrono = "0.4"
|
||||
core-foundation = "0.10"
|
||||
coreaudio-rs = "0.12"
|
||||
|
||||
@@ -36,6 +36,9 @@ typed-path = { workspace = true }
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
affine_preview = { workspace = true, features = ["mermaid", "typst"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
bytes = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ use std::{
|
||||
};
|
||||
|
||||
mod content;
|
||||
#[cfg(windows)]
|
||||
mod windows_fs;
|
||||
|
||||
use affine_importer::{
|
||||
FolderHierarchyDelta, ImportBatch, ImportBatchLimits, ImportCursor, ImportError, ImportOptions, ImportProgress,
|
||||
@@ -19,6 +21,8 @@ use onenote_parser::{
|
||||
section::{Section, SectionEntry, SectionGroup},
|
||||
};
|
||||
use typed_path::TypedPath;
|
||||
#[cfg(windows)]
|
||||
use windows_fs::WindowsFs;
|
||||
|
||||
pub struct OneNoteImportProvider;
|
||||
|
||||
@@ -81,7 +85,7 @@ impl OneNotePlanner {
|
||||
}
|
||||
|
||||
fn read_file(&mut self, path: &Path) -> ImportResult<()> {
|
||||
let parser = Parser::new();
|
||||
let parser = parser();
|
||||
match extension(path).as_deref() {
|
||||
Some("one") => {
|
||||
let source_path = path.to_string_lossy();
|
||||
@@ -121,7 +125,7 @@ impl OneNotePlanner {
|
||||
path.to_string_lossy()
|
||||
))
|
||||
})?;
|
||||
let parser = Parser::new();
|
||||
let parser = parser();
|
||||
let toc_path = toc.to_string_lossy();
|
||||
let notebook = parser
|
||||
.parse_notebook(TypedPath::derive(toc_path.as_ref()))
|
||||
@@ -210,6 +214,16 @@ impl OneNotePlanner {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn parser() -> Parser {
|
||||
Parser::new()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn parser() -> Parser<WindowsFs> {
|
||||
Parser::new_with_fs(WindowsFs)
|
||||
}
|
||||
|
||||
struct OneNoteImportCursor {
|
||||
docs: Vec<OneNoteDoc>,
|
||||
folders: Vec<FolderHierarchyDelta>,
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fs::{self, File},
|
||||
io::{Error, Read},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
use onenote_parser::{
|
||||
FileSystem,
|
||||
fs::{FileSource, file_source::CachedFileSource},
|
||||
};
|
||||
use typed_path::{TypedPath, TypedPathBuf};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct WindowsFs;
|
||||
|
||||
impl FileSystem for WindowsFs {
|
||||
fn is_directory(&self, path: TypedPath) -> Result<bool, Error> {
|
||||
match fs::metadata(resolve_path(path)?) {
|
||||
Ok(meta) => Ok(meta.is_dir()),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_dir(&self, path: TypedPath) -> Result<Vec<TypedPathBuf>, Error> {
|
||||
fs::read_dir(resolve_path(path)?)?
|
||||
.map(|entry| entry.and_then(|entry| as_typed_path(&entry.path())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_file(&self, path: TypedPath) -> Result<Vec<u8>, Error> {
|
||||
fs::read(resolve_path(path)?)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: TypedPath, data: &[u8]) -> Result<(), Error> {
|
||||
fs::write(resolve_path(path)?, data)
|
||||
}
|
||||
|
||||
fn stream_to_file(&self, path: TypedPath, reader: &mut dyn Read) -> Result<(), Error> {
|
||||
let mut file = File::create(resolve_path(path)?)?;
|
||||
std::io::copy(reader, &mut file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_dir(&self, path: TypedPath) -> Result<(), Error> {
|
||||
fs::create_dir_all(resolve_path(path)?)
|
||||
}
|
||||
|
||||
fn canonicalize(&self, path: TypedPath) -> Result<TypedPathBuf, Error> {
|
||||
as_typed_path(&fs::canonicalize(resolve_path(path)?)?)
|
||||
}
|
||||
|
||||
fn exists(&self, path: TypedPath) -> Result<bool, Error> {
|
||||
fs::exists(resolve_path(path)?)
|
||||
}
|
||||
|
||||
fn open_file(&self, path: TypedPath) -> Result<Arc<dyn FileSource>, Error> {
|
||||
let file = File::open(resolve_path(path)?)?;
|
||||
let byte_length = file.metadata()?.len();
|
||||
Ok(Arc::new(CachedFileSource::new(WindowsFileSource { file, byte_length })))
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_path(path: TypedPath) -> Result<PathBuf, Error> {
|
||||
let path = if path.is_windows() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
path
|
||||
.with_windows_encoding_checked()
|
||||
.map_err(|error| Error::new(std::io::ErrorKind::InvalidInput, error))?
|
||||
};
|
||||
let path = path
|
||||
.to_str()
|
||||
.ok_or_else(|| Error::new(std::io::ErrorKind::InvalidData, "path bytes are not valid UTF-8"))?;
|
||||
to_verbatim(PathBuf::from(path))
|
||||
}
|
||||
|
||||
fn as_typed_path(path: &Path) -> Result<TypedPathBuf, Error> {
|
||||
let path = path
|
||||
.to_str()
|
||||
.ok_or_else(|| Error::new(std::io::ErrorKind::InvalidData, "path is not valid UTF-8"))?;
|
||||
Ok(TypedPath::windows(path.as_bytes()).to_path_buf())
|
||||
}
|
||||
|
||||
fn to_verbatim(path: PathBuf) -> Result<PathBuf, Error> {
|
||||
let path = std::path::absolute(path)?.to_string_lossy().replace('/', "\\");
|
||||
if path.starts_with(r"\\?\") {
|
||||
return Ok(PathBuf::from(path));
|
||||
}
|
||||
let mut verbatim = OsString::new();
|
||||
if let Some(path) = path.strip_prefix(r"\\") {
|
||||
verbatim.push(r"\\?\UNC\");
|
||||
verbatim.push(path);
|
||||
} else {
|
||||
verbatim.push(r"\\?\");
|
||||
verbatim.push(path);
|
||||
}
|
||||
Ok(PathBuf::from(verbatim))
|
||||
}
|
||||
|
||||
struct WindowsFileSource {
|
||||
file: File,
|
||||
byte_length: u64,
|
||||
}
|
||||
|
||||
impl FileSource for WindowsFileSource {
|
||||
fn byte_length(&self) -> u64 {
|
||||
self.byte_length
|
||||
}
|
||||
|
||||
fn read_at(&self, offset: u64, len: usize) -> Result<Bytes, Error> {
|
||||
use std::os::windows::fs::FileExt;
|
||||
|
||||
let mut bytes = vec![0; len];
|
||||
let mut read = 0;
|
||||
while read < len {
|
||||
let count = self.file.seek_read(&mut bytes[read..], offset + read as u64)?;
|
||||
if count == 0 {
|
||||
return Err(Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
"unexpected end of OneNote file",
|
||||
));
|
||||
}
|
||||
read += count;
|
||||
}
|
||||
Ok(Bytes::from(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn preserves_windows_absolute_path_prefixes() {
|
||||
assert_eq!(
|
||||
resolve_path(TypedPath::derive(r"C:\Users\Alice\Notebook.one")).unwrap(),
|
||||
PathBuf::from(r"\\?\C:\Users\Alice\Notebook.one")
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_path(TypedPath::derive(r"\\server\share\Notebook.one")).unwrap(),
|
||||
PathBuf::from(r"\\?\UNC\server\share\Notebook.one")
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_path(TypedPath::derive(r"\\?\D:\Notebook.one")).unwrap(),
|
||||
PathBuf::from(r"\\?\D:\Notebook.one")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user