From 88a211b141d9bed68d5486d19735cf94b677bf3f Mon Sep 17 00:00:00 2001 From: kimdk9829 Date: Mon, 15 Jul 2024 19:36:48 +0000 Subject: [PATCH] feat: upload --- lib/storage.pl | 39 ++++++++++++++------------- lib/upload.pl | 58 ++++++++++++++++++++++++++++++++++++++++ web/js/jswish.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 18 deletions(-) create mode 100644 lib/upload.pl diff --git a/lib/storage.pl b/lib/storage.pl index 01de5ef68..2cac235bc 100644 --- a/lib/storage.pl +++ b/lib/storage.pl @@ -34,23 +34,26 @@ */ :- module(web_storage, - [ storage_file/1, % ?File - storage_file_extension/2, % ?File, ?Extension - storage_file/3, % +File, -Data, -Meta - storage_meta_data/2, % +File, -Meta - storage_meta_property/2, % +Meta, ?Property - - storage_fsck/0, - storage_repack/0, - storage_repack/1, % +Options - storage_unpack/0, - - storage_store_term/2, % +Term, -Hash - storage_load_term/2, % +Hash, -Term - - use_gitty_file/1, % +File - use_gitty_file/2 % +File, +Options - ]). + [ storage_file/1, % ?File + storage_file_extension/2, % ?File, ?Extension + storage_file_extension_head/3, % ?File, ?Extension, -Head + storage_file/3, % +File, -Data, -Meta + storage_meta_data/2, % +File, -Meta + storage_meta_property/2, % +Meta, ?Property + storage_commit/2, % +Hash, -Meta + + storage_fsck/0, + storage_repack/0, + storage_repack/1, % +Options + storage_unpack/0, + + storage_store_term/2, % +Term, -Hash + storage_load_term/2, % +Hash, -Term + + use_gitty_file/1, % +File + use_gitty_file/2, % +File, +Options + open_gittystore/1 + ]). :- use_module(library(http/http_dispatch)). :- use_module(library(http/http_parameters)). :- use_module(library(http/http_json)). @@ -67,7 +70,7 @@ :- use_module(library(dcg/basics)). :- use_module(library(pcre)). :- use_module(library(pengines_io)). - +:- use_module(upload). :- use_module(page). :- use_module(gitty). :- use_module(patch). diff --git a/lib/upload.pl b/lib/upload.pl new file mode 100644 index 000000000..e1145dde8 --- /dev/null +++ b/lib/upload.pl @@ -0,0 +1,58 @@ +:- module(upload, [handle_upload/1]). +:- use_module(library(http/thread_httpd)). +:- use_module(library(http/http_dispatch)). +:- use_module(library(http/http_header)). +:- use_module(library(http/http_multipart_plugin)). +:- use_module(library(http/http_client)). +:- use_module(library(http/html_write)). +:- use_module(library(option)). +:- use_module(library(debug)). +:- use_module(web_storage). +:- use_module(gitty). + +:- http_handler(root(upload), handle_upload, [method(post)]). + +handle_upload(Request) :- + multipart_post_request(Request), !, + http_read_data(Request, Parts, [on_filename(save_file)]), + memberchk(file=file(FileName, TempFile), Parts), + save_file_to_storage(FileName, TempFile, SavedPath), + format('Content-type: application/json~n~n'), + format('{"status":"success","filename":"~w","saved":"~w"}', [FileName, SavedPath]). +handle_upload(_Request) :- + throw(http_reply(bad_request(bad_file_upload))). + +multipart_post_request(Request) :- + memberchk(method(post), Request), + memberchk(content_type(ContentType), Request), + http_parse_header_value( + content_type, ContentType, + media(multipart/'form-data', _)). + +:- public save_file/3. + +save_file(In, file(FileName, TempFile), Options) :- + option(filename(FileName), Options), + setup_call_cleanup( + tmp_file_stream(octet, TempFile, Out), + copy_stream_data(In, Out), + close(Out)). + +save_file_to_storage(FileName, TempFile, SavedPath) :- + web_storage:open_gittystore(Dir), + setup_call_cleanup( + open(TempFile, read, In, [type(binary)]), + read_string(In, _, Data), + close(In) + ), + Meta = _{author: 'user', public: true}, + gitty:gitty_create(Dir, FileName, Data, Meta, _Commit), + directory_file_path(Dir, FileName, SavedPath). + +% error message +:- multifile prolog:message//1. + +prolog:message(bad_file_upload) --> + [ 'A file upload must be submitted as multipart/form-data using', nl, + 'name=file and providing a file-name' + ]. diff --git a/web/js/jswish.js b/web/js/jswish.js index 3ea418677..64a961193 100644 --- a/web/js/jswish.js +++ b/web/js/jswish.js @@ -120,6 +120,9 @@ preferences.setInform("preserve-state", ".unloadable"); "Download": glyph("floppy-save", function() { menuBroadcast("download"); }), + "Upload ...": glyph("upload", function() { + $('#fileInput').trigger('click'); + }), "Print ...": glyph("print", function() { menuBroadcast("print"); }) @@ -177,6 +180,72 @@ preferences.setInform("preserve-state", ".unloadable"); } }; // defaults; + $(() => { + if ($('#fileInput').length === 0) { + $('body').append(''); + } + + $(document).on('change', '#fileInput', function(event) { + var files = event.target.files; + if (files.length > 0) { + var allowedExtensions = ['.swib', '.pl']; + + var uploadFile = function(file, callback) { + var formData = new FormData(); + formData.append('file', file); + + $.ajax({ + url: '/upload', + type: 'POST', + data: formData, + processData: false, + contentType: false, + success: function(response) { + console.log('File uploaded successfully:', response); + callback(null, response.filename); + }, + error: function(error) { + console.error('File upload failed:', error); + callback(error); + } + }); + }; + + var lastUploadedFileName = null; + + var uploadNextFile = function(index) { + if (index < files.length) { + var file = files[index]; + var fileName = file.name; + var fileExtension = fileName.slice(fileName.lastIndexOf('.')).toLowerCase(); + + if (allowedExtensions.includes(fileExtension)) { + uploadFile(file, function(err, fileName) { + if (!err) { + lastUploadedFileName = fileName; + uploadNextFile(index + 1); + } else { + alert('File upload failed for file: ' + fileName); + } + }); + } else { + alert('Invalid file type: ' + fileName + '. Only .swib and .pl files are allowed.'); + uploadNextFile(index + 1); + } + } else { + // redirect to last file + if (lastUploadedFileName) { + window.location.href = '/p/' + lastUploadedFileName; + } else { + location.reload(); + } + } + }; + + uploadNextFile(0); + } + }); +}); /** @lends $.fn.swish */ var methods = {