execute method
Execute the tool with the given input.
Implementation
@override
Future<ToolResult> execute(Map<String, dynamic> input) async {
final filePath = input['file_path'] as String?;
final content = input['content'] as String?;
// Validate required parameters
if (filePath == null || filePath.isEmpty) {
return ToolResult.error('Missing required parameter: file_path');
}
if (!p.isAbsolute(filePath)) {
return ToolResult.error(
'file_path must be an absolute path, got: $filePath',
);
}
if (content == null) {
return ToolResult.error('Missing required parameter: content');
}
// Check content size
if (content.length > maxContentSize) {
return ToolResult.error(
'Content too large: ${_formatFileSize(content.length)} '
'(max ${_formatFileSize(maxContentSize)})',
);
}
// Check protected paths
final protectedCheck = _checkProtectedPath(filePath);
if (protectedCheck != null) {
return ToolResult.error(protectedCheck);
}
// Resolve symlinks for the target path
final resolvedPath = await _resolveSymlink(filePath);
final file = File(resolvedPath);
final isNewFile = !await file.exists();
String? backupPath;
try {
// Create parent directories if needed
final parent = file.parent;
if (!await parent.exists()) {
await parent.create(recursive: true);
}
// Check parent directory write permission
if (!await _isWritable(parent.path)) {
return ToolResult.error(
'Permission denied: Cannot write to directory '
'${parent.path}',
);
}
// Backup existing file before overwriting
if (!isNewFile) {
backupPath = await _createBackup(file);
}
// Atomic write: write to temp file, then rename
final tempFile = File('$resolvedPath.tmp.${_timestamp()}');
try {
await tempFile.writeAsString(content, encoding: utf8, flush: true);
// Post-write verification: read back and compare
final verifyContent = await tempFile.readAsString(encoding: utf8);
if (verifyContent != content) {
await tempFile.delete();
return ToolResult.error(
'Post-write verification failed: content mismatch. '
'The write was aborted.',
);
}
// Rename temp file to target (atomic on same filesystem)
await tempFile.rename(resolvedPath);
} catch (e) {
// Clean up temp file on failure
if (await tempFile.exists()) {
await tempFile.delete();
}
rethrow;
}
// Get final file stats
final stat = await file.stat();
final bytesWritten = stat.size;
final output = FileWriteOutput(
success: true,
message: isNewFile
? 'New file created: $filePath'
: 'File overwritten: $filePath',
bytesWritten: bytesWritten,
created: isNewFile,
backupPath: backupPath,
);
final resultBuf = StringBuffer();
resultBuf.writeln(output.message);
resultBuf.writeln('Size: ${_formatFileSize(bytesWritten)}');
if (!isNewFile && backupPath != null) {
resultBuf.writeln('Backup: $backupPath');
}
return ToolResult.success(
resultBuf.toString(),
metadata: output.toMetadata(),
);
} catch (e) {
// Attempt to restore from backup on failure
if (backupPath != null && await File(backupPath).exists()) {
try {
await File(backupPath).copy(resolvedPath);
} catch (_) {
// Restoration failed too
}
}
return ToolResult.error('Error writing file: $e');
}
}