From 56c09c1bf8f1f54454abd9d190661ef69db14ec5 Mon Sep 17 00:00:00 2001 From: Code by Ben Date: Sun, 22 Sep 2024 14:06:30 +1000 Subject: [PATCH] Replacement file upload checks for duplicates. Lastmod date is now updated to the database. --- assets/js/editor-assets.js | 59 ++++++++++++++++++++-------------- handler/app/app.go | 3 +- handler/app/dirs.go | 9 +++++- handler/htmx/transfer.go | 7 ++++ model/update.go | 2 ++ public/js/editor-assets.min.js | 2 +- view/app/artifactfile.tmpl | 2 ++ 7 files changed, 57 insertions(+), 27 deletions(-) diff --git a/assets/js/editor-assets.js b/assets/js/editor-assets.js index f7a45262..aaec2719 100644 --- a/assets/js/editor-assets.js +++ b/assets/js/editor-assets.js @@ -2,7 +2,14 @@ * @file editor-assets.js * This script is the entry point for the artifact editor assets page. */ -import { progress } from "./uploader.mjs"; +import { + checkDuplicate, + checkErrors, + checkSize, + progress, + resetInput, +} from "./uploader.mjs"; +import { getElmById } from "./helper.mjs"; (() => { "use strict"; @@ -11,29 +18,31 @@ import { progress } from "./uploader.mjs"; progress(`artifact-editor-dl-form`, `artifact-editor-dl-progress`); progress(`artifact-editor-preview-form`, `artifact-editor-preview-progress`); - const previewReset = document.getElementById(`artifact-editor-preview-reset`); - if (previewReset == null) { - console.error(`the reset preview button is missing`); - return; - } - const previewInput = document.getElementById( - `artifact-editor-replace-preview` - ); - if (previewInput == null) { - console.error(`the form preview input is missing`); - return; - } + const previewReset = getElmById(`artifact-editor-preview-reset`); + const previewInput = getElmById(`artifact-editor-replace-preview`); + const alert = getElmById(`artifact-editor-dl-alert`); + const reset = getElmById(`artifact-editor-dl-reset`); + const lastMod = getElmById(`artifact-editor-last-modified`); + const results = getElmById("artifact-editor-dl-results"); + const fileInput = getElmById(`artifact-editor-dl-up`); + fileInput.addEventListener("change", checkFile); - const reset = document.getElementById(`artifact-editor-dl-reset`); - if (reset == null) { - console.error(`the reset button is missing`); - return; - } - const artifact = document.getElementById(`artifact-editor-dl-up`); - if (artifact == null) { - console.error(`the artifact file input is missing`); - return; + async function checkFile() { + resetInput(fileInput, alert, results); + const file1 = this.files[0]; + let errors = [checkSize(file1)]; + checkErrors(errors, alert, fileInput, results); + checkDuplicate(file1, alert, fileInput, results); + + const lastModified = file1.lastModified, + currentTime = new Date().getTime(), + oneHourMs = 60 * 60 * 1000; + const underOneHour = currentTime - lastModified < oneHourMs; + if (!underOneHour) { + lastMod.value = lastModified; + } } + const dataEditor = document.getElementById("artifact-editor-modal"); if (dataEditor == null) { console.error(`the data editor modal is missing`); @@ -264,8 +273,10 @@ import { progress } from "./uploader.mjs"; // New file download form reset button. reset.addEventListener(`click`, function () { - artifact.value = ``; - artifact.classList.remove(`is-invalid`, `is-valid`); + alert.innerText = ""; + alert.classList.add("d-none"); + fileInput.value = ``; + fileInput.classList.remove(`is-invalid`, `is-valid`); }); previewReset.addEventListener(`click`, function () { diff --git a/handler/app/app.go b/handler/app/app.go index d401b59c..afd6cb41 100644 --- a/handler/app/app.go +++ b/handler/app/app.go @@ -445,7 +445,8 @@ func LinkScnr(name string) (string, error) { func LinkScnrs(s string) template.HTML { x := []string{} y := strings.Split(s, ",") - cls := "link-dark link-offset-2 link-offset-3-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover" + cls := "link-dark link-offset-2 link-offset-3-hover link-underline " + + "link-underline-opacity-0 link-underline-opacity-75-hover" for _, z := range y { z = strings.TrimSpace(z) if z == "" { diff --git a/handler/app/dirs.go b/handler/app/dirs.go index c44db0ec..eb948c79 100644 --- a/handler/app/dirs.go +++ b/handler/app/dirs.go @@ -127,7 +127,14 @@ func detectANSI(db *sql.DB, logger *zap.SugaredLogger, id int64, data map[string if db == nil { return data } - mos, numb := data["modOS"].(string), data["modMagicNumber"].(string) + mos, valid := data["modOS"].(string) + if !valid { + return data + } + numb, valid := data["modMagicNumber"].(string) + if !valid { + return data + } textfile := strings.EqualFold(mos, tags.Text.String()) if textfile && numb == magicnumber.ANSIEscapeText.Title() { if err := model.UpdatePlatform(db, id, tags.ANSI.String()); err != nil && logger != nil { diff --git a/handler/htmx/transfer.go b/handler/htmx/transfer.go index eb9b2d10..ba54e814 100644 --- a/handler/htmx/transfer.go +++ b/handler/htmx/transfer.go @@ -20,6 +20,7 @@ import ( "slices" "strconv" "strings" + "time" "github.com/Defacto2/archive" "github.com/Defacto2/helper" @@ -638,6 +639,12 @@ func UploadReplacement(c echo.Context, db *sql.DB, downloadDir string) error { return checkFileOpen(c, nil, name, err) } defer src.Close() + lastmod := c.FormValue("artifact-editor-lastmodified") + lm, err := strconv.ParseInt(lastmod, 10, 64) + if err == nil && lm > 0 { + lmod := time.UnixMilli(lm) + fu.LastMod = lmod + } sign := magicnumber.Find(src) fu.MagicNumber = sign.Title() dst, err := copier(c, nil, file, up.key) diff --git a/model/update.go b/model/update.go index 104af6c2..8ca51e38 100644 --- a/model/update.go +++ b/model/update.go @@ -728,6 +728,7 @@ type FileUpload struct { MagicNumber string Content string Filesize int64 + LastMod time.Time } // Update the file record with the values provided in the FileUpload struct. @@ -753,6 +754,7 @@ func (fu FileUpload) Update(ctx context.Context, exec boil.ContextExecutor, id i return fmt.Errorf("file upload zip content: %w", err) } f.Filesize = null.Int64From(fu.Filesize) + f.FileLastModified = null.TimeFrom(fu.LastMod) if _, err = f.Update(ctx, exec, boil.Infer()); err != nil { return fmt.Errorf("file upload update record %w: %d", err, id) } diff --git a/public/js/editor-assets.min.js b/public/js/editor-assets.min.js index 39c85225..a9e2dedd 100644 --- a/public/js/editor-assets.min.js +++ b/public/js/editor-assets.min.js @@ -1,2 +1,2 @@ /* editor-assets.min.js © Defacto2 2024 */ -(()=>{var k=100,L=1024*1024,A=100*L;function f(s,a){if(s==null)throw new Error("The formId value of progress is null.");if(a==null)throw new Error("The elementId value of progress is null.");htmx.on(`#${s}`,"htmx:xhr:progress",function(l){l.target.id==`${s}`&&htmx.find(`#${a}`).setAttribute("value",l.detail.loaded/l.detail.total*k)})}(()=>{"use strict";f("artifact-editor-dl-form","artifact-editor-dl-progress"),f("artifact-editor-preview-form","artifact-editor-preview-progress");let s=document.getElementById("artifact-editor-preview-reset");if(s==null){console.error("the reset preview button is missing");return}let a=document.getElementById("artifact-editor-replace-preview");if(a==null){console.error("the form preview input is missing");return}let l=document.getElementById("artifact-editor-dl-reset");if(l==null){console.error("the reset button is missing");return}let d=document.getElementById("artifact-editor-dl-up");if(d==null){console.error("the artifact file input is missing");return}let h=document.getElementById("artifact-editor-modal");if(h==null){console.error("the data editor modal is missing");return}let m=document.getElementById("asset-editor-modal");if(m==null){console.error("the asset editor modal is missing");return}let w=document.getElementById("emulate-editor-modal");if(w==null){console.error("the emulate editor modal is missing");return}let v=new bootstrap.Modal(h),g=new bootstrap.Modal(m),T=new bootstrap.Modal(w);switch(new URL(window.location.href).hash){case"#data-editor":v.show(),history.replaceState(null,"",window.location.pathname);break;case"#file-editor":g.show(),history.replaceState(null,"",window.location.pathname);break;case"#emulate-editor":T.show(),history.replaceState(null,"",window.location.pathname);break;default:}document.body.addEventListener("htmx:afterRequest",function(e){p(e,"artifact-editor-dl-form","artifact-editor-dl-up","artifact-editor-dl-feedback"),p(e,"artifact-editor-preview-form","artifact-editor-replace-preview","artifact-editor-preview-feedback"),c(e,"artifact-editor-image-delete","artifact-editor-image-feedback"),c(e,"artifact-editor-imagepreview-delete","artifact-editor-image-feedback"),c(e,"artifact-editor-imagethumb-delete","artifact-editor-image-feedback"),c(e,"artifact-editor-image-pixelate","artifact-editor-preview-feedback"),u(e,"artifact-editor-link-delete","artifact-editor-link-feedback"),u(e,"artifact-editor-comp-previewcopy","artifact-editor-comp-feedback"),u(e,"artifact-editor-comp-previewtext","artifact-editor-comp-feedback"),u(e,"artifact-editor-comp-textcopy","artifact-editor-comp-feedback")});function u(e,o,r){if(e.detail.elt===null||e.detail.elt.id!==`${o}`&&e.detail.elt.name!==o)return;let t=document.getElementById(r);if(t===null)throw new Error(`The htmx successful feedback element ${r} is null`);let i="text-danger",n=e.detail.xhr;if(e.detail.successful){t.innerText=`${n.responseText}`,t.classList.remove(i);return}if(e.detail.failed&&e.detail.xhr){t.classList.add(i),t.innerText=`Something on the server is not working, ${n.status} status: ${n.responseText}.`;return}t.classList.add(i),t.innerText="Something with the browser is not working, please try again or refresh the page."}function c(e,o,r){if(e.detail.elt===null||e.detail.elt.id!==`${o}`)return;let t=document.getElementById(r);if(t===null)throw new Error(`The htmx successful feedback element ${r} is null`);let i="text-danger",n="text-success",x=e.detail.xhr;if(e.detail.successful){t.innerText="The delete request was successful, about to refresh the page.",t.classList.remove(i),t.classList.add(n),setTimeout(()=>{location.reload()},500);return}if(e.detail.failed&&e.detail.xhr){t.classList.add(i),t.innerText=`Something on the server is not working, ${x.status} status: ${x.responseText}.`;return}t.classList.add(i),t.innerText="Something with the browser is not working, please try again or refresh the page."}function p(e,o,r,t){if(e.detail.elt===null||e.detail.elt.id!==`${o}`)return;let i=document.getElementById(r);if(i===null)throw new Error(`The htmx successful input element ${r} is null`);let n=document.getElementById(t);if(n===null)throw new Error(`The htmx successful feedback element ${t} is null`);if(e.detail.successful)return E(e,i,n);if(e.detail.failed&&e.detail.xhr)return b(e,i,n);y(i,n)}function E(e,o,r){let t=e.detail.xhr;r.innerText=`${t.responseText}`,r.classList.remove("invalid-feedback"),r.classList.add("valid-feedback"),o.classList.remove("is-invalid"),o.classList.add("is-valid"),setTimeout(()=>{location.reload()},500)}function b(e,o,r){let t=e.detail.xhr;r.innerText=`Something on the server is not working, ${t.status} status: ${t.responseText}.`,r.classList.remove("valid-feedback"),r.classList.add("invalid-feedback"),o.classList.remove("is-valid"),o.classList.add("is-invalid")}function y(e,o){e.classList.remove("is-valid"),e.classList.add("is-invalid"),o.innerText="Something with the browser is not working, please try again or refresh the page.",o.classList.remove("d-none")}l.addEventListener("click",function(){d.value="",d.classList.remove("is-invalid","is-valid")}),s.addEventListener("click",function(){a.value="",a.classList.remove("is-invalid","is-valid")})})();})(); +(()=>{function u(t){let r=document.getElementById(t);if(r==null)throw new Error(`The ${t} for getElmById() element is null.`);return r}var v="is-invalid",f="d-none",R=100,x=1024*1024,L=100*x;async function Y(t){if(t==null)throw new Error("The file value of checkSHA is null.");try{let r=await z(t),o=await fetch(`/uploader/sha384/${r}`,{method:"PATCH",headers:{"Content-Type":"text/plain"},body:r});if(!o.ok)throw new Error(`Hashing is not possible, server response: ${o.status}`);return await o.text()=="true"}catch(r){console.log(`Hashing is not possible: ${r}`)}}async function z(t){if(t==null)throw new Error("The file value of sha384 is null.");try{let r=await t.arrayBuffer(),o=await crypto.subtle.digest("SHA-384",r);return Array.from(new Uint8Array(o)).map(c=>c.toString(16).padStart(2,"0")).join("")}catch(r){throw new Error(`Could not use arrayBuffer or crypto.subtle: ${r}`)}}function g(t,r){if(t==null)throw new Error("The formId value of progress is null.");if(r==null)throw new Error("The elementId value of progress is null.");htmx.on(`#${t}`,"htmx:xhr:progress",function(o){o.target.id==`${t}`&&htmx.find(`#${r}`).setAttribute("value",o.detail.loaded/o.detail.total*R)})}function $(t){if(t==null)throw new Error("The file value of checkSize is null.");return t.size>L?`The chosen file is too big at ${Math.round(t.size/x)}MB, maximum size is ${L/x}MB.`:""}function B(t,r,o,c){if(t==null)throw new Error("The errors value of checkErrors is null.");if(r==null)throw new Error("The alert value of checkErrors is null.");if(o==null)throw new Error("The fileInput value of checkErrors is null.");if(c==null)throw new Error("The results value of checkErrors is null.");t=t.filter(h=>h!=""),!(t.length<=0)&&(r.innerText=t.join(" "),r.classList.remove(f),o.innerText="",o.classList.add(v),c.classList.add(f))}async function M(t,r,o,c){if(t==null)throw new Error("The file value of checkDuplicate is null.");if(r==null)throw new Error("The alert value of checkDuplicate is null.");if(o==null)throw new Error("The fileInput value of checkDuplicate is null.");if(c==null)throw new Error("The results value of checkDuplicate is null.");await Y(t)!=!1&&(r.innerText=`The chosen file already exists in the database: ${t.name}`,r.classList.remove(f),o.innerText="",o.classList.add(v),c.classList.add(f))}function I(t,r,o){if(t==null)throw new Error("The fileInput value of resetInput is null.");if(r==null)throw new Error("The alert value of resetInput is null.");if(o==null)throw new Error("The results value of resetInput is null.");t.innerText="",t.classList.remove(v),r.innerText="",r.classList.add(f),o.innerText="",o.classList.add(f)}(()=>{"use strict";g("artifact-editor-dl-form","artifact-editor-dl-progress"),g("artifact-editor-preview-form","artifact-editor-preview-progress");let t=u("artifact-editor-preview-reset"),r=u("artifact-editor-replace-preview"),o=u("artifact-editor-dl-alert"),c=u("artifact-editor-dl-reset"),h=u("artifact-editor-last-modified"),p=u("artifact-editor-dl-results"),d=u("artifact-editor-dl-up");d.addEventListener("change",S);async function S(){I(d,o,p);let e=this.files[0],s=[$(e)];B(s,o,d,p),M(e,o,d,p);let n=e.lastModified,i=new Date().getTime(),a=60*60*1e3;i-n{location.reload()},500);return}if(e.detail.failed&&e.detail.xhr){i.classList.add(a),i.innerText=`Something on the server is not working, ${y.status} status: ${y.responseText}.`;return}i.classList.add(a),i.innerText="Something with the browser is not working, please try again or refresh the page."}function k(e,s,n,i){if(e.detail.elt===null||e.detail.elt.id!==`${s}`)return;let a=document.getElementById(n);if(a===null)throw new Error(`The htmx successful input element ${n} is null`);let l=document.getElementById(i);if(l===null)throw new Error(`The htmx successful feedback element ${i} is null`);if(e.detail.successful)return A(e,a,l);if(e.detail.failed&&e.detail.xhr)return j(e,a,l);F(a,l)}function A(e,s,n){let i=e.detail.xhr;n.innerText=`${i.responseText}`,n.classList.remove("invalid-feedback"),n.classList.add("valid-feedback"),s.classList.remove("is-invalid"),s.classList.add("is-valid"),setTimeout(()=>{location.reload()},500)}function j(e,s,n){let i=e.detail.xhr;n.innerText=`Something on the server is not working, ${i.status} status: ${i.responseText}.`,n.classList.remove("valid-feedback"),n.classList.add("invalid-feedback"),s.classList.remove("is-valid"),s.classList.add("is-invalid")}function F(e,s){e.classList.remove("is-valid"),e.classList.add("is-invalid"),s.innerText="Something with the browser is not working, please try again or refresh the page.",s.classList.remove("d-none")}c.addEventListener("click",function(){o.innerText="",o.classList.add("d-none"),d.value="",d.classList.remove("is-invalid","is-valid")}),t.addEventListener("click",function(){r.value="",r.classList.remove("is-invalid","is-valid")})})();})(); diff --git a/view/app/artifactfile.tmpl b/view/app/artifactfile.tmpl index 6160f0a3..732b8c5a 100644 --- a/view/app/artifactfile.tmpl +++ b/view/app/artifactfile.tmpl @@ -119,6 +119,7 @@ + {{/* artifact-editor-unique-id-value */}}
Normally not required, upload and replace the artifact download.
{{- end}} + {{- /* Download content */}} Download content