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 @@