863 lines
26 KiB
Go
863 lines
26 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
stdruntime "runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
|
|
|
"xk/services"
|
|
)
|
|
|
|
type FileHandler struct {
|
|
ctx context.Context
|
|
pdfService *services.PDFService
|
|
wordService *services.WordService
|
|
excelService *services.ExcelService
|
|
imageService *services.ImageService
|
|
}
|
|
|
|
func NewFileHandler() *FileHandler {
|
|
return &FileHandler{
|
|
pdfService: services.NewPDFService(),
|
|
wordService: services.NewWordService(),
|
|
excelService: services.NewExcelService(),
|
|
imageService: services.NewImageService(),
|
|
}
|
|
}
|
|
|
|
func (h *FileHandler) startup(ctx context.Context) {
|
|
h.ctx = ctx
|
|
}
|
|
|
|
type FileResult struct {
|
|
Success bool `json:"success"`
|
|
Message string `json:"message"`
|
|
Path string `json:"path,omitempty"`
|
|
Size int64 `json:"size,omitempty"`
|
|
OriginalSize int64 `json:"originalSize,omitempty"`
|
|
}
|
|
|
|
type ProcessRequest struct {
|
|
InputPath string `json:"inputPath"`
|
|
InputPaths []string `json:"inputPaths,omitempty"`
|
|
OutputPath string `json:"outputPath,omitempty"`
|
|
Format string `json:"format,omitempty"`
|
|
OutputFormat string `json:"outputFormat,omitempty"`
|
|
Quality string `json:"quality,omitempty"`
|
|
QualityInt int `json:"qualityInt,omitempty"`
|
|
Width int `json:"width,omitempty"`
|
|
Height int `json:"height,omitempty"`
|
|
Angle float64 `json:"angle,omitempty"`
|
|
BgColor string `json:"bgColor,omitempty"`
|
|
BgRemoveColor *services.ColorRGB `json:"bgRemoveColor,omitempty"`
|
|
Text string `json:"text,omitempty"`
|
|
Threshold int `json:"threshold,omitempty"`
|
|
Shape *services.CropShape `json:"shape,omitempty"`
|
|
Brightness int `json:"brightness,omitempty"`
|
|
Contrast int `json:"contrast,omitempty"`
|
|
Saturation int `json:"saturation,omitempty"`
|
|
GrayscaleIntensity int `json:"grayscaleIntensity,omitempty"`
|
|
SharpenAmount float64 `json:"sharpenAmount,omitempty"`
|
|
BlurRadius float64 `json:"blurRadius,omitempty"`
|
|
FlipH bool `json:"flipH,omitempty"`
|
|
FlipV bool `json:"flipV,omitempty"`
|
|
MaintainRatio *bool `json:"maintainRatio,omitempty"`
|
|
PageRanges string `json:"pageRanges,omitempty"`
|
|
DPI float64 `json:"dpi,omitempty"`
|
|
ImageFormat string `json:"imageFormat,omitempty"`
|
|
OptimizeLevel string `json:"optimizeLevel,omitempty"`
|
|
CropX int `json:"cropX,omitempty"`
|
|
CropY int `json:"cropY,omitempty"`
|
|
}
|
|
|
|
type AppConfig struct {
|
|
DefaultOutputDir string `json:"defaultOutputDir"`
|
|
}
|
|
|
|
type Shortcut struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Category string `json:"category"`
|
|
Icon string `json:"icon"`
|
|
}
|
|
|
|
type RecentUse struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Category string `json:"category"`
|
|
UsedAt int64 `json:"usedAt"`
|
|
}
|
|
|
|
func (h *FileHandler) OpenFileDialog(title string, filters []string) (string, error) {
|
|
var fileFilters []wailsRuntime.FileFilter
|
|
if title == "" {
|
|
title = "选择文件"
|
|
}
|
|
if len(filters) == 0 {
|
|
filters = []string{"*"}
|
|
}
|
|
for _, f := range filters {
|
|
displayName := "所有文件 (*.*)"
|
|
switch f {
|
|
case "*.pdf":
|
|
displayName = "PDF 文件 (*.pdf)"
|
|
case "*.doc;*.docx":
|
|
displayName = "Word 文档 (*.doc;*.docx)"
|
|
case "*.xls;*.xlsx":
|
|
displayName = "Excel 文件 (*.xls;*.xlsx)"
|
|
case "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.webp;*.ico":
|
|
displayName = "图片文件 (*.jpg;*.png;*.gif;*.bmp;*.tiff;*.webp;*.ico)"
|
|
}
|
|
fileFilters = append(fileFilters, wailsRuntime.FileFilter{DisplayName: displayName, Pattern: f})
|
|
}
|
|
|
|
path, err := wailsRuntime.OpenFileDialog(h.ctx, wailsRuntime.OpenDialogOptions{Title: title, Filters: fileFilters})
|
|
return path, err
|
|
}
|
|
|
|
func (h *FileHandler) OpenFilesDialog(title string, filters []string) ([]string, error) {
|
|
var fileFilters []wailsRuntime.FileFilter
|
|
if title == "" {
|
|
title = "选择文件"
|
|
}
|
|
if len(filters) == 0 {
|
|
filters = []string{"*"}
|
|
}
|
|
for _, f := range filters {
|
|
displayName := "所有文件 (*.*)"
|
|
switch f {
|
|
case "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.webp;*.ico":
|
|
displayName = "图片文件 (*.jpg;*.png;*.gif;*.bmp;*.tiff;*.webp;*.ico)"
|
|
case "*.pdf":
|
|
displayName = "PDF 文件 (*.pdf)"
|
|
}
|
|
fileFilters = append(fileFilters, wailsRuntime.FileFilter{DisplayName: displayName, Pattern: f})
|
|
}
|
|
|
|
paths, err := wailsRuntime.OpenMultipleFilesDialog(h.ctx, wailsRuntime.OpenDialogOptions{Title: title, Filters: fileFilters})
|
|
return paths, err
|
|
}
|
|
|
|
func (h *FileHandler) OpenSaveDialog(title string, defaultFilename string, filters []string, defaultDir string) (string, error) {
|
|
if title == "" {
|
|
title = "保存文件"
|
|
}
|
|
var fileFilters []wailsRuntime.FileFilter
|
|
for _, f := range filters {
|
|
displayName := "所有文件 (*.*)"
|
|
switch f {
|
|
case "*.pdf":
|
|
displayName = "PDF 文件 (*.pdf)"
|
|
case "*.csv":
|
|
displayName = "CSV 文件 (*.csv)"
|
|
case "*.json":
|
|
displayName = "JSON 文件 (*.json)"
|
|
case "*.jpg;*.jpeg":
|
|
displayName = "JPEG 图片 (*.jpg;*.jpeg)"
|
|
case "*.png":
|
|
displayName = "PNG 图片 (*.png)"
|
|
case "*.ico":
|
|
displayName = "ICO 图标 (*.ico)"
|
|
}
|
|
fileFilters = append(fileFilters, wailsRuntime.FileFilter{DisplayName: displayName, Pattern: f})
|
|
}
|
|
path, err := wailsRuntime.SaveFileDialog(h.ctx, wailsRuntime.SaveDialogOptions{Title: title, DefaultFilename: defaultFilename, DefaultDirectory: defaultDir, Filters: fileFilters})
|
|
return path, err
|
|
}
|
|
|
|
func (h *FileHandler) OpenFolder(path string) error {
|
|
if path == "" {
|
|
return fmt.Errorf("路径不能为空")
|
|
}
|
|
var cmd *exec.Cmd
|
|
switch stdruntime.GOOS {
|
|
case "windows":
|
|
cmd = exec.Command("explorer", path)
|
|
case "darwin":
|
|
cmd = exec.Command("open", path)
|
|
default:
|
|
cmd = exec.Command("xdg-open", path)
|
|
}
|
|
return cmd.Start()
|
|
}
|
|
|
|
// SaveBase64ToFile 将 base64 数据写入指定路径
|
|
func (h *FileHandler) SaveBase64ToFile(base64Data string, outputPath string) FileResult {
|
|
if base64Data == "" {
|
|
return FileResult{Success: false, Message: "数据为空"}
|
|
}
|
|
if outputPath == "" {
|
|
return FileResult{Success: false, Message: "输出路径为空"}
|
|
}
|
|
data, err := base64.StdEncoding.DecodeString(base64Data)
|
|
if err != nil {
|
|
return FileResult{Success: false, Message: "解码失败: " + err.Error()}
|
|
}
|
|
if err := os.WriteFile(outputPath, data, 0644); err != nil {
|
|
return FileResult{Success: false, Message: "写入文件失败: " + err.Error()}
|
|
}
|
|
return FileResult{Success: true, Path: outputPath, Size: int64(len(data))}
|
|
}
|
|
|
|
func (h *FileHandler) GetConfig() AppConfig {
|
|
config := AppConfig{DefaultOutputDir: getDefaultOutputDir()}
|
|
data, err := os.ReadFile(getConfigPath())
|
|
if err != nil {
|
|
return config
|
|
}
|
|
_ = services.ParseJSON(data, &config)
|
|
return config
|
|
}
|
|
|
|
func (h *FileHandler) SaveConfig(config AppConfig) error {
|
|
data, err := services.ToJSON(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(getConfigPath(), data, 0644)
|
|
}
|
|
|
|
func (h *FileHandler) GetShortcuts() []Shortcut {
|
|
var shortcuts []Shortcut
|
|
data, err := os.ReadFile(getShortcutsPath())
|
|
if err != nil {
|
|
return getDefaultShortcuts()
|
|
}
|
|
_ = services.ParseJSON(data, &shortcuts)
|
|
if len(shortcuts) == 0 {
|
|
return getDefaultShortcuts()
|
|
}
|
|
return shortcuts
|
|
}
|
|
|
|
func (h *FileHandler) SaveShortcuts(shortcuts []Shortcut) error {
|
|
data, err := services.ToJSON(shortcuts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(getShortcutsPath(), data, 0644)
|
|
}
|
|
|
|
func (h *FileHandler) GetRecentUses() []RecentUse {
|
|
var recent []RecentUse
|
|
data, err := os.ReadFile(getRecentPath())
|
|
if err != nil {
|
|
return []RecentUse{}
|
|
}
|
|
_ = services.ParseJSON(data, &recent)
|
|
return recent
|
|
}
|
|
|
|
func (h *FileHandler) AddRecentUse(tool RecentUse) error {
|
|
var recent []RecentUse
|
|
data, err := os.ReadFile(getRecentPath())
|
|
if err == nil {
|
|
_ = services.ParseJSON(data, &recent)
|
|
}
|
|
for i, r := range recent {
|
|
if r.ID == tool.ID {
|
|
recent = append(recent[:i], recent[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
recent = append([]RecentUse{tool}, recent...)
|
|
if len(recent) > 5 {
|
|
recent = recent[:5]
|
|
}
|
|
data, err = services.ToJSON(recent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(getRecentPath(), data, 0644)
|
|
}
|
|
|
|
func (h *FileHandler) GetImageBase64(path string) (string, error) {
|
|
return h.imageService.GetImageBase64(path)
|
|
}
|
|
|
|
func (h *FileHandler) GetCompareImages(originalPath, processedPath string) (map[string]string, error) {
|
|
result := make(map[string]string)
|
|
if originalPath != "" {
|
|
b64, err := h.imageService.GetImageBase64(originalPath)
|
|
if err == nil {
|
|
result["original"] = b64
|
|
}
|
|
}
|
|
if processedPath != "" {
|
|
b64, err := h.imageService.GetImageBase64(processedPath)
|
|
if err == nil {
|
|
result["processed"] = b64
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (h *FileHandler) SaveResult(tempPath, outputPath string) (FileResult, error) {
|
|
if outputPath == "" {
|
|
return FileResult{}, fmt.Errorf("未指定输出路径")
|
|
}
|
|
|
|
data, err := os.ReadFile(tempPath)
|
|
if err != nil {
|
|
return FileResult{}, fmt.Errorf("读取临时文件失败: %v", err)
|
|
}
|
|
|
|
dir := filepath.Dir(outputPath)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return FileResult{}, fmt.Errorf("创建目录失败: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(outputPath, data, 0644); err != nil {
|
|
return FileResult{}, fmt.Errorf("保存文件失败: %v", err)
|
|
}
|
|
|
|
info, err := os.Stat(outputPath)
|
|
if err != nil {
|
|
return FileResult{}, fmt.Errorf("获取文件信息失败: %v", err)
|
|
}
|
|
|
|
return FileResult{
|
|
Success: true,
|
|
Message: "保存成功",
|
|
Path: outputPath,
|
|
Size: info.Size(),
|
|
}, nil
|
|
}
|
|
|
|
func (h *FileHandler) ProcessFile(req ProcessRequest) FileResult {
|
|
if req.InputPath == "" {
|
|
return FileResult{Success: false, Message: "请选择文件"}
|
|
}
|
|
|
|
ext := strings.ToLower(filepath.Ext(req.InputPath))
|
|
|
|
switch ext {
|
|
case ".pdf":
|
|
return h.processPDF(req)
|
|
case ".doc", ".docx":
|
|
return h.processWord(req)
|
|
case ".xls", ".xlsx":
|
|
return h.processExcel(req)
|
|
case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp", ".ico":
|
|
return h.processImage(req)
|
|
case ".txt", ".md", ".html":
|
|
return h.processTextToPDF(req)
|
|
default:
|
|
return FileResult{Success: false, Message: fmt.Sprintf("不支持的文件格式: %s", ext)}
|
|
}
|
|
}
|
|
|
|
func (h *FileHandler) resolveOutputPath(inputPath, outputPath, suffix, ext string) string {
|
|
if outputPath != "" {
|
|
return outputPath
|
|
}
|
|
config := h.GetConfig()
|
|
outDir := config.DefaultOutputDir
|
|
if outDir == "" {
|
|
outDir = filepath.Dir(inputPath)
|
|
}
|
|
baseName := services.GetBaseName(inputPath)
|
|
return filepath.Join(outDir, baseName+suffix+ext)
|
|
}
|
|
|
|
func (h *FileHandler) getTempPath(inputPath, suffix string) string {
|
|
tempDir := os.TempDir()
|
|
baseName := services.GetBaseName(inputPath)
|
|
return filepath.Join(tempDir, "xk_"+baseName+suffix+".png")
|
|
}
|
|
|
|
func (h *FileHandler) processPDF(req ProcessRequest) FileResult {
|
|
var err error
|
|
outputPath := h.resolveOutputPath(req.InputPath, req.OutputPath, "_processed", ".pdf")
|
|
|
|
switch req.Format {
|
|
case "compress":
|
|
err = h.pdfService.OptimizePDF(req.InputPath, outputPath, req.OptimizeLevel)
|
|
case "split":
|
|
outputDir := filepath.Dir(outputPath)
|
|
_, err = h.pdfService.SplitPDF(req.InputPath, outputDir, req.PageRanges)
|
|
case "rotate":
|
|
err = h.pdfService.RotatePDF(req.InputPath, outputPath, int(req.Angle))
|
|
case "watermark":
|
|
err = h.pdfService.AddWatermark(req.InputPath, outputPath, req.Text)
|
|
case "merge":
|
|
err = h.pdfService.MergePDFs(req.InputPaths, outputPath)
|
|
case "toImage":
|
|
outputDir := filepath.Dir(outputPath)
|
|
results, imgErr := h.pdfService.PDFToImages(req.InputPath, outputDir, req.ImageFormat, req.DPI)
|
|
if imgErr != nil {
|
|
err = imgErr
|
|
} else if len(results) > 0 {
|
|
return FileResult{Success: true, Message: fmt.Sprintf("已生成 %d 张图片", len(results)), Path: results[0], Size: 0}
|
|
}
|
|
case "toText":
|
|
text, txtErr := h.pdfService.ExtractText(req.InputPath)
|
|
if txtErr != nil {
|
|
err = txtErr
|
|
} else {
|
|
txtPath := services.ChangeExtension(outputPath, ".txt")
|
|
if writeErr := os.WriteFile(txtPath, []byte(text), 0644); writeErr != nil {
|
|
err = writeErr
|
|
} else {
|
|
return h.getFileResult(req.InputPath, txtPath)
|
|
}
|
|
}
|
|
case "toWord":
|
|
text, txtErr := h.pdfService.ExtractText(req.InputPath)
|
|
if txtErr != nil {
|
|
err = txtErr
|
|
} else {
|
|
txtPath := services.ChangeExtension(outputPath, ".txt")
|
|
if writeErr := os.WriteFile(txtPath, []byte(text), 0644); writeErr != nil {
|
|
err = writeErr
|
|
} else {
|
|
return h.getFileResult(req.InputPath, txtPath)
|
|
}
|
|
}
|
|
case "toExcel":
|
|
text, txtErr := h.pdfService.ExtractText(req.InputPath)
|
|
if txtErr != nil {
|
|
err = txtErr
|
|
} else {
|
|
csvPath := services.ChangeExtension(outputPath, ".csv")
|
|
lines := strings.Split(text, "\n")
|
|
var csvContent strings.Builder
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" {
|
|
if strings.ContainsAny(line, ",\"") {
|
|
csvContent.WriteString("\"" + strings.ReplaceAll(line, "\"", "\"\"") + "\"\n")
|
|
} else {
|
|
csvContent.WriteString(line + "\n")
|
|
}
|
|
}
|
|
}
|
|
if writeErr := os.WriteFile(csvPath, []byte(csvContent.String()), 0644); writeErr != nil {
|
|
err = writeErr
|
|
} else {
|
|
return h.getFileResult(req.InputPath, csvPath)
|
|
}
|
|
}
|
|
case "toHtml":
|
|
text, txtErr := h.pdfService.ExtractText(req.InputPath)
|
|
if txtErr != nil {
|
|
err = txtErr
|
|
} else {
|
|
htmlPath := services.ChangeExtension(outputPath, ".html")
|
|
lines := strings.Split(text, "\n")
|
|
var html strings.Builder
|
|
html.WriteString("<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>PDF Text</title>\n<style>body{font-family:sans-serif;max-width:800px;margin:0 auto;padding:20px;line-height:1.6}</style>\n</head>\n<body>\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" {
|
|
html.WriteString("<p>" + line + "</p>\n")
|
|
}
|
|
}
|
|
html.WriteString("</body>\n</html>")
|
|
if writeErr := os.WriteFile(htmlPath, []byte(html.String()), 0644); writeErr != nil {
|
|
err = writeErr
|
|
} else {
|
|
return h.getFileResult(req.InputPath, htmlPath)
|
|
}
|
|
}
|
|
case "toMarkdown":
|
|
text, txtErr := h.pdfService.ExtractText(req.InputPath)
|
|
if txtErr != nil {
|
|
err = txtErr
|
|
} else {
|
|
mdPath := services.ChangeExtension(outputPath, ".md")
|
|
if writeErr := os.WriteFile(mdPath, []byte(text), 0644); writeErr != nil {
|
|
err = writeErr
|
|
} else {
|
|
return h.getFileResult(req.InputPath, mdPath)
|
|
}
|
|
}
|
|
default:
|
|
err = h.pdfService.OptimizePDF(req.InputPath, outputPath, "medium")
|
|
}
|
|
|
|
if err != nil {
|
|
return FileResult{Success: false, Message: err.Error()}
|
|
}
|
|
return h.getFileResult(req.InputPath, outputPath)
|
|
}
|
|
|
|
func (h *FileHandler) processWord(req ProcessRequest) FileResult {
|
|
outputPath := h.resolveOutputPath(req.InputPath, req.OutputPath, "", ".pdf")
|
|
err := h.wordService.ConvertToPDF(req.InputPath, outputPath)
|
|
if err != nil {
|
|
return FileResult{Success: false, Message: err.Error()}
|
|
}
|
|
return h.getFileResult(req.InputPath, outputPath)
|
|
}
|
|
|
|
func (h *FileHandler) processExcel(req ProcessRequest) FileResult {
|
|
outputExt := ".csv"
|
|
if req.Format == "json" {
|
|
outputExt = ".json"
|
|
}
|
|
outputPath := h.resolveOutputPath(req.InputPath, req.OutputPath, "", outputExt)
|
|
|
|
var err error
|
|
switch req.Format {
|
|
case "csv":
|
|
err = h.excelService.ConvertToCSV(req.InputPath, outputPath)
|
|
case "json":
|
|
err = h.excelService.ConvertToJSON(req.InputPath, outputPath)
|
|
default:
|
|
err = h.excelService.ConvertToCSV(req.InputPath, outputPath)
|
|
}
|
|
|
|
if err != nil {
|
|
return FileResult{Success: false, Message: err.Error()}
|
|
}
|
|
return h.getFileResult(req.InputPath, outputPath)
|
|
}
|
|
|
|
func (h *FileHandler) processTextToPDF(req ProcessRequest) FileResult {
|
|
outputPath := h.resolveOutputPath(req.InputPath, req.OutputPath, "", ".pdf")
|
|
|
|
ext := strings.ToLower(filepath.Ext(req.InputPath))
|
|
var err error
|
|
switch ext {
|
|
case ".md":
|
|
err = h.pdfService.MarkdownToPDF(req.InputPath, outputPath)
|
|
case ".html":
|
|
err = h.pdfService.HTMLToPDF(req.InputPath, outputPath)
|
|
default:
|
|
err = h.pdfService.TextToPDF(req.InputPath, outputPath)
|
|
}
|
|
|
|
if err != nil {
|
|
return FileResult{Success: false, Message: err.Error()}
|
|
}
|
|
return h.getFileResult(req.InputPath, outputPath)
|
|
}
|
|
|
|
func (h *FileHandler) processImage(req ProcessRequest) FileResult {
|
|
isImageOp := req.Format == "compress" || req.Format == "resize" ||
|
|
req.Format == "rotate" || req.Format == "grayscale" || req.Format == "brightness" ||
|
|
req.Format == "removebg" || req.Format == "crop" || req.Format == "flipH" || req.Format == "flipV" ||
|
|
req.Format == "sharpen" || req.Format == "blur" || req.Format == "invert"
|
|
|
|
var outputPath string
|
|
if isImageOp {
|
|
outputPath = h.getTempPath(req.InputPath, "_processed")
|
|
} else if req.Format == "convert" {
|
|
outputPath = h.resolveOutputPath(req.InputPath, req.OutputPath, "", "."+h.getConvertFormat(req))
|
|
} else {
|
|
outputPath = h.resolveOutputPath(req.InputPath, req.OutputPath, "", "."+req.Format)
|
|
}
|
|
|
|
var err error
|
|
switch req.Format {
|
|
case "compress":
|
|
quality := 85
|
|
if req.QualityInt > 0 {
|
|
quality = req.QualityInt
|
|
} else {
|
|
switch req.Quality {
|
|
case "high":
|
|
quality = 95
|
|
case "low":
|
|
quality = 60
|
|
}
|
|
}
|
|
err = h.imageService.CompressImage(req.InputPath, outputPath, quality, req.Width, req.Height)
|
|
case "resize":
|
|
maintainRatio := true
|
|
if req.MaintainRatio != nil {
|
|
maintainRatio = *req.MaintainRatio
|
|
}
|
|
err = h.imageService.ResizeImage(req.InputPath, outputPath, req.Width, req.Height, maintainRatio)
|
|
case "rotate":
|
|
err = h.imageService.RotateImage(req.InputPath, outputPath, req.Angle, req.BgColor)
|
|
if err == nil && req.FlipH {
|
|
err = h.imageService.FlipH(outputPath, outputPath)
|
|
}
|
|
if err == nil && req.FlipV {
|
|
err = h.imageService.FlipV(outputPath, outputPath)
|
|
}
|
|
case "grayscale":
|
|
intensity := req.GrayscaleIntensity
|
|
if intensity <= 0 {
|
|
intensity = 100
|
|
}
|
|
err = h.imageService.AdjustGrayscale(req.InputPath, outputPath, intensity)
|
|
case "brightness":
|
|
brightness := float64(req.Brightness)
|
|
err = h.imageService.AdjustBrightness(req.InputPath, outputPath, brightness)
|
|
if err == nil && req.Contrast != 0 {
|
|
contrastPath := h.getTempPath(req.InputPath, "_contrast")
|
|
if err2 := h.imageService.AdjustContrast(outputPath, contrastPath, float64(req.Contrast)); err2 != nil {
|
|
err = err2
|
|
} else {
|
|
os.Remove(outputPath)
|
|
os.Rename(contrastPath, outputPath)
|
|
}
|
|
}
|
|
if err == nil && req.Saturation != 0 {
|
|
satPath := h.getTempPath(req.InputPath, "_saturation")
|
|
if err2 := h.imageService.AdjustSaturation(outputPath, satPath, float64(req.Saturation)); err2 != nil {
|
|
err = err2
|
|
} else {
|
|
os.Remove(outputPath)
|
|
os.Rename(satPath, outputPath)
|
|
}
|
|
}
|
|
case "flipH":
|
|
err = h.imageService.FlipH(req.InputPath, outputPath)
|
|
case "flipV":
|
|
err = h.imageService.FlipV(req.InputPath, outputPath)
|
|
case "sharpen":
|
|
amount := req.SharpenAmount
|
|
if amount <= 0 {
|
|
amount = 1.0
|
|
}
|
|
err = h.imageService.SharpenImage(req.InputPath, outputPath, amount)
|
|
case "blur":
|
|
radius := req.BlurRadius
|
|
if radius <= 0 {
|
|
radius = 3.0
|
|
}
|
|
err = h.imageService.BlurImage(req.InputPath, outputPath, radius)
|
|
case "invert":
|
|
err = h.imageService.InvertImage(req.InputPath, outputPath)
|
|
case "removebg":
|
|
err = h.imageService.RemoveBackground(req.InputPath, outputPath, req.Threshold, req.BgRemoveColor)
|
|
case "crop":
|
|
if req.Shape != nil {
|
|
shape := *req.Shape
|
|
if shape.X == 0 && shape.Y == 0 && (req.CropX != 0 || req.CropY != 0) {
|
|
shape.X = req.CropX
|
|
shape.Y = req.CropY
|
|
}
|
|
err = h.imageService.CropImageShape(req.InputPath, outputPath, shape)
|
|
} else {
|
|
err = h.imageService.CropImageShape(req.InputPath, outputPath, services.CropShape{
|
|
Type: "rectangle", X: req.CropX, Y: req.CropY, Width: req.Width, Height: req.Height,
|
|
})
|
|
}
|
|
case "toPdf":
|
|
if outputPath == "" {
|
|
outputPath = services.ChangeExtension(req.InputPath, ".pdf")
|
|
}
|
|
err = h.imageService.ImageToPDF(req.InputPath, outputPath)
|
|
default:
|
|
quality := 85
|
|
if req.QualityInt > 0 {
|
|
quality = req.QualityInt
|
|
}
|
|
err = h.imageService.ConvertFormat(req.InputPath, outputPath, h.getConvertFormat(req), quality)
|
|
}
|
|
|
|
if err != nil {
|
|
return FileResult{Success: false, Message: err.Error()}
|
|
}
|
|
|
|
result := h.getFileResult(req.InputPath, outputPath)
|
|
if result.Success && isImageOp {
|
|
result.Path = outputPath
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (h *FileHandler) getConvertFormat(req ProcessRequest) string {
|
|
if req.OutputFormat != "" {
|
|
return req.OutputFormat
|
|
}
|
|
if req.Format == "convert" {
|
|
return "png"
|
|
}
|
|
return req.Format
|
|
}
|
|
|
|
func (h *FileHandler) getFileResult(inputPath, outputPath string) FileResult {
|
|
info, err := os.Stat(outputPath)
|
|
if err != nil {
|
|
return FileResult{Success: false, Message: fmt.Sprintf("获取文件信息失败: %v", err)}
|
|
}
|
|
result := FileResult{Success: true, Message: "处理完成", Path: outputPath, Size: info.Size()}
|
|
if inputPath != "" {
|
|
if inputInfo, err := os.Stat(inputPath); err == nil {
|
|
result.OriginalSize = inputInfo.Size()
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (h *FileHandler) GetFileInfo(path string) map[string]interface{} {
|
|
ext := strings.ToLower(filepath.Ext(path))
|
|
result := make(map[string]interface{})
|
|
switch ext {
|
|
case ".pdf":
|
|
info, err := h.pdfService.GetPDFInfo(path)
|
|
if err == nil {
|
|
result["type"] = "pdf"
|
|
result["pages"] = info.Pages
|
|
result["size"] = info.Size
|
|
}
|
|
case ".doc", ".docx":
|
|
info, err := h.wordService.GetWordInfo(path)
|
|
if err == nil {
|
|
result["type"] = "word"
|
|
result["title"] = info.Title
|
|
result["words"] = info.Words
|
|
result["size"] = info.Size
|
|
}
|
|
case ".xls", ".xlsx":
|
|
info, err := h.excelService.GetExcelInfo(path)
|
|
if err == nil {
|
|
result["type"] = "excel"
|
|
result["sheets"] = info.Sheets
|
|
result["size"] = info.Size
|
|
}
|
|
case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp", ".ico":
|
|
info, err := h.imageService.GetImageInfo(path)
|
|
if err == nil {
|
|
result["type"] = "image"
|
|
result["width"] = info.Width
|
|
result["height"] = info.Height
|
|
result["format"] = info.Format
|
|
result["size"] = info.Size
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (h *FileHandler) ExtractPDFText(path string) (string, error) {
|
|
return h.pdfService.ExtractText(path)
|
|
}
|
|
|
|
func (h *FileHandler) StitchImages(inputPaths []string, direction string, gap int, bgColor string, outputPath string) FileResult {
|
|
if len(inputPaths) < 2 {
|
|
return FileResult{Success: false, Message: "至少需要 2 张图片才能拼接"}
|
|
}
|
|
|
|
// 确定输出路径
|
|
if outputPath == "" {
|
|
config := h.GetConfig()
|
|
outDir := config.DefaultOutputDir
|
|
if outDir == "" {
|
|
outDir = filepath.Dir(inputPaths[0])
|
|
}
|
|
outputPath = filepath.Join(outDir, "拼接长图.png")
|
|
}
|
|
|
|
resultPath, err := h.imageService.StitchImages(services.StitchRequest{
|
|
InputPaths: inputPaths,
|
|
Direction: direction,
|
|
Gap: gap,
|
|
BgColor: bgColor,
|
|
OutputPath: outputPath,
|
|
})
|
|
if err != nil {
|
|
return FileResult{Success: false, Message: err.Error()}
|
|
}
|
|
|
|
info, statErr := os.Stat(resultPath)
|
|
if statErr != nil {
|
|
return FileResult{Success: false, Message: fmt.Sprintf("获取文件信息失败: %v", statErr)}
|
|
}
|
|
|
|
return FileResult{
|
|
Success: true,
|
|
Message: fmt.Sprintf("拼接完成,共 %d 张图片", len(inputPaths)),
|
|
Path: resultPath,
|
|
Size: info.Size(),
|
|
}
|
|
}
|
|
|
|
func (h *FileHandler) LogError(msg string) {
|
|
h.writeLog("ERROR", msg)
|
|
}
|
|
|
|
func (h *FileHandler) LogInfo(msg string) {
|
|
h.writeLog("INFO", msg)
|
|
}
|
|
|
|
func (h *FileHandler) writeLog(level, msg string) {
|
|
exe, _ := os.Executable()
|
|
logDir := filepath.Join(filepath.Dir(exe), "logs")
|
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
|
return
|
|
}
|
|
logFile := filepath.Join(logDir, time.Now().Format("2006-01-02")+".log")
|
|
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer f.Close()
|
|
fmt.Fprintf(f, "[%s] [%s] %s\n", time.Now().Format("15:04:05"), level, msg)
|
|
}
|
|
|
|
func (h *FileHandler) GetLogDates() []string {
|
|
exe, _ := os.Executable()
|
|
logDir := filepath.Join(filepath.Dir(exe), "logs")
|
|
entries, err := os.ReadDir(logDir)
|
|
if err != nil {
|
|
return []string{}
|
|
}
|
|
var dates []string
|
|
for _, e := range entries {
|
|
if !e.IsDir() && strings.HasSuffix(e.Name(), ".log") {
|
|
dates = append(dates, strings.TrimSuffix(e.Name(), ".log"))
|
|
}
|
|
}
|
|
return dates
|
|
}
|
|
|
|
func (h *FileHandler) GetLogs(date string) string {
|
|
exe, _ := os.Executable()
|
|
logDir := filepath.Join(filepath.Dir(exe), "logs")
|
|
logFile := filepath.Join(logDir, date+".log")
|
|
data, err := os.ReadFile(logFile)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func (h *FileHandler) GetExeDir() string {
|
|
exe, _ := os.Executable()
|
|
return filepath.Dir(exe)
|
|
}
|
|
|
|
func getConfigPath() string {
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, ".xk-config.json")
|
|
}
|
|
|
|
func getShortcutsPath() string {
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, ".xk-shortcuts.json")
|
|
}
|
|
|
|
func getRecentPath() string {
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, ".xk-recent.json")
|
|
}
|
|
|
|
func getDefaultOutputDir() string {
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, "Desktop")
|
|
}
|
|
|
|
func getDefaultShortcuts() []Shortcut {
|
|
return []Shortcut{
|
|
{ID: "pdf-compress", Name: "压缩PDF", Category: "pdf", Icon: "FolderChecked"},
|
|
{ID: "image-compress", Name: "压缩图片", Category: "image", Icon: "FolderChecked"},
|
|
{ID: "image-convert", Name: "图片格式转换", Category: "image", Icon: "Switch"},
|
|
{ID: "excel-csv", Name: "Excel转CSV", Category: "excel", Icon: "Document"},
|
|
{ID: "word-pdf", Name: "Word转PDF", Category: "word", Icon: "Document"},
|
|
}
|
|
}
|