.tasks/core/CORE-015-windows-file-id-tracking.md
Implement Windows File ID support in the indexer to enable stable file identification across renames on Windows. This brings Windows to feature parity with Unix/Linux/macOS for change detection and UUID persistence.
Problem:
Currently, Windows files don't have stable identifiers across renames because get_inode() returns None. This means:
Solution: Use Windows NTFS File IDs (64-bit file index) as the equivalent of Unix inodes for stable file identification.
Unix/Linux/macOS:
std::os::unix::fs::MetadataExt::ino() provides stable APIWindows (current):
None for inode → falls back to path-only matchingWindows (with File IDs):
Windows NTFS File IDs are unique identifiers exposed via the Win32 API:
typedef struct _BY_HANDLE_FILE_INFORMATION {
DWORD nFileIndexHigh; // Upper 32 bits
DWORD nFileIndexLow; // Lower 32 bits
// ... other fields
} BY_HANDLE_FILE_INFORMATION;
// Combined: 64-bit unique identifier
uint64_t file_id = ((uint64_t)nFileIndexHigh << 32) | nFileIndexLow;
Properties:
// core/src/ops/indexing/database_storage.rs:145-152
#[cfg(windows)]
pub fn get_inode(_metadata: &std::fs::Metadata) -> Option<u64> {
// Windows file indices exist but are unstable across reboots and
// volume operations, making them unsuitable for change detection.
None
}
Reasons:
std::os::windows::fs::MetadataExt::file_index() is unstable (requires nightly)Reality: Modern NTFS File IDs are stable and reliable. The comment is outdated and overly conservative.
User action: Rename "Project.mp4" → "Final Project.mp4"
Spacedrive sees:
- DELETE: Project.mp4 (UUID: abc-123)
- CREATE: Final Project.mp4 (UUID: def-456) ← New UUID!
Result:
- All tags lost
- All metadata lost
- Relationships broken
- File re-indexed from scratch
- Content re-hashed (expensive for large files)
User action: Rename "Project.mp4" → "Final Project.mp4"
Spacedrive sees:
- MOVE: File ID 0x123ABC from "Project.mp4" to "Final Project.mp4"
- UUID: abc-123 (preserved)
Result:
- Tags preserved
- Metadata intact
- Relationships maintained
- No re-indexing needed
- No re-hashing needed
windows-sys dependency for File ID accessget_inode() for Windows using GetFileInformationByHandlenFileIndexHigh and nFileIndexLowNone gracefully for non-NTFS filesystems (FAT32, exFAT)None, fall back to path matching)None, log debug message)None, log debug message)windows-sys Crate (Recommended)Add dependency:
# core/Cargo.toml
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52", features = ["Win32_Storage_FileSystem"] }
Implement File ID extraction:
// core/src/ops/indexing/database_storage.rs
#[cfg(windows)]
pub fn get_inode(path: &Path) -> Option<u64> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Storage::FileSystem::{
GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION
};
// Open file to get handle
let file = match std::fs::File::open(path) {
Ok(f) => f,
Err(e) => {
tracing::debug!("Failed to open file for File ID extraction: {}", e);
return None;
}
};
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
unsafe {
if GetFileInformationByHandle(file.as_raw_handle() as isize, &mut info) != 0 {
// Combine high and low 32-bit values into 64-bit File ID
let file_id = ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64);
tracing::trace!(
"Extracted File ID: 0x{:016X} for {:?}",
file_id,
path.file_name().unwrap_or_default()
);
Some(file_id)
} else {
// GetFileInformationByHandle failed
// Common reasons: FAT32/exFAT filesystem, permission denied
tracing::debug!(
"GetFileInformationByHandle failed for {:?} (likely FAT32 or permission issue)",
path.file_name().unwrap_or_default()
);
None
}
}
}
Why windows-sys:
Track: https://github.com/rust-lang/rust/issues/63010
// Would be ideal, but unstable since 2019
#[cfg(windows)]
pub fn get_inode(metadata: &std::fs::Metadata) -> Option<u64> {
use std::os::windows::fs::MetadataExt;
metadata.file_index() // ← requires #![feature(windows_by_handle)]
}
Why not recommended:
Files to modify:
core/Cargo.toml - Add windows-sys dependencycore/src/ops/indexing/database_storage.rs - Implement get_inode() for Windowscore/src/volume/backend/local.rs - Implement get_inode() for Windows (same code)Total changes: ~30 lines of code across 3 files
File IDs are volume-specific. When files are copied between volumes:
FAT32 and exFAT don't support File IDs:
GetFileInformationByHandle returns all zeros or failsNoneFile IDs can theoretically change during defragmentation:
NTFS supports hard links for files (not directories):
| Feature | Unix/Linux | macOS | Windows (current) | Windows (after) |
|---|---|---|---|---|
| Stable file identity | ✅ inode | ✅ inode | ❌ None | ✅ File ID |
| UUID preserved on rename | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |
| Tags preserved on rename | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |
| Implementation | ino() | ino() | None | GetFileInformationByHandle |
| Stability | ✅ Stable | ✅ Stable | N/A | ✅ Stable |
// Windows file indices exist but are unstable across reboots and
// volume operations, making them unsuitable for change detection.
// Windows NTFS File IDs provide stable file identification across renames
// and reboots within a volume. They use a 64-bit index similar to Unix inodes.
// File IDs are not available on FAT32/exFAT - we return None and fall back
// to path-based matching in those cases.
Windows File Information API: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
NTFS File System Architecture: https://docs.microsoft.com/en-us/windows/win32/fileio/file-management-functions
Rust Issue #63010 (file_index unstable): https://github.com/rust-lang/rust/issues/63010
windows-sys crate: https://crates.io/crates/windows-sys
Total: 4-6 hours
Medium Priority because:
Should be elevated to High if: