在 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; package work.slhaf.snippet;
import cn.hutool.core.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import work.slhaf.snippet.common.Constant; import work.slhaf.snippet.common.Constant;
import work.slhaf.snippet.entity.db.Index; import work.slhaf.snippet.exception.LaunchCheckException;
import work.slhaf.snippet.entity.file.RebuildEntity;
import work.slhaf.snippet.gateway.CodeSnippetSocketServer; import work.slhaf.snippet.gateway.CodeSnippetSocketServer;
import work.slhaf.snippet.service.IndexManager; import work.slhaf.snippet.service.IndexManager;
import work.slhaf.snippet.service.SnippetManager; import work.slhaf.snippet.service.SnippetManager;
@@ -13,9 +11,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j @Slf4j
public class App{ public class App{
@@ -53,14 +48,7 @@ public class App{
HashMap<String, String> sha2PathMD = snippetManager.getFileStatus(); HashMap<String, String> sha2PathMD = snippetManager.getFileStatus();
if (!sha2PathMD.equals(sha2PathDB)) { if (!sha2PathMD.equals(sha2PathDB)) {
log.info("数据库与文件目录不一致,重建索引数据库"); log.info("数据库与文件目录不一致,重建索引数据库");
List<RebuildEntity> snippets = snippetManager.listAll(); indexManager.rebuildIndex();
Set<Index> indexSet = snippets.stream().map(entity -> {
Index index = new Index();
BeanUtil.copyProperties(entity, index);
return index;
})
.collect(Collectors.toSet());
indexManager.rebuildIndex(indexSet);
} }
log.info("索引数据库检查通过"); log.info("索引数据库检查通过");
} }
@@ -82,7 +70,7 @@ public class App{
boolean ok = file.mkdirs(); boolean ok = file.mkdirs();
if (!ok) { if (!ok) {
throw new RuntimeException("创建目录失败: " + dir); throw new LaunchCheckException("创建目录失败: " + dir);
} else { } else {
log.info("创建目录成功: {}", dir); log.info("创建目录成功: {}", dir);
} }
@@ -91,7 +79,7 @@ public class App{
private String getEnvOrThrow(String key) { private String getEnvOrThrow(String key) {
String value = System.getenv(key); String value = System.getenv(key);
if (value == null) throw new RuntimeException("未找到环境变量: " + key); if (value == null) throw new LaunchCheckException("未找到环境变量: " + key);
return value; 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.EditEntity;
import work.slhaf.snippet.entity.file.ListEntity; import work.slhaf.snippet.entity.file.ListEntity;
import work.slhaf.snippet.entity.file.MetaDataEntity; import work.slhaf.snippet.entity.file.MetaDataEntity;
import work.slhaf.snippet.exception.ActionHandleException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@@ -53,16 +54,22 @@ public class ActionHandler {
private SocketOutputData handleDelete(String path) throws SQLException, IOException { private SocketOutputData handleDelete(String path) throws SQLException, IOException {
if (path.isEmpty()) { 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);
} }
indexManager.delete(path);
snippetManager.delete(path);
return new SocketOutputData(Constant.Status.SUCCESS, "删除成功: " + path);
} }
private SocketOutputData handleEdit(EditEntity entity) throws IOException { private SocketOutputData handleEdit(EditEntity entity) throws IOException {
if (entity.checkEmpty()) { if (entity.checkEmpty()) {
return new SocketOutputData(Constant.Status.FAILED, "Id、Path、代码内容均不能为空!"); throw new ActionHandleException("Id、Path、代码内容均不能为空!");
} }
try { try {
snippetManager.update(entity, SnippetManager.UpdateAction.EDIT); snippetManager.update(entity, SnippetManager.UpdateAction.EDIT);
@@ -75,14 +82,13 @@ public class ActionHandler {
return new SocketOutputData(Constant.Status.SUCCESS, "文件编辑成功: " + entity.getPath()); return new SocketOutputData(Constant.Status.SUCCESS, "文件编辑成功: " + entity.getPath());
} catch (Exception e) { } catch (Exception e) {
snippetManager.update(entity, SnippetManager.UpdateAction.FALLBACK); snippetManager.update(entity, SnippetManager.UpdateAction.FALLBACK);
log.error("文件编辑失败, 已回滚: {}", e.getLocalizedMessage()); throw new ActionHandleException("文件编辑失败已回滚: " + entity, e);
return new SocketOutputData(Constant.Status.FAILED, e.getLocalizedMessage());
} }
} }
private SocketOutputData handleAdd(AddEntity entity) throws IOException { private SocketOutputData handleAdd(AddEntity entity) throws IOException {
if (entity.checkEmpty()) { 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"); Path path = Path.of(System.getenv(Constant.Property.DIR), entity.getLanguage().toLowerCase(), entity.getName() + ".md");
try { try {
@@ -94,8 +100,7 @@ public class ActionHandler {
return new SocketOutputData(Constant.Status.SUCCESS, "代码片段已添加, 路径: " + path); return new SocketOutputData(Constant.Status.SUCCESS, "代码片段已添加, 路径: " + path);
} catch (Exception e) { } catch (Exception e) {
Files.deleteIfExists(path); Files.deleteIfExists(path);
log.error("文件添加失败: {}", e.getLocalizedMessage()); throw new ActionHandleException("文件添加失败: " + entity, e);
return new SocketOutputData(Constant.Status.FAILED, e.getLocalizedMessage());
} }
} }

View File

@@ -1,12 +1,16 @@
package work.slhaf.snippet.service; package work.slhaf.snippet.service;
import cn.hutool.core.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import work.slhaf.snippet.common.Constant; import work.slhaf.snippet.common.Constant;
import work.slhaf.snippet.entity.db.Index; import work.slhaf.snippet.entity.db.Index;
import work.slhaf.snippet.entity.file.ListEntity; import work.slhaf.snippet.entity.file.ListEntity;
import work.slhaf.snippet.entity.file.RebuildEntity;
import java.io.IOException;
import java.sql.*; import java.sql.*;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
@Slf4j @Slf4j
public class IndexManager { public class IndexManager {
@@ -14,6 +18,7 @@ public class IndexManager {
private static volatile IndexManager instance; private static volatile IndexManager instance;
private final Connection connection; private final Connection connection;
private final SnippetManager snippetManager = new SnippetManager();
public static IndexManager getInstance() throws SQLException { public static IndexManager getInstance() throws SQLException {
if (instance == null) { if (instance == null) {
@@ -47,9 +52,16 @@ public class IndexManager {
statement.close(); statement.close();
} }
public void rebuildIndex(Set<Index> indexSet) throws SQLException { public void rebuildIndex() throws SQLException, IOException {
log.info("重建索引数据库"); log.info("重建索引数据库");
resetIndex(); 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) { for (Index index : indexSet) {
add(index); 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.Snippet;
import work.slhaf.snippet.entity.file.EditEntity; import work.slhaf.snippet.entity.file.EditEntity;
import work.slhaf.snippet.entity.file.RebuildEntity; import work.slhaf.snippet.entity.file.RebuildEntity;
import work.slhaf.snippet.exception.SnippetManagerException;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
@@ -52,7 +53,7 @@ public class SnippetManager {
String markdown = snippet.toMarkdown(); String markdown = snippet.toMarkdown();
File file = path.toFile(); File file = path.toFile();
if (file.exists()) { if (file.exists()) {
throw new RuntimeException("文件已存在: " + file.getAbsolutePath()); throw new SnippetManagerException("文件已存在: " + file.getAbsolutePath());
} }
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
Files.writeString(file.toPath(), markdown, StandardCharsets.UTF_8); Files.writeString(file.toPath(), markdown, StandardCharsets.UTF_8);
@@ -102,7 +103,7 @@ public class SnippetManager {
public String sha(String filePath) { public String sha(String filePath) {
File file = new File(filePath); File file = new File(filePath);
if (!file.exists()) { if (!file.exists()) {
throw new RuntimeException("文件不存在: " + filePath); throw new SnippetManagerException("文件不存在: " + filePath);
} }
return DigestUtil.sha256Hex(file); return DigestUtil.sha256Hex(file);
} }

View File

@@ -2,13 +2,16 @@ import os
code_snippet_dir = os.getenv("CODE_SNIPPET_DIR") code_snippet_dir = os.getenv("CODE_SNIPPET_DIR")
code_snippet_port = os.getenv("CODE_SNIPPET_PORT") 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_list = "LIST"
action_add = "ADD" action_add = "ADD"
action_edit = "EDIT" action_edit = "EDIT"
action_delete = "DELETE" action_delete = "DELETE"
editor_class = "code_snippet_editor"
template_add = """ template_add = """
## Snippet ## Snippet

View File

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

View File

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

View File

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

View File

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