main
dart 132 lines 3.61 KB
Raw
1 import 'dart:convert';
2 import 'dart:io';
3
4 import 'package:pdfrx/pdfrx.dart';
5
6 class AttachmentHelpers {
7 AttachmentHelpers._();
8
9 static const _imageExtensions = {'jpg', 'jpeg', 'png', 'gif', 'webp'};
10 static const _textExtensions = {
11 'txt',
12 'md',
13 'csv',
14 'json',
15 'yaml',
16 'yml',
17 'xml',
18 'html',
19 'htm',
20 'log',
21 };
22
23 static const supportedDocumentExtensions = [..._textExtensions, 'pdf'];
24
25 static String extensionOf(String path) {
26 final name = path.split(Platform.pathSeparator).last;
27 final dot = name.lastIndexOf('.');
28 if (dot <= 0 || dot == name.length - 1) return '';
29 return name.substring(dot + 1).toLowerCase();
30 }
31
32 static String fileNameOf(String path) =>
33 path.split(Platform.pathSeparator).last;
34
35 static bool isImagePath(String path) =>
36 _imageExtensions.contains(extensionOf(path));
37
38 static bool isTextPath(String path) =>
39 _textExtensions.contains(extensionOf(path));
40
41 static bool isPdfPath(String path) => extensionOf(path) == 'pdf';
42
43 static bool isDocumentPath(String path) =>
44 isTextPath(path) || isPdfPath(path);
45
46 static String mimeTypeForImage(String path) {
47 return switch (extensionOf(path)) {
48 'png' => 'image/png',
49 'webp' => 'image/webp',
50 'gif' => 'image/gif',
51 _ => 'image/jpeg',
52 };
53 }
54
55 static Future<String?> readTextFile(String path) async {
56 try {
57 final file = File(path);
58 if (!await file.exists()) return null;
59 return await file.readAsString();
60 } catch (_) {
61 return null;
62 }
63 }
64
65 static Future<String?> readDocumentFile(String path) {
66 if (isPdfPath(path)) return _readPdfText(path);
67 if (isTextPath(path)) return readTextFile(path);
68 return Future.value(null);
69 }
70
71 static Future<String?> _readPdfText(String path) async {
72 PdfDocument? document;
73 try {
74 final file = File(path);
75 if (!await file.exists()) return null;
76
77 document = await PdfDocument.openFile(path);
78 final text = StringBuffer();
79 for (final page in document.pages) {
80 final pageText = await page.loadText();
81 final content = pageText?.fullText.trim() ?? '';
82 if (content.isEmpty) continue;
83 if (text.isNotEmpty) text.writeln('\n--- Page ${page.pageNumber} ---');
84 text.write(content);
85 }
86 final extracted = text.toString().trim();
87 return extracted.isEmpty ? null : extracted;
88 } catch (_) {
89 return null;
90 } finally {
91 await document?.dispose();
92 }
93 }
94
95 static Future<String?> readImageBase64(String path) async {
96 try {
97 final file = File(path);
98 if (!await file.exists()) return null;
99 final bytes = await file.readAsBytes();
100 return base64Encode(bytes);
101 } catch (_) {
102 return null;
103 }
104 }
105
106 static String appendTextAttachment(
107 String content,
108 String fileName,
109 String text,
110 ) {
111 final block = '--- $fileName ---\n$text';
112 if (content.trim().isEmpty) return block;
113 return '$content\n\n$block';
114 }
115
116 /// Copies [file] into [directory] under a timestamp-prefixed unique name
117 /// and returns the new path, or null if the copy fails (e.g. a locked
118 /// file or a permissions error) so callers can skip that one attachment
119 /// instead of aborting the whole send.
120 static Future<String?> saveAttachment(File file, Directory directory) async {
121 try {
122 final fileName = fileNameOf(file.path);
123 final timestamp = DateTime.now().millisecondsSinceEpoch;
124 final newPath =
125 '${directory.path}${Platform.pathSeparator}${timestamp}_$fileName';
126 await file.copy(newPath);
127 return newPath;
128 } catch (_) {
129 return null;
130 }
131 }
132 }