Merge branch 'dev' of ssh://gitea.fleyx.com:222/fanxb/open-renamer into dev

# Conflicts:
#	openRenamerBackend/api/AutoPlanApi.ts
This commit is contained in:
fanxb 2023-03-07 00:09:15 +08:00
commit d52398f14c
6 changed files with 277 additions and 149 deletions

View File

@ -0,0 +1,14 @@
import { Context } from "koa";
import AutoPlanService from "../service/AutoPlanService";
const router = {};
/**
*
*/
router["POST /autoPlan/save"] = async function (ctx: Context) {
ctx.body = await AutoPlanService.saveAutoConfig(ctx.request.body);
};
export default router;

View File

@ -4,6 +4,7 @@ import GlobalConfigDao from '../dao/GlobalConfigDao';
import { DEFAULT_TEMPLETE_ID } from '../entity/constants/GlobalConfigCodeConstant'; import { DEFAULT_TEMPLETE_ID } from '../entity/constants/GlobalConfigCodeConstant';
import GlobalConfig from '../entity/po/GlobalConfig'; import GlobalConfig from '../entity/po/GlobalConfig';
import ErrorHelper from '../util/ErrorHelper';
class ApplicationRuleService { class ApplicationRuleService {
@ -25,6 +26,11 @@ class ApplicationRuleService {
} }
static async deleteById(id: number): Promise<void> { static async deleteById(id: number): Promise<void> {
//禁止删除默认模板
let idStr = await GlobalConfigDao.getByCode(DEFAULT_TEMPLETE_ID);
if (id.toString() === idStr) {
throw ErrorHelper.Error400("禁止删除默认模板");
}
await ApplicationRuleDao.delete(id); await ApplicationRuleDao.delete(id);
} }

View File

@ -2,16 +2,15 @@ import config from '../config';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import ProcessHelper from '../util/ProcesHelper';
import FileObj from '../entity/vo/FileObj';
import SavePathDao from '../dao/SavePathDao';
import SavePath from '../entity/po/SavePath';
import AutoPlanConfigDto from '../entity/dto/AutoPlanConfigDto'; import AutoPlanConfigDto from '../entity/dto/AutoPlanConfigDto';
import GlobalConfig from 'entity/po/GlobalConfig'; import GlobalConfig from 'entity/po/GlobalConfig';
import GlobalConfigService from './GlobalConfigService'; import GlobalConfigService from './GlobalConfigService';
import ErrorHelper from 'util/ErrorHelper'; import ErrorHelper from '../util/ErrorHelper';
import TimeUtil from '../util/TimeUtil';
import { isSub, isVideo } from '../util/MediaUtil';
import log from '../util/LogUtil';
const autoConfigCode = "autoConfig"; const autoConfigCode = "autoConfig";
let isReadDir = false;
/** /**
* *
*/ */
@ -34,12 +33,25 @@ class AutoPlanService {
} else { } else {
autoConfig = JSON.parse(str); autoConfig = JSON.parse(str);
} }
setTimeout(async () => {
while (true) {
try {
await TimeUtil.sleep(1000);
await work();
} catch (err) {
console.log(err);
}
}
}, 1000);
} }
/** /**
* *
*/ */
static async saveAutoConfig(body: AutoPlanConfigDto): Promise<void> { static async saveAutoConfig(body: AutoPlanConfigDto): Promise<void> {
if (isReadDir) {
throw ErrorHelper.Error400("正在处理中,请稍后再试");
}
if (body.start) { if (body.start) {
if (body.paths.length == 0) { if (body.paths.length == 0) {
throw ErrorHelper.Error400("视频路径为空"); throw ErrorHelper.Error400("视频路径为空");
@ -56,28 +68,48 @@ class AutoPlanService {
await GlobalConfigService.insertOrReplace(configBody); await GlobalConfigService.insertOrReplace(configBody);
autoConfig = body; autoConfig = body;
if (body.start && !body.ignoreExist) { if (body.start && !body.ignoreExist) {
setTimeout(async () => {
isReadDir = true;
try {
await readDir(body.paths); await readDir(body.paths);
} finally {
isReadDir = false;
}
}, 1);
}
} }
} }
/**
*
} * @param dirList
*/
async function readDir(dirList: Array<string>): Promise<void> { async function readDir(dirList: Array<string>): Promise<void> {
if (!dirList) { if (!dirList) {
return; return;
} }
for (let i in dirList) { for (let i in dirList) {
let pathStr = dirList[i]; let pathStr = dirList[i];
if (checkIgnore(pathStr)) { if (checkIgnore(path.basename(pathStr))) {
continue; continue;
} }
if (!(await fs.stat(pathStr)).isDirectory()) { if (!(await fs.stat(pathStr)).isDirectory()) {
let fileName = path.basename(pathStr);
let strs = fileName.split('.').reverse();
if (strs.length > 0 && (isSub(strs[0]) || isVideo(strs[1]))) {
needDeal.push(pathStr); needDeal.push(pathStr);
}
continue; continue;
} }
await readDir((await fs.readdir(pathStr)).map(item => path.join(pathStr, item))); let childs = null;
try {
childs = await fs.readdir(pathStr);
} catch (error) {
console.warn("读取报错:{}", error);
}
if (childs != null) {
await readDir(childs.map(item => path.join(pathStr, item)));
}
} }
} }
@ -93,4 +125,48 @@ function checkIgnore(str: string): boolean {
return false; return false;
} }
/**
*
*/
async function work() {
if (autoConfig == null || !autoConfig.start) {
return;
}
while (needDeal.length > 0) {
let file = needDeal.pop();
try {
await dealOnePath(file);
} catch (error) {
log.error("处理文件报错:{}", file);
console.error(error);
}
}
}
/**
*
* @param filePath
* @returns
*/
async function dealOnePath(filePath: string) {
let exist = await fs.pathExists(filePath);
if (!exist) {
return;
}
let basePath = null;
for (let i in autoConfig.paths) {
if (filePath.startsWith(autoConfig.paths[i])) {
basePath = autoConfig.paths[i];
break;
}
}
if (basePath == null) {
log.warn("无法识别的文件:{}", filePath);
return;
}
let relativePath = filePath.replace(basePath, "");
let pathArrs = relativePath.split(path.sep).filter(item => item.length > 0);
}
export default AutoPlanService; export default AutoPlanService;

View File

@ -0,0 +1,23 @@
const videoSet = new Set(["flv", 'avi', 'wmv', 'dat', 'vob', 'mpg', 'mpeg', 'mp4', '3gp', '3g2', 'mkv', 'rm', 'rmvb', 'mov', 'qt', 'ogg', 'ogv', 'oga', 'mod']);
/**
*
* @param str
*/
export function isVideo(str: string) {
if (!str) {
return false;
}
return videoSet.has(str.toLowerCase());
}
const subSet = new Set(['sub', 'sst', 'son', 'srt', 'ssa', 'ass', 'smi', 'psb', 'pjs', 'stl', 'tts', 'vsf', 'zeg']);
/**
*
* @param str
*/
export function isSub(str: string) {
if (!str) {
return false;
}
return subSet.has(str.toLowerCase());
}

View File

@ -23,9 +23,9 @@
</el-tooltip> </el-tooltip>
</el-button> </el-button>
<el-button type="primary" size="small" @click="move('bottom')"> <el-button type="primary" size="small" @click="move('bottom')">
<el-tooltip effect="dark" content="下移规则" placement="top"><el-icon> <el-tooltip effect="dark" content="下移规则" placement="top"
<bottom /> ><el-icon> <bottom /> </el-icon
</el-icon></el-tooltip> ></el-tooltip>
</el-button> </el-button>
</template> </template>
</div> </div>
@ -87,6 +87,12 @@ export default {
await this.ruleUpdate(); await this.ruleUpdate();
} }
}, },
watch: {
rules: function (newVal, oldVal) {
console.log("rules变化", newVal);
this.ruleList = JSON.parse(JSON.stringify(newVal));
},
},
methods: { methods: {
// //
ruleUpdate() { ruleUpdate() {

View File

@ -18,11 +18,15 @@
<template v-for="(item, index) in body.ignorePaths" :key="item"> <template v-for="(item, index) in body.ignorePaths" :key="item">
<el-tag closable @close="closePath(index, 'ignore')">{{ item }}</el-tag> <br /> <el-tag closable @close="closePath(index, 'ignore')">{{ item }}</el-tag> <br />
</template> </template>
<el-input v-if="ignoreInputVisible" v-model="ignoreInput" class="ml-1 w-20" size="small" <el-input
@keyup.enter="addIngoreFile" @blur="addIngoreFile" /> v-if="ignoreInputVisible"
<el-button v-else class="button-new-tag ml-1" size="small" @click="ignoreInputVisible = true"> v-model="ignoreInput"
+ 忽略 class="ml-1 w-20"
</el-button> size="small"
@keyup.enter="addIngoreFile"
@blur="addIngoreFile"
/>
<el-button v-else class="button-new-tag ml-1" size="small" @click="ignoreInputVisible = true"> + 忽略 </el-button>
<tips message="名字匹配的文件/文件夹将会忽略处理,支持js正则表达式" /> <tips message="名字匹配的文件/文件夹将会忽略处理,支持js正则表达式" />
</div> </div>
</el-form-item> </el-form-item>
@ -56,8 +60,8 @@
<script setup> <script setup>
import FileChose from "@/components/FileChose.vue"; import FileChose from "@/components/FileChose.vue";
import RuleBlock from "@/components/rules/RuleBlock.vue"; import RuleBlock from "@/components/rules/RuleBlock.vue";
import Tips from '@/components/Tips.vue'; import Tips from "@/components/Tips.vue";
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from "element-plus";
import { ref, reactive, onMounted } from "vue"; import { ref, reactive, onMounted } from "vue";
import http from "@/utils/HttpUtil"; import http from "@/utils/HttpUtil";
@ -66,30 +70,29 @@ let body = ref({
paths: [], paths: [],
version: 1, version: 1,
ignoreSeason0: true, ignoreSeason0: true,
ignorePaths: ['.*\.jpg', 'tvshow.nfo', 'season.nfo', 'metadata'], ignorePaths: [".*\.jpg", "tvshow.nfo", "season.nfo", "metadata"],
deleteSmallVideo: true, deleteSmallVideo: true,
rules: [], rules: [],
ignoreExist: false, ignoreExist: false,
start: false start: false,
}); });
let bodyRule = reactive({ let bodyRule = reactive({
paths: { type: 'array', required: true, message: '目录不能为空', trigger: 'change' }, paths: { type: "array", required: true, message: "目录不能为空", trigger: "change" },
rules: { type: 'array', required: true, message: '目录不能为空', trigger: 'change' }, rules: { type: "array", required: true, message: "目录不能为空", trigger: "change" },
}) });
const configRuleRef = ref(); const configRuleRef = ref();
let showFolderDialog = ref(false); let showFolderDialog = ref(false);
let ignoreInputVisible = ref(false); let ignoreInputVisible = ref(false);
let ignoreInput = ref(""); let ignoreInput = ref("");
onMounted(async () => { onMounted(async () => {
let res = await http.post("/config/multCode", null, ['autoConfig', 'firstUse']); let res = await http.post("/config/multCode", null, ["autoConfig", "firstUse"]);
if (res.autoConfig == undefined && res.firstUse == undefined) { if (res.autoConfig == undefined && res.firstUse == undefined) {
await http.post("/config/insertOrUpdate", null, { code: "firstUse", val: "1" }); await http.post("/config/insertOrUpdate", null, { code: "firstUse", val: "1" });
await ElMessageBox.alert("似乎是首次使用自动化,是否需要查看使用文档?", "提示", { await ElMessageBox.alert("似乎是首次使用自动化,是否需要查看使用文档?", "提示", {
confirmButtonText: "是", confirmButtonText: "是",
cancelButtonText: "否", cancelButtonText: "否",
showCancelButton: true showCancelButton: true,
}); });
alert("跳转到自动化使用文档"); alert("跳转到自动化使用文档");
return; return;
@ -102,7 +105,7 @@ onMounted(async () => {
// //
async function folderChose(data) { async function folderChose(data) {
if (body.value.paths.indexOf(data) > -1) { if (body.value.paths.indexOf(data) > -1) {
ElMessage({ type: 'warning', message: "路径已存在" }); ElMessage({ type: "warning", message: "路径已存在" });
return; return;
} }
body.value.paths.push(data); body.value.paths.push(data);
@ -111,7 +114,7 @@ async function folderChose (data) {
// //
async function addIngoreFile() { async function addIngoreFile() {
if (body.value.ignorePaths.indexOf(ignoreInput.value) > -1) { if (body.value.ignorePaths.indexOf(ignoreInput.value) > -1) {
ElMessage({ type: 'warning', message: "名称已存在" }); ElMessage({ type: "warning", message: "名称已存在" });
return; return;
} }
if (ignoreInput.value.length > 0) { if (ignoreInput.value.length > 0) {
@ -124,7 +127,7 @@ async function addIngoreFile () {
// //
async function closePath(index, type) { async function closePath(index, type) {
(type === 'folder' ? body.value.paths : body.value.ignorePaths).splice(index, 1); (type === "folder" ? body.value.paths : body.value.ignorePaths).splice(index, 1);
} }
// //
function ruleUpdate(rules) { function ruleUpdate(rules) {
@ -133,13 +136,13 @@ function ruleUpdate (rules) {
// //
async function submit() { async function submit() {
console.dir(configRuleRef.value); console.dir(configRuleRef.value);
configRuleRef.value.validate(async valid => { configRuleRef.value.validate(async (valid) => {
if (!valid) { if (!valid) {
return; return;
} }
await http.post("/config/insertOrUpdate", null, { code: "autoConfig", val: JSON.stringify(body.value), description: "自动化配置" }); await http.post("/autoPlan/save", null, body.value);
ElMessage({ type: 'success', message: "保存成功" }); ElMessage({ type: "success", message: "保存成功" });
}) });
} }
</script> </script>