mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
1 parent
0eae7c72d0
commit
a0899fa150
15 files changed
+549
-119
No files matched your search
@@ -424,6 +424,7 @@ type businessEngine struct {
|
||||
copiedStyle *ofdgo.GraphicObject
|
||||
ctx context.Context
|
||||
resolveSource func(context.Context, any) (io.ReaderAt, int64, error)
|
||||
conversionPool *ofdgo.ConversionPool
|
||||
}
|
||||
|
||||
// Open 打开浏览器内存中的OFD文档
|
||||
@@ -1391,6 +1392,7 @@ func (e *businessEngine) convertFile(args []platformValue) (any, error) {
|
||||
}
|
||||
options := ofdgo.ConvertOptions{
|
||||
Format: settings.Get("format").String(), PageRange: settings.Get("pages").String(),
|
||||
Pool: e.conversionPool,
|
||||
SkipUnchanged: true,
|
||||
Backends: &backends,
|
||||
RendererOptions: []ofdgo.RendererOption{ofdgo.WithDPI(settings.Get("dpi").Float())},
|
||||
|
||||
@@ -67,6 +67,8 @@ type NativeOutputTarget struct {
|
||||
// hooks应在首次Call前配置,Resolve返回的资源需保持有效直到相应会话关闭
|
||||
type NativeEngine struct {
|
||||
businessEngine
|
||||
// ConversionPool 协调不同会话的批量转换资源
|
||||
ConversionPool *ofdgo.ConversionPool
|
||||
// Resolve 按授权标识提供资源读取源
|
||||
Resolve NativeResolver
|
||||
// LoadFont 按名称读取系统字体数据
|
||||
@@ -134,7 +136,11 @@ func NewNativeEngine(resolve NativeResolver) *NativeEngine { return &NativeEngin
|
||||
// 批量输出到非nil目标时必须同时提供ReserveOutput和CleanupOutput,避免误清理既有文件
|
||||
// 取消为协作式检查,不能抢占宿主阻塞的ReadAt、字体读取或加密库内部计算
|
||||
func (e *NativeEngine) Call(ctx context.Context, name string, args []any, emit func(any)) (result map[string]any) {
|
||||
e.once.Do(func() { e.gate = make(chan struct{}, 1); e.resolveSource = e.source })
|
||||
e.once.Do(func() {
|
||||
e.gate = make(chan struct{}, 1)
|
||||
e.resolveSource = e.source
|
||||
e.conversionPool = e.ConversionPool
|
||||
})
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
+92
-13
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows && amd64
|
||||
//go:build !js || !wasm
|
||||
|
||||
package webview
|
||||
|
||||
@@ -44,17 +44,19 @@ const applicationID = "me.aite.ofdgo"
|
||||
|
||||
// Host 连接桌面系统能力和共用文档业务
|
||||
type Host struct {
|
||||
mu sync.Mutex
|
||||
window *guigo.Window
|
||||
files *fileStore
|
||||
engines map[string]*webui.NativeEngine
|
||||
engineStops map[string]func() bool
|
||||
cancels map[string]context.CancelFunc
|
||||
fontOnce sync.Once
|
||||
fonts []map[string]any
|
||||
fontFiles map[string]string
|
||||
fontError error
|
||||
pendingPaths []string
|
||||
mu sync.Mutex
|
||||
window *guigo.Window
|
||||
files *fileStore
|
||||
engines map[string]*webui.NativeEngine
|
||||
engineStops map[string]func() bool
|
||||
conversionPool *ofdgo.ConversionPool
|
||||
cancels map[string]context.CancelFunc
|
||||
batchResults map[string]any
|
||||
fontOnce sync.Once
|
||||
fonts []map[string]any
|
||||
fontFiles map[string]string
|
||||
fontError error
|
||||
pendingPaths []string
|
||||
}
|
||||
|
||||
// resourceReader 保存资源读取位置及关闭操作
|
||||
@@ -76,9 +78,14 @@ func Run(ctx context.Context, paths []string, dataDir string) error {
|
||||
return err
|
||||
}
|
||||
h := &Host{files: files, engines: make(map[string]*webui.NativeEngine), cancels: make(map[string]context.CancelFunc), fontFiles: make(map[string]string), pendingPaths: append([]string(nil), paths...)}
|
||||
h.conversionPool, err = ofdgo.NewConversionPool(ofdgo.ConversionPoolOptions{AvailableMemory: conversionMemory})
|
||||
if err != nil {
|
||||
files.close()
|
||||
return err
|
||||
}
|
||||
defer h.close()
|
||||
bindings := make(map[string]guigo.HandlerFunc)
|
||||
for _, name := range []string{"engine.open", "engine.close", "engine.call", "open", "save", "directory", "file", "release", "child", "remove", "write.begin", "write.data", "write.commit", "write.abort", "fonts", "ready", "title"} {
|
||||
for _, name := range []string{"engine.open", "engine.close", "engine.call", "engine.start", "engine.cancel", "engine.finish", "batch", "open", "save", "directory", "file", "release", "child", "remove", "write.begin", "write.data", "write.commit", "write.abort", "fonts", "ready", "title"} {
|
||||
bindings["ofdgo.host."+name] = func(ctx context.Context, args any) (any, error) { return h.call(ctx, name, args) }
|
||||
}
|
||||
static := http.FileServerFS(webuiassets.NativeFS)
|
||||
@@ -260,6 +267,7 @@ func (h *Host) call(ctx context.Context, name string, value any) (any, error) {
|
||||
switch name {
|
||||
case "engine.open":
|
||||
engine := webui.NewNativeEngine(h.files.resolve)
|
||||
engine.ConversionPool = h.conversionPool
|
||||
engine.OpenWriter = h.openWriter
|
||||
engine.ReserveOutput = h.reserveOutput
|
||||
engine.CleanupOutput = h.cleanupOutput
|
||||
@@ -273,8 +281,69 @@ func (h *Host) call(ctx context.Context, name string, value any) (any, error) {
|
||||
h.engineStops[id] = context.AfterFunc(guigo.PageContext(ctx), func() { _ = h.closeEngine(id) })
|
||||
h.mu.Unlock()
|
||||
return guigo.Result{Value: map[string]any{"engine": id}, Discard: func() { _ = h.closeEngine(id) }}, nil
|
||||
case "batch":
|
||||
return map[string]any{"parallelism": h.conversionPool.Parallelism()}, nil
|
||||
case "engine.close":
|
||||
return nil, h.closeEngine(text("engine"))
|
||||
case "engine.finish":
|
||||
key := text("engine") + "/" + fmt.Sprint(args["id"])
|
||||
h.mu.Lock()
|
||||
delete(h.batchResults, key)
|
||||
h.mu.Unlock()
|
||||
return nil, nil
|
||||
case "engine.cancel":
|
||||
key := text("engine") + "/" + fmt.Sprint(args["id"])
|
||||
h.mu.Lock()
|
||||
cancel := h.cancels[key]
|
||||
h.mu.Unlock()
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
return nil, nil
|
||||
case "engine.start":
|
||||
operationName := text("name")
|
||||
if operationName != "ofdgoConvertFile" && operationName != "ofdgoPackFiles" {
|
||||
return nil, fmt.Errorf("unsupported batch operation")
|
||||
}
|
||||
id := text("engine")
|
||||
key := id + "/" + fmt.Sprint(args["id"])
|
||||
page := guigo.PageContext(ctx)
|
||||
operation, cancel := context.WithCancel(page)
|
||||
h.mu.Lock()
|
||||
engine := h.engines[id]
|
||||
_, duplicate := h.cancels[key]
|
||||
if engine == nil || duplicate {
|
||||
h.mu.Unlock()
|
||||
cancel()
|
||||
return nil, fmt.Errorf("invalid batch operation")
|
||||
}
|
||||
h.cancels[key] = cancel
|
||||
h.mu.Unlock()
|
||||
values, _ := args["args"].([]any)
|
||||
go func() {
|
||||
defer cancel()
|
||||
defer func() { h.mu.Lock(); delete(h.cancels, key); h.mu.Unlock() }()
|
||||
result := engine.Call(operation, operationName, values, func(event any) {
|
||||
if value, ok := event.(map[string]any); ok {
|
||||
value["id"] = args["id"]
|
||||
h.emit("ofdgo.engine."+id, value)
|
||||
}
|
||||
})
|
||||
encoded := h.encode(page, result)
|
||||
h.mu.Lock()
|
||||
if page.Err() != nil || h.engines[id] != engine {
|
||||
h.mu.Unlock()
|
||||
h.discard(encoded)
|
||||
return
|
||||
}
|
||||
if h.batchResults == nil {
|
||||
h.batchResults = make(map[string]any)
|
||||
}
|
||||
h.batchResults[key] = encoded
|
||||
h.mu.Unlock()
|
||||
h.emit("ofdgo.engine."+id, map[string]any{"id": args["id"], "type": "result", "result": encoded})
|
||||
}()
|
||||
return guigo.Result{Value: true, Discard: cancel}, nil
|
||||
case "engine.call":
|
||||
id := text("engine")
|
||||
key := id + "/" + fmt.Sprint(args["id"])
|
||||
@@ -670,6 +739,7 @@ func (h *Host) loadFont(ctx context.Context, name string) ([]byte, error) {
|
||||
// 入参: id 会话标识
|
||||
// 返回: error 关闭错误
|
||||
func (h *Host) closeEngine(id string) error {
|
||||
var results []any
|
||||
h.mu.Lock()
|
||||
engine := h.engines[id]
|
||||
delete(h.engines, id)
|
||||
@@ -682,7 +752,16 @@ func (h *Host) closeEngine(id string) error {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
for key, result := range h.batchResults {
|
||||
if strings.HasPrefix(key, id+"/") {
|
||||
results = append(results, result)
|
||||
delete(h.batchResults, key)
|
||||
}
|
||||
}
|
||||
h.mu.Unlock()
|
||||
for _, result := range results {
|
||||
h.discard(result)
|
||||
}
|
||||
if engine != nil {
|
||||
return engine.Close()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package webview
|
||||
|
||||
// conversionMemory 未提供系统内存探测时仅按并行数调度
|
||||
var conversionMemory func() int64
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// memoryStatusProc 查询系统物理内存和提交额度
|
||||
var memoryStatusProc = syscall.NewLazyDLL("kernel32.dll").NewProc("GlobalMemoryStatusEx")
|
||||
|
||||
// conversionMemory 预留系统内存后返回可用于新增转换的额度
|
||||
// 返回: int64 可用字节数,查询失败时暂停扩容
|
||||
func conversionMemory() int64 {
|
||||
status := struct {
|
||||
length uint32
|
||||
load uint32
|
||||
total uint64
|
||||
available uint64
|
||||
totalPage uint64
|
||||
availablePage uint64
|
||||
totalVirtual uint64
|
||||
availableVirtual uint64
|
||||
extended uint64
|
||||
}{}
|
||||
status.length = uint32(unsafe.Sizeof(status))
|
||||
ok, _, _ := memoryStatusProc.Call(uintptr(unsafe.Pointer(&status)))
|
||||
if ok == 0 {
|
||||
return 0
|
||||
}
|
||||
return max(0, int64(min(status.available, status.availablePage))-int64(status.total/8))
|
||||
}
|
||||
Reference in new issue
Block a user