confuse 0.1.0

A fuser-compatible filesystem API facade using Dokan on Windows and fuser elsewhere.
Documentation
use std::ffi::OsStr;
use std::time::SystemTime;

use widestring::{U16CStr, U16CString};
use winapi::shared::ntstatus::*;

use super::{AdapterContext, CreatedPath, DokanAdapter};
use crate::dokan_impl::{
    advance_offset_on_emitted, errno_to_ntstatus, filetype_to_windows_attr,
    find_files_attr_from_kind_and_perm, ino, ino_from_context_or_path, join_child_path,
};
use crate::fuser_facade::filesystem::Filesystem;
use crate::fuser_facade::reply::{
    DirectoryEntryPayload, DirectoryPlusEntryPayload, ReplyAttr, ReplyDirectory, ReplyDirectoryPlus,
};
use crate::fuser_facade::request::Request;
use crate::fuser_facade::types::{FileAttr, INodeNo};

impl<FS: Filesystem> DokanAdapter<FS> {
    pub(super) fn directory_ino(
        &self, fs: &FS, req: &Request, path_key: &str, context: &AdapterContext,
    ) -> dokan::OperationResult<INodeNo> {
        match ino_from_context_or_path(context).filter(|v| *v != ino(0)) {
            Some(ino) => Ok(ino),
            None => self
                .resolve_path_ino(fs, req, OsStr::new(path_key))
                .map_err(errno_to_ntstatus),
        }
    }

    pub(super) fn directory_offset(&self, path_key: &str) -> i64 {
        self.dir_offsets
            .lock()
            .ok()
            .and_then(|m| m.get(path_key).copied())
            .unwrap_or(0)
    }

    pub(super) fn remember_directory_offset(
        &self, path_key: String, req_offset: i64, emitted_offset: Option<i64>,
    ) {
        let next_offset = advance_offset_on_emitted(req_offset, emitted_offset);
        if let Ok(mut m) = self.dir_offsets.lock() {
            m.insert(path_key, next_offset);
        }
    }

    pub(super) fn emit_readdir_entries(
        &self, fs: &FS, req: &Request, entries: &[DirectoryEntryPayload],
        fill_find_data: &mut impl FnMut(&dokan::FindData) -> dokan::FillDataResult,
    ) -> dokan::OperationResult<Option<i64>> {
        let mut emitted_offset = None;
        for entry in entries {
            let perm = self
                .readdir_entry_attr(fs, req, entry.ino)
                .map(|attr| attr.perm);
            let find_data = dokan::FindData {
                attributes: find_files_attr_from_kind_and_perm(entry.kind, perm),
                creation_time: SystemTime::UNIX_EPOCH,
                last_access_time: SystemTime::UNIX_EPOCH,
                last_write_time: SystemTime::UNIX_EPOCH,
                file_size: 0,
                file_name: U16CString::from_str(entry.name.to_string_lossy().as_ref())
                    .map_err(|_| STATUS_NOT_IMPLEMENTED)?,
            };
            if fill_find_data(&find_data).is_err() {
                break;
            }
            emitted_offset = Some(entry.offset as i64);
        }
        Ok(emitted_offset)
    }

    pub(super) fn emit_readdir_pattern_entries(
        &self, fs: &FS, req: &Request, pattern: &U16CStr, entries: &[DirectoryEntryPayload],
        fill_find_data: &mut impl FnMut(&dokan::FindData) -> dokan::FillDataResult,
    ) -> dokan::OperationResult<Option<i64>> {
        let mut emitted_offset = None;
        for entry in entries {
            let name_u16 = U16CString::from_str(entry.name.to_string_lossy().as_ref())
                .map_err(|_| STATUS_NOT_IMPLEMENTED)?;
            if !dokan::is_name_in_expression(pattern, name_u16.as_ucstr(), true) {
                continue;
            }
            let attr = self.readdir_entry_attr(fs, req, entry.ino);
            let size = attr.map_or(0, |attr| attr.size);
            let times = attr.map_or(
                (
                    SystemTime::UNIX_EPOCH,
                    SystemTime::UNIX_EPOCH,
                    SystemTime::UNIX_EPOCH,
                ),
                |attr| (attr.ctime, attr.atime, attr.mtime),
            );
            let perm = attr.map(|attr| attr.perm);
            let find_data = dokan::FindData {
                attributes: find_files_attr_from_kind_and_perm(entry.kind, perm),
                creation_time: times.0,
                last_access_time: times.1,
                last_write_time: times.2,
                file_size: size,
                file_name: name_u16,
            };
            if fill_find_data(&find_data).is_err() {
                break;
            }
            emitted_offset = Some(entry.offset as i64);
        }
        Ok(emitted_offset)
    }

    fn readdir_entry_attr(&self, fs: &FS, req: &Request, ino: INodeNo) -> Option<FileAttr> {
        let attr_reply = ReplyAttr::capture();
        fs.getattr(req, ino, None, attr_reply.duplicate());
        match *attr_reply.status.lock().ok()? {
            Some(Ok(attr)) => Some(attr),
            Some(Err(_)) | None => None,
        }
    }

    pub(super) fn emit_readdirplus_pattern_entries(
        &self, fs: &FS, req: &Request, route: (&U16CStr, &U16CStr, INodeNo),
        entries: &[DirectoryPlusEntryPayload],
        fill_find_data: &mut impl FnMut(&dokan::FindData) -> dokan::FillDataResult,
    ) -> dokan::OperationResult<Option<i64>> {
        let (file_name, pattern, ino) = route;
        let mut emitted_offset = None;
        for entry in entries {
            let attr = entry.attr;
            self.remember_created_path(
                fs,
                req,
                CreatedPath {
                    path: U16CString::from_str(join_child_path(file_name, &entry.name))
                        .map_err(|_| STATUS_NOT_IMPLEMENTED)?
                        .as_ucstr(),
                    parent: ino,
                    name: &entry.name,
                    attr,
                    generation: entry.generation,
                    ttl: entry.ttl,
                },
            );
            let name_u16 = U16CString::from_str(entry.name.to_string_lossy().as_ref())
                .map_err(|_| STATUS_NOT_IMPLEMENTED)?;
            if !dokan::is_name_in_expression(pattern, name_u16.as_ucstr(), true) {
                continue;
            }
            let find_data = dokan::FindData {
                attributes: filetype_to_windows_attr(attr.kind, attr.perm),
                creation_time: attr.ctime,
                last_access_time: attr.atime,
                last_write_time: attr.mtime,
                file_size: attr.size,
                file_name: name_u16,
            };
            if fill_find_data(&find_data).is_err() {
                break;
            }
            emitted_offset = Some(entry.offset as i64);
        }
        Ok(emitted_offset)
    }

    pub(super) fn reply_directory_status(
        &self, reply: &ReplyDirectory,
    ) -> dokan::OperationResult<()> {
        match *reply.status.lock().map_err(|_| STATUS_NOT_IMPLEMENTED)? {
            Some(Ok(())) => Ok(()),
            Some(Err(err)) => Err(errno_to_ntstatus(err)),
            None => Err(crate::dokan_impl::missing_reply_status()),
        }
    }

    pub(super) fn reply_directoryplus_status(
        &self, reply: &ReplyDirectoryPlus,
    ) -> dokan::OperationResult<()> {
        match *reply.status.lock().map_err(|_| STATUS_NOT_IMPLEMENTED)? {
            Some(Ok(())) => Ok(()),
            Some(Err(err)) => Err(errno_to_ntstatus(err)),
            None => Err(crate::dokan_impl::missing_reply_status()),
        }
    }
}