split file_viewer into focused sub-widgets
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
enum FileKind { image, svg, pdf, text, video, audio, unknown }
|
||||
|
||||
const Set<String> _imageExtensions = {
|
||||
'png',
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'webp',
|
||||
'gif',
|
||||
'bmp',
|
||||
'wbmp',
|
||||
};
|
||||
|
||||
const Set<String> _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<String> _audioExtensions = {
|
||||
'mp3',
|
||||
'm4a',
|
||||
'aac',
|
||||
'wav',
|
||||
'flac',
|
||||
'ogg',
|
||||
'oga',
|
||||
'opus',
|
||||
};
|
||||
|
||||
/// Unknown extensions still get a content sniff via [_looksLikeText].
|
||||
const Set<String> _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<FileKind> 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<bool> _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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user