mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
253debb3f9
commit
e261287baa
5 files changed
+190
-35
No files matched your search
@@ -58,7 +58,11 @@ class NativeHandle {
|
|||||||
async createWritable() {
|
async createWritable() {
|
||||||
const {writer} = await this.host.call("write.begin", {handle:this.nativeHandle});
|
const {writer} = await this.host.call("write.begin", {handle:this.nativeHandle});
|
||||||
return {
|
return {
|
||||||
write: async data => this.host.call("write.data", {writer, data:await this.host.encode(data)}),
|
write: async data => {
|
||||||
|
const resources = new Set();
|
||||||
|
try { return await this.host.call("write.data", {writer, data:await this.host.encode(data, "", resources)}); }
|
||||||
|
finally { for (const file of resources) await this.host.call("release", {file}).catch(() => {}); }
|
||||||
|
},
|
||||||
close: () => this.host.call("write.commit", {writer}),
|
close: () => this.host.call("write.commit", {writer}),
|
||||||
abort: () => this.host.call("write.abort", {writer}),
|
abort: () => this.host.call("write.abort", {writer}),
|
||||||
};
|
};
|
||||||
@@ -77,7 +81,7 @@ class NativeHost {
|
|||||||
return this.bridge.call(`ofdgo.host.${name}`, value);
|
return this.bridge.call(`ofdgo.host.${name}`, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async encode(value, key = "") {
|
async encode(value, key = "", resources = null) {
|
||||||
if (value?.nativeFile) return {nativeFile:value.nativeFile, size:value.size, name:value.name};
|
if (value?.nativeFile) return {nativeFile:value.nativeFile, size:value.size, name:value.name};
|
||||||
if (value?.nativeHandle) return {nativeHandle:value.nativeHandle, name:value.name, kind:value.kind};
|
if (value?.nativeHandle) return {nativeHandle:value.nativeHandle, name:value.name, kind:value.kind};
|
||||||
if (value instanceof Blob || value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
if (value instanceof Blob || value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
||||||
@@ -87,12 +91,19 @@ class NativeHost {
|
|||||||
}
|
}
|
||||||
const response = await fetch("/__native/input", {method:"POST", body:value});
|
const response = await fetch("/__native/input", {method:"POST", body:value});
|
||||||
if (!response.ok) throw new Error("传递数据失败");
|
if (!response.ok) throw new Error("传递数据失败");
|
||||||
return response.json();
|
const file = await response.json();
|
||||||
|
resources?.add(file.nativeFile);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const results = await Promise.allSettled(value.map(item => this.encode(item, "", resources)));
|
||||||
|
const failure = results.find(item => item.status === "rejected");
|
||||||
|
if (failure) throw failure.reason;
|
||||||
|
return results.map(item => item.value);
|
||||||
}
|
}
|
||||||
if (Array.isArray(value)) return Promise.all(value.map(item => this.encode(item)));
|
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
const result = {};
|
const result = {};
|
||||||
for (const [key, item] of Object.entries(value)) result[key] = await this.encode(item, key);
|
for (const [key, item] of Object.entries(value)) result[key] = await this.encode(item, key, resources);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@@ -118,12 +129,23 @@ class NativeHost {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async release(value) {
|
||||||
|
if (value?.nativeFile && value.temporary) {
|
||||||
|
await this.call("release", {file:value.nativeFile}).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
for (const item of Object.values(value)) await this.release(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
worker() {
|
worker() {
|
||||||
const host = this;
|
const host = this;
|
||||||
let closed = false;
|
let closed = false;
|
||||||
let unsubscribe;
|
let unsubscribe;
|
||||||
let pending = Promise.resolve();
|
let pending = Promise.resolve();
|
||||||
const canceled = new Set();
|
const canceled = new Set();
|
||||||
|
const resources = new Set();
|
||||||
const emit = data => { if (!closed) worker.onmessage?.({data}); };
|
const emit = data => { if (!closed) worker.onmessage?.({data}); };
|
||||||
const ready = host.call("engine.open", {}).then(({engine}) => {
|
const ready = host.call("engine.open", {}).then(({engine}) => {
|
||||||
if (closed) { host.call("engine.close", {engine}).catch(() => {}); return engine; }
|
if (closed) { host.call("engine.close", {engine}).catch(() => {}); return engine; }
|
||||||
@@ -146,18 +168,26 @@ class NativeHost {
|
|||||||
const engine = await ready;
|
const engine = await ready;
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
if (canceled.has(data.id)) throw Object.assign(new Error("操作已取消"), {name:"AbortError"});
|
if (canceled.has(data.id)) throw Object.assign(new Error("操作已取消"), {name:"AbortError"});
|
||||||
const args = await host.encode(data.args);
|
const args = await host.encode(data.args, "", resources);
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
if (canceled.has(data.id)) throw Object.assign(new Error("操作已取消"), {name:"AbortError"});
|
if (canceled.has(data.id)) throw Object.assign(new Error("操作已取消"), {name:"AbortError"});
|
||||||
const result = await host.call("engine.call", {engine, id:data.id, name:data.name, args});
|
const result = await host.call("engine.call", {engine, id:data.id, name:data.name, args});
|
||||||
emit({id:data.id, ...await host.decode(result, data.name)});
|
if (closed) { await host.release(result); return; }
|
||||||
|
const decoded = await host.decode(result, data.name);
|
||||||
|
if (closed) { await host.release(result); return; }
|
||||||
|
emit({id:data.id, ...decoded});
|
||||||
}).catch(error => emit({id:data.id, ok:false, error:error.message, code:error.code, canceled:error.name === "AbortError"})).finally(() => canceled.delete(data.id));
|
}).catch(error => emit({id:data.id, ok:false, error:error.message, code:error.code, canceled:error.name === "AbortError"})).finally(() => canceled.delete(data.id));
|
||||||
},
|
},
|
||||||
terminate() {
|
terminate() {
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
closed = true;
|
closed = true;
|
||||||
unsubscribe?.();
|
unsubscribe?.();
|
||||||
ready.then(engine => host.call("engine.close", {engine})).catch(() => {});
|
const closing = ready.then(engine => host.call("engine.close", {engine})).catch(() => {});
|
||||||
|
pending.finally(async () => {
|
||||||
|
await closing;
|
||||||
|
for (const file of resources) await host.call("release", {file}).catch(() => {});
|
||||||
|
resources.clear();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
ready.catch(() => {});
|
ready.catch(() => {});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ require (
|
|||||||
github.com/go-text/typesetting v0.3.5
|
github.com/go-text/typesetting v0.3.5
|
||||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||||
github.com/tdewolff/font v0.0.0-20261010204837-88a7f6b56918
|
github.com/tdewolff/font v0.0.0-20261010204837-88a7f6b56918
|
||||||
github.com/xiaoqidun/guigo v0.0.0-20261017114758-498e989ad05f
|
github.com/xiaoqidun/guigo v0.0.0-20261017122947-f0c082d8bbcd
|
||||||
github.com/xiaoqidun/j2kgo v1.0.2
|
github.com/xiaoqidun/j2kgo v1.0.2
|
||||||
github.com/xiaoqidun/jbig2 v1.1.8
|
github.com/xiaoqidun/jbig2 v1.1.8
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261016003351-18348c283ac1
|
github.com/xiaoqidun/pdfgo v0.0.0-20261016003351-18348c283ac1
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ github.com/tdewolff/parse/v2 v2.8.16 h1:bLk5svUOQRkW/Y2SJ+DeENSIkZBcTIkq+Atyv5D8
|
|||||||
github.com/tdewolff/parse/v2 v2.8.16/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ=
|
github.com/tdewolff/parse/v2 v2.8.16/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ=
|
||||||
github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
||||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||||
github.com/xiaoqidun/guigo v0.0.0-20261017114758-498e989ad05f h1:LReZHupfHnvaOvjGMdPShKDArLMOEF5Fwm8HjfOR9n4=
|
github.com/xiaoqidun/guigo v0.0.0-20261017122947-f0c082d8bbcd h1:IzKVmVXKjcaClHFCou3MQmSuSq+dWIde7m4AwSmuCM0=
|
||||||
github.com/xiaoqidun/guigo v0.0.0-20261017114758-498e989ad05f/go.mod h1:QAHK2Sa/KvY6dO+6dtvVUeOXVQO4Zh8R5oSSU+av7kE=
|
github.com/xiaoqidun/guigo v0.0.0-20261017122947-f0c082d8bbcd/go.mod h1:QAHK2Sa/KvY6dO+6dtvVUeOXVQO4Zh8R5oSSU+av7kE=
|
||||||
github.com/xiaoqidun/j2kgo v1.0.2 h1:OAjjEOfaBaQzBNcj9wx+eM68UI5pewR6SzNZa1R1DIw=
|
github.com/xiaoqidun/j2kgo v1.0.2 h1:OAjjEOfaBaQzBNcj9wx+eM68UI5pewR6SzNZa1R1DIw=
|
||||||
github.com/xiaoqidun/j2kgo v1.0.2/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
|
github.com/xiaoqidun/j2kgo v1.0.2/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.8 h1:dERPW7F8gs18a/DOD9gXHR3fP1Vl6KhCNsLnq5l3BIo=
|
github.com/xiaoqidun/jbig2 v1.1.8 h1:dERPW7F8gs18a/DOD9gXHR3fP1Vl6KhCNsLnq5l3BIo=
|
||||||
|
|||||||
+93
-13
@@ -17,6 +17,7 @@
|
|||||||
package webview
|
package webview
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -56,9 +57,10 @@ type Host struct {
|
|||||||
pendingPaths []string
|
pendingPaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// resourceReader 保持资源独立的读取位置,文件生命周期由窗口管理
|
// resourceReader 保存资源读取位置及关闭操作
|
||||||
type resourceReader struct {
|
type resourceReader struct {
|
||||||
*io.SectionReader
|
*io.SectionReader
|
||||||
|
close func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run 启动共用WebUI的原生入口
|
// Run 启动共用WebUI的原生入口
|
||||||
@@ -115,27 +117,105 @@ func Run(ctx context.Context, paths []string, dataDir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Close 结束本次资源读取
|
// Close 结束本次资源读取
|
||||||
func (r *resourceReader) Close() error { return nil }
|
// 返回: error 关闭错误
|
||||||
|
func (r *resourceReader) Close() error {
|
||||||
|
if r.close != nil {
|
||||||
|
return r.close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// resource 将大文件直接交给宿主资源流
|
// resource 将静态资源及文件直接交给宿主资源流
|
||||||
|
// 入参: r 资源请求
|
||||||
|
// 返回: *guigo.Resource 资源响应, error 读取错误
|
||||||
func (h *Host) resource(r *http.Request) (*guigo.Resource, error) {
|
func (h *Host) resource(r *http.Request) (*guigo.Resource, error) {
|
||||||
if r.Method != http.MethodGet || !strings.HasPrefix(r.URL.Path, "/__native/data/") || r.Header.Get("Range") != "" {
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
id := strings.TrimPrefix(r.URL.Path, "/__native/data/")
|
for _, name := range []string{"If-Match", "If-None-Match", "If-Modified-Since", "If-Unmodified-Since", "If-Range"} {
|
||||||
reader, size, err := h.files.resolve(id)
|
if r.Header.Get(name) != "" {
|
||||||
if err != nil {
|
return nil, nil
|
||||||
return nil, nil
|
}
|
||||||
|
}
|
||||||
|
var reader io.ReaderAt
|
||||||
|
var size int64
|
||||||
|
var close func() error
|
||||||
|
transferred := false
|
||||||
|
defer func() {
|
||||||
|
if !transferred && close != nil {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/__native/data/") {
|
||||||
|
var err error
|
||||||
|
reader, size, close, err = h.files.resource(strings.TrimPrefix(r.URL.Path, "/__native/data/"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
|
if name == "" {
|
||||||
|
name = "index.html"
|
||||||
|
}
|
||||||
|
data, err := webuiassets.NativeFS.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
reader, size = bytes.NewReader(data), int64(len(data))
|
||||||
}
|
}
|
||||||
header := make([]byte, min(size, 512))
|
header := make([]byte, min(size, 512))
|
||||||
_, err = reader.ReadAt(header, 0)
|
_, err := reader.ReadAt(header, 0)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &guigo.Resource{
|
contentType := mime.TypeByExtension(filepath.Ext(r.URL.Path))
|
||||||
Body: &resourceReader{io.NewSectionReader(reader, 0, size)},
|
if contentType == "" {
|
||||||
Header: http.Header{"Content-Type": {http.DetectContentType(header)}, "Content-Length": {strconv.FormatInt(size, 10)}, "Cache-Control": {"no-store"}},
|
contentType = http.DetectContentType(header)
|
||||||
}, nil
|
}
|
||||||
|
headers := http.Header{"Content-Type": {contentType}, "Content-Length": {strconv.FormatInt(size, 10)}, "Cache-Control": {"no-store"}, "Accept-Ranges": {"bytes"}}
|
||||||
|
start, length, status := int64(0), size, http.StatusOK
|
||||||
|
if value := r.Header.Get("Range"); value != "" && r.Method == http.MethodGet {
|
||||||
|
if !strings.HasPrefix(value, "bytes=") || strings.Contains(value, ",") {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
first, last, ok := strings.Cut(strings.TrimPrefix(value, "bytes="), "-")
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if first == "" {
|
||||||
|
count, err := strconv.ParseInt(last, 10, 64)
|
||||||
|
if err != nil || count <= 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
length = min(count, size)
|
||||||
|
start = size - length
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
start, err = strconv.ParseInt(first, 10, 64)
|
||||||
|
if err != nil || start < 0 || start >= size {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
end := size - 1
|
||||||
|
if last != "" {
|
||||||
|
end, err = strconv.ParseInt(last, 10, 64)
|
||||||
|
if err != nil || end < start {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length = min(end, size-1) - start + 1
|
||||||
|
}
|
||||||
|
if length == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
status = http.StatusPartialContent
|
||||||
|
headers.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, start+length-1, size))
|
||||||
|
headers.Set("Content-Length", strconv.FormatInt(length, 10))
|
||||||
|
}
|
||||||
|
if r.Method == http.MethodHead {
|
||||||
|
length = 0
|
||||||
|
}
|
||||||
|
transferred = true
|
||||||
|
return &guigo.Resource{Body: &resourceReader{SectionReader: io.NewSectionReader(reader, start, length), close: close}, StatusCode: status, Header: headers}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// emit 发送当前窗口事件
|
// emit 发送当前窗口事件
|
||||||
|
|||||||
@@ -39,11 +39,13 @@ type fileEntry struct {
|
|||||||
file *os.File
|
file *os.File
|
||||||
directory bool
|
directory bool
|
||||||
owned bool
|
owned bool
|
||||||
|
temporary bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileStore 管理窗口范围内的文件授权和临时数据
|
// fileStore 管理窗口范围内的文件授权和临时数据
|
||||||
type fileStore struct {
|
type fileStore struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
transfers sync.WaitGroup
|
||||||
entries map[string]*fileEntry
|
entries map[string]*fileEntry
|
||||||
writers map[string]*fileWriter
|
writers map[string]*fileWriter
|
||||||
temporary string
|
temporary string
|
||||||
@@ -206,12 +208,43 @@ func (s *fileStore) bytes(data []byte) map[string]any {
|
|||||||
return map[string]any{"nativeFile": id, "size": len(data), "name": "data", "url": "/__native/data/" + id, "temporary": true}
|
return map[string]any{"nativeFile": id, "size": len(data), "name": "data", "url": "/__native/data/" + id, "temporary": true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resource 创建独立资源读取句柄
|
||||||
|
// 入参: id 文件标识
|
||||||
|
// 返回: io.ReaderAt 随机读取器, int64 文件大小, func() error 关闭函数, error 读取错误
|
||||||
|
func (s *fileStore) resource(id string) (io.ReaderAt, int64, func() error, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
entry := s.entries[id]
|
||||||
|
if entry == nil || entry.directory {
|
||||||
|
return nil, 0, nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
if entry.data != nil {
|
||||||
|
return bytes.NewReader(entry.data), int64(len(entry.data)), nil, nil
|
||||||
|
}
|
||||||
|
file, err := openReadFile(entry.path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, nil, err
|
||||||
|
}
|
||||||
|
info, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
file.Close()
|
||||||
|
return nil, 0, nil, err
|
||||||
|
}
|
||||||
|
return file, info.Size(), file.Close, nil
|
||||||
|
}
|
||||||
|
|
||||||
// release 释放已传递完成的内存结果
|
// release 释放已传递完成的内存结果
|
||||||
func (s *fileStore) release(id string) {
|
func (s *fileStore) release(id string) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
if entry := s.entries[id]; entry != nil && entry.data != nil {
|
if entry := s.entries[id]; entry != nil && (entry.data != nil || entry.temporary) {
|
||||||
delete(s.entries, id)
|
delete(s.entries, id)
|
||||||
|
if entry.file != nil {
|
||||||
|
entry.file.Close()
|
||||||
|
}
|
||||||
|
if entry.temporary {
|
||||||
|
os.Remove(entry.path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,28 +482,39 @@ func (w *fileWriter) release() {
|
|||||||
func (s *fileStore) serve(w http.ResponseWriter, r *http.Request) {
|
func (s *fileStore) serve(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
if r.URL.Path == "/__native/input" && r.Method == http.MethodPost {
|
if r.URL.Path == "/__native/input" && r.Method == http.MethodPost {
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.closed {
|
||||||
|
s.mu.Unlock()
|
||||||
|
http.Error(w, os.ErrClosed.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.transfers.Add(1)
|
||||||
|
s.mu.Unlock()
|
||||||
|
defer s.transfers.Done()
|
||||||
file, err := temporaryFile(s.temporary, "input-")
|
file, err := temporaryFile(s.temporary, "input-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = io.Copy(file, r.Body)
|
size, err := io.Copy(file, r.Body)
|
||||||
err = errors.Join(err, file.Close())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
file.Close()
|
||||||
os.Remove(file.Name())
|
os.Remove(file.Name())
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handle, err := s.add(file.Name(), false)
|
s.mu.Lock()
|
||||||
if err != nil {
|
if s.closed {
|
||||||
http.Error(w, err.Error(), 500)
|
s.mu.Unlock()
|
||||||
return
|
file.Close()
|
||||||
}
|
os.Remove(file.Name())
|
||||||
value, err := s.file(handle["nativeHandle"].(string))
|
http.Error(w, os.ErrClosed.Error(), 500)
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), 500)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
id := rand.Text()
|
||||||
|
s.entries[id] = &fileEntry{path: file.Name(), name: "data", file: file, temporary: true}
|
||||||
|
s.mu.Unlock()
|
||||||
|
value := map[string]any{"nativeFile": id, "size": size, "name": "data", "url": "/__native/data/" + id, "temporary": true}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(value)
|
json.NewEncoder(w).Encode(value)
|
||||||
return
|
return
|
||||||
@@ -508,6 +552,7 @@ func (s *fileStore) close() error {
|
|||||||
s.writers = make(map[string]*fileWriter)
|
s.writers = make(map[string]*fileWriter)
|
||||||
s.entries = make(map[string]*fileEntry)
|
s.entries = make(map[string]*fileEntry)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
s.transfers.Wait()
|
||||||
var err error
|
var err error
|
||||||
for _, writer := range writers {
|
for _, writer := range writers {
|
||||||
err = errors.Join(err, writer.Abort())
|
err = errors.Join(err, writer.Abort())
|
||||||
|
|||||||
Reference in new issue
Block a user