import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:path/path.dart' as p; import '../l10n/tr.dart'; /// X-plore P3 보충 (관리자 지시 10/18): 파일 · 폴더의 숨김 · 읽기 전용 · 시스템 속성과 만든 때 - 보기만 (바꾸지 않음). /// Windows 는 GetFileAttributesExW (kernel32), 그 밖 (Android) 은 이름이 점으로 시작하면 숨김 · 만든 때는 모름. class FileAttributes { final bool hidden; final bool readOnly; final bool system; /// 만든 때 (모르면 null) final DateTime? created; const FileAttributes({this.hidden = false, this.readOnly = false, this.system = false, this.created}); /// 화면에 보일 속성 이름들 (없으면 빈 목록) List get labels => [if (hidden) tr('숨김'), if (readOnly) tr('읽기 전용'), if (system) tr('시스템')]; } typedef _GetAttrsExC = Int32 Function(Pointer name, Int32 level, Pointer info); typedef _GetAttrsExD = int Function(Pointer name, int level, Pointer info); /// [path] 의 속성 (읽지 못하면 null). 로컬 경로만 (원격은 부르지 않는다) FileAttributes? readFileAttributes(String path) { if (!Platform.isWindows) return FileAttributes(hidden: p.basename(path).startsWith('.')); final name = path.toNativeUtf16(); // WIN32_FILE_ATTRIBUTE_DATA: DWORD 속성 · FILETIME 만든 · 열어 본 · 바뀐 때 · DWORD 크기 (위 · 아래) = 36 바이트 final info = calloc(36); try { final f = DynamicLibrary.open('kernel32.dll').lookupFunction<_GetAttrsExC, _GetAttrsExD>('GetFileAttributesExW'); if (f(name, 0 /* GetFileExInfoStandard */, info) == 0) return null; final b = ByteData.sublistView(info.asTypedList(36)); final attrs = b.getUint32(0, Endian.little); final ft = b.getUint32(4, Endian.little) | (b.getUint32(8, Endian.little) << 32); // FILETIME: 1601-01-01 부터 100ns 단위 final created = ft == 0 ? null : DateTime.fromMicrosecondsSinceEpoch(ft ~/ 10 - 11644473600000000, isUtc: true).toLocal(); return FileAttributes(hidden: attrs & 0x2 != 0, readOnly: attrs & 0x1 != 0, system: attrs & 0x4 != 0, created: created); } catch (_) { return null; } finally { calloc.free(name); calloc.free(info); } }