diff --git a/openRenamerBackend/.idea/.gitignore b/openRenamerBackend/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/openRenamerBackend/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/openRenamerBackend/.idea/modules.xml b/openRenamerBackend/.idea/modules.xml
new file mode 100644
index 0000000..723fdfc
--- /dev/null
+++ b/openRenamerBackend/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openRenamerBackend/.idea/openRenamerBackend.iml b/openRenamerBackend/.idea/openRenamerBackend.iml
new file mode 100644
index 0000000..c0d38ce
--- /dev/null
+++ b/openRenamerBackend/.idea/openRenamerBackend.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openRenamerBackend/.idea/vcs.xml b/openRenamerBackend/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/openRenamerBackend/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openRenamerBackend/api/AutoPlanApi.ts b/openRenamerBackend/api/AutoPlanApi.ts
deleted file mode 100644
index 355eece..0000000
--- a/openRenamerBackend/api/AutoPlanApi.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Context } from "koa";
-import FileService from "../service/FileService";
-import config from "../config";
-
-const router = {};
-
-/**
- * 获取目录下的文件列表
- */
-router["GET /file/query"] = async function (ctx: Context) {
- ctx.body = await FileService.readPath(ctx.query.path as string, ctx.query.showHidden === '1');
-};
-
-/**
- *是否windows
- */
-router['GET /file/isWindows'] = async function (ctx: Context) {
- ctx.body = config.isWindows;
-}
-
-/**
- * 检查路径是否存在
- */
-router["GET /file/path/exist"] = async function (ctx: Context) {
- ctx.body = await FileService.checkExist(ctx.query.path as string);
-};
-
-/**
- * 收藏路径
- */
-router["POST /file/path/save"] = async function (ctx: Context) {
- ctx.body = await FileService.savePath(ctx.request.body);
-};
-
-/**
- * 获取收藏路径
- */
-router["GET /file/path"] = async function (ctx: Context) {
- ctx.body = await FileService.getSaveList();
-};
-
-/**
- * 获取收藏路径
- */
-router["DELETE /file/path/delete"] = async function (ctx: Context) {
- ctx.body = await FileService.deleteOne(ctx.query.id);
-};
-
-export default router;
diff --git a/openRenamerBackend/api/FileApi.ts b/openRenamerBackend/api/FileApi.ts
index 7c85bca..0081170 100644
--- a/openRenamerBackend/api/FileApi.ts
+++ b/openRenamerBackend/api/FileApi.ts
@@ -1,25 +1,32 @@
-import { Context } from "koa";
+import {Context} from "koa";
import FileService from "../service/FileService";
import config from "../config";
const router = {};
/**
- * 获取目录下的文件列表
+ * 获取目录下的文件列表
*/
router["GET /file/query"] = async function (ctx: Context) {
ctx.body = await FileService.readPath(ctx.query.path as string, ctx.query.showHidden === '1');
};
/**
- *是否windows
+ * 递归读取文件夹下所有的文件
+ */
+router["POST /file/recursionQuery"] = async function (ctx: Context) {
+ ctx.body = await FileService.readRecursion(ctx.request.body);
+};
+
+/**
+ *是否windows
*/
router['GET /file/isWindows'] = async function (ctx: Context) {
ctx.body = config.isWindows;
}
/**
- * 检查路径是否存在
+ * 检查路径是否存在
*/
router["GET /file/path/exist"] = async function (ctx: Context) {
ctx.body = await FileService.checkExist(ctx.query.path as string);
@@ -46,4 +53,19 @@ router["DELETE /file/path/delete"] = async function (ctx: Context) {
ctx.body = await FileService.deleteOne(ctx.query.id);
};
+/**
+ * delete file batch
+ */
+router["POST /file/deleteBatch"] = async function (ctx: Context) {
+ ctx.body = await FileService.deleteBatch(ctx.request.body);
+};
+
+/**
+ * rename file
+ */
+router["POST /file/rename"] = async function (ctx: Context) {
+ await FileService.rename(ctx.request.body.source, ctx.request.body.target);
+ ctx.body = "";
+};
+
export default router;
diff --git a/openRenamerBackend/entity/bo/rules/AutoRule.ts b/openRenamerBackend/entity/bo/rules/AutoRule.ts
index 761dda5..0504e1a 100644
--- a/openRenamerBackend/entity/bo/rules/AutoRule.ts
+++ b/openRenamerBackend/entity/bo/rules/AutoRule.ts
@@ -4,86 +4,88 @@ import path from 'path';
let pattern = new RegExp(/s(eason)?(\d+)/);
-let eNumPatternArr = [new RegExp(/e[p]?(\d+)/), new RegExp(/[\(\[(](\d+)[\)\])]/), new RegExp(/[\.-](\d+)/), new RegExp(/(\d+)/)];
+let eNumPatternArr = [new RegExp(/ep?(\d+)/), new RegExp(/[\(\[(](\d+)[\)\])]/), new RegExp(/[\.-](\d+)/), new RegExp(/(\d+)/)];
let resolutionPattern = new RegExp(/(\d{3,}[pP])/);
let resolutionArr = ['1k', '1K', '2k', '2K', '4k', '4K', '8k', '8K'];
let charSet = new Set([' ', '[', '.', '(', '(']);
export default class InsertRule implements RuleInterface {
- /**
- * 识别类型,season:季号,name:剧名/电影名识别
- */
- type: string;
- /**
- * 前面追加
- */
- frontAdd: string;
- /**
- * 后面追加
- */
- endAdd: string;
- eNumWidth: number;
+ /**
+ * 识别类型,season:季号,name:剧名/电影名识别
+ */
+ type: string;
+ /**
+ * 前面追加
+ */
+ frontAdd: string;
+ /**
+ * 后面追加
+ */
+ endAdd: string;
+ eNumWidth: number;
- constructor(data: any) {
- this.type = data.type;
- this.frontAdd = data.frontAdd;
- this.endAdd = data.endAdd;
- }
+ constructor(data: any) {
+ this.type = data.type;
+ this.frontAdd = data.frontAdd;
+ this.endAdd = data.endAdd;
+ }
- deal(file: FileObj): void {
- //识别到的内容
- let getStr = null;
- let patternRes = path.basename(file.path).replace(/[ ]+/, "").toLocaleLowerCase().match(pattern);
- if (this.type === 'season') {
- if (patternRes && patternRes[2]) {
- getStr = patternRes[2];
- }
- } else if (this.type === 'name') {
- let originName = null;
- if (patternRes && patternRes[2]) {
- //说明是剧集,取父文件夹的父文件夹名称
- originName = path.basename(path.resolve(file.path, '..'));
- } else {
- //说明是电影
- originName = path.basename(file.path);
- }
- getStr = '';
- for (let i = 0; i < originName.length; i++) {
- let char = originName.charAt(i);
- if (charSet.has(char)) {
- break;
- }
- getStr += char;
- }
- } else if (this.type === 'eNum') {
- let lowName = file.originName.toLocaleLowerCase().replace(/ /g, '');
- for (let i in eNumPatternArr) {
- let patternRes = lowName.match(eNumPatternArr[i]);
- if (patternRes && patternRes.length > 1) {
- getStr = patternRes[1];
- for (let i = 0; i < this.eNumWidth - getStr.length; i++) {
- getStr = '0' + getStr;
- }
- break;
- }
- }
- } else if (this.type === 'resolution') {
- let res = file.originName.match(resolutionPattern);
- if (res && res.length > 1) {
- getStr = res[1];
- } else {
- for (let i = 0; i < resolutionArr.length; i++) {
- if (file.originName.indexOf(resolutionArr[i]) > -1) {
- getStr = resolutionArr[i];
- break;
- }
- }
- }
- }
- if (getStr && getStr.length > 0) {
- file.realName = file.realName + this.frontAdd + getStr + this.endAdd;
- file.name = file.realName + file.expandName;
- }
- }
+ deal(file: FileObj): void {
+ //识别到的内容
+ let getStr = null;
+ let patternRes = path.basename(file.path).replace(/[ ]+/, "").toLocaleLowerCase().match(pattern);
+ if (this.type === 'season') {
+ if (patternRes && patternRes[2]) {
+ getStr = patternRes[2];
+ }
+ } else if (this.type === 'name') {
+ let originName = null;
+ if (patternRes && patternRes[2]) {
+ //说明是剧集,取父文件夹的父文件夹名称
+ originName = path.basename(path.resolve(file.path, '..'));
+ } else {
+ //说明是电影
+ originName = path.basename(file.path);
+ }
+ getStr = '';
+ for (let i = 0; i < originName.length; i++) {
+ let char = originName.charAt(i);
+ if (charSet.has(char)) {
+ break;
+ }
+ getStr += char;
+ }
+ } else if (this.type === 'eNum') {
+ let lowName = file.originName.toLocaleLowerCase().replace(/ /g, '')
+ .replace(/\d+[a-z]/g, '')//去除4k,1080p等
+ .replace(/[xh]\d+/g, '')//去除x264,h264等 ;
+ for (let i in eNumPatternArr) {
+ let patternRes = lowName.match(eNumPatternArr[i]);
+ if (patternRes && patternRes.length > 1) {
+ getStr = patternRes[1];
+ for (let i = 0; i < this.eNumWidth - getStr.length; i++) {
+ getStr = '0' + getStr;
+ }
+ break;
+ }
+ }
+ } else if (this.type === 'resolution') {
+ let res = file.originName.match(resolutionPattern);
+ if (res && res.length > 1) {
+ getStr = res[1];
+ } else {
+ for (let i = 0; i < resolutionArr.length; i++) {
+ if (file.originName.indexOf(resolutionArr[i]) > -1) {
+ getStr = resolutionArr[i];
+ break;
+ }
+ }
+ }
+ }
+ if (getStr && getStr.length > 0) {
+ file.realName = file.realName + this.frontAdd + getStr + this.endAdd;
+ file.name = file.realName + file.expandName;
+ }
+ }
}
\ No newline at end of file
diff --git a/openRenamerBackend/index.ts b/openRenamerBackend/index.ts
index 81a621f..b3a844f 100644
--- a/openRenamerBackend/index.ts
+++ b/openRenamerBackend/index.ts
@@ -9,14 +9,15 @@ import handleError from "./middleware/handleError";
import init from "./middleware/init";
import SqliteUtil from './util/SqliteHelper';
import log from './util/LogUtil';
-import { updateQbInfo } from './util/QbApiUtil';
+import {updateQbInfo} from './util/QbApiUtil';
console.log(config);
+
const app = new koa();
let router = new Router({
- prefix: config.urlPrefix
+ prefix: config.urlPrefix
});
app.use(require('koa-static')(path.join(config.rootPath, 'static')));
@@ -30,12 +31,12 @@ app.use(handleError);
app.use(RouterMW(router, path.join(config.rootPath, "dist/api")));
(async () => {
- await SqliteUtil.createPool();
- await updateQbInfo(null, null);
- app.listen(config.port);
- log.info(`server listened `, config.port);
+ await SqliteUtil.createPool();
+ await updateQbInfo(null, null);
+ app.listen(config.port);
+ log.info(`server listened `, config.port);
})();
app.on("error", (error) => {
- console.error(error);
+ console.error(error);
})
diff --git a/openRenamerBackend/service/FileService.ts b/openRenamerBackend/service/FileService.ts
index 7fa4e24..fde1eb3 100644
--- a/openRenamerBackend/service/FileService.ts
+++ b/openRenamerBackend/service/FileService.ts
@@ -6,136 +6,190 @@ import ProcessHelper from '../util/ProcesHelper';
import FileObj from '../entity/vo/FileObj';
import SavePathDao from '../dao/SavePathDao';
import SavePath from '../entity/po/SavePath';
+import ErrorHelper from "../util/ErrorHelper";
let numberSet = new Set(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
class FileService {
- static async readPath(pathStr: string, showHidden: boolean): Promise> {
- pathStr = decodeURIComponent(pathStr);
- let fileList = new Array();
- if (pathStr.trim().length == 0) {
- //获取根目录路径
- if (config.isWindows) {
- //windows下
- let std: string = (await ProcessHelper.exec('wmic logicaldisk get caption')).replace('Caption', '');
- fileList = std
- .split('\r\n')
- .filter((item) => item.trim().length > 0)
- .map((item) => item.trim());
- } else {
- //linux下
- pathStr = '/';
- fileList = await fs.readdir(pathStr);
- }
- } else {
- if (!(fs.pathExists(pathStr))) {
- throw new Error("路径不存在");
- }
- fileList = await fs.readdir(pathStr);
- }
- let folderList: Array = new Array();
- let files: Array = new Array();
- for (let index in fileList) {
- try {
- let stat = await fs.stat(path.join(pathStr, fileList[index]));
- if (fileList[index].startsWith('.')) {
- if (showHidden) {
- (stat.isDirectory() ? folderList : files).push(
- new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
- );
- }
- } else {
- (stat.isDirectory() ? folderList : files).push(
- new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
- );
- }
- } catch (e) {
- console.error(e);
- }
- }
- folderList.sort((a, b) => FileService.compareStr(a.name, b.name)).push(...files.sort((a, b) => FileService.compareStr(a.name, b.name)));
- return folderList;
- }
+ static async readPath(pathStr: string, showHidden: boolean): Promise> {
+ pathStr = decodeURIComponent(pathStr);
+ let fileList = [];
+ if (pathStr.trim().length == 0) {
+ //获取根目录路径
+ if (config.isWindows) {
+ //windows下
+ let std: string = (await ProcessHelper.exec('wmic logicaldisk get caption')).replace('Caption', '');
+ fileList = std
+ .split('\r\n')
+ .filter((item) => item.trim().length > 0)
+ .map((item) => item.trim());
+ } else {
+ //linux下
+ pathStr = '/';
+ fileList = await fs.readdir(pathStr);
+ }
+ } else {
+ if (!(fs.pathExists(pathStr))) {
+ throw new Error("路径不存在");
+ }
+ fileList = await fs.readdir(pathStr);
+ }
+ let folderList: Array = [];
+ let files: Array = [];
+ for (let index in fileList) {
+ try {
+ let stat = await fs.stat(path.join(pathStr, fileList[index]));
+ if (fileList[index].startsWith('.')) {
+ if (showHidden) {
+ (stat.isDirectory() ? folderList : files).push(
+ new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
+ );
+ }
+ } else {
+ (stat.isDirectory() ? folderList : files).push(
+ new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
+ );
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ folderList.sort((a, b) => FileService.compareStr(a.name, b.name)).push(...files.sort((a, b) => FileService.compareStr(a.name, b.name)));
+ return folderList;
+ }
- static async checkExist(pathStr: string) {
- return await fs.pathExists(pathStr);
- }
+ /**
+ * 递归读取文件夹下所有的文件
+ */
+ static async readRecursion(folders: Array): Promise> {
+ let res = [];
+ await this.readDirRecursion(res, folders, 1);
+ return res;
+ }
- /**
- * 收藏路径
- * @param saveObj
- * @returns
- */
- static async savePath(saveObj: SavePath) {
- await SavePathDao.addOne(saveObj);
- return saveObj;
- }
+ private static async readDirRecursion(res: Array, folders: Array, depth: number): Promise {
+ if (depth > 10) {
+ throw ErrorHelper.Error400("递归读取超过10层,强制结束");
+ }
+ if (folders == null || folders.length == 0) {
+ return;
+ }
+ for (let i in folders) {
+ let file = folders[i];
+ if (!file.isFolder) {
+ res.push(file);
+ } else {
+ let filePath = path.join(file.path, file.name);
+ let temp = (await fs.readdir(filePath)).map(item => {
+ let stat = fs.statSync(path.join(filePath, item));
+ return new FileObj(item, filePath, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime());
+ });
+ await FileService.readDirRecursion(res, temp, depth + 1);
+ }
+ }
+ }
- /**
- * 获取保存列表
- * @returns
- */
- static async getSaveList() {
- return await SavePathDao.getAll();
- }
+ static async checkExist(pathStr: string) {
+ return await fs.pathExists(pathStr);
+ }
- /**
- * 删除
- * @param id
- * @returns
- */
- static async deleteOne(id) {
- return await SavePathDao.delete(id);
- }
+ /**
+ * 收藏路径
+ * @param saveObj
+ * @returns
+ */
+ static async savePath(saveObj: SavePath) {
+ await SavePathDao.addOne(saveObj);
+ return saveObj;
+ }
- /**
- * 数字字母混合排序
- * @param a str
- * @param b str
- */
- static compareStr(a: string, b: string) {
- let an = a.length;
- let bn = b.length;
- for (let i = 0; i < an;) {
- let charA = FileService.readChar(a, i, an);
- let charB = FileService.readChar(b, i, bn);
- if (charB.length == 0) {
- return 1;
- }
- if (charA !== charB) {
- //读取字符串不相等说明可以得到排序结果
- //如果都为数字,按照数字的比较方法,否则按照字符串比较
- return numberSet.has(charA.charAt(0)) && numberSet.has(charB.charAt(0)) ? Number(charA) - Number(charB) : charA.localeCompare(charB);
- }
- i += charA.length;
- }
- //排到最后都没分结果说明相等
- return 0;
- }
+ /**
+ * 获取保存列表
+ * @returns
+ */
+ static async getSaveList() {
+ return await SavePathDao.getAll();
+ }
- /**
- * 读取字符,如果字符为数字就读取整个数字
- * @param a a
- * @param n 数字长度
- */
- static readChar(a: string, i: number, n: number) {
- let res = "";
- for (; i < n; i++) {
- let char = a.charAt(i);
- if (numberSet.has(char)) {
- //如果当前字符是数字,添加到结果中
- res += char;
- } else {
- //如果不为数字,但是为第一个字符,直接返回,否则返回res
- if (res.length == 0) {
- return char;
- } else {
- return res;
- }
- }
- }
- return res;
- }
+ /**
+ * 删除
+ * @param id
+ * @returns
+ */
+ static async deleteOne(id) {
+ return await SavePathDao.delete(id);
+ }
+
+ /**
+ * 数字字母混合排序
+ * @param a str
+ * @param b str
+ */
+ static compareStr(a: string, b: string) {
+ let an = a.length;
+ let bn = b.length;
+ for (let i = 0; i < an;) {
+ let charA = FileService.readChar(a, i, an);
+ let charB = FileService.readChar(b, i, bn);
+ if (charB.length == 0) {
+ return 1;
+ }
+ if (charA !== charB) {
+ //读取字符串不相等说明可以得到排序结果
+ //如果都为数字,按照数字的比较方法,否则按照字符串比较
+ return numberSet.has(charA.charAt(0)) && numberSet.has(charB.charAt(0)) ? Number(charA) - Number(charB) : charA.localeCompare(charB);
+ }
+ i += charA.length;
+ }
+ //排到最后都没分结果说明相等
+ return 0;
+ }
+
+ /**
+ * 读取字符,如果字符为数字就读取整个数字
+ * @param a a
+ * @param n 数字长度
+ */
+ static readChar(a: string, i: number, n: number) {
+ let res = "";
+ for (; i < n; i++) {
+ let char = a.charAt(i);
+ if (numberSet.has(char)) {
+ //如果当前字符是数字,添加到结果中
+ res += char;
+ } else {
+ //如果不为数字,但是为第一个字符,直接返回,否则返回res
+ if (res.length == 0) {
+ return char;
+ } else {
+ return res;
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * delete batch
+ * @param files files
+ */
+ static async deleteBatch(files: Array): Promise {
+ if (files == null || files.length == 0) {
+ return;
+ }
+ for (let i in files) {
+ await fs.remove(path.join(files[i].path, files[i].name));
+ }
+ }
+
+ /**
+ * rename file from source to target
+ * @param source sourceFile
+ * @param target targetFile
+ */
+ static async rename(source: FileObj, target: FileObj): Promise {
+ await fs.rename(path.join(source.path, source.name), path.join(target.path, target.name));
+ }
}
export default FileService;
diff --git a/openRenamerFront/src/App.vue b/openRenamerFront/src/App.vue
index 2416362..550b644 100644
--- a/openRenamerFront/src/App.vue
+++ b/openRenamerFront/src/App.vue
@@ -1,9 +1,9 @@
+ active-text-color="#ffd04b" router>
重命名
- 自动化
+
-
+
@@ -20,22 +20,23 @@