在 daemon 优化了异常处理行为; 为编辑器等也设置了可定义的环境变量; 更新 README;

This commit is contained in:
2025-10-05 14:28:07 +08:00
parent a6b2905ad2
commit 5f5a492342
13 changed files with 109 additions and 51 deletions

View File

@@ -1,10 +1,8 @@
package work.slhaf.snippet;
import cn.hutool.core.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import work.slhaf.snippet.common.Constant;
import work.slhaf.snippet.entity.db.Index;
import work.slhaf.snippet.entity.file.RebuildEntity;
import work.slhaf.snippet.exception.LaunchCheckException;
import work.slhaf.snippet.gateway.CodeSnippetSocketServer;
import work.slhaf.snippet.service.IndexManager;
import work.slhaf.snippet.service.SnippetManager;
@@ -13,9 +11,6 @@ import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
public class App{
@@ -53,14 +48,7 @@ public class App{
HashMap<String, String> sha2PathMD = snippetManager.getFileStatus();
if (!sha2PathMD.equals(sha2PathDB)) {
log.info("数据库与文件目录不一致,重建索引数据库");
List<RebuildEntity> snippets = snippetManager.listAll();
Set<Index> indexSet = snippets.stream().map(entity -> {
Index index = new Index();
BeanUtil.copyProperties(entity, index);
return index;
})
.collect(Collectors.toSet());
indexManager.rebuildIndex(indexSet);
indexManager.rebuildIndex();
}
log.info("索引数据库检查通过");
}
@@ -82,7 +70,7 @@ public class App{
boolean ok = file.mkdirs();
if (!ok) {
throw new RuntimeException("创建目录失败: " + dir);
throw new LaunchCheckException("创建目录失败: " + dir);
} else {
log.info("创建目录成功: {}", dir);
}
@@ -91,7 +79,7 @@ public class App{
private String getEnvOrThrow(String key) {
String value = System.getenv(key);
if (value == null) throw new RuntimeException("未找到环境变量: " + key);
if (value == null) throw new LaunchCheckException("未找到环境变量: " + key);
return value;
}

View File

@@ -0,0 +1,12 @@
package work.slhaf.snippet.exception;
public class ActionHandleException extends RuntimeException
{
public ActionHandleException(String message) {
super(message);
}
public ActionHandleException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,11 @@
package work.slhaf.snippet.exception;
public class LaunchCheckException extends RuntimeException{
public LaunchCheckException(String message) {
super(message);
}
public LaunchCheckException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,11 @@
package work.slhaf.snippet.exception;
public class SnippetManagerException extends RuntimeException{
public SnippetManagerException(String message) {
super(message);
}
public SnippetManagerException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -12,6 +12,7 @@ import work.slhaf.snippet.entity.file.AddEntity;
import work.slhaf.snippet.entity.file.EditEntity;
import work.slhaf.snippet.entity.file.ListEntity;
import work.slhaf.snippet.entity.file.MetaDataEntity;
import work.slhaf.snippet.exception.ActionHandleException;
import java.io.IOException;
import java.nio.file.Files;
@@ -53,16 +54,22 @@ public class ActionHandler {
private SocketOutputData handleDelete(String path) throws SQLException, IOException {
if (path.isEmpty()) {
return new SocketOutputData(Constant.Status.FAILED, "Path不能为空!");
throw new ActionHandleException("Path不能为空!");
}
try {
indexManager.delete(path);
snippetManager.delete(path);
return new SocketOutputData(Constant.Status.SUCCESS, "删除成功: " + path);
} catch (Exception e) {
log.warn("代码片段删除失败,尝试重建索引");
indexManager.rebuildIndex();
throw new ActionHandleException("代码片段删除失败,路径: " + path, e);
}
}
private SocketOutputData handleEdit(EditEntity entity) throws IOException {
if (entity.checkEmpty()) {
return new SocketOutputData(Constant.Status.FAILED, "Id、Path、代码内容均不能为空!");
throw new ActionHandleException("Id、Path、代码内容均不能为空!");
}
try {
snippetManager.update(entity, SnippetManager.UpdateAction.EDIT);
@@ -75,14 +82,13 @@ public class ActionHandler {
return new SocketOutputData(Constant.Status.SUCCESS, "文件编辑成功: " + entity.getPath());
} catch (Exception e) {
snippetManager.update(entity, SnippetManager.UpdateAction.FALLBACK);
log.error("文件编辑失败, 已回滚: {}", e.getLocalizedMessage());
return new SocketOutputData(Constant.Status.FAILED, e.getLocalizedMessage());
throw new ActionHandleException("文件编辑失败已回滚: " + entity, e);
}
}
private SocketOutputData handleAdd(AddEntity entity) throws IOException {
if (entity.checkEmpty()) {
return new SocketOutputData(Constant.Status.FAILED, "Language、Name、代码片段内容均不能为空!");
throw new ActionHandleException("Language、Name、代码片段内容均不能为空!");
}
Path path = Path.of(System.getenv(Constant.Property.DIR), entity.getLanguage().toLowerCase(), entity.getName() + ".md");
try {
@@ -94,8 +100,7 @@ public class ActionHandler {
return new SocketOutputData(Constant.Status.SUCCESS, "代码片段已添加, 路径: " + path);
} catch (Exception e) {
Files.deleteIfExists(path);
log.error("文件添加失败: {}", e.getLocalizedMessage());
return new SocketOutputData(Constant.Status.FAILED, e.getLocalizedMessage());
throw new ActionHandleException("文件添加失败: " + entity, e);
}
}

View File

@@ -1,12 +1,16 @@
package work.slhaf.snippet.service;
import cn.hutool.core.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import work.slhaf.snippet.common.Constant;
import work.slhaf.snippet.entity.db.Index;
import work.slhaf.snippet.entity.file.ListEntity;
import work.slhaf.snippet.entity.file.RebuildEntity;
import java.io.IOException;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class IndexManager {
@@ -14,6 +18,7 @@ public class IndexManager {
private static volatile IndexManager instance;
private final Connection connection;
private final SnippetManager snippetManager = new SnippetManager();
public static IndexManager getInstance() throws SQLException {
if (instance == null) {
@@ -47,9 +52,16 @@ public class IndexManager {
statement.close();
}
public void rebuildIndex(Set<Index> indexSet) throws SQLException {
public void rebuildIndex() throws SQLException, IOException {
log.info("重建索引数据库");
resetIndex();
List<RebuildEntity> snippets = snippetManager.listAll();
Set<Index> indexSet = snippets.stream().map(entity -> {
Index index = new Index();
BeanUtil.copyProperties(entity, index);
return index;
})
.collect(Collectors.toSet());
for (Index index : indexSet) {
add(index);
}

View File

@@ -8,6 +8,7 @@ import work.slhaf.snippet.common.Constant;
import work.slhaf.snippet.entity.Snippet;
import work.slhaf.snippet.entity.file.EditEntity;
import work.slhaf.snippet.entity.file.RebuildEntity;
import work.slhaf.snippet.exception.SnippetManagerException;
import java.io.File;
import java.io.FileReader;
@@ -52,7 +53,7 @@ public class SnippetManager {
String markdown = snippet.toMarkdown();
File file = path.toFile();
if (file.exists()) {
throw new RuntimeException("文件已存在: " + file.getAbsolutePath());
throw new SnippetManagerException("文件已存在: " + file.getAbsolutePath());
}
file.getParentFile().mkdirs();
Files.writeString(file.toPath(), markdown, StandardCharsets.UTF_8);
@@ -102,7 +103,7 @@ public class SnippetManager {
public String sha(String filePath) {
File file = new File(filePath);
if (!file.exists()) {
throw new RuntimeException("文件不存在: " + filePath);
throw new SnippetManagerException("文件不存在: " + filePath);
}
return DigestUtil.sha256Hex(file);
}

View File

@@ -2,13 +2,16 @@ import os
code_snippet_dir = os.getenv("CODE_SNIPPET_DIR")
code_snippet_port = os.getenv("CODE_SNIPPET_PORT")
code_snippet_rofi = os.getenv("CODE_SNIPPET_ROFI")
code_snippet_rofi = os.getenv("CODE_SNIPPET_ROFI", "rofi")
code_snippet_editor = os.getenv("CODE_SNIPPET_EDITOR","nvim")
action_list = "LIST"
action_add = "ADD"
action_edit = "EDIT"
action_delete = "DELETE"
editor_class = "code_snippet_editor"
template_add = """
## Snippet

View File

@@ -25,11 +25,11 @@
#
import atexit
from datetime import datetime
from decimal import Decimal, InvalidOperation
import signal
import subprocess
import time
from datetime import datetime
from decimal import Decimal, InvalidOperation
# Python < 3.2 doesn't provide a context manager interface for Popen.
# Let's make our own wrapper if needed.

View File

@@ -8,6 +8,8 @@ import subprocess
import tempfile
from pathlib import Path
from CodeSnippetRofi.common.constant import editor_class, code_snippet_editor
from common.constant import template_add, template_edit
from entity.result import ActionResult
from helper.api_helper import run_edit, run_add
@@ -201,23 +203,23 @@ def _open_with_nvim(file_path: str) -> None:
# 根据终端类型添加特定参数
if name == 'alacritty':
cmd.extend(['--class', 'floating-nvim', '-e', 'nvim', file_path])
cmd.extend(['--class', editor_class, '-e', code_snippet_editor, file_path])
elif name == 'kitty':
cmd.extend(['--class', 'floating-nvim', 'nvim', file_path])
cmd.extend(['--class', editor_class, code_snippet_editor, file_path])
elif name == 'foot':
cmd.extend(['-a', 'floating-nvim', 'nvim', file_path])
cmd.extend(['-a', editor_class, code_snippet_editor, file_path])
elif name == 'wezterm':
cmd.extend(['start', '--class', 'floating-nvim', '--', 'nvim', file_path])
cmd.extend(['start', '--class', editor_class, '--', code_snippet_editor, file_path])
elif name.startswith('gnome-terminal'):
cmd.extend(['--class=floating-nvim', '--', 'nvim', file_path])
cmd.extend([f'--class={editor_class}', '--', code_snippet_editor, file_path])
elif name == 'konsole':
cmd.extend(['--class', 'floating-nvim', '-e', 'nvim', file_path])
cmd.extend(['--class', editor_class, '-e', code_snippet_editor, file_path])
elif name == 'urxvt':
cmd.extend(['-name', 'floating-nvim', '-e', 'nvim', file_path])
cmd.extend(['-name', editor_class, '-e', code_snippet_editor, file_path])
elif name == 'xterm':
cmd.extend(['-class', 'floating-nvim', '-e', 'nvim', file_path])
cmd.extend(['-class', editor_class, '-e', code_snippet_editor, file_path])
else:
cmd.extend(['-e', 'nvim', file_path])
cmd.extend(['-e', code_snippet_editor, file_path])
# 阻塞执行命令,直到进程结束
subprocess.run(cmd)

View File

@@ -1,11 +1,6 @@
import os
from common.constant import code_snippet_rofi
from common import rofi
from common.constant import code_snippet_rofi
from menu.MainMenu import MainMenu
if code_snippet_rofi is None:
r = rofi.Rofi()
else:
r = rofi.Rofi(code_snippet_rofi)
r = rofi.Rofi(code_snippet_rofi)
MainMenu(r).run()

View File

@@ -1,4 +1,4 @@
# CodeSnippetDaemon
~~# CodeSnippet
使用 Rofi 前端 + Java 守护进程管理代码片段,支持 Markdown 存储、跨平台同步和快速索引。
## 项目概述
@@ -26,9 +26,17 @@
### 前端
- 默认使用 Rofi 作为启动器,已涵盖后端所需的所有操作类型
- 借助 Python 完成复杂操作
- 涉及到代码片段的编辑行为时,将通过调用 nvim 来编辑临时文件,编辑完毕后将发送请求至守护进程
- 涉及到代码片段的编辑行为时,默认将通过调用 nvim 来编辑临时文件,编辑完毕后将发送请求至守护进程
## 使用方法
## 快速开始
### 安装
1. 下载RELEASE
2. 下载所需库
- `pip install python-rofi`
2. 解压,目录结构如下:
> ![目录结构](doc/resource/dir.png)
### 使用
1. 设置环境变量
- CODE_SNIPPET_CONF
- 配置文件目录
@@ -42,11 +50,21 @@
- AI服务商提供的 base_url
- CODE_SNIPPET_MODEL
- 所用模型名称
- CODE_SNIPPET_EDITOR (可选若未指定则使用nvim)
- 所用的编辑器默认使用nvim
- Typora 好像也可以编辑,只是会额外打开一个终端窗口
- 其他编辑器未测试
- CODE_SNIPPET_ROFI (可选,若未指定则使用默认 rofi)
- 指定的 rofi 脚本,可以添加参数,比如: `rofi -theme ~/.config/rofi/launchers/type-4/style-1.rasi`
2. 启动 Java 守护进程
- `java -jar daemon/CodeSnippetDaemon-1.0.jar`
3. 启动 rofi 脚本
- `python rofi/launcher.py`
> 已将打开的编辑器窗口的class属性设置为`code_snippet_editor`,如果使用的是 Hyprland 等 wm可以据此对打开的编辑器窗口设置所需规则
>
> 该部分只针对一些常见的终端应用进行了设置,如有需要,可以自行编辑文件: [file_helper(第205行开始)](CodeSnippetRofi/helper/file_helper.py)
## 说明

BIN
doc/resource/dir.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB