import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; enum FileKind { image, svg, pdf, text, video, audio, unknown } const Set _imageExtensions = { 'png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'wbmp', }; const Set _videoExtensions = { 'mp4', 'm4v', 'mov', 'webm', 'mkv', '3gp', }; /// ogg/opus/flac are Android-only; iOS init errors fall through to the /// "format not supported" message. const Set _audioExtensions = { 'mp3', 'm4a', 'aac', 'wav', 'flac', 'ogg', 'oga', 'opus', }; /// Unknown extensions still get a content sniff via [_looksLikeText]. const Set _textExtensions = { 'txt', 'md', 'markdown', 'rst', 'log', 'json', 'json5', 'xml', 'yaml', 'yml', 'toml', 'csv', 'tsv', 'tab', 'ini', 'conf', 'cfg', 'env', 'properties', 'html', 'htm', 'xhtml', 'css', 'scss', 'sass', 'less', 'js', 'mjs', 'cjs', 'ts', 'jsx', 'tsx', 'dart', 'java', 'kt', 'kts', 'groovy', 'scala', 'swift', 'py', 'rb', 'pl', 'lua', 'r', 'go', 'rs', 'zig', 'c', 'cpp', 'cc', 'cxx', 'h', 'hpp', 'cs', 'm', 'mm', 'php', 'sh', 'bash', 'zsh', 'fish', 'ps1', 'bat', 'cmd', 'sql', 'graphql', 'gql', 'gitignore', 'gitattributes', 'editorconfig', 'dockerignore', 'dockerfile', 'makefile', 'cmake', 'tex', 'bib', 'srt', 'vtt', }; /// Detects the [FileKind] of the file at [path] from its extension, falling /// back to an 8 KB content sniff ([_looksLikeText]) for unknown extensions. Future detectFileKind(String path) async { final ext = path.split('.').last.toLowerCase(); if (_imageExtensions.contains(ext)) return FileKind.image; if (ext == 'svg') return FileKind.svg; if (ext == 'pdf') return FileKind.pdf; if (_videoExtensions.contains(ext)) return FileKind.video; if (_audioExtensions.contains(ext)) return FileKind.audio; if (_textExtensions.contains(ext)) return FileKind.text; if (await _looksLikeText(path)) return FileKind.text; return FileKind.unknown; } /// 8 KB sniff: NUL bytes or non-UTF-8 sequences disqualify. Future _looksLikeText(String path) async { final file = File(path); RandomAccessFile? raf; try { final length = await file.length(); if (length == 0) return true; raf = await file.open(); final sample = await raf.read(min(length, 8192)); if (sample.contains(0)) return false; utf8.decode(sample); return true; } on Object { return false; } finally { await raf?.close(); } }