diff --git a/web/file_uploads/FileManager.gd b/web/file_uploads/FileManager.gd new file mode 100644 index 0000000..6cb68f0 --- /dev/null +++ b/web/file_uploads/FileManager.gd @@ -0,0 +1,346 @@ +extends WebNode + +export(bool) var render_back_arrow : bool = true +export(String) var serve_folder : String + +enum EditActions { + UPLOAD = 0, + DELETE, + CREATE_FOLDER, + DELETE_FOLDER, + #RENAME, TODO +} + +class BFSNEntry: + var uri : String + var data : String + +var _folder_indexes : Array +var _folder_uris : PoolStringArray +var _index : String + +var _file_cache : FileCache = FileCache.new() + +var _lock : RWLock = RWLock.new() + +func _ready(): + var dir : Directory = Directory.new() + + if !dir.dir_exists(serve_folder): + dir.make_dir_recursive(serve_folder) + + load_dir() + +func _handle_request_main(request : WebServerRequest) -> void: + if (web_permission): + if (web_permission.activate(request)): + return; + + if request.get_method() == HTTPServerEnums.HTTP_METHOD_POST: + var folder_name : String = request.get_path(true, false); + + if !_folder_uris.contains(folder_name): + PLogger.log_error("!_folder_uris.contains(folder_name) ! '{0}'".format([ folder_name ])) + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_500_INTERNAL_SERVER_ERROR) + return + + var action : int = int(request.get_post_parameter("action")) + + if action == EditActions.UPLOAD: + var file_name : String = request.get_file_file_name(0) + + if file_name.empty(): + PLogger.log_error("file_name.empty()!") + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_500_INTERNAL_SERVER_ERROR) + return + + var www_file_path : String = folder_name.append_path(file_name) + var tfp : String = _file_cache.wwwroot_get_simplified_abs_path(www_file_path) + + if tfp.empty(): + PLogger.log_error("tfp.empty()! " + www_file_path) + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_500_INTERNAL_SERVER_ERROR) + return + + request.move_file(0, tfp) + + load_dir() + elif action == EditActions.CREATE_FOLDER: + var new_folder_name : String = request.get_post_parameter("folder") + + if !new_folder_name.empty(): + var www_file_path : String = folder_name.append_path(new_folder_name) + var full_path : String = _file_cache.wwwroot_get_simplified_abs_path(www_file_path) + + if full_path.empty(): + PLogger.log_error("full_path.empty()!") + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_500_INTERNAL_SERVER_ERROR) + return + + var d : Directory = Directory.new() + d.make_dir_recursive(full_path) + load_dir() + elif action == EditActions.DELETE_FOLDER: + var folder : String = request.get_post_parameter("folder") + var folder_uri : String = folder_name.append_path(folder) + + if !_folder_uris.contains(folder_uri): + PLogger.log_error("!folder.contains(folder_name) ! '{0}'".format([ folder_uri ])) + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_500_INTERNAL_SERVER_ERROR) + return + + if !folder.empty(): + var full_path : String = serve_folder.append_path(folder_uri) + + if full_path.empty(): + PLogger.log_error("full_path.empty()!") + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_500_INTERNAL_SERVER_ERROR) + return + + var d : Directory = Directory.new() + d.remove(full_path) + load_dir() + elif action == EditActions.DELETE: + var file : String = request.get_post_parameter("file") + var file_uri_path : String = folder_name.append_path(file) + + _lock.read_lock() + if !_file_cache.wwwroot_has_file(file_uri_path): + _lock.read_unlock() + PLogger.log_error("!_file_cache.wwwroot_has_file(file_uri_path)") + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_500_INTERNAL_SERVER_ERROR) + return + _lock.read_unlock() + + if !file_uri_path.empty(): + var full_path : String = serve_folder.append_path(file_uri_path) + + if full_path.empty(): + PLogger.log_error("full_path.empty()!") + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_500_INTERNAL_SERVER_ERROR) + return + + var d : Directory = Directory.new() + d.remove(full_path) + load_dir() + + var rp : String = request.get_current_path_segment(); + + if (rp.empty()): + handle_request(request); + return; + + var file_name : String = request.get_path(true, false); + file_name = file_name.to_lower(); + + var fabspath : String = _file_cache.wwwroot_get_file_abspath(file_name) + + if (!fabspath.empty()): + request.send_file(fabspath); + + return; + + if (!try_route_request_to_children(request)): + handle_request(request); + +func _handle_request(request : WebServerRequest): + var file_name : String = request.get_path(true, false); + + _lock.read_lock() + + for e in _folder_indexes: + if (e.uri == file_name): + render_menu(request); + + if render_back_arrow: + var b : HTMLBuilder = HTMLBuilder.new() + + b.div("row mb-4") + b.div("col-2") + b.cdiv() + + b.div("col-8 pt-2 pb-2 panel_content") + + b.h4() + b.a(get_full_uri_parent()).f().w("<--- back").ca() + b.ch4() + + b.cdiv() + + b.div("col-2") + b.cdiv() + b.cdiv() + + b.write_tag() + + request.body += b.result + + request.body += e.data.replace("%%csrf_token", request.get_csrf_token()); + request.compile_and_send_body(); + + _lock.read_unlock() + + return; + + _lock.read_unlock() + + request.send_error(HTTPServerEnums.HTTP_STATUS_CODE_404_NOT_FOUND); + +func load_dir() -> void: + _lock.write_lock() + + _folder_indexes.clear() + _folder_uris.clear() + + _file_cache.clear(); + + if (serve_folder.empty()): + _file_cache.set_wwwroot(serve_folder); + _file_cache.clear(); + else: + _file_cache.set_wwwroot(serve_folder); + evaluate_dir(_file_cache.get_wwwroot_abs(), true); + + _lock.write_unlock() + +func evaluate_dir(path : String, top_level : bool = false) -> void: + var dir : Directory = Directory.new() + dir.open(path) + + var serve_folder : String = _file_cache.get_wwwroot_abs(); + + var dir_uri : String + + if (!top_level): + dir_uri = path.substr(serve_folder.length(), path.length() - serve_folder.length()); + else: + dir_uri = "/"; + + _folder_uris.push_back(dir_uri) + + var folders : PoolStringArray = PoolStringArray() + var files : PoolStringArray = PoolStringArray() + + dir.list_dir_begin(true); + + var file : String = dir.get_next(); + + while !file.empty(): + var np : String = path.append_path(file); + var nnp : String = np.substr(serve_folder.length(), np.length() - serve_folder.length()); + + if (dir.current_is_dir()): + folders.push_back(nnp); + evaluate_dir(np); + else: + files.push_back(nnp); + + file = dir.get_next(); + + dir.list_dir_end(); + + folders.sort(); + files.sort(); + + render_dir_page(dir_uri, folders, files, top_level); + + +func render_dir_page(dir_uri : String, folders : PoolStringArray, files : PoolStringArray, top_level : bool) -> void: + var b : HTMLBuilder = HTMLBuilder.new() + + var uri : String = get_full_uri(false); + + if uri == "/": + uri = "" + + #b.div("file_list"); + if true: + if (!top_level): + b.div("row").f().div("col-2").f().cdiv().div("col-8 pt-2 pb-2 panel_content") + if true: + b.a(uri + dir_uri.path_get_prev_dir()).f().w("..").ca(); + b.cdiv().div("col-2").f().cdiv().cdiv() + + + for i in range(folders.size()): + b.div("row").f().div("col-2").f().cdiv().div("col-8 pt-2 pb-2 panel_content") + if true: + b.form_post(uri + dir_uri, "form-inline") + + b.div("mr-5") + b.a(uri + folders[i]).f().w("(Folder) ").w(folders[i].get_file()).ca(); + b.cdiv() + + if true: + b.csrf_token("%%csrf_token") + b.input_hidden("action", str(EditActions.DELETE_FOLDER)) + b.input_hidden("folder", folders[i].get_file()) + b.input_submit("X") + b.cform() + b.cdiv().div("col-2").f().cdiv().cdiv() + + + for i in range(files.size()): + b.div("row").f().div("col-2").f().cdiv().div("col-8 pt-2 pb-2 panel_content") + if true: + b.form_post(uri + dir_uri, "form-inline") + + b.div("mr-5") + b.a(uri + files[i]).f().w("(File) ").w(files[i].get_file()).ca(); + b.cdiv() + + if true: + b.csrf_token("%%csrf_token") + b.input_hidden("action", str(EditActions.DELETE)) + b.input_hidden("file", files[i].get_file()) + b.input_submit("X") + b.cform() + b.cdiv().div("col-2").f().cdiv().cdiv() + + if folders.size() == 0 && files.size() == 0: + b.div("row").f().div("col-2").f().cdiv().div("col-8 pt-2 pb-2 panel_content") + if true: + b.w("Directory is empty.") + b.cdiv().div("col-2").f().cdiv().cdiv() + + #b.cdiv(); + + b.div("row mt-4").f().div("col-2").f().cdiv().div("col-8 pt-2 pb-2 panel_content") + b.w("Upload new file") + b.cdiv().div("col-2").f().cdiv().cdiv() + + b.div("row").f().div("col-2").f().cdiv().div("col-8 pt-2 pb-2") + b.form_post(uri + dir_uri).enctype_multipart_form_data() + if true: + b.csrf_token("%%csrf_token") + b.input_hidden("action", str(EditActions.UPLOAD)) + b.input_file("file") + b.input_submit("Upload") + b.cform() + b.cdiv().div("col-2").f().cdiv().cdiv() + + b.div("row mt-4").f().div("col-2").f().cdiv().div("col-8 pt-2 pb-2 panel_content") + b.w("Create new folder") + b.cdiv().div("col-2").f().cdiv().cdiv() + + b.div("row").f().div("col-2").f().cdiv().div("col-8 pt-2 pb-2") + b.form_post(uri + dir_uri) + if true: + b.csrf_token("%%csrf_token") + b.input_hidden("action", str(EditActions.CREATE_FOLDER)) + b.input_text("folder") + b.input_submit("Create") + b.cform() + b.cdiv().div("col-2").f().cdiv().cdiv() + + b.write_tag() + + var e : BFSNEntry = BFSNEntry.new() + e.uri = dir_uri; + e.data = b.result; + + _folder_indexes.push_back(e); + + if (dir_uri == "/"): + _index = b.result; + diff --git a/web/file_uploads/Main.tscn b/web/file_uploads/Main.tscn new file mode 100644 index 0000000..7866ae4 --- /dev/null +++ b/web/file_uploads/Main.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://FileManager.gd" type="Script" id=1] + +[node name="Main" type="Node"] + +[node name="WebServerSimple" type="WebServerSimple" parent="."] +start_on_ready = true +upload_file_store_type = 1 +upload_temp_file_store_path = "user://http_temp_files/" +upload_request_max_file_size_type = 3 +upload_request_max_file_size = 1 + +[node name="FileManager" type="WebNode" parent="WebServerSimple"] +script = ExtResource( 1 ) +render_back_arrow = false +serve_folder = "user://managed_folder/" diff --git a/web/file_uploads/default_env.tres b/web/file_uploads/default_env.tres new file mode 100644 index 0000000..4f08e8f --- /dev/null +++ b/web/file_uploads/default_env.tres @@ -0,0 +1,7 @@ +[gd_resource type="Environment3D" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] + +[resource] +background_mode = 2 +background_sky = SubResource( 1 ) diff --git a/web/file_uploads/icon.png b/web/file_uploads/icon.png new file mode 100644 index 0000000..87f1f75 Binary files /dev/null and b/web/file_uploads/icon.png differ diff --git a/web/file_uploads/icon.png.import b/web/file_uploads/icon.png.import new file mode 100644 index 0000000..a4c02e6 --- /dev/null +++ b/web/file_uploads/icon.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/web/file_uploads/project.pandemonium b/web/file_uploads/project.pandemonium new file mode 100644 index 0000000..8f72ce9 --- /dev/null +++ b/web/file_uploads/project.pandemonium @@ -0,0 +1,25 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +[application] + +config/name="File Uploads" +run/main_scene="res://Main.tscn" +config/icon="res://icon.png" + +[physics] + +common/enable_pause_aware_picking=true + +[rendering] + +vram_compression/import_etc=true +vram_compression/import_etc2=false +environment/default_environment="res://default_env.tres"