113 lines
3.3 KiB
GDScript
113 lines
3.3 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
|
|
const ErrorCodes := preload("res://addons/godot_ai/utils/error_codes.gd")
|
|
|
|
## Handles file read/write operations and reimport within the Godot project.
|
|
|
|
|
|
func read_file(params: Dictionary) -> Dictionary:
|
|
var path: String = params.get("path", "")
|
|
|
|
var path_err = McpPathValidator.path_error(path, "path")
|
|
if path_err != null:
|
|
return path_err
|
|
|
|
if not FileAccess.file_exists(path):
|
|
return ErrorCodes.make(ErrorCodes.RESOURCE_NOT_FOUND, "File not found: %s" % path)
|
|
|
|
var file := FileAccess.open(path, FileAccess.READ)
|
|
if file == null:
|
|
return ErrorCodes.make(ErrorCodes.INTERNAL_ERROR, "Failed to open file: %s" % path)
|
|
|
|
var content := file.get_as_text()
|
|
file.close()
|
|
|
|
return {
|
|
"data": {
|
|
"path": path,
|
|
"content": content,
|
|
"size": content.length(),
|
|
"line_count": content.count("\n") + (1 if not content.is_empty() else 0),
|
|
}
|
|
}
|
|
|
|
|
|
func write_file(params: Dictionary) -> Dictionary:
|
|
var path: String = params.get("path", "")
|
|
var content: String = params.get("content", "")
|
|
|
|
var path_err = McpPathValidator.path_error(path, "path", true)
|
|
if path_err != null:
|
|
return path_err
|
|
|
|
# Ensure parent directory exists
|
|
var dir_path := path.get_base_dir()
|
|
if not DirAccess.dir_exists_absolute(dir_path):
|
|
var err := DirAccess.make_dir_recursive_absolute(dir_path)
|
|
if err != OK:
|
|
return ErrorCodes.make(ErrorCodes.INTERNAL_ERROR, "Failed to create directory: %s" % dir_path)
|
|
|
|
var existed_before := FileAccess.file_exists(path)
|
|
|
|
var file := FileAccess.open(path, FileAccess.WRITE)
|
|
if file == null:
|
|
return ErrorCodes.make(ErrorCodes.INTERNAL_ERROR, "Failed to open file for writing: %s" % path)
|
|
|
|
file.store_string(content)
|
|
file.close()
|
|
|
|
# Single-file register, not a full scan() — a scan() per write stacks
|
|
# filesystem WorkerThreadPool tasks under concurrent writes and can SIGABRT
|
|
# in the global-class update (see dsarno/godot#6 and create_script in
|
|
# script_handler.gd). update_file() is what reimport()/material/theme use.
|
|
var efs := EditorInterface.get_resource_filesystem()
|
|
if efs != null:
|
|
efs.update_file(path)
|
|
|
|
var data := {
|
|
"path": path,
|
|
"size": content.length(),
|
|
"undoable": false,
|
|
"reason": "File system operations cannot be undone via editor undo",
|
|
}
|
|
McpResourceIO.attach_cleanup_hint(data, existed_before, [path])
|
|
return {"data": data}
|
|
|
|
|
|
func reimport(params: Dictionary) -> Dictionary:
|
|
var paths: Array = params.get("paths", [])
|
|
|
|
if paths.is_empty():
|
|
return ErrorCodes.make(ErrorCodes.MISSING_REQUIRED_PARAM, "Missing required param: paths (non-empty array)")
|
|
|
|
var efs := EditorInterface.get_resource_filesystem()
|
|
if efs == null:
|
|
return ErrorCodes.make(ErrorCodes.EDITOR_NOT_READY, "EditorFileSystem not available")
|
|
|
|
var reimported: Array[String] = []
|
|
var not_found: Array[String] = []
|
|
|
|
for path_variant in paths:
|
|
var path: String = str(path_variant)
|
|
var path_err := McpPathValidator.validate_resource_path(path)
|
|
if not path_err.is_empty():
|
|
not_found.append("%s (%s)" % [path, path_err])
|
|
continue
|
|
if not FileAccess.file_exists(path):
|
|
not_found.append("%s (file does not exist)" % path)
|
|
continue
|
|
efs.update_file(path)
|
|
reimported.append(path)
|
|
|
|
return {
|
|
"data": {
|
|
"reimported": reimported,
|
|
"not_found": not_found,
|
|
"reimported_count": reimported.size(),
|
|
"not_found_count": not_found.size(),
|
|
"undoable": false,
|
|
"reason": "Reimport is a file system operation",
|
|
}
|
|
}
|