๐ Click to reveal complete solution
import 'dart:io';
import 'dart:convert';
// Step 1: Custom exception hierarchy
class FileReadException implements Exception {
final String filePath;
final String message;
FileReadException(this.filePath, this.message);
@override
String toString() => 'FileReadException: $message (File: $filePath)';
}
class FileNotFoundException extends FileReadException {
FileNotFoundException(String filePath) : super(filePath, 'File not found');
}
class FilePermissionException extends FileReadException {
FilePermissionException(String filePath) : super(filePath, 'Permission denied');
}
class FileFormatException extends FileReadException {
FileFormatException(String filePath, String expected)
: super(filePath, 'Invalid format. Expected: $expected');
}
class EmptyFileException extends FileReadException {
EmptyFileException(String filePath) : super(filePath, 'File is empty');
}
// Step 2: File reader result
class FileReadResult {
final String filePath;
final String? content;
final bool success;
final String? error;
final Duration readTime;
FileReadResult({
required this.filePath,
this.content,
required this.success,
this.error,
required this.readTime,
});
}
// Step 3: Safe file reader class
class SafeFileReader {
final String _errorLogPath;
int _retryCount = 0;
static const int maxRetries = 3;
SafeFileReader({String? errorLogPath})
: _errorLogPath = errorLogPath ?? 'error.log';
FileReadResult readTextFile(String filePath) {
final stopwatch = Stopwatch()..start();
try {
// Check if file exists
if (!File(filePath).existsSync()) {
throw FileNotFoundException(filePath);
}
// Attempt to read
String content = File(filePath).readAsStringSync();
stopwatch.stop();
// Check if file is empty
if (content.trim().isEmpty) {
throw EmptyFileException(filePath);
}
return FileReadResult(
filePath: filePath,
content: content,
success: true,
readTime: stopwatch.elapsed,
);
} on FileSystemException catch (e) {
stopwatch.stop();
_logError(filePath, e);
if (_retryCount < maxRetries) {
_retryCount++;
print('Retrying... (Attempt ${_retryCount + 1})');
return readTextFile(filePath);
}
return FileReadResult(
filePath: filePath,
success: false,
error: 'Permission denied: ${e.message}',
readTime: stopwatch.elapsed,
);
} on FileReadException catch (e) {
stopwatch.stop();
_logError(filePath, e);
return FileReadResult(
filePath: filePath,
success: false,
error: e.message,
readTime: stopwatch.elapsed,
);
} catch (e, stackTrace) {
stopwatch.stop();
_logError(filePath, e, stackTrace);
rethrow; // Rethrow unexpected errors
} finally {
_retryCount = 0; // Reset retry counter
}
}
List> parseCSV(String filePath) {
final result = readTextFile(filePath);
if (!result.success || result.content == null) {
throw FileReadException(filePath, result.error ?? 'Unknown error');
}
try {
final lines = result.content.split('\n');
if (lines.length < 2) {
throw FileFormatException(filePath, 'CSV with header and data rows');
}
final headers = lines[0].split(',').map((h) => h.trim()).toList();
final data = >[];
for (int i = 1; i < lines.length; i++) {
if (lines[i].trim().isEmpty) continue;
final values = lines[i].split(',').map((v) => v.trim()).toList();
final row = {};
for (int j = 0; j < headers.length && j < values.length; j++) {
row[headers[j]] = values[j];
}
data.add(row);
}
return data;
} catch (e) {
throw FileFormatException(filePath, 'Valid CSV format');
}
}
Map parseJSON(String filePath) {
final result = readTextFile(filePath);
if (!result.success || result.content == null) {
throw FileReadException(filePath, result.error ?? 'Unknown error');
}
try {
final decoded = jsonDecode(result.content);
if (decoded is! Map) {
throw FileFormatException(filePath, 'JSON object');
}
return decoded;
} on FormatException {
throw FileFormatException(filePath, 'Valid JSON');
}
}
void _logError(String filePath, Object error, [StackTrace? stackTrace]) {
try {
final logFile = File(_errorLogPath);
final timestamp = DateTime.now().toIso8601String();
final logEntry = '[$timestamp] Error reading $filePath: $error\n'
'${stackTrace != null ? "Stack: $stackTrace\n" : ""}'
'---\n';
logFile.writeAsStringSync(logEntry, mode: FileMode.append);
} catch (e) {
print('Failed to write error log: $e');
}
}
}
// Step 4: Main program
void main() {
print('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
print('โ SAFE FILE READER โ');
print('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n');
final reader = SafeFileReader();
// Create test files
File('test.txt').writeAsStringSync('Hello, World!\nThis is a test file.');
File('data.json').writeAsStringSync('{"name": "Dart", "version": "3.12"}');
File('data.csv').writeAsStringSync('name,age,city\nAlice,30,NYC\nBob,25,LA');
// Test reading files
final testFiles = ['test.txt', 'missing.txt', 'data.json', 'data.csv'];
for (var filePath in testFiles) {
print('๐ Reading: $filePath');
try {
final result = reader.readTextFile(filePath);
if (result.success) {
print('โ
Success (${result.readTime.inMilliseconds}ms)');
print(' Preview: ${result.content!.substring(0, result.content!.length.clamp(0, 50))}...');
} else {
print('โ Failed: ${result.error}');
}
} catch (e) {
print('๐ฅ Unexpected error: $e');
}
print('');
}
// Test JSON parsing
print('๐ Parsing JSON...');
try {
final jsonData = reader.parseJSON('data.json');
print('โ
Parsed: $jsonData');
} catch (e) {
print('โ Error: $e');
}
// Test CSV parsing
print('\n๐ Parsing CSV...');
try {
final csvData = reader.parseCSV('data.csv');
print('โ
Found ${csvData.length} rows');
for (var row in csvData) {
print(' $row');
}
} catch (e) {
print('โ Error: $e');
}
// Cleanup test files
print('\n๐งน Cleaning up test files...');
for (var file in ['test.txt', 'data.json', 'data.csv']) {
File(file).deleteSync();
}
print('โ
Done!');
}